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 #30 - TagManager component: - Create/delete tags with name, category, icon, color - Color picker with hex input - Organized display by category - Integrated in settings page with tabs Issue #31 - Tag filter for inventory: - TagFilter component with multi-select - Filter button in inventory header - Active filter display with removable badges - Filters items by selected tags (OR logic) - Clean "Clear" button Updates: - Extended useTags composable with createTag, deleteTag - Enhanced settings page with tab navigation - Improved inventory filtering UX Closes #30, #31
148 lines
3.7 KiB
Vue
148 lines
3.7 KiB
Vue
<template>
|
|
<div class="space-y-4">
|
|
<!-- Loading State -->
|
|
<div v-if="loading" class="text-center py-12">
|
|
<UIcon name="i-heroicons-arrow-path" class="w-8 h-8 text-gray-400 animate-spin mx-auto mb-2" />
|
|
<p class="text-gray-600">Loading inventory...</p>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div v-else-if="error" class="text-center py-12">
|
|
<UIcon name="i-heroicons-exclamation-triangle" class="w-12 h-12 text-red-500 mx-auto mb-4" />
|
|
<p class="text-red-600 mb-4">{{ error }}</p>
|
|
<UButton @click="loadInventory" color="gray">Retry</UButton>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div v-else-if="!items || items.length === 0" class="text-center py-12">
|
|
<UIcon name="i-heroicons-inbox" class="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
|
No items yet
|
|
</h3>
|
|
<p class="text-gray-600 mb-6">
|
|
Start by scanning a barcode or adding an item manually.
|
|
</p>
|
|
<div class="flex gap-2 justify-center">
|
|
<UButton
|
|
to="/scan"
|
|
color="primary"
|
|
icon="i-heroicons-qr-code"
|
|
>
|
|
Scan First Item
|
|
</UButton>
|
|
<UButton
|
|
@click="$emit('add-item')"
|
|
color="white"
|
|
icon="i-heroicons-plus"
|
|
>
|
|
Add Manually
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Inventory Grid -->
|
|
<div v-else class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
<InventoryCard
|
|
v-for="item in filteredItems"
|
|
:key="item.id"
|
|
:item="item"
|
|
@edit="$emit('edit-item', item)"
|
|
@delete="handleDelete(item.id)"
|
|
@update-quantity="handleQuantityUpdate"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const { getInventory, deleteInventoryItem, updateQuantity } = useInventory()
|
|
|
|
const props = defineProps<{
|
|
refresh?: boolean
|
|
tagFilters?: string[]
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'add-item': []
|
|
'edit-item': [item: any]
|
|
}>()
|
|
|
|
const items = ref<any[]>([])
|
|
const loading = ref(true)
|
|
const error = ref<string | null>(null)
|
|
|
|
const loadInventory = async () => {
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
const { data, error: fetchError } = await getInventory()
|
|
|
|
if (fetchError) {
|
|
error.value = 'Failed to load inventory. Please try again.'
|
|
loading.value = false
|
|
return
|
|
}
|
|
|
|
items.value = data || []
|
|
loading.value = false
|
|
}
|
|
|
|
// Computed filtered items
|
|
const filteredItems = computed(() => {
|
|
if (!props.tagFilters || props.tagFilters.length === 0) {
|
|
return items.value
|
|
}
|
|
|
|
// Filter items that have at least one of the selected tags
|
|
return items.value.filter(item => {
|
|
if (!item.tags || item.tags.length === 0) return false
|
|
|
|
const itemTagIds = item.tags.map((t: any) => t.tag.id)
|
|
return props.tagFilters!.some(filterId => itemTagIds.includes(filterId))
|
|
})
|
|
})
|
|
|
|
const handleDelete = async (id: string) => {
|
|
if (!confirm('Are you sure you want to delete this item?')) {
|
|
return
|
|
}
|
|
|
|
const { error: deleteError } = await deleteInventoryItem(id)
|
|
|
|
if (deleteError) {
|
|
alert('Failed to delete item')
|
|
return
|
|
}
|
|
|
|
// Remove from local list
|
|
items.value = items.value.filter(item => item.id !== id)
|
|
}
|
|
|
|
const handleQuantityUpdate = async (id: string, change: number) => {
|
|
const result = await updateQuantity(id, change)
|
|
|
|
if (result.error) {
|
|
alert('Failed to update quantity')
|
|
return
|
|
}
|
|
|
|
// Reload inventory after update
|
|
await loadInventory()
|
|
}
|
|
|
|
// Load on mount
|
|
onMounted(loadInventory)
|
|
|
|
// Watch for refresh prop
|
|
watch(() => props.refresh, (newVal) => {
|
|
if (newVal) {
|
|
loadInventory()
|
|
}
|
|
})
|
|
|
|
// Expose reload method
|
|
defineExpose({
|
|
reload: loadInventory
|
|
})
|
|
</script>
|