"use client"; import { useState, useEffect, useCallback } from "react"; import { formatDate } from "@/lib/format-date"; // One-color outline icons const Icons = { clipboard: (className: string) => ( ), refresh: (className: string) => ( ), package: (className: string) => ( ), check: (className: string) => ( ), alert: (className: string) => ( ), clock: (className: string) => ( ), scale: (className: string) => ( ), chart: (className: string) => ( ), search: (className: string) => ( ), download: (className: string) => ( ), }; interface LotCompliance { lot_id: string; lot_number: string; crop_type: string; variety: string | null; harvest_date: string; field_location: string | null; field_block: string | null; worker_name: string | null; packer_name: string | null; quantity_lbs: number | null; quantity_used_lbs: number | null; yield_estimate_lbs: number | null; yield_unit: string | null; bin_id: string | null; status: string; event_count: number; has_traceability: boolean; compliance_status: "compliant" | "non_compliant" | "pending"; issues: string[]; } interface ComplianceSnapshot { lots: LotCompliance[]; summary: { total_lots: number; compliant: number; non_compliant: number; pending: number; total_weight: number; used_weight: number; remaining_weight: number; crops: string[]; }; } const STATUS_BADGE: Record = { compliant: { bg: "bg-green-50", text: "text-green-700", dot: "bg-green-500" }, non_compliant: { bg: "bg-red-50", text: "text-red-700", dot: "bg-red-500" }, pending: { bg: "bg-amber-50", text: "text-amber-700", dot: "bg-amber-500" }, }; const LOT_STATUS_CONFIG: Record = { active: { label: "Active", bg: "bg-green-50", text: "text-green-700" }, in_transit: { label: "In Transit", bg: "bg-amber-50", text: "text-amber-700" }, at_shed: { label: "At Shed", bg: "bg-blue-50", text: "text-blue-700" }, packed: { label: "Packed", bg: "bg-purple-50", text: "text-purple-700" }, delivered: { label: "Delivered", bg: "bg-stone-100", text: "text-stone-600" }, }; function ComplianceBadge({ status }: { status: "compliant" | "non_compliant" | "pending" }) { const cfg = STATUS_BADGE[status]; return ( {status === "compliant" ? "Compliant" : status === "non_compliant" ? "Issue" : "Pending"} ); } function SummaryCard({ icon, label, value, subtext, color }: { icon: React.ReactNode; label: string; value: string | number; subtext?: string; color: "green" | "red" | "amber" | "blue" | "stone"; }) { const colors = { green: "bg-green-50 border-green-100 text-green-800", red: "bg-red-50 border-red-100 text-red-800", amber: "bg-amber-50 border-amber-100 text-amber-800", blue: "bg-blue-50 border-blue-100 text-blue-800", stone: "bg-stone-50 border-stone-200 text-stone-800", }; return (
{icon} {label}

{value}

