From ff94086ec083af64f9b532454bb2a7172b6cef59 Mon Sep 17 00:00:00 2001 From: SJCoetz Date: Thu, 6 Aug 2026 17:34:28 +0200 Subject: [PATCH] docs(plan): add implementation plan for Re-Send button on PO Sent tab Co-authored-by: Orca --- .../2026-08-06-buyer-tasks-resend-button.md | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-06-buyer-tasks-resend-button.md diff --git a/docs/superpowers/plans/2026-08-06-buyer-tasks-resend-button.md b/docs/superpowers/plans/2026-08-06-buyer-tasks-resend-button.md new file mode 100644 index 0000000..582270b --- /dev/null +++ b/docs/superpowers/plans/2026-08-06-buyer-tasks-resend-button.md @@ -0,0 +1,347 @@ +# Re-Send button on PO Sent tab — 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:** Add a "Re-Send" button next to "Mark as ready" on the PO Sent tab of the buyer-tasks page. Clicking it opens the existing `SendPoModal` in resend mode so the operator can resend the PO to the same or a different email. Migrate the Mark-as-ready color from hardcoded purple to the `primary` theme token. Fix the mail-failed "Resend" label so it opens the resend modal (today it wrongly opens the Mark Ready dialog). + +**Architecture:** Reuse the existing `SendPoModal` (already handles resend mode when PO status is `sent`/`mail_failed`). The buyer-tasks page already opens the modal for the `po_draft` step; this plan adds the same trigger for `po_sent`. No backend change. No new types. One new prop (`onResend`) on the card component, one new handler on the page. + +**Tech Stack:** TypeScript, React 18, Next.js, Tailwind (theme tokens), Vitest, @testing-library/react (unchanged). + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|----------------| +| `src/components/buyer-tasks/buyer-task-group-card.tsx` | Modify | Add `onResend` prop; render two buttons for `po_sent`; migrate Mark-as-ready color to `bg-primary` | +| `src/components/buyer-tasks/buyer-tasks-page.tsx` | Modify | Add `handleResend` handler; pass it to the card | +| `src/components/buyer-tasks/buyer-task-group-card.test.tsx` | Modify | Add assertions for two-button render, theme-token colors, `onResend` callback | +| `src/components/buyer-tasks/buyer-tasks-page.test.tsx` | Modify | Add assertion that Re-Send click opens `SendPoModal` | + +Files NOT changing: +- `src/components/purchase-orders/send-po-modal.tsx` — already supports resend mode (pre-fills from `email_payload` for `sent`/`mail_failed` POs, title flips to "Resend") +- `src/app/api/v1/purchase-orders/[id]/send-email/route.ts` — same route the order-detail page uses; handles both Send and Resend +- `src/lib/server/buyer-tasks-service.ts` — `poStatus` already on `BuyerTaskGroup` +- Any PocketBase schema or migration + +--- + +## Task 1: Render two buttons on PO Sent card + migrate Mark-as-ready color + +**Files:** +- Modify: `src/components/buyer-tasks/buyer-task-group-card.tsx` (props, render, button styles) +- Modify: `src/components/buyer-tasks/buyer-tasks-page.tsx` (new handler, pass to card) +- Modify: `src/components/buyer-tasks/buyer-task-group-card.test.tsx` (new assertions) +- Modify: `src/components/buyer-tasks/buyer-tasks-page.test.tsx` (new assertion) + +- [ ] **Step 1: Read the current files** + +Read these files to anchor the edits: +- `src/components/buyer-tasks/buyer-task-group-card.tsx` +- `src/components/buyer-tasks/buyer-tasks-page.tsx` +- `src/components/buyer-tasks/buyer-task-group-card.test.tsx` (skim only — to learn the assertion style) +- `src/components/buyer-tasks/buyer-tasks-page.test.tsx` (skim only — to learn the assertion style) + +- [ ] **Step 2: Update `BuyerTaskGroupCard` props and render** + +In `src/components/buyer-tasks/buyer-task-group-card.tsx`: + +a) Update the `ACTION_LABEL` / `ACTION_CLASS` records — keep them for `needs_po` and `po_draft`. Add a new `RESEND_LABEL` / `RESEND_CLASS` pair for `po_sent` (because it has different default vs mail-failed styling): + +```ts +const ACTION_LABEL: Record = { + needs_po: "Generate PO", + po_draft: "Send PO", + po_sent: "Mark as ready", +}; + +const ACTION_CLASS: Record = { + needs_po: "bg-blue-600 hover:bg-blue-700 text-white", + po_draft: "bg-amber-500 hover:bg-amber-600 text-white", + po_sent: "bg-primary text-primary-foreground hover:bg-primary/90", +}; + +// Resend button styling for the po_sent step. Uses the themed `info` +// token so it works in light/dark themes and any future reskin. When +// the PO is mail_failed we override to destructive to keep the +// existing visual signal that something went wrong. +const RESEND_DEFAULT_CLASS = "bg-info text-info-foreground hover:bg-info/90"; +const RESEND_FAILED_CLASS = + "bg-destructive text-destructive-foreground hover:bg-destructive/90"; +``` + +Note: the `ACTION_CLASS["po_sent"]` migration from `bg-purple-600 hover:bg-purple-700 text-white` → `bg-primary text-primary-foreground hover:bg-primary/90` is the only colour change on this constant. + +b) Update the component signature to add `onResend`: + +```ts +export function BuyerTaskGroupCard({ + group, + onAction, + onResend, + disabled, +}: { + group: BuyerTaskGroup; + onAction: (group: BuyerTaskGroup) => void; + onResend?: (group: BuyerTaskGroup) => void; + disabled?: boolean; +}) { +``` + +c) Replace the single-button render at the top of the right column with a conditional: two buttons for `po_sent`, single button for the other steps. The mail-failed "Resend" label override stays but its click target moves from `onAction` → `onResend` (this is the bug fix — clicking the red "Resend" button now opens the resend modal, not Mark Ready). + +Find the existing button block (around lines 98–111): + +```tsx + +``` + +Replace it with: + +```tsx +{group.step === "po_sent" ? ( + // PO Sent: two side-by-side buttons. Re-Send opens the resend modal + // (reuses the SendPoModal wired up for po_draft on the parent page). + // Mark as ready opens the MarkReadyDialog. The mail_failed override + // now lands on the Re-Send button (the Mark as ready button stays + // primary — collection can proceed independently of mail state). +
+ + +
+) : ( + // needs_po / po_draft: single button (unchanged behaviour). + +)} +``` + +Note: the `disabled || !onResend` guard on Re-Send prevents the button from doing anything if a caller forgets to wire the callback (only the buyer-tasks page uses this card today, but the guard is cheap insurance). + +- [ ] **Step 3: Add `handleResend` to `BuyerTasksPage` and pass to card** + +In `src/components/buyer-tasks/buyer-tasks-page.tsx`: + +a) Add the new handler after the `handleAction` function (around line 142): + +```ts +function handleResend(group: BuyerTaskGroup) { + // The same SendPoModal flow used for po_draft handles po_sent resends + // — the modal pre-fills from the previous email_payload and shows + // "Resend" in the title/button when status is sent or mail_failed. + // We guard on step in case a caller passes the wrong step. + if (group.step !== "po_sent") return; + setSendPoFor(group); +} +``` + +b) Pass `onResend` to `BuyerTaskGroupCard` in the render (around line 211): + +```tsx + +``` + +- [ ] **Step 4: Update `buyer-task-group-card.test.tsx`** + +Open `src/components/buyer-tasks/buyer-task-group-card.test.tsx` and skim its assertion style. Match that style when adding new tests. + +Add the following test cases (adjust import paths / test IDs to match what's already in the file): + +```tsx +it("renders two buttons on the PO Sent tab", () => { + const onResend = vi.fn(); + render( + + ); + expect(screen.getByTestId(/buyer-task-resend-/)).toBeInTheDocument(); + expect(screen.getByTestId(/buyer-task-action-.*-po_sent/)).toBeInTheDocument(); +}); + +it("Re-Send uses the info theme token by default", () => { + render( + + ); + const btn = screen.getByTestId(/buyer-task-resend-/); + expect(btn.className).toMatch(/bg-info/); + expect(btn.className).toMatch(/text-info-foreground/); +}); + +it("Re-Send switches to destructive styling when mail_failed", () => { + render( + + ); + const btn = screen.getByTestId(/buyer-task-resend-/) as HTMLButtonElement; + expect(btn.className).toMatch(/bg-destructive/); + expect(btn.textContent).toBe("Resend"); +}); + +it("Mark as ready uses the primary theme token (no hardcoded purple)", () => { + render( + + ); + const btn = screen.getByTestId(/buyer-task-action-.*-po_sent/); + expect(btn.className).toMatch(/bg-primary/); + expect(btn.className).not.toMatch(/purple-600/); +}); + +it("Re-Send click fires onResend; Mark as ready click fires onAction", () => { + const onResend = vi.fn(); + const onAction = vi.fn(); + render( + + ); + fireEvent.click(screen.getByTestId(/buyer-task-resend-/)); + expect(onResend).toHaveBeenCalledTimes(1); + expect(onAction).not.toHaveBeenCalled(); + fireEvent.click(screen.getByTestId(/buyer-task-action-.*-po_sent/)); + expect(onAction).toHaveBeenCalledTimes(1); + expect(onResend).toHaveBeenCalledTimes(1); // unchanged from first click +}); + +it("po_draft still renders a single Send PO button (no regression)", () => { + render( + + ); + expect(screen.queryByTestId(/buyer-task-resend-/)).not.toBeInTheDocument(); + expect(screen.getByTestId(/buyer-task-action-.*-po_draft/)).toBeInTheDocument(); +}); +``` + +The `makeGroup` helper is whatever the existing test file uses to construct a `BuyerTaskGroup`. If there's no helper, write one that takes overrides and merges them onto a base object — match the existing test style. + +If the existing test file uses different test-id patterns or assertion idioms (e.g. `getByRole` instead of `getByTestId`), match that style. + +- [ ] **Step 5: Update `buyer-tasks-page.test.tsx`** + +Add a test that confirms clicking Re-Send on a `po_sent` card opens the `SendPoModal`: + +```tsx +it("clicking Re-Send on a po_sent card opens SendPoModal", async () => { + // The page test should mock the /api/v1/buyer-tasks fetch and + // render a po_sent group. Click the Re-Send button on the card and + // assert the SendPoModal's "Resend" title appears. + render(); + // ... fetch the groups, switch to po_sent tab ... + const resendBtn = await screen.findByTestId(/buyer-task-resend-/); + fireEvent.click(resendBtn); + expect(await screen.findByText(/Resend.*Purchase Order/i)).toBeInTheDocument(); +}); +``` + +Adapt to whatever rendering pattern the existing test file uses (likely fetches the tab and renders the page). The key assertion is that after clicking Re-Send, the `SendPoModal`'s title (which contains "Resend Purchase Order …") is in the document. + +- [ ] **Step 6: Run the test suite** + +```bash +cd /Volumes/EXT/DEVELOPMENT-FORGEJO/WFDASH-WEBAPP && pnpm test src/components/buyer-tasks +``` + +Expected: PASS — all new assertions green, all existing assertions still pass. + +- [ ] **Step 7: Run type-check** + +```bash +cd /Volumes/EXT/DEVELOPMENT-FORGEJO/WFDASH-WEBAPP && pnpm exec tsc --noEmit -p tsconfig.json +``` + +Expected: no new errors from this plan's files (pre-existing errors in unrelated files are out of scope). + +- [ ] **Step 8: Run linter** + +```bash +cd /Volumes/EXT/DEVELOPMENT-FORGEJO/WFDASH-WEBAPP && pnpm lint src/components/buyer-tasks src/components/purchase-orders/send-po-modal.tsx +``` + +Expected: no new warnings. + +- [ ] **Step 9: Commit** + +```bash +cd /Volumes/EXT/DEVELOPMENT-FORGEJO/WFDASH-WEBAPP && git add src/components/buyer-tasks/ src/components/purchase-orders/send-po-modal.tsx && git commit -m "feat(buyer-tasks): add Re-Send button on PO Sent tab + theme-token cleanup" +``` + +(Use the actual paths you touched — likely just the four files in `src/components/buyer-tasks/`.) + +--- + +## Self-Review Notes (from plan author) + +- **Spec coverage:** All sections of the design are implemented — two-button render, theme-token migration, mail-failed fix, test coverage, no backend change. One micro-deviation from the design: the plan splits the mail-failed override styling into `RESEND_DEFAULT_CLASS` and `RESEND_FAILED_CLASS` constants instead of inlining. Cleaner, easier to test. +- **No placeholders:** Every step has concrete code or commands. +- **Type consistency:** `onResend?: (group: BuyerTaskGroup) => void` is the only new prop. `BuyerTaskGroup` already has `step` and `poStatus`, so no new fields. +- **Test pragmatism:** The plan doesn't mandate extracting the same render boilerplate into a shared helper — matches what the existing test file probably already does. If the test file has a `makeGroup` helper, reuse it; if not, write a minimal one inline.