import { permissions } from "@mrp/shared"; import type { ProjectDetailDto } from "@mrp/shared/dist/projects/types.js"; import type { SalesOrderPlanningDto } from "@mrp/shared/dist/sales/types.js"; import type { SalesDocumentDetailDto } from "@mrp/shared/dist/sales/types.js"; import type { ShipmentDetailDto } from "@mrp/shared/dist/shipping/types.js"; import type { WorkOrderSummaryDto } from "@mrp/shared"; import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { FileAttachmentsPanel } from "../../components/FileAttachmentsPanel"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { projectMilestoneStatusPalette } from "./config"; import { ProjectPriorityBadge } from "./ProjectPriorityBadge"; import { ProjectStatusBadge } from "./ProjectStatusBadge"; export function ProjectDetailPage() { const { token, user } = useAuth(); const { projectId } = useParams(); const [project, setProject] = useState(null); const [workOrders, setWorkOrders] = useState([]); const [planning, setPlanning] = useState(null); const [quote, setQuote] = useState(null); const [salesOrder, setSalesOrder] = useState(null); const [shipment, setShipment] = useState(null); const [status, setStatus] = useState("Loading project..."); const canManage = user?.permissions.includes(permissions.projectsWrite) ?? false; useEffect(() => { if (!token || !projectId) { return; } api.getProject(token, projectId) .then(async (nextProject) => { setProject(nextProject); setStatus("Project loaded."); const [nextPlanning, nextWorkOrders, nextQuote, nextSalesOrder, nextShipment] = await Promise.all([ nextProject.salesOrderId ? api.getSalesOrderPlanning(token, nextProject.salesOrderId).catch(() => null) : Promise.resolve(null), api.getWorkOrders(token, { projectId: nextProject.id }), nextProject.salesQuoteId ? api.getQuote(token, nextProject.salesQuoteId).catch(() => null) : Promise.resolve(null), nextProject.salesOrderId ? api.getSalesOrder(token, nextProject.salesOrderId).catch(() => null) : Promise.resolve(null), nextProject.shipmentId ? api.getShipment(token, nextProject.shipmentId).catch(() => null) : Promise.resolve(null), ]); setPlanning(nextPlanning); setWorkOrders(nextWorkOrders); setQuote(nextQuote); setSalesOrder(nextSalesOrder); setShipment(nextShipment); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load project."; setStatus(message); }); }, [projectId, token]); if (!project) { return
{status}
; } const sortedMilestones = [...project.milestones].sort((left, right) => { if (left.status === "COMPLETE" && right.status !== "COMPLETE") { return 1; } if (left.status !== "COMPLETE" && right.status === "COMPLETE") { return -1; } if (left.dueDate && right.dueDate) { return new Date(left.dueDate).getTime() - new Date(right.dueDate).getTime(); } if (left.dueDate) { return -1; } if (right.dueDate) { return 1; } return left.sortOrder - right.sortOrder; }); const nextMilestone = sortedMilestones.find((milestone) => milestone.status !== "COMPLETE") ?? null; const activeWorkOrders = workOrders.filter((workOrder) => workOrder.status === "RELEASED" || workOrder.status === "IN_PROGRESS" || workOrder.status === "ON_HOLD"); const nextWorkOrder = [...activeWorkOrders] .sort((left, right) => { if (left.dueDate && right.dueDate) { return new Date(left.dueDate).getTime() - new Date(right.dueDate).getTime(); } if (left.dueDate) { return -1; } if (right.dueDate) { return 1; } return left.workOrderNumber.localeCompare(right.workOrderNumber); })[0] ?? null; const materialExceptionItems = planning ? planning.items.filter((item) => item.uncoveredQuantity > 0 || item.recommendedBuildQuantity > 0 || item.recommendedPurchaseQuantity > 0).slice(0, 5) : []; const completionPercent = project.rollups.milestoneCount > 0 ? Math.round((project.rollups.completedMilestoneCount / project.rollups.milestoneCount) * 100) : 0; return (

Project

{project.projectNumber}

{project.name}

Back to projects {canManage ? Edit project : null}

Customer

{project.customerName}

Owner

{project.ownerName || "Unassigned"}

Due Date

{project.dueDate ? new Date(project.dueDate).toLocaleDateString() : "Not set"}

Created

{new Date(project.createdAt).toLocaleDateString()}

Milestones

{project.rollups.completedMilestoneCount}/{project.rollups.milestoneCount}
{project.rollups.openMilestoneCount} open

Overdue Milestones

{project.rollups.overdueMilestoneCount}

Linked Work Orders

{project.rollups.workOrderCount}
{project.rollups.activeWorkOrderCount} active

Overdue Work Orders

{project.rollups.overdueWorkOrderCount}
{project.rollups.completedWorkOrderCount} complete

Project Cockpit

Cross-functional execution view

Commercial, supply, execution, and delivery signals for this program in one place.

Milestone Progress
{completionPercent}%

Commercial

{salesOrder ? `$${salesOrder.total.toFixed(2)}` : quote ? `$${quote.total.toFixed(2)}` : "Not linked"}
{salesOrder ? `${salesOrder.documentNumber} · ${salesOrder.status}` : quote ? `${quote.documentNumber} · ${quote.status}` : "Link a quote or sales order"}

Supply

{planning ? planning.summary.uncoveredItemCount : 0} shortage items
{planning ? `Build ${planning.summary.totalBuildQuantity} · Buy ${planning.summary.totalPurchaseQuantity}` : "No sales-order planning linked"}

Execution

{project.rollups.activeWorkOrderCount} active work orders
{nextWorkOrder ? `${nextWorkOrder.workOrderNumber} due ${nextWorkOrder.dueDate ? new Date(nextWorkOrder.dueDate).toLocaleDateString() : "unscheduled"}` : "No active work order due date"}

Delivery

{shipment ? shipment.status.replace("_", " ") : "Not linked"}
{shipment ? `${shipment.shipmentNumber} · ${shipment.packageCount} package(s)` : "Link a shipment to track delivery"}

Next Checkpoints

Milestone
{nextMilestone ? nextMilestone.title : "All milestones complete"}
{nextMilestone ? `${nextMilestone.status.replace("_", " ")} · ${nextMilestone.dueDate ? new Date(nextMilestone.dueDate).toLocaleDateString() : "No due date"}` : "No open milestone remains."}
Work Order
{nextWorkOrder ? nextWorkOrder.workOrderNumber : "No active work orders"}
{nextWorkOrder ? `${nextWorkOrder.itemSku} · ${nextWorkOrder.completedQuantity}/${nextWorkOrder.quantity} complete` : "Launch or link a work order to populate execution checkpoints."}

Material Watchlist

{materialExceptionItems.length === 0 ? (
No current build/buy exception items from linked sales-order planning.
) : (
{materialExceptionItems.map((item) => (
{item.itemSku}
{item.itemName}
Build {item.recommendedBuildQuantity} · Buy {item.recommendedPurchaseQuantity} · Uncovered {item.uncoveredQuantity}
))}
)}

Customer Linkage

Account
{project.customerName}
Email
{project.customerEmail}
Phone
{project.customerPhone}

Program Notes

{project.notes || "No project notes recorded."}

Commercial + Delivery Links

Quote
{project.salesQuoteNumber ? {project.salesQuoteNumber} : "Not linked"}
Sales Order
{project.salesOrderNumber ? {project.salesOrderNumber} : "Not linked"}
Shipment
{project.shipmentNumber ? {project.shipmentNumber} : "Not linked"}

Milestones

Track project checkpoints, blockers, and completion progress.

{canManage ? ( Edit milestones ) : null}
{project.milestones.length === 0 ? (
No milestones are defined for this project yet.
) : (
{project.milestones.map((milestone) => (
{milestone.title}
{milestone.status.replace("_", " ")} Due {milestone.dueDate ? new Date(milestone.dueDate).toLocaleDateString() : "not scheduled"} {milestone.completedAt ? ( Completed {new Date(milestone.completedAt).toLocaleDateString()} ) : null}
{milestone.notes ?
{milestone.notes}
: null}
))}
)}
{planning ? (

Material Readiness

Build Qty

{planning.summary.totalBuildQuantity}

Buy Qty

{planning.summary.totalPurchaseQuantity}

Uncovered Qty

{planning.summary.totalUncoveredQuantity}

Shortage Items

{planning.summary.uncoveredItemCount}
{planning.items .filter((item) => item.recommendedBuildQuantity > 0 || item.recommendedPurchaseQuantity > 0 || item.uncoveredQuantity > 0) .slice(0, 8) .map((item) => (
{item.itemSku}
{item.itemName}
Build {item.recommendedBuildQuantity} · Buy {item.recommendedPurchaseQuantity} · Uncovered {item.uncoveredQuantity}
))}
) : null}

Manufacturing Links

Work orders already linked to this project.

{canManage ? ( New work order ) : null}
{workOrders.length === 0 ? (
No work orders are linked to this project yet.
) : (
{workOrders.map((workOrder) => (
{workOrder.workOrderNumber}
{workOrder.itemSku} · {workOrder.completedQuantity}/{workOrder.quantity} complete
{workOrder.status.replace("_", " ")}
))}
)}
{status}
); }