Some checks failed
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 / Code Quality (pull_request) Has been cancelled
Deploy to Coolify / Run Tests (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
Week 2 core inventory management: **Composables:** - useInventory: Full CRUD operations for inventory items - useUnits: Unit fetching and conversion helpers - useTags: Tag fetching and category filtering **Components:** - InventoryList (#18): Grid view with loading/empty/error states - InventoryCard: Item card with image, quantity controls, tags, expiry - AddItemForm (#19): Form with tag picker, unit selector, validation - EditItemModal (#20): Modal form for editing existing items - Delete functionality (#21): Confirm dialog + cascade tag cleanup **Features:** - Quantity quick-actions (+/- buttons on cards) - Auto-delete when quantity reaches zero - Expiry date tracking with color-coded warnings - Tag selection by category in add form - Responsive grid layout (1-4 columns) - Product image display from barcode cache - Form validation and loading states Closes #18, #19, #20, #21
185 lines
4.0 KiB
Vue
185 lines
4.0 KiB
Vue
<template>
|
|
<UModal v-model="isOpen">
|
|
<UCard>
|
|
<template #header>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold">Edit Item</h3>
|
|
<UButton
|
|
icon="i-heroicons-x-mark"
|
|
color="gray"
|
|
variant="ghost"
|
|
@click="close"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
<!-- Item Name -->
|
|
<UFormGroup label="Item Name" required>
|
|
<UInput
|
|
v-model="form.name"
|
|
placeholder="Item name"
|
|
size="lg"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<!-- Quantity & Unit -->
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<UFormGroup label="Quantity" required>
|
|
<UInput
|
|
v-model.number="form.quantity"
|
|
type="number"
|
|
min="0.01"
|
|
step="0.01"
|
|
size="lg"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<UFormGroup label="Unit" required>
|
|
<USelect
|
|
v-model="form.unit_id"
|
|
:options="unitOptions"
|
|
option-attribute="label"
|
|
value-attribute="value"
|
|
size="lg"
|
|
/>
|
|
</UFormGroup>
|
|
</div>
|
|
|
|
<!-- Expiry Date -->
|
|
<UFormGroup label="Expiry Date" hint="Optional">
|
|
<UInput
|
|
v-model="form.expiry_date"
|
|
type="date"
|
|
size="lg"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<!-- Notes -->
|
|
<UFormGroup label="Notes" hint="Optional">
|
|
<UTextarea
|
|
v-model="form.notes"
|
|
placeholder="Any additional notes..."
|
|
:rows="2"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<!-- Submit -->
|
|
<div class="flex gap-2 pt-2">
|
|
<UButton
|
|
type="submit"
|
|
color="primary"
|
|
size="lg"
|
|
class="flex-1"
|
|
:loading="submitting"
|
|
:disabled="!isValid"
|
|
>
|
|
Save Changes
|
|
</UButton>
|
|
<UButton
|
|
color="gray"
|
|
size="lg"
|
|
variant="soft"
|
|
@click="close"
|
|
>
|
|
Cancel
|
|
</UButton>
|
|
</div>
|
|
</form>
|
|
</UCard>
|
|
</UModal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const { updateInventoryItem } = useInventory()
|
|
const { getUnits } = useUnits()
|
|
|
|
const props = defineProps<{
|
|
item: any | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
close: []
|
|
updated: [item: any]
|
|
}>()
|
|
|
|
const isOpen = ref(false)
|
|
const submitting = ref(false)
|
|
const units = ref<any[]>([])
|
|
|
|
const form = reactive({
|
|
name: '',
|
|
quantity: 1,
|
|
unit_id: '',
|
|
expiry_date: '',
|
|
notes: ''
|
|
})
|
|
|
|
// Load units
|
|
onMounted(async () => {
|
|
const { data } = await getUnits()
|
|
units.value = data || []
|
|
})
|
|
|
|
// Unit options for select
|
|
const unitOptions = computed(() => {
|
|
return units.value.map(unit => ({
|
|
label: `${unit.name} (${unit.abbreviation})`,
|
|
value: unit.id
|
|
}))
|
|
})
|
|
|
|
// Watch for item changes (open modal)
|
|
watch(() => props.item, (newItem) => {
|
|
if (newItem) {
|
|
form.name = newItem.name
|
|
form.quantity = Number(newItem.quantity)
|
|
form.unit_id = newItem.unit_id
|
|
form.expiry_date = newItem.expiry_date || ''
|
|
form.notes = newItem.notes || ''
|
|
isOpen.value = true
|
|
}
|
|
}, { immediate: true })
|
|
|
|
// Watch modal close
|
|
watch(isOpen, (val) => {
|
|
if (!val) {
|
|
emit('close')
|
|
}
|
|
})
|
|
|
|
// Validation
|
|
const isValid = computed(() => {
|
|
return form.name.trim().length > 0 && form.quantity > 0 && form.unit_id
|
|
})
|
|
|
|
const close = () => {
|
|
isOpen.value = false
|
|
}
|
|
|
|
// Submit
|
|
const handleSubmit = async () => {
|
|
if (!isValid.value || !props.item) return
|
|
|
|
submitting.value = true
|
|
|
|
const { data, error } = await updateInventoryItem(props.item.id, {
|
|
name: form.name.trim(),
|
|
quantity: form.quantity,
|
|
unit_id: form.unit_id,
|
|
expiry_date: form.expiry_date || null,
|
|
notes: form.notes.trim() || null
|
|
})
|
|
|
|
if (error) {
|
|
alert('Failed to update item: ' + error.message)
|
|
submitting.value = false
|
|
return
|
|
}
|
|
|
|
emit('updated', data)
|
|
submitting.value = false
|
|
close()
|
|
}
|
|
</script>
|