"use client"; import { AlertTriangle, Boxes, PackageX, Pill, Plus, Search } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { AddInventoryDialog } from "@/components/pharmacy/add-inventory-dialog"; import { InventoryDetailDialog } from "@/components/pharmacy/inventory-detail-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { type Availability, type InventoryInput, type InventoryItem, availabilityOf, createInventory, deleteInventory, listInventory, } from "@/lib/inventory"; import { notify } from "@/lib/toast"; const availabilityVariant: Record< Availability, "success" | "warning" | "destructive" > = { "in-stock": "success", low: "warning", out: "destructive", }; function Kpi({ label, value, icon: Icon, }: { label: string; value: string; icon: typeof Pill; }) { return (
{label} {value}
); } function ItemRow({ item, onOpen, }: { item: InventoryItem; onOpen: () => void; }) { const { t } = useTranslation(); const availability = availabilityOf(item); const descriptor = [item.strength, item.form].filter(Boolean).join(" · "); return (
{ if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onOpen(); } }} role="button" tabIndex={0} >
{item.name} {descriptor && ( {" "} · {descriptor} )} {t("inventory.stock", { count: item.stockQuantity, unit: item.unit || "units", })} {item.reorderThreshold > 0 && ` · ${t("inventory.reorderAt", { count: item.reorderThreshold })}`} {item.location && ` · ${t("inventory.location", { location: item.location })}`}
{t(`inventory.availability.${availability}`)}
); } // The pharmacy inventory page: a searchable view of the clinic's medication // stock with at-a-glance availability. Pharmacy holds inventory read/write; // this view is read/search only (stock edits are a later step). export function InventoryView() { const { t } = useTranslation(); const [items, setItems] = useState([]); const [query, setQuery] = useState(""); const [addOpen, setAddOpen] = useState(false); const [selected, setSelected] = useState(null); const [detailOpen, setDetailOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false); const openItem = (item: InventoryItem) => { setSelected(item); setDetailOpen(true); }; const removeItem = async () => { if (!selected) return; const id = selected.id; try { await deleteInventory(id); setItems((prev) => prev.filter((it) => it.id !== id)); setDetailOpen(false); notify.success(t("inventory.delete.doneTitle"), selected.name); } catch { notify.error( t("inventory.delete.failedTitle"), t("inventory.delete.failedBody"), ); } finally { setConfirmOpen(false); } }; // Persist a new item, then prepend the saved record so it appears immediately. const addItem = async (input: InventoryInput) => { try { const created = await createInventory(input); setItems((prev) => [created, ...prev]); } catch { notify.error( t("inventory.dialog.failedTitle"), t("inventory.dialog.failedBody"), ); } }; useEffect(() => { let active = true; listInventory() .then((data) => { if (active) setItems(data); }) .catch(() => { /* api-client redirects on 401; otherwise leave the list empty */ }); return () => { active = false; }; }, []); const search = query.trim().toLowerCase(); const results = useMemo(() => { const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name)); if (!search) return sorted; return sorted.filter( (item) => item.name.toLowerCase().includes(search) || item.form.toLowerCase().includes(search) || item.strength.toLowerCase().includes(search) || item.location.toLowerCase().includes(search), ); }, [items, search]); const lowCount = items.filter((i) => availabilityOf(i) === "low").length; const outCount = items.filter((i) => availabilityOf(i) === "out").length; const kpis = [ { label: t("inventory.kpi.total"), value: String(items.length), icon: Boxes }, { label: t("inventory.kpi.low"), value: String(lowCount), icon: AlertTriangle }, { label: t("inventory.kpi.out"), value: String(outCount), icon: PackageX }, ]; return (

{t("inventory.title")}

{t("inventory.subtitle")}

setQuery(event.target.value)} placeholder={t("inventory.searchPlaceholder")} value={query} />
setConfirmOpen(true)} onOpenChange={setDetailOpen} open={detailOpen} />
{kpis.map((k) => ( ))}

{t("inventory.list.title")}

{t("inventory.list.description")}

{results.map((item) => ( openItem(item)} /> ))} {results.length === 0 && (

{search ? t("inventory.list.noMatches") : t("inventory.list.empty")}

)}
); }