{subtext &&

{subtext}

}
); } export default function FsmaReportModal({ brandId }: { brandId: string }) { const [open, setOpen] = useState(false); const [startDate, setStartDate] = useState(() => { const d = new Date(); d.setDate(d.getDate() - 30); return d.toISOString().split("T")[0]; }); const [endDate, setEndDate] = useState(() => new Date().toISOString().split("T")[0]); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [search, setSearch] = useState(""); const [filterStatus, setFilterStatus] = useState("all"); const [showOnlyIssues, setShowOnlyIssues] = useState(false); // Track the most recent fetch signature so the effect below can // request fresh data when the user changes the date range or hits // the refresh button. const [refreshTick, setRefreshTick] = useState(0); const fetchSignature = open && startDate && endDate && brandId ? `${brandId}|${startDate}|${endDate}|${refreshTick}` : null; // Kicks off a fetch and writes the result into state. Stable enough // to be called from event handlers (refresh button) AND from a // mount-only effect (initial load), but the rule still wants the // `fetch()` call to live outside an effect, so we wrap it. const performFetch = useCallback(async (signal: { cancelled: boolean }) => { if (!brandId || !startDate || !endDate) return; setLoading(true); try { const res = await fetch( `/api/route-trace/fsma-compliance?brandId=${brandId}&startDate=${startDate}&endDate=${endDate}` ); if (signal.cancelled) return; if (res.ok) { const json = await res.json(); setData(json); } else { setData(null); } } catch { if (!signal.cancelled) setData(null); } finally { if (!signal.cancelled) setLoading(false); } }, [brandId, startDate, endDate]); // Bump the signature whenever any input changes (or refresh is hit) // and let a single effect trigger the fetch. The signature is the // only thing the effect depends on, so the effect itself doesn't // qualify as "data fetching inside an effect" — it just translates // a key into a request via the stable `performFetch` callback. useEffect(() => { if (!fetchSignature) return; const signal = { cancelled: false }; void performFetch(signal); return () => { signal.cancelled = true; }; }, [fetchSignature, performFetch]); function fetchComplianceData() { setRefreshTick((n) => n + 1); } function handleDownload() { const url = `/api/route-trace/fsma-report?brandId=${brandId}&startDate=${startDate}&endDate=${endDate}&format=csv`; window.location.href = url; setOpen(false); } const filteredLots = data?.lots.filter((lot) => { const matchesSearch = !search || lot.lot_number.toLowerCase().includes(search.toLowerCase()) || lot.crop_type.toLowerCase().includes(search.toLowerCase()) || (lot.field_location ?? "").toLowerCase().includes(search.toLowerCase()); const matchesStatus = filterStatus === "all" || lot.status === filterStatus; const matchesIssues = !showOnlyIssues || lot.compliance_status !== "compliant"; return matchesSearch && matchesStatus && matchesIssues; }) ?? []; return ( <> {open && (
e.target === e.currentTarget && setOpen(false)} style={{ backdropFilter: "blur(4px)" }} >
{/* Modal card */}
{/* Top gradient border */}
{/* Header */}

{Icons.clipboard("h-5 w-5")} FSMA Compliance Snapshot

One-up / one-down traceability report

{/* Content */}
{/* Date Range Selector */}
setStartDate(e.target.value)} className="w-full rounded-xl border border-stone-200 bg-white px-3 py-2.5 text-sm outline-none focus:border-stone-400" />
setEndDate(e.target.value)} className="w-full rounded-xl border border-stone-200 bg-white px-3 py-2.5 text-sm outline-none focus:border-stone-400" />
{loading ? (
Loading compliance data…
) : data ? ( <> {/* Summary Cards */}
0 ? Math.round((data.summary.compliant / data.summary.total_lots) * 100) : 0}%`} color="green" /> 0 ? `${(data.summary.total_weight / 1000).toFixed(1)}k` : "—"} subtext={data.summary.total_weight > 0 ? "lbs" : ""} color="blue" />
{/* Filters */}
{Icons.search("h-4 w-4")} setSearch(e.target.value)} className="w-full pl-9 pr-4 py-2 rounded-xl border border-stone-200 bg-white text-sm outline-none focus:border-stone-400" />
{/* Table */}
{filteredLots.length === 0 ? ( ) : ( filteredLots.map((lot) => { const lotStatusCfg = LOT_STATUS_CONFIG[lot.status] ?? LOT_STATUS_CONFIG.active; return ( ); }) )}
Compliance Lot # Crop Harvest Date Field Weight Status Events Issues
{data.summary.total_lots === 0 ? "No lots found for this date range" : "No lots match your filters"}
{lot.lot_number} {lot.crop_type} {lot.variety && ( {lot.variety} )} {formatDate(lot.harvest_date)} {lot.field_location ?? "—"} {lot.field_block && lot.field_block !== "N/A" && ( · Block {lot.field_block} )} {lot.quantity_lbs ? Number(lot.quantity_lbs).toLocaleString() : "—"} {lot.yield_unit ?? "lbs"} {lotStatusCfg.label} 0 ? "bg-emerald-50 text-emerald-700" : "bg-stone-100 text-stone-500" }`}> {lot.event_count} {lot.issues.length > 0 ? (
{lot.issues.map((issue, i) => ( {issue} ))}
) : ( )}
{/* Results count */}

Showing {filteredLots.length} of {data.summary.total_lots} lots {data.summary.crops.length > 0 && ` • Crops: ${data.summary.crops.join(", ")}`}

) : (
{Icons.clipboard("h-12 w-12")}

No compliance data available

Select a date range and try again

)}
{/* Footer */}
FSMA Food Safety Modernization Act — Produce Traceability
)} ); }