fix click dosent do nothing

This commit is contained in:
CelaniDe 2026-02-11 22:10:39 +01:00
parent 4fbc8003a0
commit 2ad1edfea9
6 changed files with 504 additions and 37 deletions

View File

@ -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(

View File

@ -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<Subcategory[]>([]);
const [loading, setLoading] = useState(true);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [editIngredientId, setEditIngredientId] = useState<string | null>(null);
const [viewIngredientId, setViewIngredientId] = useState<string | null>(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() {
<p className="text-gray-500">No ingredients found.</p>
</div>
) : (
<IngredientTable ingredients={ingredients} />
<IngredientTable
ingredients={ingredients}
onView={(id) => setViewIngredientId(id)}
onEdit={(id) => setEditIngredientId(id)}
onDelete={(id, name) => setDeleteIngredient({ id, name })}
/>
)}
{/* Pagination */}
@ -308,9 +318,28 @@ export default function IngredientsPage() {
</main>
<AddIngredientModal
open={isAddModalOpen}
onClose={() => setIsAddModalOpen(false)}
open={isAddModalOpen || !!editIngredientId}
onClose={() => { setIsAddModalOpen(false); setEditIngredientId(null); }}
onSaved={fetchIngredients}
ingredientId={editIngredientId ?? undefined}
/>
<ViewIngredientModal
open={!!viewIngredientId}
ingredientId={viewIngredientId}
onClose={() => setViewIngredientId(null)}
/>
<DeleteConfirmModal
open={!!deleteIngredient}
ingredientName={deleteIngredient?.name ?? ''}
onClose={() => setDeleteIngredient(null)}
onConfirm={async () => {
if (!deleteIngredient) return;
await fetch(`/api/ingredients/${deleteIngredient.id}`, { method: 'DELETE' });
setDeleteIngredient(null);
fetchIngredients();
}}
/>
</div>
);

View File

@ -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<Category[]>([]);
const [subcategories, setSubcategories] = useState<Subcategory[]>([]);
@ -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(() => {
if (categoryUserChanged) {
setSubcategoryId('');
}, [categoryId]);
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
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-2xl mx-4 my-8 sm:my-16">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">Add Ingredient</h2>
<h2 className="text-lg font-semibold text-gray-900">{isEditMode ? 'Edit Ingredient' : 'Add Ingredient'}</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@ -269,12 +310,22 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred
{/* Body */}
<div className="px-6 py-5 space-y-5">
{error && (
{loadingData && (
<div className="flex items-center justify-center py-12">
<svg className="animate-spin h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
<span className="ml-2 text-sm text-gray-500">Loading ingredient data...</span>
</div>
)}
{!loadingData && error && (
<div className="px-4 py-3 rounded-lg bg-red-50 border border-red-200 text-sm text-red-700">
{error}
</div>
)}
{!loadingData && <>
{/* Ingredient Name */}
<div>
<label className={labelClass}>Ingredient Name</label>
@ -295,7 +346,7 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred
<label className={labelClass}>Category</label>
<select
value={categoryId}
onChange={e => setCategoryId(e.target.value)}
onChange={e => { setCategoryId(e.target.value); setCategoryUserChanged(true); }}
className={selectClass}
>
<option value="">Select category</option>
@ -593,6 +644,7 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred
</div>
)}
</div>
</>}
</div>
{/* Footer */}
@ -606,7 +658,7 @@ export default function AddIngredientModal({ open, onClose, onSaved }: AddIngred
</button>
<button
onClick={handleSave}
disabled={saving}
disabled={saving || loadingData}
className="px-5 py-2.5 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors disabled:opacity-50"
>
{saving ? 'Saving…' : 'Save'}

View File

@ -0,0 +1,61 @@
'use client';
import React, { useState } from 'react';
interface DeleteConfirmModalProps {
open: boolean;
ingredientName: string;
onClose: () => void;
onConfirm: () => Promise<void>;
}
export default function DeleteConfirmModal({ open, ingredientName, onClose, onConfirm }: DeleteConfirmModalProps) {
const [deleting, setDeleting] = useState(false);
if (!open) return null;
const handleDelete = async () => {
setDeleting(true);
await onConfirm();
setDeleting(false);
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-black/40" onClick={onClose} />
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-md mx-4">
<div className="px-6 py-5">
<div className="flex items-center gap-3 mb-4">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
<svg className="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</div>
<h2 className="text-lg font-semibold text-gray-900">Delete Ingredient</h2>
</div>
<p className="text-sm text-gray-600">
Are you sure you want to delete <span className="font-semibold text-gray-900">{ingredientName}</span>? This action cannot be undone.
</p>
</div>
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-200">
<button
onClick={onClose}
disabled={deleting}
className="px-4 py-2.5 rounded-lg border border-gray-300 bg-white text-gray-700 text-sm font-medium hover:bg-gray-50 transition-colors disabled:opacity-50"
>
Cancel
</button>
<button
onClick={handleDelete}
disabled={deleting}
className="px-4 py-2.5 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors disabled:opacity-50"
>
{deleting ? 'Deleting...' : 'Delete'}
</button>
</div>
</div>
</div>
);
}

View File

@ -1,6 +1,6 @@
'use client';
import React, { useState } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
export interface Ingredient {
_id: string;
@ -19,10 +19,33 @@ export interface Ingredient {
interface IngredientTableProps {
ingredients: Ingredient[];
onView: (id: string) => void;
onEdit: (id: string) => void;
onDelete: (id: string, name: string) => void;
}
export default function IngredientTable({ ingredients }: IngredientTableProps) {
export default function IngredientTable({ ingredients, onView, onEdit, onDelete }: IngredientTableProps) {
const [selectedIngredients, setSelectedIngredients] = useState<Set<string>>(new Set());
const [openMenuId, setOpenMenuId] = useState<string | null>(null);
const [menuPos, setMenuPos] = useState<{ top: number; right: number } | null>(null);
const menuRef = useRef<HTMLDivElement>(null);
// Close menu on outside click or scroll
useEffect(() => {
if (!openMenuId) return;
const handleClick = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setOpenMenuId(null);
}
};
const handleScroll = () => setOpenMenuId(null);
document.addEventListener('mousedown', handleClick);
window.addEventListener('scroll', handleScroll, true);
return () => {
document.removeEventListener('mousedown', handleClick);
window.removeEventListener('scroll', handleScroll, true);
};
}, [openMenuId]);
const toggleIngredient = (id: string) => {
const newSelected = new Set(selectedIngredients);
@ -46,10 +69,41 @@ export default function IngredientTable({ ingredients }: IngredientTableProps) {
return unitPrice * (1 + vat / 100);
};
const handleMenuAction = useCallback((action: 'view' | 'edit' | 'delete', id: string, name: string) => {
setOpenMenuId(null);
if (action === 'view') onView(id);
else if (action === 'edit') onEdit(id);
else onDelete(id, name);
}, [onView, onEdit, onDelete]);
const toggleMenu = (ingredientId: string, e: React.MouseEvent<HTMLButtonElement>) => {
if (openMenuId === ingredientId) {
setOpenMenuId(null);
setMenuPos(null);
} else {
const rect = e.currentTarget.getBoundingClientRect();
setMenuPos({ top: rect.bottom + 4, right: window.innerWidth - rect.right });
setOpenMenuId(ingredientId);
}
};
const renderMenuButton = (ingredient: Ingredient) => (
<button
onClick={(e) => toggleMenu(ingredient._id, e)}
className="text-gray-400 hover:text-gray-600"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
</svg>
</button>
);
const openIngredient = openMenuId ? ingredients.find(i => i._id === openMenuId) : null;
return (
<>
{/* Desktop & Tablet Table View */}
<div className="hidden md:block bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="hidden md:block bg-white rounded-lg border border-gray-200">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
@ -127,11 +181,7 @@ export default function IngredientTable({ ingredients }: IngredientTableProps) {
</a>
</td>
<td className="px-4 lg:px-6 py-4">
<button className="text-gray-400 hover:text-gray-600">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
</svg>
</button>
{renderMenuButton(ingredient)}
</td>
</tr>
))}
@ -157,11 +207,7 @@ export default function IngredientTable({ ingredients }: IngredientTableProps) {
<h3 className="text-sm font-semibold text-gray-900">{ingredient.name}</h3>
<p className="text-xs text-gray-500 mt-0.5">ID: {ingredient.code}</p>
</div>
<button className="text-gray-400 hover:text-gray-600">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
</svg>
</button>
{renderMenuButton(ingredient)}
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
@ -210,6 +256,44 @@ export default function IngredientTable({ ingredients }: IngredientTableProps) {
</div>
))}
</div>
{/* Fixed-position dropdown menu (renders outside overflow containers) */}
{openMenuId && menuPos && openIngredient && (
<div
ref={menuRef}
className="fixed w-40 bg-white rounded-lg border border-gray-200 shadow-lg z-50 py-1"
style={{ top: menuPos.top, right: menuPos.right }}
>
<button
onClick={() => handleMenuAction('view', openIngredient._id, openIngredient.name)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
View
</button>
<button
onClick={() => handleMenuAction('edit', openIngredient._id, openIngredient.name)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
Edit
</button>
<button
onClick={() => handleMenuAction('delete', openIngredient._id, openIngredient.name)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button>
</div>
)}
</>
);
}

View File

@ -0,0 +1,224 @@
'use client';
import React, { useState, useEffect } from 'react';
interface IngredientDetail {
_id: string;
code: string;
name: string;
category: string;
subcategory: string;
quantity: number;
unit: string;
unitPrice: number;
vat: number;
supplier: string;
discountType?: 'value' | 'percent';
discountValue?: number;
applyDiscountToNet?: boolean;
minStockLevel?: number;
storageInstructions?: string;
shelfLifeDays?: number;
notes?: string;
createdAt: string;
updatedAt: string;
}
interface ViewIngredientModalProps {
open: boolean;
ingredientId: string | null;
onClose: () => void;
}
export default function ViewIngredientModal({ open, ingredientId, onClose }: ViewIngredientModalProps) {
const [ingredient, setIngredient] = useState<IngredientDetail | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!open || !ingredientId) return;
setLoading(true);
setIngredient(null);
fetch(`/api/ingredients/${ingredientId}`)
.then(r => r.json())
.then(data => {
setIngredient(data);
setLoading(false);
});
}, [open, ingredientId]);
if (!open) return null;
const fmt = (n: number) => n < 0 ? `-\u20AC${Math.abs(n).toFixed(2)}` : `\u20AC${n.toFixed(2)}`;
const netPrice = ingredient ? ingredient.unitPrice * (1 + ingredient.vat / 100) : 0;
const hasDiscount = ingredient && ingredient.discountValue && ingredient.discountValue > 0;
const hasAdvanced = ingredient && (ingredient.minStockLevel || ingredient.storageInstructions || ingredient.shelfLifeDays || ingredient.notes);
return (
<div className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto">
<div className="fixed inset-0 bg-black/40" onClick={onClose} />
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-2xl mx-4 my-8 sm:my-16">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">Ingredient Details</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Body */}
<div className="px-6 py-5">
{loading && (
<div className="flex items-center justify-center py-12">
<svg className="animate-spin h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
<span className="ml-2 text-sm text-gray-500">Loading...</span>
</div>
)}
{!loading && ingredient && (
<div className="space-y-5">
{/* Name & Code */}
<div>
<h3 className="text-xl font-bold text-gray-900">{ingredient.name}</h3>
<p className="text-sm text-gray-500 mt-0.5">Code: {ingredient.code}</p>
</div>
{/* Category / Subcategory */}
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Category</p>
<p className="text-sm text-gray-900 mt-1">{ingredient.category}</p>
</div>
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Subcategory</p>
<p className="text-sm text-gray-900 mt-1">{ingredient.subcategory}</p>
</div>
</div>
{/* Quantity / Unit */}
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Quantity</p>
<p className="text-sm text-gray-900 mt-1">{ingredient.quantity} {ingredient.unit}</p>
</div>
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Supplier</p>
<p className="text-sm text-blue-600 font-medium mt-1">{ingredient.supplier}</p>
</div>
</div>
{/* Pricing */}
<div>
<h4 className="text-sm font-semibold text-gray-900 mb-3">Pricing</h4>
<div className="grid grid-cols-3 gap-4 rounded-lg bg-gray-50 border border-gray-200 p-4">
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Gross Price</p>
<p className="text-lg font-bold text-gray-900 mt-1">{fmt(ingredient.unitPrice)}</p>
</div>
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">VAT</p>
<p className="text-lg font-bold text-gray-900 mt-1">{ingredient.vat}%</p>
</div>
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Net Price</p>
<p className="text-lg font-bold text-gray-900 mt-1">{fmt(netPrice)}</p>
</div>
</div>
</div>
{/* Discount */}
{hasDiscount && (
<div>
<h4 className="text-sm font-semibold text-gray-900 mb-3">Discount</h4>
<div className="grid grid-cols-2 gap-4 rounded-lg bg-gray-50 border border-gray-200 p-4">
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Type</p>
<p className="text-sm text-gray-900 mt-1">
{ingredient.discountType === 'percent' ? 'Percentage' : 'Fixed value'}
</p>
</div>
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Value</p>
<p className="text-sm text-gray-900 mt-1">
{ingredient.discountType === 'percent'
? `${ingredient.discountValue}%`
: fmt(ingredient.discountValue!)}
</p>
</div>
{ingredient.applyDiscountToNet && (
<div className="col-span-2">
<p className="text-xs text-gray-500 italic">Applied to net price</p>
</div>
)}
</div>
</div>
)}
{/* Advanced */}
{hasAdvanced && (
<div>
<h4 className="text-sm font-semibold text-gray-900 mb-3">Additional Details</h4>
<div className="space-y-3 pl-4 border-l-2 border-gray-200">
{ingredient.minStockLevel != null && (
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Min Stock Level</p>
<p className="text-sm text-gray-900 mt-0.5">{ingredient.minStockLevel}</p>
</div>
)}
{ingredient.shelfLifeDays != null && (
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Shelf Life</p>
<p className="text-sm text-gray-900 mt-0.5">{ingredient.shelfLifeDays} days</p>
</div>
)}
{ingredient.storageInstructions && (
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Storage Instructions</p>
<p className="text-sm text-gray-900 mt-0.5">{ingredient.storageInstructions}</p>
</div>
)}
{ingredient.notes && (
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Notes</p>
<p className="text-sm text-gray-900 mt-0.5">{ingredient.notes}</p>
</div>
)}
</div>
</div>
)}
{/* Timestamps */}
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200">
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Created</p>
<p className="text-sm text-gray-700 mt-0.5">{new Date(ingredient.createdAt).toLocaleString()}</p>
</div>
<div>
<p className="text-xs text-gray-500 uppercase tracking-wide font-medium">Updated</p>
<p className="text-sm text-gray-700 mt-0.5">{new Date(ingredient.updatedAt).toLocaleString()}</p>
</div>
</div>
</div>
)}
</div>
{/* Footer */}
<div className="flex items-center justify-end px-6 py-4 border-t border-gray-200">
<button
onClick={onClose}
className="px-4 py-2.5 rounded-lg border border-gray-300 bg-white text-gray-700 text-sm font-medium hover:bg-gray-50 transition-colors"
>
Close
</button>
</div>
</div>
</div>
);
}