'use client'; import { useState, useRef, useEffect } from 'react'; import { OrderItem } from '@/lib/types'; import { cn, formatDate, formatCurrency, parseCoordinates } from '@/lib/utils'; import { usePopoverPlacement } from '@/lib/hooks'; import { getItemDisplayDescription, getItemDisplayQty } from '@/lib/pocketbase'; import { Check, AlertTriangle, Navigation, ChevronDown, ChevronUp, Package, Wrench, Hammer, ShoppingCart, AlertCircle, Clock, MapPin, FileText, MessageSquare, ExternalLink, } from 'lucide-react'; interface CollectionItemProps { item: OrderItem; supplierColor?: { dot: string; light: string; text: string; ring: string }; compact?: boolean; onCollect: (item: OrderItem) => void; onProblem: (item: OrderItem) => void; onNavigate: (lat: number, lng: number, name: string) => void; // Opens the ItemDetailModal showing PO document + buyer notes onShowDetails: (item: OrderItem) => void; // Notifies the parent that a popover has opened/closed so it can // lift its own stacking layer — the card's z-30 alone can't escape // the section's stacking context, so the section needs to know. onPopoverChange?: (open: boolean) => void; } function getItemType(item: OrderItem): { type: string; icon: React.ReactNode; color: string; bgColor: string; label: string; } { if (item.problem_flag) { return { type: 'problem', icon: , color: 'text-[#b3224a]', bgColor: 'bg-[#fbeaef]', label: 'Problem', }; } const orderNumber = item.expand?.order?.order_number || ''; const poNumber = item.expand?.purchase_order?.po_number || ''; if (item.is_stock_item) { return { type: 'stock', icon: , color: 'text-[#4a3d6b]', bgColor: 'bg-[#d4cae3]', label: 'Stock', }; } if (orderNumber.startsWith('RPR') || poNumber.startsWith('RPR')) { return { type: 'repair', icon: , color: 'text-[#8a5a02]', bgColor: 'bg-[#f6e3a4]', label: 'Repair', }; } if (orderNumber.startsWith('BRK') || poNumber.startsWith('BRK')) { return { type: 'breakdown', icon: , color: 'text-[#8a5a02]', bgColor: 'bg-[#f6e3a4]', label: 'Breakdown', }; } return { type: 'normal', icon: , color: 'text-[#26251e]', bgColor: 'bg-[#ebeae5]', label: 'Order', }; } // Closes tooltip when clicking outside function useClickOutside(ref: React.RefObject, onClose: () => void) { useEffect(() => { function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { onClose(); } } document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [ref, onClose]); } export default function CollectionItemCard({ item, supplierColor, compact, onCollect, onProblem, onNavigate, onShowDetails, onPopoverChange, }: CollectionItemProps) { const [expanded, setExpanded] = useState(false); const [showNotesTooltip, setShowNotesTooltip] = useState(false); const [showPoTooltip, setShowPoTooltip] = useState(false); const notesTooltipRef = useRef(null); const poTooltipRef = useRef(null); // Button refs feed the placement hook so the popover flips above // the button when there's not enough room below it (e.g. on the // last item in a long list, where the default top-full direction // would push the popover under the BottomNav). const poButtonRef = useRef(null); const notesButtonRef = useRef(null); const { placement: poPlacement, measure: measurePoPlacement } = usePopoverPlacement(poButtonRef); const { placement: notesPlacement, measure: measureNotesPlacement } = usePopoverPlacement(notesButtonRef); useClickOutside(notesTooltipRef, () => setShowNotesTooltip(false)); useClickOutside(poTooltipRef, () => setShowPoTooltip(false)); // Bubble the open/close state up so the wrapping supplier/area // group can lift itself above the *next* group in the list. const popoverOpen = showNotesTooltip || showPoTooltip; useEffect(() => { onPopoverChange?.(popoverOpen); }, [popoverOpen, onPopoverChange]); const supplier = item.expand?.supplier; const itemType = getItemType(item); const coords = parseCoordinates(supplier?.coordinates); const hasCoords = coords !== null; const description = getItemDisplayDescription(item); const expectedQty = getItemDisplayQty(item); const po = item.expand?.purchase_order; const order = item.expand?.order; const poNumber = po?.po_number; const orderNumber = order?.order_number; const hasNotes = Boolean(item.notes?.trim()); const hasProblemNotes = Boolean(item.problem_notes?.trim()); const hasAnyNotes = hasNotes || hasProblemNotes; return (
{/* Header */} {/* PO tooltip popover */} {showPoTooltip && (
{po ? ( <>

Purchase Order

PO Number
{po.po_number}
{po.status && (
Status
{po.status}
)} {po.total_amount != null && (
Total
R{(po.total_amount / 100).toFixed(2)}
)} {orderNumber && (
Order
{orderNumber}
)}
) : (

No PO attached

)}
)} {/* Notes tooltip button */} {hasAnyNotes && (
{/* Notes tooltip popover */} {showNotesTooltip && (

Buyer Notes

{hasNotes ? ( <>

{item.notes}

{hasProblemNotes && (

Problem: {item.problem_notes}

)} ) : hasProblemNotes ? (

{item.problem_notes}

) : (

No notes

)}
)}
)} ) : ( /* Full/non-compact right side */
{item.selling_price && expectedQty > 0 && ( {formatCurrency(item.selling_price * expectedQty)} )} {expanded ? (
)} {/* Expanded Details */} {expanded && (
{/* Notes */} {item.notes && (

)} {item.problem_notes && (

)} {/* Details grid */}
{orderNumber && (
Order
{orderNumber}
)} {poNumber && (
PO
{poNumber}
)} {supplier?.supplieraddress && (
Address
{supplier.supplieraddress}
)}
{/* Action Buttons */}
{/* Row 1 — core workflow */}
{hasCoords && coords && supplier ? ( ) : (
)}
{/* Row 2 — PO document and buyer notes */}
)}
); }