Add collection map date filter and unassign implementation plan

This commit is contained in:
SJCoetz 2026-07-10 00:47:11 +02:00
parent 72b2ec09e2
commit f15fb5b49f

View file

@ -0,0 +1,216 @@
# Collection Map — date filter bug + unassign-clears-date fix
**Goal:** Fix two related bugs that make the collection map show almost no stops, and make sure the unassign path correctly clears `assigned_at` so the item returns to the un-dated pool.
**Architecture:** Two one-line server changes + one client date-format fix + four new tests.
**Tech Stack:** Next.js client + server routes, PocketBase admin SDK, Vitest.
**Spec:** [docs/superpowers/specs/2026-07-09-collection-map-date-filter-and-unassign-design.md](file:///Volumes/EXT/DEVELOPMENT/WFDASH_NEXTJS/docs/superpowers/specs/2026-07-09-collection-map-date-filter-and-unassign-design.md)
---
## File Structure
| File | Responsibility | Touched? |
| --- | --- | --- |
| `src/app/api/v1/collection-map/items/[id]/assign/route.ts` | Clear `assigned_at` on unassign | Modify |
| `src/app/api/v1/collection-map/items/[id]/assign/route.test.ts` | Cover the unassign-clears-date path | Modify |
| `src/app/(app)/collection-map/page.tsx` | Fix the day-bound date format in the PB filter | Modify |
---
## Task 1: Unassign clears `assigned_at`
**Files:**
- Modify: `src/app/api/v1/collection-map/items/[id]/assign/route.ts`
- [ ] **Step 1: Find the patch construction**
In the route, find the `const patch: Record<string, unknown> = normalizeOrderItemStatusInput({...})` block (around line 74–79).
- [ ] **Step 2: Clear `assigned_at` after the normaliser call**
Right after the existing `if (typeof body.ready_for_collection === "boolean")` block, add the clear. The intent: when the operator is returning an item to the pool (no driver being assigned), `assigned_at` should be empty so the item reappears in the un-dated pool catchall in the collection-map filter.
```ts
// Returning an item to the pool (no driver being assigned) also
// clears the date the operator originally assigned. Without this,
// the item keeps its old `assigned_at` and the collection map's
// date filter (which matches by string compare on `assigned_at`)
// may still surface it under the wrong day, or hide it entirely
// on the un-dated fallback.
if (!isAssigning) {
patch.assigned_at = "";
}
```
Place this after the `if (typeof body.ready_for_collection === "boolean")` block, before the `const pb = await getAdminPocketBase();` line.
- [ ] **Step 3: Lint**
```bash
npx eslint 'src/app/api/v1/collection-map/items/[id]/assign/route.ts'
```
Expected: exit 0.
- [ ] **Step 4: Commit**
```bash
git add 'src/app/api/v1/collection-map/items/[id]/assign/route.ts'
git commit -m "fix(collection-map): clear assigned_at on unassign so item returns to pool"
```
---
## Task 2: Cover the unassign-clears-date path
**Files:**
- Modify: `src/app/api/v1/collection-map/items/[id]/assign/route.test.ts`
- [ ] **Step 1: Find the existing tests**
The test file already exists with 5 tests from the prior "mark ready notes" task. Find the `it("accepts the unmark path without a note")` test. That's the test that exercises the unmark path (with `ready_for_collection: false`).
- [ ] **Step 2: Add a new test for the driver-unassign case**
Add a new test below the unmark-without-note test:
```ts
it("clears assigned_at when returning an item to the pool (no driverId)", async () => {
getOneMock.mockResolvedValueOnce({
id: "item-1",
assigned_at: "2026-07-09T00:00:00.000Z",
});
updateMock.mockResolvedValueOnce({ id: "item-1" });
const res = await PATCH(
makeReq({ date: "" }),
{ params: Promise.resolve({ id: "item-1" }) },
);
expect(res.status).toBe(200);
const payload = updateMock.mock.calls[0][1] as Record<string, unknown>;
expect(payload.assigned_at).toBe("");
expect(payload.item_status).toBe("collection_pool");
expect(payload.drv_status).toBe("unassigned");
});
```
- [ ] **Step 3: Run the test file**
```bash
npm run test:run -- 'src/app/api/v1/collection-map/items/[id]/assign/route.test.ts'
```
Expected: 6/6 pass.
- [ ] **Step 4: Lint**
```bash
npx eslint 'src/app/api/v1/collection-map/items/[id]/assign/route.test.ts'
```
Expected: exit 0.
- [ ] **Step 5: Commit**
```bash
git add 'src/app/api/v1/collection-map/items/[id]/assign/route.test.ts'
git commit -m "test(collection-map): cover assigned_at clear on unassign"
```
---
## Task 3: Fix the day-bound date format in the map filter
**Files:**
- Modify: `src/app/(app)/collection-map/page.tsx`
- [ ] **Step 1: Find the day-bound construction**
In `fetchRows` (the function that loads `order_items`), find the `const dayStart = ...` and `const dayEnd = ...` lines. The current code is:
```ts
const dayStart = `${date} 00:00:00`;
const dayEnd = `${date} 23:59:59`;
```
- [ ] **Step 2: Replace with ISO 8601 UTC bounds**
`assigned_at` is stored as a UTC ISO string (e.g. `2026-07-09T00:00:00.000Z`). The filter must use the same format so the string comparison actually orders correctly.
```ts
// Build a UTC ISO 8601 day window. `assigned_at` is stored at
// "YYYY-MM-DDT00:00:00.000Z" (see /api/v1/collection-map/items/[id]/assign),
// so the bounds need the same "T" and trailing "Z" or string
// comparison puts `T` (0x54) before the space (0x20) and the bound
// excludes every real row.
const dayStart = `${date}T00:00:00.000Z`;
const dayEnd = `${date}T23:59:59.999Z`;
```
- [ ] **Step 3: Update the second `getFullList` call (the unmapped-rows one)**
The same `assigned_at` filter is used in the second `getFullList` call for unmapped rows. The bounds there are duplicated. Update the same way:
```ts
const dayStart = `${date}T00:00:00.000Z`;
const dayEnd = `${date}T23:59:59.999Z`;
```
If the bounds already exist in a local variable, reuse them; otherwise duplicate the fix. The current code (lines 396–401) has the bounds inlined:
```ts
filter: `(item_status = "assigned" || item_status = "collection_pool") && ((assigned_at >= "${date} 00:00:00" && assigned_at <= "${date} 23:59:59") || assigned_at = "" || assigned_at = null)`,
```
Replace with:
```ts
filter: `(item_status = "assigned" || item_status = "collection_pool") && ((assigned_at >= "${date}T00:00:00.000Z" && assigned_at <= "${date}T23:59:59.999Z") || assigned_at = "" || assigned_at = null)`,
```
- [ ] **Step 4: Lint**
```bash
npx eslint 'src/app/(app)/collection-map/page.tsx'
```
Expected: exit 0.
- [ ] **Step 5: Commit**
```bash
git add 'src/app/(app)/collection-map/page.tsx'
git commit -m "fix(collection-map): use ISO 8601 UTC bounds for assigned_at day filter"
```
---
## Task 4: Final verification
- [ ] **Step 1: Targeted tests**
```bash
npm run test:run -- 'src/app/api/v1/collection-map/items/[id]/assign'
```
Expected: 6/6 pass.
- [ ] **Step 2: Full suite**
```bash
npm run test:run
```
Expected: same passing set as before plus the new test. The 6 pre-existing failures are acceptable.
- [ ] **Step 3: Manual smoke**
1. Open the Collection Map page.
2. Pick today's date. All assigned + pool items for today should appear.
3. Pick a past date — items with that `assigned_at` should appear, others shouldn't.
4. From the Collection Pool, click `Mark Awaiting` on a row (unmark). The row should disappear from any "today" / "tomorrow" map view and reappear under the un-dated pool.
5. From the Collection Map, click a pin → `Reassign` modal → clear the date (or remove the driver). The item should return to the un-dated pool.