import { permissions } from "@mrp/shared"; import type { SalesDocumentDetailDto, SalesDocumentStatus } from "@mrp/shared/dist/sales/types.js"; import type { ShipmentSummaryDto } from "@mrp/shared/dist/shipping/types.js"; import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { salesConfigs, salesStatusOptions, type SalesDocumentEntity } from "./config"; import { SalesStatusBadge } from "./SalesStatusBadge"; import { ShipmentStatusBadge } from "../shipping/ShipmentStatusBadge"; export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) { const { token, user } = useAuth(); const navigate = useNavigate(); const { quoteId, orderId } = useParams(); const config = salesConfigs[entity]; const documentId = entity === "quote" ? quoteId : orderId; const [document, setDocument] = useState(null); const [status, setStatus] = useState(`Loading ${config.singularLabel.toLowerCase()}...`); const [isUpdatingStatus, setIsUpdatingStatus] = useState(false); const [isConverting, setIsConverting] = useState(false); const [isOpeningPdf, setIsOpeningPdf] = useState(false); const [shipments, setShipments] = useState([]); const canManage = user?.permissions.includes(permissions.salesWrite) ?? false; const canManageShipping = user?.permissions.includes(permissions.shippingWrite) ?? false; const canReadShipping = user?.permissions.includes(permissions.shippingRead) ?? false; useEffect(() => { if (!token || !documentId) { return; } const loader = entity === "quote" ? api.getQuote(token, documentId) : api.getSalesOrder(token, documentId); loader .then((nextDocument) => { setDocument(nextDocument); setStatus(`${config.singularLabel} loaded.`); if (entity === "order" && canReadShipping) { api.getShipments(token, { salesOrderId: nextDocument.id }).then(setShipments).catch(() => setShipments([])); } }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : `Unable to load ${config.singularLabel.toLowerCase()}.`; setStatus(message); }); }, [canReadShipping, config.singularLabel, documentId, entity, token]); if (!document) { return
{status}
; } const activeDocument = document; async function handleStatusChange(nextStatus: SalesDocumentStatus) { if (!token) { return; } setIsUpdatingStatus(true); setStatus(`Updating ${config.singularLabel.toLowerCase()} status...`); try { const nextDocument = entity === "quote" ? await api.updateQuoteStatus(token, activeDocument.id, nextStatus) : await api.updateSalesOrderStatus(token, activeDocument.id, nextStatus); setDocument(nextDocument); setStatus(`${config.singularLabel} status updated.`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : `Unable to update ${config.singularLabel.toLowerCase()} status.`; setStatus(message); } finally { setIsUpdatingStatus(false); } } async function handleConvert() { if (!token || entity !== "quote") { return; } setIsConverting(true); setStatus("Converting quote to sales order..."); try { const order = await api.convertQuoteToSalesOrder(token, activeDocument.id); navigate(`/sales/orders/${order.id}`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to convert quote to sales order."; setStatus(message); setIsConverting(false); } } async function handleOpenPdf() { if (!token) { return; } setIsOpeningPdf(true); setStatus(`Rendering ${config.singularLabel.toLowerCase()} PDF...`); try { const blob = entity === "quote" ? await api.getQuotePdf(token, activeDocument.id) : await api.getSalesOrderPdf(token, activeDocument.id); const objectUrl = URL.createObjectURL(blob); window.open(objectUrl, "_blank", "noopener,noreferrer"); window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000); setStatus(`${config.singularLabel} PDF ready.`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : `Unable to render ${config.singularLabel.toLowerCase()} PDF.`; setStatus(message); } finally { setIsOpeningPdf(false); } } return (

{config.detailEyebrow}

{activeDocument.documentNumber}

{activeDocument.customerName}

Back to {config.collectionLabel.toLowerCase()} {canManage ? ( <> Edit {config.singularLabel.toLowerCase()} {entity === "quote" ? ( ) : null} {entity === "order" && canManageShipping ? ( New shipment ) : null} ) : null}
{canManage ? (

Quick Actions

Update document status without opening the full editor.

{salesStatusOptions.map((option) => ( ))}
) : null}

Issue Date

{new Date(activeDocument.issueDate).toLocaleDateString()}

Expires

{activeDocument.expiresAt ? new Date(activeDocument.expiresAt).toLocaleDateString() : "N/A"}

Lines

{activeDocument.lineCount}

Subtotal

${activeDocument.subtotal.toFixed(2)}

Discount

-${activeDocument.discountAmount.toFixed(2)}
{activeDocument.discountPercent.toFixed(2)}%

Tax

${activeDocument.taxAmount.toFixed(2)}
{activeDocument.taxPercent.toFixed(2)}%

Freight

${activeDocument.freightAmount.toFixed(2)}

Total

${activeDocument.total.toFixed(2)}

Customer

Account
{activeDocument.customerName}
Email
{activeDocument.customerEmail}

Notes

{activeDocument.notes || "No notes recorded for this document."}

Line Items

{activeDocument.lines.length === 0 ? (
No line items have been added yet.
) : (
{activeDocument.lines.map((line: SalesDocumentDetailDto["lines"][number]) => ( ))}
Item Description Qty UOM Unit Price Total
{line.itemSku}
{line.itemName}
{line.description} {line.quantity} {line.unitOfMeasure} ${line.unitPrice.toFixed(2)} ${line.lineTotal.toFixed(2)}
)}
{entity === "order" && canReadShipping ? (

Shipping

Shipment records currently tied to this sales order.

{canManageShipping ? ( Create shipment ) : null}
{shipments.length === 0 ? (
No shipments have been created for this sales order yet.
) : (
{shipments.map((shipment) => (
{shipment.shipmentNumber}
{shipment.carrier || "Carrier not set"} ยท {shipment.trackingNumber || "No tracking"}
))}
)}
) : null}
); }