From 56626c82700b9325e84a6fcc47ddebcbef1736eb Mon Sep 17 00:00:00 2001 From: SJCoetz Date: Sun, 12 Jul 2026 12:12:41 +0200 Subject: [PATCH] feat(pin-model): add aggregateDriverStatus helper (TDD) --- src/app/(app)/collection-map/pin-model.ts | 38 +++++++++++ src/lib/pin-model.test.ts | 78 +++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/lib/pin-model.test.ts diff --git a/src/app/(app)/collection-map/pin-model.ts b/src/app/(app)/collection-map/pin-model.ts index f00e057..2cb3924 100644 --- a/src/app/(app)/collection-map/pin-model.ts +++ b/src/app/(app)/collection-map/pin-model.ts @@ -48,3 +48,41 @@ export function splitSupplierBucket< } return { assignedByDriver, poolItems }; } + +export interface DriverStatus { + total: number; + collected: number; + ready: number; + waiting: number; +} + +/** + * Counts a driver's stops into the four buckets the driver card surfaces: + * - collected : item_status === "collected" (the green anchor) + * - ready : ready_for_collection === true and not collected + * - waiting : ready_for_collection !== true and not collected + * - total : items.length + * + * Items with `ready_for_collection` undefined or null are treated as + * not ready (waiting). Pure — no DB / side effects. + */ +export function aggregateDriverStatus< + T extends { + item_status: string; + ready_for_collection?: boolean | null; + } +>(items: T[]): DriverStatus { + let collected = 0; + let ready = 0; + let waiting = 0; + for (const it of items) { + if (it.item_status === "collected") { + collected++; + } else if (it.ready_for_collection) { + ready++; + } else { + waiting++; + } + } + return { total: items.length, collected, ready, waiting }; +} diff --git a/src/lib/pin-model.test.ts b/src/lib/pin-model.test.ts new file mode 100644 index 0000000..cae74bd --- /dev/null +++ b/src/lib/pin-model.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "vitest"; +import { aggregateDriverStatus } from "@/app/(app)/collection-map/pin-model"; + +describe("aggregateDriverStatus", () => { + it("returns zeros for an empty array", () => { + expect(aggregateDriverStatus([])).toEqual({ + total: 0, + collected: 0, + ready: 0, + waiting: 0, + }); + }); + + it("counts every item as collected when every item is collected", () => { + const items = [ + { item_status: "collected" as const, ready_for_collection: true }, + { item_status: "collected" as const, ready_for_collection: true }, + { item_status: "collected" as const, ready_for_collection: false }, + ]; + expect(aggregateDriverStatus(items)).toEqual({ + total: 3, + collected: 3, + ready: 0, + waiting: 0, + }); + }); + + it("counts every item as waiting when nothing is ready or collected", () => { + const items = [ + { item_status: "collection_pool" as const, ready_for_collection: false }, + { item_status: "assigned" as const, ready_for_collection: false }, + ]; + expect(aggregateDriverStatus(items)).toEqual({ + total: 2, + collected: 0, + ready: 0, + waiting: 2, + }); + }); + + it("rolls up mixed states (collected + ready + waiting)", () => { + const items = [ + { item_status: "collected" as const, ready_for_collection: true }, + { item_status: "collected" as const, ready_for_collection: true }, + { item_status: "assigned" as const, ready_for_collection: true }, + { item_status: "assigned" as const, ready_for_collection: true }, + { item_status: "assigned" as const, ready_for_collection: true }, + { item_status: "collection_pool" as const, ready_for_collection: false }, + ]; + expect(aggregateDriverStatus(items)).toEqual({ + total: 6, + collected: 2, + ready: 3, + waiting: 1, + }); + }); + + it("treats undefined ready_for_collection as waiting", () => { + const items = [ + { item_status: "assigned" as const }, + { item_status: "collection_pool" as const }, + ]; + expect(aggregateDriverStatus(items).waiting).toBe(2); + expect(aggregateDriverStatus(items).ready).toBe(0); + expect(aggregateDriverStatus(items).collected).toBe(0); + expect(aggregateDriverStatus(items).total).toBe(2); + }); + + it("treats null ready_for_collection as waiting", () => { + const items = [ + { item_status: "assigned" as const, ready_for_collection: null }, + { item_status: "collection_pool" as const, ready_for_collection: null }, + ]; + expect(aggregateDriverStatus(items).waiting).toBe(2); + expect(aggregateDriverStatus(items).ready).toBe(0); + expect(aggregateDriverStatus(items).collected).toBe(0); + }); +});