fix: align frontend InventoryItem type with actual API response

InventoryItemResponse returns flat fields (product_name, barcode, category)
not a nested product object. Frontend interface and templates were using
item.product.name / item.product.brand which threw TypeError on render,
blanking the inventory tab.

- InventoryItem: remove product:Product, add product_name/barcode/category
- InventoryStats: fix total_products→available_items, expired→expired_items,
  items_by_location→locations
- item IDs are int not string — update deleteItem/updateItem/consumeItem sigs
- EditItemModal, RecipesView, InventoryList: fix all item.product.xxx refs
This commit is contained in:
pyr0ball 2026-04-01 17:30:21 -07:00
parent addcd88625
commit cfd6ef88cc
5 changed files with 26 additions and 25 deletions

View file

@ -10,8 +10,8 @@
<div class="form-group"> <div class="form-group">
<label>Product</label> <label>Product</label>
<div class="product-info"> <div class="product-info">
<strong>{{ item.product.name }}</strong> <strong>{{ item.product_name || 'Unknown Product' }}</strong>
<span v-if="item.product.brand" class="brand">({{ item.product.brand }})</span> <span v-if="item.category" class="brand">{{ item.category }}</span>
</div> </div>
</div> </div>

View file

@ -9,8 +9,8 @@
<div class="stat-label">Total Items</div> <div class="stat-label">Total Items</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-value">{{ stats.total_products }}</div> <div class="stat-value">{{ stats.available_items }}</div>
<div class="stat-label">Unique Products</div> <div class="stat-label">Available</div>
</div> </div>
<div class="stat-card expiry-soon"> <div class="stat-card expiry-soon">
<div class="stat-value">{{ store.expiringItems.length }}</div> <div class="stat-value">{{ store.expiringItems.length }}</div>
@ -263,8 +263,8 @@
:class="getItemClass(item)" :class="getItemClass(item)"
> >
<div class="item-header"> <div class="item-header">
<h3 class="item-name">{{ item.product.name }}</h3> <h3 class="item-name">{{ item.product_name || 'Unknown Product' }}</h3>
<span v-if="item.product.brand" class="item-brand">{{ item.product.brand }}</span> <span v-if="item.category" class="item-brand">{{ item.category }}</span>
</div> </div>
<div class="item-details"> <div class="item-details">
@ -485,11 +485,11 @@ async function handleSave() {
async function confirmDelete(item: InventoryItem) { async function confirmDelete(item: InventoryItem) {
showConfirm( showConfirm(
`Are you sure you want to delete ${item.product.name}?`, `Are you sure you want to delete ${item.product_name || 'item'}?`,
async () => { async () => {
try { try {
await store.deleteItem(item.id) await store.deleteItem(item.id)
showToast(`${item.product.name} deleted successfully`, 'success') showToast(`${item.product_name || 'item'} deleted successfully`, 'success')
} catch (err) { } catch (err) {
showToast('Failed to delete item', 'error') showToast('Failed to delete item', 'error')
} }
@ -504,12 +504,12 @@ async function confirmDelete(item: InventoryItem) {
async function markAsConsumed(item: InventoryItem) { async function markAsConsumed(item: InventoryItem) {
showConfirm( showConfirm(
`Mark ${item.product.name} as consumed?`, `Mark ${item.product_name || 'item'} as consumed?`,
async () => { async () => {
try { try {
await inventoryAPI.consumeItem(item.id) await inventoryAPI.consumeItem(item.id)
await refreshItems() await refreshItems()
showToast(`${item.product.name} marked as consumed`, 'success') showToast(`${item.product_name || 'item'} marked as consumed`, 'success')
} catch (err) { } catch (err) {
showToast('Failed to mark item as consumed', 'error') showToast('Failed to mark item as consumed', 'error')
} }
@ -542,7 +542,7 @@ async function handleScannerGunInput() {
const item = result.results[0] const item = result.results[0]
scannerResults.value.push({ scannerResults.value.push({
type: 'success', type: 'success',
message: `✓ Added: ${item.product.name}${item.product.brand ? ' (' + item.product.brand + ')' : ''} to ${scannerLocation.value}`, message: `✓ Added: ${item.product_name || 'item'}${''} to ${scannerLocation.value}`,
}) })
await refreshItems() await refreshItems()
} else { } else {
@ -588,7 +588,7 @@ async function handleBarcodeImageSelect(e: Event) {
const item = result.results[0] const item = result.results[0]
barcodeResults.value.push({ barcodeResults.value.push({
type: 'success', type: 'success',
message: `✓ Found: ${item.product.name}${item.product.brand ? ' (' + item.product.brand + ')' : ''}`, message: `✓ Found: ${item.product_name || 'item'}${''}`,
}) })
await refreshItems() await refreshItems()
} else { } else {

View file

@ -291,7 +291,7 @@ const pantryItems = computed(() => {
if (!b.expiration_date) return -1 if (!b.expiration_date) return -1
return new Date(a.expiration_date).getTime() - new Date(b.expiration_date).getTime() return new Date(a.expiration_date).getTime() - new Date(b.expiration_date).getTime()
}) })
return sorted.map((item) => item.product.name) return sorted.map((item) => item.product_name).filter(Boolean) as string[]
}) })
// Grocery links relevant to a specific recipe's missing ingredients // Grocery links relevant to a specific recipe's missing ingredients

View file

@ -80,9 +80,11 @@ export interface Tag {
} }
export interface InventoryItem { export interface InventoryItem {
id: string id: number
product_id: string product_id: number
product: Product product_name: string | null
barcode: string | null
category: string | null
quantity: number quantity: number
unit: string unit: string
location: string location: string
@ -109,11 +111,10 @@ export interface InventoryItemUpdate {
export interface InventoryStats { export interface InventoryStats {
total_items: number total_items: number
total_products: number available_items: number
expiring_soon: number expiring_soon: number
expired: number expired_items: number
items_by_location: Record<string, number> locations: Record<string, number>
items_by_status: Record<string, number>
} }
export interface Receipt { export interface Receipt {
@ -185,7 +186,7 @@ export const inventoryAPI = {
/** /**
* Update an inventory item * Update an inventory item
*/ */
async updateItem(itemId: string, update: InventoryItemUpdate): Promise<InventoryItem> { async updateItem(itemId: number, update: InventoryItemUpdate): Promise<InventoryItem> {
const response = await api.patch(`/inventory/items/${itemId}`, update) const response = await api.patch(`/inventory/items/${itemId}`, update)
return response.data return response.data
}, },
@ -193,7 +194,7 @@ export const inventoryAPI = {
/** /**
* Delete an inventory item * Delete an inventory item
*/ */
async deleteItem(itemId: string): Promise<void> { async deleteItem(itemId: number): Promise<void> {
await api.delete(`/inventory/items/${itemId}`) await api.delete(`/inventory/items/${itemId}`)
}, },
@ -234,7 +235,7 @@ export const inventoryAPI = {
/** /**
* Mark item as consumed * Mark item as consumed
*/ */
async consumeItem(itemId: string): Promise<void> { async consumeItem(itemId: number): Promise<void> {
await api.post(`/inventory/items/${itemId}/consume`) await api.post(`/inventory/items/${itemId}/consume`)
}, },

View file

@ -76,7 +76,7 @@ export const useInventoryStore = defineStore('inventory', () => {
} }
} }
async function updateItem(itemId: string, update: InventoryItemUpdate) { async function updateItem(itemId: number, update: InventoryItemUpdate) {
loading.value = true loading.value = true
error.value = null error.value = null
@ -99,7 +99,7 @@ export const useInventoryStore = defineStore('inventory', () => {
} }
} }
async function deleteItem(itemId: string) { async function deleteItem(itemId: number) {
loading.value = true loading.value = true
error.value = null error.value = null