135 lines
4.0 KiB
Vue
135 lines
4.0 KiB
Vue
<template>
|
|
<div class="flex justify-between">
|
|
<div class="flex space-x-2">
|
|
<div
|
|
:class="[
|
|
imageClasses,
|
|
'relative flex flex-shrink-0 items-center justify-center overflow-hidden rounded-md bg-gray-100 ring-1 ring-black ring-opacity-5',
|
|
]"
|
|
>
|
|
<img
|
|
class="object-contain"
|
|
v-if="storedItem.item.image"
|
|
:src="storedItem.item.image"
|
|
:alt="storedItem.item.displayName"
|
|
/>
|
|
<CubeIcon :class="iconClasses" />
|
|
</div>
|
|
<div class="-mt-1 flex flex-col">
|
|
<div class="flex flex-col md:flex-row md:items-center md:space-x-1">
|
|
<span :class="`text-${size} font-bold`">{{
|
|
storedItem.item.displayName
|
|
}}</span>
|
|
<span
|
|
:class="`md:mt-0.5 ${fontSize} text-gray-500`"
|
|
v-if="storedItem.addedBy"
|
|
><span class="hidden md:inline">· </span>Added
|
|
{{
|
|
dateToLocaleString(storedItem.acquiredAt || storedItem.createdAt)
|
|
}}
|
|
by {{ storedItem.addedBy.name }}</span
|
|
>
|
|
</div>
|
|
<div
|
|
:class="`flex items-center space-x-1 ${fontSize} text-gray-500`"
|
|
:title="`${ItemTypeName[storedItem.item.type]} - ${
|
|
ItemTypeDescription[storedItem.item.type]
|
|
}`"
|
|
>
|
|
<InformationCircleIcon class="h-4 w-4" />
|
|
<span>{{ ItemTypeName[storedItem.item.type] }}</span>
|
|
<span v-if="storedItem.item.consumable">· Consumable</span>
|
|
<span v-if="storedItem.consumedAt"
|
|
>· Consumed at {{ dateToLocaleString(storedItem.consumedAt) }}</span
|
|
>
|
|
</div>
|
|
<div
|
|
:class="[
|
|
'flex items-center space-x-1',
|
|
fontSize,
|
|
expiresSoon ? 'font-bold text-red-600' : '',
|
|
hasExpired ? 'uppercase' : '',
|
|
]"
|
|
v-if="storedItem.expiresAt"
|
|
>
|
|
<ExclamationTriangleIcon class="h-4 w-4" />
|
|
<span
|
|
>{{ hasExpired ? 'Expired' : 'Expires' }} at
|
|
{{ dateToLocaleString(storedItem.expiresAt) }}</span
|
|
>
|
|
</div>
|
|
<div
|
|
:class="`flex items-center space-x-1 ${fontSize} text-gray-500`"
|
|
title="Barcode"
|
|
v-if="storedItem.item.barcode"
|
|
>
|
|
<QrCodeIcon class="h-4 w-4" />
|
|
<span>{{ storedItem.item.barcode }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col items-end">
|
|
<slot name="actions"></slot>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {
|
|
CubeIcon,
|
|
ExclamationTriangleIcon,
|
|
InformationCircleIcon,
|
|
QrCodeIcon,
|
|
} from '@heroicons/vue/24/outline';
|
|
import { computed } from 'vue';
|
|
import { ItemTypeDescription, ItemTypeName } from '../../enums/item-type.enum';
|
|
import { StoredItem } from '../../interfaces/storage.interfaces';
|
|
|
|
const props = defineProps<{
|
|
storedItem: StoredItem;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
hideExtras?: boolean;
|
|
hideCost?: boolean;
|
|
}>();
|
|
|
|
const imageClasses = computed(() => {
|
|
if (props.size === 'sm') return 'h-8 w-8';
|
|
if (props.size === 'md') return 'h-12 w-12';
|
|
return 'sm:h-16 sm:w-16 h-8 w-8';
|
|
});
|
|
|
|
const iconClasses = computed(() => {
|
|
if (props.size === 'sm') return 'h-4 w-4';
|
|
if (props.size === 'md') return 'h-8 w-8';
|
|
return 'sm:h-12 sm:w-12 h-4 w-4';
|
|
});
|
|
|
|
const fontSize = computed(() => {
|
|
if (props.size === 'sm') return 'text-[0.75rem]';
|
|
return 'text-sm';
|
|
});
|
|
|
|
const expiresSoon = computed(
|
|
() =>
|
|
(!!props.storedItem.expiresAt &&
|
|
!props.storedItem.consumedAt &&
|
|
new Date(props.storedItem.expiresAt).getTime() <
|
|
Date.now() + 604800000) ??
|
|
false
|
|
);
|
|
|
|
const hasExpired = computed(
|
|
() =>
|
|
(!!props.storedItem.expiresAt &&
|
|
!props.storedItem.consumedAt &&
|
|
new Date(props.storedItem.expiresAt).getTime() < Date.now()) ??
|
|
false
|
|
);
|
|
|
|
const dateToLocaleString = (dateField: string) => {
|
|
const converted = new Date(dateField);
|
|
return `${converted.toLocaleString('en-UK')}`;
|
|
};
|
|
</script>
|