import { Box, Button, Card, CardContent, Dialog, DialogContent, DialogTitle, IconButton, Stack, TextField, Typography, } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import { ChangeEvent, useEffect, useState } from 'react'; import { Product } from '../models/Product'; import { BarcodeScannerView } from './BarcodeScannerView'; interface ProductRowProps { product: Product; // list of category display names for the select categories: string[]; // map of category id -> category name, used to resolve ids to names categoriesById: Map; onSave: ( productId: string, updates: { name: string; category: string; // this is the *name* when passed back to parent barcode?: string; }, ) => Promise | void; onDelete: (productId: string) => Promise | void; } interface ProductFormState { name: string; category: string; barcode: string; } const getInitialFormState = (product: Product, categoriesById: Map): ProductFormState => ({ name: product.name, // If product.category is an id, resolve to name; otherwise assume it is already a name category: categoriesById.get(product.category) ?? product.category ?? '', barcode: product.barcode ?? '', }); export const ProductRow = ({ product, categories, categoriesById, onSave, onDelete }: ProductRowProps) => { const [isEditing, setIsEditing] = useState(false); const [formState, setFormState] = useState(() => getInitialFormState(product, categoriesById)); const [isScannerOpen, setIsScannerOpen] = useState(false); const [fieldErrors, setFieldErrors] = useState<{ name?: string; barcode?: string }>({}); useEffect(() => { setFormState(getInitialFormState(product, categoriesById)); setFieldErrors({}); }, [product, categoriesById]); const handleChange = (field: keyof ProductFormState) => (event: ChangeEvent) => { setFormState((prev) => ({ ...prev, [field]: event.target.value })); setFieldErrors((prev) => ({ ...prev, [field]: undefined })); }; const handleSave = async () => { if (!formState.name) return; try { await onSave(product.id, { name: formState.name, category: formState.category, barcode: formState.barcode || undefined, }); setIsEditing(false); setFieldErrors({}); } catch (error) { if (error instanceof Error && error.name === 'DuplicateNameError') { setFieldErrors({ name: 'A product with this name already exists.' }); return; } if (error instanceof Error && error.name === 'DuplicateBarcodeError') { setFieldErrors({ barcode: 'This barcode is already assigned to another product.' }); return; } throw error; } }; const handleCancel = () => { setIsEditing(false); setFormState(getInitialFormState(product, categoriesById)); setFieldErrors({}); }; return ( {isEditing ? ( {categories.map((cat) => ( ))} {formState.barcode ? ( { setFormState((prev) => ({ ...prev, barcode: '' })); setFieldErrors((prev) => ({ ...prev, barcode: undefined })); }} > Clear ), }} /> ) : ( )} onDelete(product.id)} size="small" color="error"> ) : ( {product.name} {/* Resolve id -> name for display */} {categoriesById.get(product.category) ?? product.category ?? ''} {product.barcode ? ( Barcode: {product.barcode} ) : null} setIsEditing(true)} size="small"> onDelete(product.id)} size="small"> )} setIsScannerOpen(false)} fullWidth> Scan Barcode { setFormState((prev) => ({ ...prev, barcode: code })); setIsScannerOpen(false); }} /> ); };