diff --git a/src/app/(app)/collection-pool/page.tsx b/src/app/(app)/collection-pool/page.tsx index 55b19c3..0e21562 100644 --- a/src/app/(app)/collection-pool/page.tsx +++ b/src/app/(app)/collection-pool/page.tsx @@ -15,6 +15,7 @@ import { AssignDriverDialog, type AssignDriverSubmitPayload, } from "@/components/ui/assign-driver-dialog"; +import { MarkReadyDialog } from "@/components/ui/mark-ready-dialog"; import { parseCoordinates } from "@/lib/geo"; import { getPocketBase } from "@/lib/pocketbase"; import { getMappableSuppliers } from "@/lib/pb-helpers"; @@ -111,6 +112,8 @@ export default function CollectionPoolPage() { const supFetchPromise = useRef | null>(null); const [driversById, setDriversById] = useState>({}); const [assignDialog, setAssignDialog] = useState(null); + const [readyDialogRow, setReadyDialogRow] = useState(null); + const [readyDialogError, setReadyDialogError] = useState(null); const closeAssignDialog = useCallback(() => setAssignDialog(null), []); @@ -284,28 +287,71 @@ export default function CollectionPoolPage() { ); const handleToggleReady = useCallback( - async (itemId: string, ready: boolean) => { - setBusyId(itemId); + async (row: CollectionPoolVisibleRow, ready: boolean) => { + // Unmarking is a single PATCH with no note. The modal only opens + // for the "mark ready" direction. + if (!ready) { + setBusyId(row.id); + try { + const res = await fetch(`/api/v1/collection-map/items/${row.id}/assign`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ready_for_collection: false }), + }); + if (!res.ok) { + const text = await res.text().catch(() => ""); + throw new Error(text || `HTTP ${res.status}`); + } + toast.success("Marked awaiting supplier confirmation."); + await load(); + } catch (err) { + toast.error(err instanceof Error ? err.message : String(err)); + } finally { + setBusyId(null); + } + return; + } + // Marking ready opens the modal. The actual PATCH is fired from + // `submitMarkReady` once the operator has typed a note. + setReadyDialogError(null); + setReadyDialogRow(row); + }, + [load], + ); + + const submitMarkReady = useCallback( + async (note: string) => { + const row = readyDialogRow; + if (!row) return; + setBusyId(row.id); + setReadyDialogError(null); try { - const res = await fetch(`/api/v1/collection-map/items/${itemId}/assign`, { + const res = await fetch(`/api/v1/collection-map/items/${row.id}/assign`, { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ ready_for_collection: ready }), + body: JSON.stringify({ + ready_for_collection: true, + ready_notes: note, + }), }); if (!res.ok) { - const text = await res.text().catch(() => ""); - throw new Error(text || `HTTP ${res.status}`); + const body = await res.json().catch(() => ({})); + const message = body?.error || `HTTP ${res.status}`; + // Keep the modal open so the operator can fix the error. + setReadyDialogError(message); + throw new Error(message); } - toast.success(ready ? "Marked ready for collection." : "Marked awaiting supplier confirmation."); + toast.success("Marked ready — note saved."); + setReadyDialogRow(null); await load(); } catch (err) { - const message = err instanceof Error ? err.message : String(err); - toast.error(message); + // Error already shown via readyDialogError / toast. + console.error("Mark ready failed:", err); } finally { setBusyId(null); } }, - [load], + [load, readyDialogRow], ); return ( @@ -482,6 +528,22 @@ export default function CollectionPoolPage() { ) : undefined } /> + {readyDialogRow && ( + { + if (!o) { + setReadyDialogRow(null); + setReadyDialogError(null); + } + }} + onSubmit={submitMarkReady} + poLabel={readyDialogRow.poNumber} + itemLabel={readyDialogRow.description} + busy={busyId === readyDialogRow.id} + serverError={readyDialogError} + /> + )} ); } @@ -499,7 +561,7 @@ function PoolRow({ markerToken?: string; todayYmd: string; onOpenAssign: (row: CollectionPoolVisibleRow) => void; - onToggleReady: (id: string, ready: boolean) => void; + onToggleReady: (row: CollectionPoolVisibleRow, ready: boolean) => void; }) { const assignmentState = getAssignmentState(row, todayYmd); const assignmentBadge = getAssignmentBadge(assignmentState); @@ -560,6 +622,14 @@ function PoolRow({ > {readyLabel} + {row.notes && row.notes.trim().length > 0 && ( + 120 ? `${row.notes.slice(0, 120)}…` : row.notes} + > + Notes + + )}
@@ -569,7 +639,7 @@ function PoolRow({