= {
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-purple-600 hover:bg-purple-700 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";
+
export function BuyerTaskGroupCard({
group,
onAction,
+ onResend,
disabled,
}: {
group: BuyerTaskGroup;
onAction: (group: BuyerTaskGroup) => void;
+ onResend?: (group: BuyerTaskGroup) => void;
disabled?: boolean;
}) {
// Deterministic supplier stripe colour. Same supplier โ same colour
@@ -95,20 +105,53 @@ export function BuyerTaskGroupCard({
{group.itemCount} item{group.itemCount === 1 ? "" : "s"} ยท Cost R{group.totalCost.toFixed(2)}
-
+ {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).
+
+ )}
{group.poStatus === "mail_failed" && group.emailError && (
{
});
});
+ it("clicking Re-Send on a po_sent card opens SendPoModal", async () => {
+ const poSentGroup = {
+ ...sampleGroup,
+ groupId: "order-1::sup-1::po-sent",
+ purchaseOrderId: "po-1",
+ poStatus: "sent" as const,
+ poNumber: "PO-TEST-001",
+ supplierEmail: "supplier@example.com",
+ step: "po_sent" as const,
+ };
+ vi.spyOn(global, "fetch").mockImplementation(async (input) => {
+ const url = String(input);
+ const groups = url.includes("tab=po_sent") ? [poSentGroup] : [];
+ return new Response(JSON.stringify({ groups }), { status: 200 });
+ });
+
+ render();
+ fireEvent.click(screen.getByTestId("buyer-task-tab-po_sent"));
+
+ const resendBtn = await screen.findByTestId(/buyer-task-resend-/);
+ fireEvent.click(resendBtn);
+ expect(await screen.findByText(/resend purchase order po-test-001/i)).toBeInTheDocument();
+ });
+
it("calls generate-po when the action button is clicked", async () => {
vi.spyOn(global, "fetch")
.mockResolvedValueOnce(
diff --git a/src/components/buyer-tasks/buyer-tasks-page.tsx b/src/components/buyer-tasks/buyer-tasks-page.tsx
index 8d2b216..05c5161 100644
--- a/src/components/buyer-tasks/buyer-tasks-page.tsx
+++ b/src/components/buyer-tasks/buyer-tasks-page.tsx
@@ -140,6 +140,15 @@ export function BuyerTasksPage() {
}
}
+ 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);
+ }
+
async function handleMarkReadyConfirm(note: string) {
if (!markReadyFor) return;
const group = markReadyFor;
@@ -210,6 +219,7 @@ export function BuyerTasksPage() {
key={group.groupId}
group={group}
onAction={handleAction}
+ onResend={handleResend}
disabled={busyGroupId === group.groupId}
/>
))}