Add collection confirm rewrite spec

This commit is contained in:
SJCoetz 2026-07-10 00:59:26 +02:00
parent 579acb300f
commit 683b6c8d0a

View file

@ -0,0 +1,120 @@
# Collection Confirm — layout rewrite + actor hydration fix
**Goal:** Two distinct fixes on the Collection Confirm page:
1. **Rewrite the row layout.** The current 3-column grid crams every field, action, attachment, invoice chip, and problem chip into a single row. Stacked vertical sections per row (header / details / actions / notes) read better, give the actions more room, and let the notes feed breathe.
2. **Fix the "actor id is empty" error.** The actor state hydrates from `localStorage.wfdash.actor`, which is never written. The real identity lives in the `wfdash_dev_email` cookie. Hydrate from the cookie via `getClientIdentity()` instead.
## Scope
### In Scope
- Rewrite the `ConfirmCard` JSX in `src/app/(app)/collection-confirm/page.tsx` from the current 3-column grid to a stacked vertical layout. Sections: header (supplier + description + order/customer links) → details (ordered/collected/problem/invoice/attachments, two-column on `md:`) → actions (full-width buttons in a 2x3 grid on `sm:`, single column on `xs:`) → notes feed (existing).
- Replace the `localStorage.wfdash.actor` hydration with `getClientIdentity()` from `@/lib/dev-identity`. `actor.id = email`, `actor.name = email.split("@")[0]`.
- Add a tiny "Signed in as X" indicator in the page header (so the operator can see at a glance which identity is active, mirroring the dev-identity-switcher widget in the sprint page).
- Keep all existing behaviour: search, group by PO, bulk confirm, return to pool, all four dialogs (Quantity, Note, Attach, Confirm Invoice).
### Out of Scope
- Changes to the dev-identity switcher, dev-identity helpers, or the `wfdash_dev_email` cookie lifecycle.
- Changes to the `view-model.ts` (the page's existing helpers all stay).
- The `dev-identity-switcher` widget itself — it lives in the sprint page. We just borrow its style (a tiny header indicator) for consistency.
- Server routes — the actor flows entirely client-side via the cookie.
## Current State
- `ConfirmCard` (lines 504–752) uses `grid md:grid-cols-[minmax(0,1.2fr)_minmax(0,0.9fr)_auto]` to lay out header / details / actions side-by-side. The third column is a `flex flex-col` with five stacked buttons. On a typical 1440px screen the action column has ~12rem of width for five buttons + text — buttons truncate or wrap awkwardly.
- The `actor` state is hydrated from `localStorage.wfdash.actor` (lines 151–163). The dev-identity switcher writes to the `wfdash_dev_email` cookie via `setClientIdentity`, never to localStorage. The hydration finds nothing, `actor` stays empty, the submit handlers trip the "actor id is empty" toast.
- The sprint page (`src/app/(app)/sprint/page.tsx`) renders the `DevIdentitySwitcher` widget in its header. The collection-confirm page does not — so the operator has no way to see or change the active identity from this page.
## User Confirmed Rule
- Pick the best layout direction. (Option (b): stacked vertical sections per row, in a single card.)
- Actor id and name can be any shape derived from the cookie. (Use `id = email`, `name = local-part`.)
## UX
### Row card layout
```
┌─ Card ─────────────────────────────────────────────────────────┐
│ [✓] Supplier name PO-2026-07-009 │
│ Description text │
│ 👤 Customer · 🏢 Company · 🔗 Order link │
│ ───────────────────────────────────────────────────────────── │
│ ORDERED 12 │
│ COLLECTED 10 [Problem: short] [Invoice INV-1 · ...] │
│ ATTACHMENTS 📎 proof.pdf 📎 photo.jpg │
│ ───────────────────────────────────────────────────────────── │
│ [ Adjust Quantity ] [ Add Note ] [ Attach ] │
│ [ Confirm for invoicing ] [ Return to pool ] │
│ ───────────────────────────────────────────────────────────── │
│ Notes │
│ • 2026-07-09 · david.els1 · QUANTITY │
│ Adjusted qty 10 → 12. Operator error. │
└────────────────────────────────────────────────────────────┘
```
- The checkbox moves to the absolute top-left of the card, beside the supplier name.
- The actions grid is `grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2` — five buttons distribute cleanly. The primary "Confirm for invoicing" spans 2 columns on `sm:` to give it visual weight, the rest are 1 column each.
- The notes feed stays at the bottom, unchanged.
- The PO group header (the existing `<header>` above each group of rows) gets a `Selected: N` chip on the right and the "Confirm N of M" button. Unchanged otherwise.
### Identity indicator in the page header
A small, monospaced pill next to the "Refresh" button, mirroring the sprint page's identity switcher:
```
[ Refresh ] [ 👤 david.els1 ]
```
No edit affordance on this page — operators can change identity from the sprint page if needed. The pill is read-only here.
## Behavior
### Actor hydration
Replace the localStorage effect with a call to `getClientIdentity()`:
```ts
useEffect(() => {
if (typeof window === "undefined") return;
const email = getClientIdentity();
if (!email) return;
setActor({ id: email, name: email.split("@")[0] });
}, []);
```
The shape is unchanged: `actor.id` is the email (so `buildConfirmPatch`'s `setBy`/`setByName` fields stay valid), `actor.name` is the local-part (so badges and notes display the right operator name).
If the cookie is unset, `getClientIdentity()` returns the `DEFAULT_DEV_EMAIL` (`david.els1@outlook.com`). The actor hydrates to that default, and the "actor id is empty" toast never fires.
### Identity indicator
A `useEffect` reads `getClientIdentity()` on mount and renders the local-part as a small pill. Updates only on full page reload (cookie is set elsewhere); no live updates needed.
## Testing
### Unit / component tests
- `getClientIdentity()` returns the cookie value when set; falls back to `DEFAULT_DEV_EMAIL` otherwise.
- The page reads the actor from the cookie, not from localStorage.
- The "actor id is empty" toast does not fire when the cookie is set.
The page itself has no test file today. The simplest test target is the hydration logic if we extract it into a helper, but that may be over-engineering. **Recommendation: add a tiny unit test for the new hydration effect via `@testing-library/react` for the `actor` state derivation.**
If adding a test file is too heavy for this single change, leave it and document that the change is verified by manual smoke (open the page after a sprint-page identity switch; the cookie persists, the page hydrates, the confirm button works).
## Acceptance Criteria
- The row card reads top-to-bottom: header → details → actions → notes. No three-column cram.
- The Confirm for invoicing button is visually the primary action in the actions grid.
- The page header shows a "Signed in as X" pill that reflects the active dev identity.
- The "actor id is empty" toast never fires for a user who is signed in via the dev-identity switcher.
- All existing tests (if any) still pass; full suite non-regressive.
## Risk / Rollback
- **Layout risk:** every row's JSX changes. Visual regressions are likely in places I can't anticipate. The good news: there's no test on the page layout, so manual smoke is the only verification path.
- **Hydration risk:** low. The new effect reads the same cookie the rest of the app already uses. No new persistence surface.
- **Rollback:** revert the file. The actor hydration and layout changes are scoped to `page.tsx`. No shared component is touched.