diff --git a/app/api/ingredients/[id]/route.ts b/app/api/ingredients/[id]/route.ts index 469e850..fce650e 100644 --- a/app/api/ingredients/[id]/route.ts +++ b/app/api/ingredients/[id]/route.ts @@ -45,11 +45,21 @@ export async function GET( name: 1, category: { $arrayElemAt: ['$categoryDoc.name', 0] }, subcategory: { $arrayElemAt: ['$subcategoryDoc.name', 0] }, + categoryId: 1, + subcategoryId: 1, + supplierId: 1, quantity: 1, unit: 1, unitPrice: 1, vat: 1, supplier: { $arrayElemAt: ['$supplierDoc.name', 0] }, + discountType: 1, + discountValue: 1, + applyDiscountToNet: 1, + minStockLevel: 1, + storageInstructions: 1, + shelfLifeDays: 1, + notes: 1, createdAt: 1, updatedAt: 1, }, @@ -85,6 +95,13 @@ export async function PUT( if (body.unitPrice !== undefined) update.unitPrice = body.unitPrice; if (body.vat !== undefined) update.vat = body.vat; if (body.supplierId !== undefined) update.supplierId = new ObjectId(body.supplierId); + if (body.discountType !== undefined) update.discountType = body.discountType; + if (body.discountValue !== undefined) update.discountValue = body.discountValue; + if (body.applyDiscountToNet !== undefined) update.applyDiscountToNet = body.applyDiscountToNet; + if (body.minStockLevel !== undefined) update.minStockLevel = body.minStockLevel; + if (body.storageInstructions !== undefined) update.storageInstructions = body.storageInstructions; + if (body.shelfLifeDays !== undefined) update.shelfLifeDays = body.shelfLifeDays; + if (body.notes !== undefined) update.notes = body.notes; const db = await getDb(); const result = await db.collection('ingredients').findOneAndUpdate( diff --git a/app/dashboard/ingredients/page.tsx b/app/dashboard/ingredients/page.tsx index 843e04b..0c29dff 100644 --- a/app/dashboard/ingredients/page.tsx +++ b/app/dashboard/ingredients/page.tsx @@ -4,6 +4,8 @@ import React, { useState, useEffect, useCallback } from 'react'; import Sidebar from '@/components/Sidebar'; import IngredientTable, { Ingredient } from '@/components/IngredientTable'; import AddIngredientModal from '@/components/AddIngredientModal'; +import ViewIngredientModal from '@/components/ViewIngredientModal'; +import DeleteConfirmModal from '@/components/DeleteConfirmModal'; interface Category { _id: string; @@ -36,6 +38,9 @@ export default function IngredientsPage() { const [allSubcategories, setAllSubcategories] = useState([]); const [loading, setLoading] = useState(true); const [isAddModalOpen, setIsAddModalOpen] = useState(false); + const [editIngredientId, setEditIngredientId] = useState(null); + const [viewIngredientId, setViewIngredientId] = useState(null); + const [deleteIngredient, setDeleteIngredient] = useState<{ id: string; name: string } | null>(null); const limit = 10; const totalPages = Math.ceil(total / limit); @@ -252,7 +257,12 @@ export default function IngredientsPage() {

No ingredients found.

) : ( - + setViewIngredientId(id)} + onEdit={(id) => setEditIngredientId(id)} + onDelete={(id, name) => setDeleteIngredient({ id, name })} + /> )} {/* Pagination */} @@ -308,9 +318,28 @@ export default function IngredientsPage() { setIsAddModalOpen(false)} + open={isAddModalOpen || !!editIngredientId} + onClose={() => { setIsAddModalOpen(false); setEditIngredientId(null); }} onSaved={fetchIngredients} + ingredientId={editIngredientId ?? undefined} + /> + + setViewIngredientId(null)} + /> + + setDeleteIngredient(null)} + onConfirm={async () => { + if (!deleteIngredient) return; + await fetch(`/api/ingredients/${deleteIngredient.id}`, { method: 'DELETE' }); + setDeleteIngredient(null); + fetchIngredients(); + }} /> ); diff --git a/components/AddIngredientModal.tsx b/components/AddIngredientModal.tsx index 861bdcc..f6af172 100644 --- a/components/AddIngredientModal.tsx +++ b/components/AddIngredientModal.tsx @@ -22,11 +22,14 @@ interface AddIngredientModalProps { open: boolean; onClose: () => void; onSaved: () => void; + ingredientId?: string; } const CREATE_NEW = '__create_new__'; -export default function AddIngredientModal({ open, onClose, onSaved }: AddIngredientModalProps) { +export default function AddIngredientModal({ open, onClose, onSaved, ingredientId }: AddIngredientModalProps) { + const isEditMode = !!ingredientId; + const [loadingData, setLoadingData] = useState(false); // Reference data const [categories, setCategories] = useState([]); const [subcategories, setSubcategories] = useState([]); @@ -80,9 +83,48 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred }); }, [open]); - // Reset form when modal opens + // Reset form when modal opens (add mode) or fetch data (edit mode) useEffect(() => { - if (open) { + if (!open) return; + + // Always reset transient state + setNewCategoryName(''); + setNewSubcategoryName(''); + setNewSupplierName(''); + setCreatingCategory(false); + setCreatingSubcategory(false); + setCreatingSupplier(false); + setSaving(false); + setError(''); + + if (ingredientId) { + // Edit mode: fetch ingredient data after reference data loads + setLoadingData(true); + fetch(`/api/ingredients/${ingredientId}`) + .then(r => r.json()) + .then(data => { + setName(data.name || ''); + setCategoryId(data.categoryId || ''); + setSubcategoryId(data.subcategoryId || ''); + setQuantity(String(data.quantity ?? '')); + setUnit(data.unit || ''); + setGrossPrice(String(data.unitPrice ?? '')); + setVat(String(data.vat ?? '9')); + setSupplierId(data.supplierId || ''); + setDiscountType(data.discountType || 'value'); + setDiscountValue(data.discountValue ? String(data.discountValue) : ''); + setApplyDiscountToNet(data.applyDiscountToNet || false); + const hasAdvanced = data.minStockLevel || data.storageInstructions || data.shelfLifeDays || data.notes; + setShowAdvanced(!!hasAdvanced); + setMinStockLevel(data.minStockLevel ? String(data.minStockLevel) : ''); + setStorageInstructions(data.storageInstructions || ''); + setShelfLifeDays(data.shelfLifeDays ? String(data.shelfLifeDays) : ''); + setNotes(data.notes || ''); + setLoadingData(false); + }); + } else { + // Add mode: reset all fields + setLoadingData(false); setName(''); setCategoryId(''); setSubcategoryId(''); @@ -99,21 +141,17 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred setStorageInstructions(''); setShelfLifeDays(''); setNotes(''); - setNewCategoryName(''); - setNewSubcategoryName(''); - setNewSupplierName(''); - setCreatingCategory(false); - setCreatingSubcategory(false); - setCreatingSupplier(false); - setSaving(false); - setError(''); } - }, [open]); + }, [open, ingredientId]); - // Clear subcategory when category changes + // Clear subcategory when category changes (only in add mode to avoid overwriting fetched data) + const [categoryUserChanged, setCategoryUserChanged] = useState(false); useEffect(() => { - setSubcategoryId(''); - }, [categoryId]); + if (categoryUserChanged) { + setSubcategoryId(''); + setCategoryUserChanged(false); + } + }, [categoryId, categoryUserChanged]); const filteredSubcategories = categoryId && categoryId !== CREATE_NEW ? subcategories.filter(s => s.categoryId === categoryId) @@ -224,8 +262,11 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred if (notes.trim()) payload.notes = notes.trim(); } - const res = await fetch('/api/ingredients', { - method: 'POST', + const url = isEditMode ? `/api/ingredients/${ingredientId}` : '/api/ingredients'; + const method = isEditMode ? 'PUT' : 'POST'; + + const res = await fetch(url, { + method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); @@ -259,7 +300,7 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred
{/* Header */}
-

Add Ingredient

+

{isEditMode ? 'Edit Ingredient' : 'Add Ingredient'}