'use client'; import React, { useState, useEffect, useMemo } from 'react'; interface Category { _id: string; name: string; } interface Subcategory { _id: string; name: string; categoryId: string; } interface Supplier { _id: string; name: string; } interface AddIngredientModalProps { open: boolean; onClose: () => void; onSaved: () => void; } const CREATE_NEW = '__create_new__'; export default function AddIngredientModal({ open, onClose, onSaved }: AddIngredientModalProps) { // Reference data const [categories, setCategories] = useState([]); const [subcategories, setSubcategories] = useState([]); const [suppliers, setSuppliers] = useState([]); // Form fields const [name, setName] = useState(''); const [categoryId, setCategoryId] = useState(''); const [subcategoryId, setSubcategoryId] = useState(''); const [quantity, setQuantity] = useState(''); const [unit, setUnit] = useState(''); const [grossPrice, setGrossPrice] = useState(''); const [vat, setVat] = useState('9'); const [supplierId, setSupplierId] = useState(''); // Discount const [discountType, setDiscountType] = useState<'value' | 'percent'>('value'); const [discountValue, setDiscountValue] = useState(''); const [applyDiscountToNet, setApplyDiscountToNet] = useState(false); // Advanced const [showAdvanced, setShowAdvanced] = useState(false); const [minStockLevel, setMinStockLevel] = useState(''); const [storageInstructions, setStorageInstructions] = useState(''); const [shelfLifeDays, setShelfLifeDays] = useState(''); const [notes, setNotes] = useState(''); // Create-new inline states const [newCategoryName, setNewCategoryName] = useState(''); const [newSubcategoryName, setNewSubcategoryName] = useState(''); const [newSupplierName, setNewSupplierName] = useState(''); const [creatingCategory, setCreatingCategory] = useState(false); const [creatingSubcategory, setCreatingSubcategory] = useState(false); const [creatingSupplier, setCreatingSupplier] = useState(false); // Save state const [saving, setSaving] = useState(false); const [error, setError] = useState(''); // Fetch reference data useEffect(() => { if (!open) return; Promise.all([ fetch('/api/categories').then(r => r.json()), fetch('/api/subcategories').then(r => r.json()), fetch('/api/suppliers').then(r => r.json()), ]).then(([cats, subs, supps]) => { setCategories(cats); setSubcategories(subs); setSuppliers(supps); }); }, [open]); // Reset form when modal opens useEffect(() => { if (open) { setName(''); setCategoryId(''); setSubcategoryId(''); setQuantity(''); setUnit(''); setGrossPrice(''); setVat('9'); setSupplierId(''); setDiscountType('value'); setDiscountValue(''); setApplyDiscountToNet(false); setShowAdvanced(false); setMinStockLevel(''); setStorageInstructions(''); setShelfLifeDays(''); setNotes(''); setNewCategoryName(''); setNewSubcategoryName(''); setNewSupplierName(''); setCreatingCategory(false); setCreatingSubcategory(false); setCreatingSupplier(false); setSaving(false); setError(''); } }, [open]); // Clear subcategory when category changes useEffect(() => { setSubcategoryId(''); }, [categoryId]); const filteredSubcategories = categoryId && categoryId !== CREATE_NEW ? subcategories.filter(s => s.categoryId === categoryId) : subcategories; // Price calculations const gross = parseFloat(grossPrice) || 0; const vatRate = parseFloat(vat) || 0; const netPrice = gross * (1 + vatRate / 100); const discount = parseFloat(discountValue) || 0; const { effectiveNet, effectiveGross } = useMemo(() => { if (discount <= 0) return { effectiveNet: netPrice, effectiveGross: gross }; if (applyDiscountToNet) { const discountAmount = discountType === 'value' ? discount : netPrice * discount / 100; const effNet = netPrice - discountAmount; const effGross = vatRate > 0 ? effNet / (1 + vatRate / 100) : effNet; return { effectiveNet: effNet, effectiveGross: effGross }; } else { const discountAmount = discountType === 'value' ? discount : gross * discount / 100; const effGross = gross - discountAmount; const effNet = effGross * (1 + vatRate / 100); return { effectiveNet: effNet, effectiveGross: effGross }; } }, [gross, vatRate, netPrice, discount, discountType, applyDiscountToNet]); // Create-new handlers const handleCreateCategory = async () => { if (!newCategoryName.trim()) return; setCreatingCategory(true); const res = await fetch('/api/categories', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newCategoryName.trim() }), }); const created = await res.json(); setCategories(prev => [...prev, created].sort((a, b) => a.name.localeCompare(b.name))); setCategoryId(created._id); setNewCategoryName(''); setCreatingCategory(false); }; const handleCreateSubcategory = async () => { if (!newSubcategoryName.trim() || !categoryId || categoryId === CREATE_NEW) return; setCreatingSubcategory(true); const res = await fetch('/api/subcategories', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newSubcategoryName.trim(), categoryId }), }); const created = await res.json(); setSubcategories(prev => [...prev, created].sort((a, b) => a.name.localeCompare(b.name))); setSubcategoryId(created._id); setNewSubcategoryName(''); setCreatingSubcategory(false); }; const handleCreateSupplier = async () => { if (!newSupplierName.trim()) return; setCreatingSupplier(true); const res = await fetch('/api/suppliers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newSupplierName.trim() }), }); const created = await res.json(); setSuppliers(prev => [...prev, created].sort((a, b) => a.name.localeCompare(b.name))); setSupplierId(created._id); setNewSupplierName(''); setCreatingSupplier(false); }; const handleSave = async () => { setError(''); if (!name.trim()) { setError('Ingredient name is required.'); return; } if (!categoryId || categoryId === CREATE_NEW) { setError('Please select a category.'); return; } if (!subcategoryId || subcategoryId === CREATE_NEW) { setError('Please select a subcategory.'); return; } if (!quantity || parseFloat(quantity) <= 0) { setError('Quantity must be greater than 0.'); return; } if (!unit.trim()) { setError('Unit is required.'); return; } if (!grossPrice || gross <= 0) { setError('Gross price must be greater than 0.'); return; } if (!supplierId || supplierId === CREATE_NEW) { setError('Please select a supplier.'); return; } setSaving(true); const payload: Record = { name: name.trim(), categoryId, subcategoryId, quantity: parseFloat(quantity), unit: unit.trim(), unitPrice: gross, vat: vatRate, supplierId, }; if (discount > 0) { payload.discountType = discountType; payload.discountValue = discount; payload.applyDiscountToNet = applyDiscountToNet; } if (showAdvanced) { if (minStockLevel) payload.minStockLevel = parseFloat(minStockLevel); if (storageInstructions.trim()) payload.storageInstructions = storageInstructions.trim(); if (shelfLifeDays) payload.shelfLifeDays = parseInt(shelfLifeDays); if (notes.trim()) payload.notes = notes.trim(); } const res = await fetch('/api/ingredients', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!res.ok) { const data = await res.json(); setError(data.error || 'Failed to save ingredient.'); setSaving(false); return; } setSaving(false); onSaved(); onClose(); }; if (!open) return null; const inputClass = 'w-full px-3 py-2 rounded-lg border border-gray-300 bg-white text-gray-900 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors'; const labelClass = 'block text-sm font-medium text-gray-700 mb-1'; const selectClass = 'w-full px-3 py-2 rounded-lg border border-gray-300 bg-white text-gray-900 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors'; const fmt = (n: number) => n < 0 ? `-€${Math.abs(n).toFixed(2)}` : `€${n.toFixed(2)}`; return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

Add Ingredient

{/* Body */}
{error && (
{error}
)} {/* Ingredient Name */}
setName(e.target.value)} placeholder="e.g. Organic Butter" className={inputClass} autoFocus />
{/* Category & Subcategory */}
{/* Category */}
{categoryId === CREATE_NEW && (
setNewCategoryName(e.target.value)} placeholder="Category name" className={inputClass} onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), handleCreateCategory())} />
)}
{/* Subcategory */}
{subcategoryId === CREATE_NEW && categoryId && categoryId !== CREATE_NEW && (
setNewSubcategoryName(e.target.value)} placeholder="Subcategory name" className={inputClass} onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), handleCreateSubcategory())} />
)}
{/* Quantity, Unit & Supplier */}
setQuantity(e.target.value)} placeholder="0" min="0" step="any" className={inputClass} />
setUnit(e.target.value)} placeholder="kg, L, pcs…" className={inputClass} />
{supplierId === CREATE_NEW && (
setNewSupplierName(e.target.value)} placeholder="Supplier name" className={inputClass} onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), handleCreateSupplier())} />
)}
{/* Pricing */}

Pricing

setGrossPrice(e.target.value)} placeholder="0.00" min="0" step="0.01" className={inputClass} />
setVat(e.target.value)} placeholder="0" min="0" step="0.1" className={inputClass} />
{fmt(netPrice)}
{/* Discount */}

Discount

setDiscountValue(e.target.value)} placeholder="0" min="0" step={discountType === 'value' ? '0.01' : '0.1'} className={inputClass} />
{/* Summary */}

Effective Gross

{fmt(effectiveGross)}

Effective Net

{fmt(effectiveNet)}

{/* Advanced Options */}
{showAdvanced && (
setMinStockLevel(e.target.value)} placeholder="0" min="0" step="any" className={inputClass} />
setShelfLifeDays(e.target.value)} placeholder="0" min="0" className={inputClass} />
setStorageInstructions(e.target.value)} placeholder="e.g. Keep refrigerated at 2-8°C" className={inputClass} />