Some checks failed
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled
Pull Request Checks / Validate PR (pull_request) Has been cancelled
Issue #28 - Tag assignment in AddItemForm: - Replace custom tag selection with TagPicker component - Simplified code (removed manual tag state management) - Cleaner UI with reusable component Issue #29 - Display tags in InventoryList: - Replace UBadge with TagBadge in InventoryCard - Automatic contrast color for readability - Consistent tag display across app Closes #28, #29
149 lines
4.1 KiB
Vue
149 lines
4.1 KiB
Vue
<template>
|
|
<UCard class="hover:shadow-lg transition-shadow">
|
|
<!-- Item Image -->
|
|
<div class="aspect-square bg-gray-100 rounded-lg mb-3 overflow-hidden">
|
|
<img
|
|
v-if="item.product?.image_url"
|
|
:src="item.product.image_url"
|
|
:alt="item.name"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
<div v-else class="w-full h-full flex items-center justify-center">
|
|
<UIcon name="i-heroicons-cube" class="w-16 h-16 text-gray-300" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Item Info -->
|
|
<div class="space-y-2">
|
|
<div>
|
|
<h3 class="font-semibold text-gray-900 truncate">{{ item.name }}</h3>
|
|
<p v-if="item.product?.brand" class="text-sm text-gray-600 truncate">
|
|
{{ item.product.brand }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Quantity -->
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-lg font-medium text-gray-900">
|
|
{{ item.quantity }} {{ item.unit?.abbreviation }}
|
|
</span>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="flex gap-1">
|
|
<UButton
|
|
icon="i-heroicons-minus"
|
|
size="xs"
|
|
color="gray"
|
|
variant="ghost"
|
|
@click="$emit('update-quantity', item.id, -1)"
|
|
:disabled="item.quantity <= 1"
|
|
/>
|
|
<UButton
|
|
icon="i-heroicons-plus"
|
|
size="xs"
|
|
color="gray"
|
|
variant="ghost"
|
|
@click="$emit('update-quantity', item.id, 1)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tags -->
|
|
<div v-if="item.tags && item.tags.length > 0" class="flex flex-wrap gap-1">
|
|
<TagsTagBadge
|
|
v-for="tagItem in item.tags.slice(0, 3)"
|
|
:key="tagItem.tag.id"
|
|
:tag="tagItem.tag"
|
|
size="sm"
|
|
/>
|
|
<UBadge v-if="item.tags.length > 3" size="xs" color="gray">
|
|
+{{ item.tags.length - 3 }}
|
|
</UBadge>
|
|
</div>
|
|
|
|
<!-- Expiry Warning -->
|
|
<div v-if="daysUntilExpiry !== null" class="text-xs">
|
|
<UBadge
|
|
:color="expiryColor"
|
|
variant="soft"
|
|
class="w-full justify-center"
|
|
>
|
|
<UIcon :name="expiryIcon" class="mr-1" />
|
|
{{ expiryText }}
|
|
</UBadge>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<template #footer>
|
|
<div class="flex gap-2">
|
|
<UButton
|
|
icon="i-heroicons-pencil"
|
|
size="sm"
|
|
color="gray"
|
|
variant="soft"
|
|
class="flex-1"
|
|
@click="$emit('edit', item)"
|
|
>
|
|
Edit
|
|
</UButton>
|
|
<UButton
|
|
icon="i-heroicons-trash"
|
|
size="sm"
|
|
color="red"
|
|
variant="soft"
|
|
@click="$emit('delete', item.id)"
|
|
>
|
|
Delete
|
|
</UButton>
|
|
</div>
|
|
</template>
|
|
</UCard>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
item: any
|
|
}>()
|
|
|
|
defineEmits<{
|
|
edit: [item: any]
|
|
delete: [id: string]
|
|
'update-quantity': [id: string, change: number]
|
|
}>()
|
|
|
|
// Calculate days until expiry
|
|
const daysUntilExpiry = computed(() => {
|
|
if (!props.item.expiry_date) return null
|
|
|
|
const today = new Date()
|
|
const expiry = new Date(props.item.expiry_date)
|
|
const diff = Math.ceil((expiry.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
|
|
|
|
return diff
|
|
})
|
|
|
|
// Expiry badge styling
|
|
const expiryColor = computed(() => {
|
|
if (daysUntilExpiry.value === null) return 'gray'
|
|
if (daysUntilExpiry.value < 0) return 'red'
|
|
if (daysUntilExpiry.value <= 3) return 'orange'
|
|
if (daysUntilExpiry.value <= 7) return 'yellow'
|
|
return 'green'
|
|
})
|
|
|
|
const expiryIcon = computed(() => {
|
|
if (daysUntilExpiry.value === null) return 'i-heroicons-calendar'
|
|
if (daysUntilExpiry.value < 0) return 'i-heroicons-exclamation-triangle'
|
|
return 'i-heroicons-clock'
|
|
})
|
|
|
|
const expiryText = computed(() => {
|
|
if (daysUntilExpiry.value === null) return 'No expiry'
|
|
if (daysUntilExpiry.value < 0) return `Expired ${Math.abs(daysUntilExpiry.value)} days ago`
|
|
if (daysUntilExpiry.value === 0) return 'Expires today'
|
|
if (daysUntilExpiry.value === 1) return 'Expires tomorrow'
|
|
return `Expires in ${daysUntilExpiry.value} days`
|
|
})
|
|
</script>
|