// ui/src/components/inventory/InventoryItemFormPage.tsx
import { useEffect, useState } from 'react';
import { ChevronLeft } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Skeleton } from '@/components/ui/skeleton';
import { toast } from 'sonner';
import {
createInventoryItem,
updateInventoryItem,
getInventoryItems,
getLaboratories,
type InventoryItem,
type CreateInventoryItemDto,
type Laboratory,
} from '@/lib/serverComm';
import { numberOrUndefined, numberOrZero, parseNumberInput, type NumberInputValue } from '@/lib/number-input';
interface Props {
itemId: 'new' | string;
onBack: () => void;
}
type FormState = {
name: string;
category: string;
sku: string;
quantity: NumberInputValue;
unit: string;
minStock: NumberInputValue;
maxStock: NumberInputValue;
reorderPoint: NumberInputValue;
reorderQuantity: NumberInputValue;
unitCost: NumberInputValue;
supplier: string;
expiryDate: string;
location: string;
};
const EMPTY: FormState = {
name: '', category: 'supplies', sku: '', quantity: '', unit: 'piece',
minStock: '', maxStock: '', reorderPoint: '', reorderQuantity: '',
unitCost: '', supplier: '', expiryDate: '', location: '',
};
export default function InventoryItemFormPage({ itemId, onBack }: Props) {
const isNew = itemId === 'new';
const [form, setForm] = useState<FormState>(EMPTY);
const [labs, setLabs] = useState<Laboratory[]>([]);
const [loadingItem, setLoadingItem] = useState(!isNew);
const [saving, setSaving] = useState(false);
// Load labs for supplier dropdown
useEffect(() => {
getLaboratories().then(setLabs).catch(() => {});
}, []);
// Load existing item for edit
useEffect(() => {
if (isNew) return;
setLoadingItem(true);
getInventoryItems()
.then(items => {
const item = items.find((i: InventoryItem) => i.id === itemId);
if (!item) { toast.error('Item not found'); onBack(); return; }
setForm({
name: item.name || '',
category: item.category || 'supplies',
sku: item.sku || '',
quantity: item.quantity ?? '',
unit: item.unit || 'piece',
minStock: item.minStock ?? '',
maxStock: item.maxStock ?? '',
reorderPoint: (item.reorderPoint ?? '') as NumberInputValue,
reorderQuantity: (item.reorderQuantity ?? '') as NumberInputValue,
unitCost: item.unitCost ? Number(item.unitCost) : '',
supplier: item.supplier || '',
expiryDate: item.expiryDate || '',
location: item.location || '',
});
})
.catch(() => { toast.error('Failed to load item'); onBack(); })
.finally(() => setLoadingItem(false));
}, [itemId, isNew, onBack]);
const set = (key: keyof FormState, value: unknown) =>
setForm(prev => ({ ...prev, [key]: value }));
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!form.name.trim()) { toast.error('Name is required'); return; }
try {
setSaving(true);
const payload: CreateInventoryItemDto = {
name: form.name.trim(),
category: form.category || undefined,
sku: form.sku.trim() || undefined,
quantity: numberOrZero(form.quantity),
unit: form.unit || undefined,
minStock: numberOrUndefined(form.minStock),
maxStock: numberOrUndefined(form.maxStock),
reorderPoint: numberOrUndefined(form.reorderPoint),
reorderQuantity: numberOrUndefined(form.reorderQuantity),
unitCost: form.unitCost === '' ? undefined : String(numberOrZero(form.unitCost)),
supplier: form.supplier.trim() || undefined,
expiryDate: form.expiryDate.trim() || undefined,
location: form.location.trim() || undefined,
};
if (isNew) {
await createInventoryItem(payload);
toast.success('Item added');
} else {
await updateInventoryItem(itemId, payload);
toast.success('Item updated');
}
onBack();
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to save item');
} finally {
setSaving(false);
}
};
if (loadingItem) {
return (
<div className="space-y-6">
<Skeleton className="h-8 w-48" />
<div className="grid grid-cols-2 gap-4">
{[1,2,3,4,5,6].map(i => <Skeleton key={i} className="h-16 w-full" />)}
</div>
</div>
);
}
return (
<div className="space-y-6 max-w-3xl">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={onBack} className="gap-1 -ml-1">
<ChevronLeft className="h-4 w-4" />
Back
</Button>
<div>
<h1 className="text-xl font-bold">{isNew ? 'New inventory item' : 'Edit inventory item'}</h1>
<p className="text-sm text-muted-foreground">
{isNew ? 'Add a new item to the clinic inventory.' : 'Update details for this item.'}
</p>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Row 1 */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label htmlFor="inv-name" className="text-xs">Item name *</Label>
<Input id="inv-name" value={form.name} onChange={e => set('name', e.target.value)} required placeholder="e.g., Nitrile Gloves (M)" />
</div>
<div className="space-y-1.5">
<Label htmlFor="inv-sku" className="text-xs">SKU</Label>
<Input id="inv-sku" value={form.sku} onChange={e => set('sku', e.target.value)} placeholder="Auto-generated if empty" />
</div>
</div>
{/* Row 2 */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label className="text-xs">Category</Label>
<Select value={form.category} onValueChange={v => set('category', v)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="supplies">Supplies</SelectItem>
<SelectItem value="medications">Medications</SelectItem>
<SelectItem value="equipment">Equipment</SelectItem>
<SelectItem value="materials">Materials</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Supplier / Lab</Label>
{labs.length > 0 ? (
<Select value={form.supplier} onValueChange={v => set('supplier', v)}>
<SelectTrigger><SelectValue placeholder="Select lab or supplier" /></SelectTrigger>
<SelectContent>
{labs.map(lab => (
<SelectItem key={lab.id} value={lab.name}>{lab.name}</SelectItem>
))}
<SelectItem value="__other__">Other (type below)</SelectItem>
</SelectContent>
</Select>
) : (
<Input
value={form.supplier}
onChange={e => set('supplier', e.target.value)}
placeholder="Supplier name — or add labs in Settings"
/>
)}
{labs.length > 0 && form.supplier === '__other__' && (
<Input
className="mt-1.5"
value={''}
onChange={e => set('supplier', e.target.value)}
placeholder="Enter supplier name"
autoFocus
/>
)}
</div>
</div>
{/* Row 3 */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="space-y-1.5">
<Label htmlFor="inv-qty" className="text-xs">Opening quantity *</Label>
<Input id="inv-qty" type="number" min={0} value={form.quantity}
onChange={e => set('quantity', parseNumberInput(e.target.value))} required />
</div>
<div className="space-y-1.5">
<Label className="text-xs">Unit</Label>
<Select value={form.unit} onValueChange={v => set('unit', v)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="piece">Piece</SelectItem>
<SelectItem value="box">Box</SelectItem>
<SelectItem value="pack">Pack</SelectItem>
<SelectItem value="bottle">Bottle</SelectItem>
<SelectItem value="kit">Kit</SelectItem>
<SelectItem value="roll">Roll</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label htmlFor="inv-cost" className="text-xs">Unit cost</Label>
<Input id="inv-cost" type="number" min={0} step="0.01" value={form.unitCost}
onChange={e => set('unitCost', parseNumberInput(e.target.value))} />
</div>
</div>
{/* Row 4 — stock thresholds */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label htmlFor="inv-reorder" className="text-xs">Reorder point</Label>
<Input id="inv-reorder" type="number" min={0} value={form.reorderPoint}
onChange={e => set('reorderPoint', parseNumberInput(e.target.value))}
placeholder="Triggers low-stock alert email" />
</div>
<div className="space-y-1.5">
<Label htmlFor="inv-reorderqty" className="text-xs">Reorder quantity</Label>
<Input id="inv-reorderqty" type="number" min={0} value={form.reorderQuantity}
onChange={e => set('reorderQuantity', parseNumberInput(e.target.value))}
placeholder="Suggested order size" />
</div>
</div>
{/* Row 5 */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label htmlFor="inv-minstock" className="text-xs">Min stock</Label>
<Input id="inv-minstock" type="number" min={0} value={form.minStock}
onChange={e => set('minStock', parseNumberInput(e.target.value))} />
</div>
<div className="space-y-1.5">
<Label htmlFor="inv-maxstock" className="text-xs">Max stock</Label>
<Input id="inv-maxstock" type="number" min={0} value={form.maxStock}
onChange={e => set('maxStock', parseNumberInput(e.target.value))} />
</div>
</div>
{/* Row 6 */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label htmlFor="inv-expiry" className="text-xs">Expiry date</Label>
<Input id="inv-expiry" type="date" value={form.expiryDate}
onChange={e => set('expiryDate', e.target.value)} />
</div>
<div className="space-y-1.5">
<Label htmlFor="inv-location" className="text-xs">Storage location</Label>
<Input id="inv-location" value={form.location}
onChange={e => set('location', e.target.value)}
placeholder="e.g., Cabinet A, Shelf 2" />
</div>
</div>
<div className="flex items-center gap-3 pt-2">
<Button type="submit" disabled={saving}>
{saving ? 'Saving…' : isNew ? 'Add item' : 'Save changes'}
</Button>
<Button type="button" variant="outline" onClick={onBack} disabled={saving}>Cancel</Button>
</div>
</form>
</div>
);
}