From 050b49e75cad3a7c44f7651229c550b63ba883ac Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 18 Oct 2026 10:31:41 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20one=20layout=20=E2=80=94=20compact=20ro?= =?UTF-8?q?ws=20grouped=20by=20PO,=20no=20view=20switches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed the Supplier/Invoice/Area and Compact/Detailed switches; the list is always compact and grouped supplier → PO. Six layouts behind small icons confused drivers. - POs start open, so opening a supplier shows its items straight away. - PO header: the full PO number and item count on line one; the PO / Invoice / Collect all / flag buttons on line two (bigger, 36px) only while the PO is open. The number used to be squeezed to "PO2026…" or hidden by the buttons. - Collect all / flag-all only show when the PO has more than one item. Co-Authored-By: Claude Opus 5.5 --- app/page.tsx | 109 +++++------------------------------ components/PoActions.tsx | 4 +- components/SupplierGroup.tsx | 93 +++++++++++++++--------------- 3 files changed, 65 insertions(+), 141 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 9f175a9..1f7b6e9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -41,8 +41,6 @@ import { Search, X, Package, - Layers, - FileText, CheckCheck, AlertTriangle, } from 'lucide-react'; @@ -90,16 +88,19 @@ export default function CollectionsPage() { const [showDetailModal, setShowDetailModal] = useState(false); const [showProblemModal, setShowProblemModal] = useState(false); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null); - const [viewMode, setViewMode] = useState<'compact' | 'detailed'>('compact'); - const [groupMode, setGroupMode] = useState<'supplier' | 'po' | 'area'>('supplier'); + // One layout for every driver: compact rows, grouped supplier → PO. + // The supplier/invoice/area and compact/detailed switches were removed + // on 2026-10-18 — six layouts behind small icons just confused drivers. + const [viewMode] = useState<'compact' | 'detailed'>('compact'); + const [groupMode] = useState<'supplier' | 'po' | 'area'>('po'); const [statusFilter, setStatusFilter] = useState<'assigned' | 'collection_pool' | 'problem'>('assigned'); // Track which supplier groups are expanded const [expandedSuppliers, setExpandedSuppliers] = useState>(new Set()); const [expandedAreas, setExpandedAreas] = useState>(new Set()); - // Per-PO expand state for 'po' groupMode — PO sub-headers can be - // collapsed independently of the supplier they're inside. - const [expandedPOs, setExpandedPOs] = useState>(new Set()); + // POs the driver has folded away. POs start open, so opening a + // supplier shows its items straight away instead of one more tap per PO. + const [collapsedPOs, setCollapsedPOs] = useState>(new Set()); // Mirror items.length into a ref so loadItems can decide whether to // flash the loading skeleton WITHOUT making items.length a callback @@ -171,7 +172,7 @@ const loadItems = useCallback(async (showLoading = true) => { // driver wants everything expanded at once. setExpandedSuppliers(new Set()); setExpandedAreas(new Set()); - setExpandedPOs(new Set()); + setCollapsedPOs(new Set()); } catch (err) { console.error('Failed to load collections:', err); setError('Failed to load collections. Pull down to retry.'); @@ -497,7 +498,7 @@ const loadItems = useCallback(async (showLoading = true) => { }; const togglePO = (poId: string) => { - setExpandedPOs(prev => { + setCollapsedPOs(prev => { const next = new Set(prev); if (next.has(poId)) next.delete(poId); else next.add(poId); @@ -519,14 +520,7 @@ const loadItems = useCallback(async (showLoading = true) => { } // For PO mode, expandAll also opens every PO sub-header so the // driver sees the full breakdown without an extra tap. - if (groupMode === 'po') { - const allPoIds = new Set(); - filteredItems.forEach(i => { - const po = i.expand?.purchase_order; - allPoIds.add(po?.id || '__no_po__'); - }); - setExpandedPOs(allPoIds); - } + if (groupMode === 'po') setCollapsedPOs(new Set()); }; const collapseAll = () => { @@ -536,7 +530,9 @@ const loadItems = useCallback(async (showLoading = true) => { setExpandedAreas(new Set()); } if (groupMode === 'po') { - setExpandedPOs(new Set()); + const allPoIds = new Set(); + filteredItems.forEach(i => allPoIds.add(i.expand?.purchase_order?.id || '__no_po__')); + setCollapsedPOs(allPoIds); } }; @@ -698,81 +694,6 @@ const loadItems = useCallback(async (showLoading = true) => { )} - {/* View toggle — buttons stay icon-only on the smallest screens - (label hidden at - {/* Group mode toggle */} -
- - - -
- - {/* View density toggle */} -
- - -
- {/* Status filter chips — show counts of each state so drivers @@ -918,7 +839,7 @@ const loadItems = useCallback(async (showLoading = true) => { onNavigate={handleNavigate} onShowDetails={handleShowDetails} poGroups={isPoMode ? group.pos : undefined} - expandedPOs={isPoMode ? expandedPOs : undefined} + collapsedPOs={isPoMode ? collapsedPOs : undefined} onTogglePO={isPoMode ? togglePO : undefined} onBulkCollect={isPoMode ? handleBulkCollect : undefined} onBulkProblem={isPoMode ? handleBulkProblem : undefined} diff --git a/components/PoActions.tsx b/components/PoActions.tsx index 8b19eb0..a1423c8 100644 --- a/components/PoActions.tsx +++ b/components/PoActions.tsx @@ -59,8 +59,8 @@ export default function PoActions({ const lg = size === 'lg'; const btn = lg ? 'h-12 w-full rounded-xl text-[15px] font-semibold flex items-center justify-center gap-2 transition-colors touch-manipulation' - : 'h-7 px-2 rounded-md text-[11px] font-semibold flex items-center gap-1 transition-colors touch-manipulation'; - const icon = lg ? 18 : 11; + : 'h-9 px-3 rounded-lg text-[13px] font-semibold flex items-center gap-1.5 transition-colors touch-manipulation'; + const icon = lg ? 18 : 14; return (
diff --git a/components/SupplierGroup.tsx b/components/SupplierGroup.tsx index e188bb1..ce32b0b 100644 --- a/components/SupplierGroup.tsx +++ b/components/SupplierGroup.tsx @@ -52,10 +52,10 @@ interface SupplierGroupProps { onShowDetails: (item: OrderItem) => void; // When provided, the supplier section renders its items bucketed by // PO sub-group instead of as a single flat list. Each sub-group can - // be collapsed independently via expandedPOs, and bulk actions + // be collapsed independently via collapsedPOs, and bulk actions // target the items inside the tapped sub-group. poGroups?: POSubGroup[]; - expandedPOs?: Set; + collapsedPOs?: Set; onTogglePO?: (poId: string) => void; onBulkCollect?: (poLabel: string, items: OrderItem[]) => void; onBulkProblem?: (poLabel: string, items: OrderItem[]) => void; @@ -267,7 +267,7 @@ export default function SupplierGroup({ onNavigate, onShowDetails, poGroups, - expandedPOs, + collapsedPOs, onTogglePO, onBulkCollect, onBulkProblem, @@ -407,67 +407,70 @@ export default function SupplierGroup({
    {showPoHeaders && poGroups && onTogglePO ? ( poGroups.flatMap((po, poIdx) => { - const poExpanded = expandedPOs?.has(po.poId) ?? true; - const poTotalQty = po.items.reduce( - (s, i) => s + (i.order_qty || i.quantity || 0), - 0 - ); + const poExpanded = !collapsedPOs?.has(po.poId); const poLabel = `PO ${po.poNumber}`; + const showBulk = poGroups.length > 1 && po.items.length > 1; return [
  • -
    + {/* Line 1: the PO number in full (it used to be squeezed + to "PO2026…" or hidden by the buttons). Line 2, only + while the PO is open: the PO's buttons. */} +
    - {po.poId !== '__no_po__' && ( - - )} - {/* Per-PO bulk actions — only rendered when the - supplier actually has more than one PO (a - single-PO supplier renders the flat list - above, no point in redundant bulk buttons). */} - {poGroups.length > 1 && poExpanded && ( -
    - - + {poExpanded && (po.poId !== '__no_po__' || showBulk) && ( +
    + {po.poId !== '__no_po__' && ( + + )} + {/* Per-PO bulk actions — only when the supplier + has more than one PO and this PO has more than + one item (otherwise the row's own buttons do it). */} + {showBulk && ( + <> + + + + )}
    )}