This commit is contained in:
2026-03-15 10:13:53 -05:00
parent 552d4e2844
commit 6644ba2932
30 changed files with 1768 additions and 64 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 { FileAttachmentsPanel } from "../../components/FileAttachmentsPanel";
import { shipmentStatusOptions } from "./config";
import { ShipmentStatusBadge } from "./ShipmentStatusBadge";
@@ -15,7 +16,7 @@ export function ShipmentDetailPage() {
const [relatedShipments, setRelatedShipments] = useState<ShipmentSummaryDto[]>([]);
const [status, setStatus] = useState("Loading shipment...");
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
const [isRenderingPdf, setIsRenderingPdf] = useState(false);
const [activeDocumentAction, setActiveDocumentAction] = useState<"packing-slip" | "label" | "bol" | null>(null);
const canManage = user?.permissions.includes(permissions.shippingWrite) ?? false;
@@ -56,24 +57,48 @@ export function ShipmentDetailPage() {
}
}
async function handleOpenPackingSlip() {
async function handleOpenDocument(kind: "packing-slip" | "label" | "bol") {
if (!token || !shipment) {
return;
}
setIsRenderingPdf(true);
setStatus("Rendering packing slip PDF...");
setActiveDocumentAction(kind);
setStatus(
kind === "packing-slip"
? "Rendering packing slip PDF..."
: kind === "label"
? "Rendering shipping label PDF..."
: "Rendering bill of lading PDF..."
);
try {
const blob = await api.getShipmentPackingSlipPdf(token, shipment.id);
const blob =
kind === "packing-slip"
? await api.getShipmentPackingSlipPdf(token, shipment.id)
: kind === "label"
? await api.getShipmentLabelPdf(token, shipment.id)
: await api.getShipmentBillOfLadingPdf(token, shipment.id);
const objectUrl = URL.createObjectURL(blob);
window.open(objectUrl, "_blank", "noopener,noreferrer");
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000);
setStatus("Packing slip PDF rendered.");
setStatus(
kind === "packing-slip"
? "Packing slip PDF rendered."
: kind === "label"
? "Shipping label PDF rendered."
: "Bill of lading PDF rendered."
);
} catch (error: unknown) {
const message = error instanceof ApiError ? error.message : "Unable to render packing slip PDF.";
const message =
error instanceof ApiError
? error.message
: kind === "packing-slip"
? "Unable to render packing slip PDF."
: kind === "label"
? "Unable to render shipping label PDF."
: "Unable to render bill of lading PDF.";
setStatus(message);
} finally {
setIsRenderingPdf(false);
setActiveDocumentAction(null);
}
}
@@ -94,8 +119,14 @@ export function ShipmentDetailPage() {
<div className="flex flex-wrap gap-3">
<Link to="/shipping/shipments" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Back to shipments</Link>
<Link to={`/sales/orders/${shipment.salesOrderId}`} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">Open sales order</Link>
<button type="button" onClick={handleOpenPackingSlip} disabled={isRenderingPdf} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60">
{isRenderingPdf ? "Rendering PDF..." : "Open packing slip"}
<button type="button" onClick={() => handleOpenDocument("packing-slip")} disabled={activeDocumentAction !== null} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60">
{activeDocumentAction === "packing-slip" ? "Rendering PDF..." : "Open packing slip"}
</button>
<button type="button" onClick={() => handleOpenDocument("label")} disabled={activeDocumentAction !== null} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60">
{activeDocumentAction === "label" ? "Rendering PDF..." : "Open shipping label"}
</button>
<button type="button" onClick={() => handleOpenDocument("bol")} disabled={activeDocumentAction !== null} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60">
{activeDocumentAction === "bol" ? "Rendering PDF..." : "Open bill of lading"}
</button>
{canManage ? (
<Link to={`/shipping/shipments/${shipment.id}/edit`} className="inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">Edit shipment</Link>
@@ -168,6 +199,14 @@ export function ShipmentDetailPage() {
</div>
)}
</section>
<FileAttachmentsPanel
ownerType="SHIPMENT"
ownerId={shipment.id}
eyebrow="Logistics Attachments"
title="Shipment files"
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."
/>
</section>
);
}