8.2 KiB
Collection Confirm — layout rewrite + actor hydration fix
Goal: Two distinct fixes on the Collection Confirm page:
- 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.
- Fix the "actor id is empty" error. The actor state hydrates from
localStorage.wfdash.actor, which is never written. The real identity lives in thewfdash_dev_emailcookie. Hydrate from the cookie viagetClientIdentity()instead.
Scope
In Scope
- Rewrite the
ConfirmCardJSX insrc/app/(app)/collection-confirm/page.tsxfrom 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 onmd:) → actions (full-width buttons in a 2x3 grid onsm:, single column onxs:) → notes feed (existing). - Replace the
localStorage.wfdash.actorhydration withgetClientIdentity()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_emailcookie lifecycle. - Changes to the
view-model.ts(the page's existing helpers all stay). - The
dev-identity-switcherwidget 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) usesgrid 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 aflex flex-colwith 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
actorstate is hydrated fromlocalStorage.wfdash.actor(lines 151–163). The dev-identity switcher writes to thewfdash_dev_emailcookie viasetClientIdentity, never to localStorage. The hydration finds nothing,actorstays empty, the submit handlers trip the "actor id is empty" toast. - The sprint page (
src/app/(app)/sprint/page.tsx) renders theDevIdentitySwitcherwidget 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 onsm: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 aSelected: Nchip 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():
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 toDEFAULT_DEV_EMAILotherwise.- 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.