This commit is contained in:
2026-03-15 19:22:20 -05:00
parent df041254da
commit 275c73b584
8 changed files with 268 additions and 23 deletions

View File

@@ -5,6 +5,7 @@ import { Link, useParams } from "react-router-dom";
import { useAuth } from "../../auth/AuthProvider";
import { api, ApiError } from "../../lib/api";
import { ConfirmActionDialog } from "../../components/ConfirmActionDialog";
import { FileAttachmentsPanel } from "../../components/FileAttachmentsPanel";
import { shipmentStatusOptions } from "./config";
import { ShipmentStatusBadge } from "./ShipmentStatusBadge";
@@ -17,6 +18,19 @@ export function ShipmentDetailPage() {
const [status, setStatus] = useState("Loading shipment...");
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
const [activeDocumentAction, setActiveDocumentAction] = useState<"packing-slip" | "label" | "bol" | null>(null);
const [pendingConfirmation, setPendingConfirmation] = useState<
| {
title: string;
description: string;
impact: string;
recovery: string;
confirmLabel: string;
confirmationLabel?: string;
confirmationValue?: string;
nextStatus: ShipmentStatus;
}
| null
>(null);
const canManage = user?.permissions.includes(permissions.shippingWrite) ?? false;
@@ -38,7 +52,7 @@ export function ShipmentDetailPage() {
});
}, [shipmentId, token]);
async function handleStatusChange(nextStatus: ShipmentStatus) {
async function applyStatusChange(nextStatus: ShipmentStatus) {
if (!token || !shipment) {
return;
}
@@ -48,7 +62,7 @@ export function ShipmentDetailPage() {
try {
const nextShipment = await api.updateShipmentStatus(token, shipment.id, nextStatus);
setShipment(nextShipment);
setStatus("Shipment status updated.");
setStatus("Shipment status updated. Verify carrier paperwork and sales-order expectations if the shipment moved into a terminal state.");
} catch (error: unknown) {
const message = error instanceof ApiError ? error.message : "Unable to update shipment status.";
setStatus(message);
@@ -57,6 +71,29 @@ export function ShipmentDetailPage() {
}
}
function handleStatusChange(nextStatus: ShipmentStatus) {
if (!shipment) {
return;
}
const label = shipmentStatusOptions.find((option) => option.value === nextStatus)?.label ?? nextStatus;
setPendingConfirmation({
title: `Set shipment to ${label}`,
description: `Update shipment ${shipment.shipmentNumber} from ${shipment.status} to ${nextStatus}.`,
impact:
nextStatus === "DELIVERED"
? "This marks delivery complete and can affect customer communication and project/shipping readiness views."
: nextStatus === "SHIPPED"
? "This marks the shipment as outbound and can trigger customer-facing tracking and downstream delivery expectations."
: "This changes the logistics state used by related shipping and sales workflows.",
recovery: "If the status is wrong, return the shipment to the correct state and confirm the linked sales order still reflects reality.",
confirmLabel: `Set ${label}`,
confirmationLabel: nextStatus === "DELIVERED" ? "Type shipment number to confirm:" : undefined,
confirmationValue: nextStatus === "DELIVERED" ? shipment.shipmentNumber : undefined,
nextStatus,
});
}
async function handleOpenDocument(kind: "packing-slip" | "label" | "bol") {
if (!token || !shipment) {
return;
@@ -207,6 +244,30 @@ export function ShipmentDetailPage() {
description="Store carrier paperwork, signed delivery records, bills of lading, and related logistics support files on the shipment record."
emptyMessage="No logistics attachments have been uploaded for this shipment yet."
/>
<ConfirmActionDialog
open={pendingConfirmation != null}
title={pendingConfirmation?.title ?? "Confirm shipment action"}
description={pendingConfirmation?.description ?? ""}
impact={pendingConfirmation?.impact}
recovery={pendingConfirmation?.recovery}
confirmLabel={pendingConfirmation?.confirmLabel ?? "Confirm"}
confirmationLabel={pendingConfirmation?.confirmationLabel}
confirmationValue={pendingConfirmation?.confirmationValue}
isConfirming={isUpdatingStatus}
onClose={() => {
if (!isUpdatingStatus) {
setPendingConfirmation(null);
}
}}
onConfirm={async () => {
if (!pendingConfirmation) {
return;
}
await applyStatusChange(pendingConfirmation.nextStatus);
setPendingConfirmation(null);
}}
/>
</section>
);
}