Add collection pool nearby sort implementation plan
This commit is contained in:
parent
8d742e5e87
commit
7a7ad71ac0
1 changed files with 244 additions and 0 deletions
244
docs/superpowers/plans/2026-07-09-collection-pool-nearby-sort.md
Normal file
244
docs/superpowers/plans/2026-07-09-collection-pool-nearby-sort.md
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
# Collection Pool: Nearby sort works on Ready/Pending tabs — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** The `Nearby suppliers` sort produces a visibly different order from `Operational` on every filter tab. Rows without supplier coordinates go to the end of the list under `Nearby`.
|
||||
|
||||
**Architecture:** One-line change in the sort comparator in `view-model.ts` (use `"\uffff"` instead of `"zzz"` so missing keys sink to the end). Add three unit tests pinning the behaviour.
|
||||
|
||||
**Tech Stack:** TypeScript, Vitest, existing buildVisibleRows pattern.
|
||||
|
||||
**Spec:** [docs/superpowers/specs/2026-07-09-collection-pool-nearby-sort-design.md](file:///Volumes/EXT/DEVELOPMENT/WFDASH_NEXTJS/docs/superpowers/specs/2026-07-09-collection-pool-nearby-sort-design.md)
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility | Touched? |
|
||||
| --- | --- | --- |
|
||||
| `src/app/(app)/collection-pool/view-model.ts` | Sort comparator — sink missing coords to the end | Modify |
|
||||
| `src/app/(app)/collection-pool/view-model.test.ts` | Three new sort tests | Create if missing, modify if present |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Check whether the test file already exists
|
||||
|
||||
- [ ] **Step 1: Check for the test file**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ls -1 'src/app/(app)/collection-pool/view-model.test.ts' 2>/dev/null && echo "EXISTS" || echo "MISSING"
|
||||
```
|
||||
|
||||
If the file exists, Task 2 just appends to it. If it doesn't, Task 2 creates it.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add the new sort tests
|
||||
|
||||
**Files:**
|
||||
- Modify or create: `src/app/(app)/collection-pool/view-model.test.ts`
|
||||
|
||||
- [ ] **Step 1: Read the existing test file (if any) to match its import + helper conventions**
|
||||
|
||||
Look at the top of the file. If it exists, mirror its existing `beforeEach` / mock patterns. The tests below are written to work standalone if you have to create the file from scratch.
|
||||
|
||||
- [ ] **Step 2: Append (or create) the three tests**
|
||||
|
||||
```ts
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildVisibleRows, type CollectionPoolViewRow } from "./view-model";
|
||||
|
||||
function makeRow(overrides: Partial<CollectionPoolViewRow>): CollectionPoolViewRow {
|
||||
return {
|
||||
id: overrides.id ?? "row-1",
|
||||
description: overrides.description ?? "",
|
||||
poNumber: overrides.poNumber ?? "PO-1",
|
||||
orderNumber: overrides.orderNumber ?? "O-1",
|
||||
supplierName: overrides.supplierName ?? "Supplier",
|
||||
companyName: overrides.companyName ?? "Company",
|
||||
customerName: overrides.customerName ?? "Customer",
|
||||
ready_for_collection: overrides.ready_for_collection ?? false,
|
||||
clusterSortKey: overrides.clusterSortKey,
|
||||
assignmentState: overrides.assignmentState ?? "unassigned",
|
||||
updated: overrides.updated ?? "2026-07-01T00:00:00.000Z",
|
||||
} as CollectionPoolViewRow;
|
||||
}
|
||||
|
||||
describe("buildVisibleRows — sort modes", () => {
|
||||
const todayYmd = "2026-07-09";
|
||||
|
||||
it("operational sort is stable on the same input regardless of readyFilter", () => {
|
||||
const rows: CollectionPoolViewRow[] = [
|
||||
makeRow({ id: "a", poNumber: "PO-A", updated: "2026-07-02T00:00:00.000Z" }),
|
||||
makeRow({ id: "b", poNumber: "PO-B", updated: "2026-07-01T00:00:00.000Z" }),
|
||||
];
|
||||
|
||||
const all = buildVisibleRows(rows, {
|
||||
todayYmd,
|
||||
searchText: "",
|
||||
readyFilter: "all",
|
||||
sortMode: "operational",
|
||||
});
|
||||
const ready = buildVisibleRows(rows, {
|
||||
todayYmd,
|
||||
searchText: "",
|
||||
readyFilter: "ready",
|
||||
sortMode: "operational",
|
||||
});
|
||||
const pending = buildVisibleRows(rows, {
|
||||
todayYmd,
|
||||
searchText: "",
|
||||
readyFilter: "unready",
|
||||
sortMode: "operational",
|
||||
});
|
||||
|
||||
expect(all.map((r) => r.id)).toEqual(["a", "b"]);
|
||||
expect(ready.map((r) => r.id)).toEqual(["a", "b"]);
|
||||
expect(pending.map((r) => r.id)).toEqual(["a", "b"]);
|
||||
});
|
||||
|
||||
it("nearby sort groups rows by clusterSortKey ascending", () => {
|
||||
const rows: CollectionPoolViewRow[] = [
|
||||
makeRow({ id: "c", clusterSortKey: "cluster-c" }),
|
||||
makeRow({ id: "a", clusterSortKey: "cluster-a" }),
|
||||
makeRow({ id: "b", clusterSortKey: "cluster-b" }),
|
||||
];
|
||||
|
||||
const out = buildVisibleRows(rows, {
|
||||
todayYmd,
|
||||
searchText: "",
|
||||
readyFilter: "all",
|
||||
sortMode: "nearby",
|
||||
});
|
||||
|
||||
expect(out.map((r) => r.id)).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
|
||||
it("nearby sort sinks rows with missing clusterSortKey to the end", () => {
|
||||
const rows: CollectionPoolViewRow[] = [
|
||||
makeRow({ id: "no-coords-1", clusterSortKey: undefined }),
|
||||
makeRow({ id: "a", clusterSortKey: "cluster-a" }),
|
||||
makeRow({ id: "no-coords-2", clusterSortKey: undefined }),
|
||||
makeRow({ id: "b", clusterSortKey: "cluster-b" }),
|
||||
];
|
||||
|
||||
const out = buildVisibleRows(rows, {
|
||||
todayYmd,
|
||||
searchText: "",
|
||||
readyFilter: "all",
|
||||
sortMode: "nearby",
|
||||
});
|
||||
|
||||
// Rows WITH coords come first, ordered by cluster key.
|
||||
// Rows WITHOUT coords come last (sink to the end).
|
||||
expect(out.map((r) => r.id)).toEqual(["a", "b", "no-coords-1", "no-coords-2"]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run the new tests**
|
||||
|
||||
```bash
|
||||
npm run test:run -- 'src/app/(app)/collection-pool/view-model'
|
||||
```
|
||||
|
||||
Expected: 3 failed (the new tests fail because the production code still uses `"zzz"`, which sorts missing-coord rows to the *start* of the list, not the end).
|
||||
|
||||
- [ ] **Step 4: Commit (test only)**
|
||||
|
||||
```bash
|
||||
git add 'src/app/(app)/collection-pool/view-model.test.ts'
|
||||
git commit -m "test(collection-pool): pin nearby-sort behaviour for missing coords"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Fix the sort comparator
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/app/(app)/collection-pool/view-model.ts`
|
||||
|
||||
- [ ] **Step 1: Find the `nearby` block in the sort**
|
||||
|
||||
Open `src/app/(app)/collection-pool/view-model.ts` and locate the sort block. The current code is:
|
||||
|
||||
```ts
|
||||
if (sortMode === "nearby") {
|
||||
const clusterDiff = (a.clusterSortKey ?? "zzz").localeCompare(b.clusterSortKey ?? "zzz");
|
||||
if (clusterDiff !== 0) return clusterDiff;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace the sentinel with `"\uffff"`**
|
||||
|
||||
```ts
|
||||
if (sortMode === "nearby") {
|
||||
// Rows without a cluster key (no supplier coords) sink to the
|
||||
// end of the list. We use "\uffff" instead of "zzz" so the
|
||||
// missing-coords group sorts after every real cluster key,
|
||||
// and within that group the existing tie-breakers apply.
|
||||
const keyA = a.clusterSortKey ?? "\uffff";
|
||||
const keyB = b.clusterSortKey ?? "\uffff";
|
||||
const clusterDiff = keyA.localeCompare(keyB);
|
||||
if (clusterDiff !== 0) return clusterDiff;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run the new tests**
|
||||
|
||||
```bash
|
||||
npm run test:run -- 'src/app/(app)/collection-pool/view-model'
|
||||
```
|
||||
|
||||
Expected: 3/3 pass.
|
||||
|
||||
- [ ] **Step 4: Lint**
|
||||
|
||||
```bash
|
||||
npx eslint 'src/app/(app)/collection-pool/view-model.ts'
|
||||
```
|
||||
|
||||
Expected: exit 0. Pre-existing warnings on unrelated lines are fine.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add 'src/app/(app)/collection-pool/view-model.ts'
|
||||
git commit -m "fix(collection-pool): sink missing-coord rows to the end of Nearby sort"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Final verification
|
||||
|
||||
- [ ] **Step 1: Targeted tests**
|
||||
|
||||
```bash
|
||||
npm run test:run -- 'src/app/(app)/collection-pool/view-model'
|
||||
```
|
||||
|
||||
Expected: green.
|
||||
|
||||
- [ ] **Step 2: Full collection-pool suite**
|
||||
|
||||
```bash
|
||||
npm run test:run -- 'src/app/(app)/collection-pool'
|
||||
```
|
||||
|
||||
Expected: same passing set as before plus the 3 new sort tests. The 1 pre-existing failure on `page.test.tsx:175` is acceptable.
|
||||
|
||||
- [ ] **Step 3: Full suite**
|
||||
|
||||
```bash
|
||||
npm run test:run
|
||||
```
|
||||
|
||||
Expected: same passing set as before plus the 3 new tests. Pre-existing failures (the 6 we documented across the repo) are acceptable.
|
||||
|
||||
- [ ] **Step 4: Manual smoke**
|
||||
|
||||
1. Open the Collection Pool page on `All`. Click `Nearby suppliers`. Rows with coordinates should group at the top; rows without coordinates should sink to the bottom.
|
||||
2. Switch to `Ready to collect`. The sort should still produce a visibly different order from `Operational`.
|
||||
3. Switch to `Pending`. Same as above.
|
||||
4. Switch back to `Operational`. Order should match the previous behavior (assignment state + updated).
|
||||
Loading…
Reference in a new issue