"use client"; import { AlertTriangle, Boxes, PackageX, Pill, Search } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { type Availability, type InventoryItem, availabilityOf, listInventory, } from "@/lib/inventory"; 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 }: { item: InventoryItem }) { const { t } = useTranslation(); const availability = availabilityOf(item); const descriptor = [item.strength, item.form].filter(Boolean).join(" · "); return (
{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(""); 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} />
{kpis.map((k) => ( ))}

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

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

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

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

)}
); }