"use client"; import { useState, useMemo, Fragment, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Search, X, ChevronDown, ChevronRight, MapPin, Tag } from "lucide-react"; import { formatCurrency } from "@/lib/utils"; import { addToCart } from "@/lib/cart"; type VendorOption = { vendorId: string; vendorName: string; isVerified: boolean; price: number; distanceKm: number | null; }; type CatalogItem = { id: string; code: string; name: string; description: string; vendors: VendorOption[]; }; function formatDist(km: number) { return km < 1 ? `${Math.round(km * 1000)} m` : `${km.toFixed(0)} km`; } type SiteOption = { id: string; name: string; code: string }; export function ItemsTable({ items, hasSite, sites = [], currentSiteId = null, }: { items: CatalogItem[]; hasSite: boolean; sites?: SiteOption[]; currentSiteId?: string | null; }) { const router = useRouter(); const [query, setQuery] = useState(""); const [expandedId, setExpandedId] = useState(null); const [sortBy, setSortBy] = useState<"distance" | "price">(hasSite ? "distance" : "price"); const [added, setAdded] = useState>({}); // Reset sort to distance whenever the selected site changes useEffect(() => { setSortBy(currentSiteId ? "distance" : "price"); }, [currentSiteId]); const filtered = useMemo(() => { const q = query.toLowerCase().trim(); if (!q) return items; return items.filter( (item) => item.name.toLowerCase().includes(q) || item.code.toLowerCase().includes(q) || item.description.toLowerCase().includes(q) ); }, [items, query]); function getSortedVendors(vendors: VendorOption[]) { const v = [...vendors]; if (sortBy === "distance") { v.sort((a, b) => { if (a.distanceKm !== null && b.distanceKm !== null) return a.distanceKm - b.distanceKm; if (a.distanceKm !== null) return -1; if (b.distanceKm !== null) return 1; return a.price - b.price; }); } else { v.sort((a, b) => a.price - b.price); } return v; } function handleAdd(item: CatalogItem, vendor: VendorOption) { addToCart({ productId: item.id, name: item.name, description: item.description || undefined, quantity: 1, unit: "pc", unitPrice: vendor.price, vendorId: vendor.vendorId, vendorName: vendor.vendorName, }); const key = `${item.id}-${vendor.vendorId}`; setAdded((prev) => ({ ...prev, [key]: true })); setTimeout(() => setAdded((prev) => ({ ...prev, [key]: false })), 1500); } function toggleRow(id: string) { setExpandedId((prev) => (prev === id ? null : id)); } return (
{/* Site selector */} {sites.length > 0 && (
{currentSiteId && ( Distances shown from selected site )}
)} {/* Toolbar */}
setQuery(e.target.value)} placeholder="Search by name, code or description…" className="w-full rounded-lg border border-neutral-200 py-2 pl-8 pr-8 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20" /> {query && ( )}
{filtered.length} item{filtered.length !== 1 ? "s" : ""} {/* Vendor sort toggle — only shown when an item is expanded */} {expandedId && (
Vendors sorted by:
)}
{/* Table */}
{filtered.length === 0 && ( )} {filtered.map((item) => { const isOpen = expandedId === item.id; const lowestPrice = item.vendors.length > 0 ? Math.min(...item.vendors.map((v) => v.price)) : null; const sortedVendors = getSortedVendors(item.vendors); // Cheapest and closest are independent of current sort order const minPrice = sortedVendors.length > 1 ? Math.min(...sortedVendors.map((v) => v.price)) : null; const closestVendorId = hasSite ? (sortedVendors.filter((v) => v.distanceKm !== null).sort((a, b) => a.distanceKm! - b.distanceKm!)[0]?.vendorId ?? null) : null; return ( {/* Item row */} toggleRow(item.id)} > {/* Expanded vendor sub-rows */} {isOpen && ( )} ); })}
Item Code Vendors From
{query ? `No items match "${query}"` : "No items in catalogue yet."}
{isOpen ? : } {item.name} {item.description && ( {item.description} )} {item.code} {item.vendors.length > 0 ? item.vendors.length : } {lowestPrice !== null ? {formatCurrency(lowestPrice)} : No price}
{sortedVendors.length === 0 ? (
No vendors on record. Add this item manually in the PO form.
) : ( {hasSite && } {sortedVendors.map((vendor) => { const key = `${item.id}-${vendor.vendorId}`; const isCheapest = minPrice !== null && vendor.price === minPrice; const isClosest = closestVendorId !== null && vendor.vendorId === closestVendorId; return ( {hasSite && ( )} ); })}
Vendor PriceDistance
e.stopPropagation()} className="font-medium text-neutral-800 hover:text-primary-600 hover:underline" > {vendor.vendorName} {vendor.isVerified && ( Verified )} {isClosest && ( ★ Closest )} {isCheapest && ( Cheapest )}
{formatCurrency(vendor.price)} {vendor.distanceKm !== null ? formatDist(vendor.distanceKm) : "—"}
)}
{filtered.length > 0 && (

Click any row to see vendors. Prices shown are last known from paid POs.

)}
); }