2026-03-15 11:12:58 -05:00
import { permissions } from "@mrp/shared" ;
import type { WorkOrderCompletionInput , WorkOrderDetailDto , WorkOrderMaterialIssueInput , WorkOrderStatus } from "@mrp/shared" ;
import type { WarehouseLocationOptionDto } from "@mrp/shared/dist/inventory/types.js" ;
import { useEffect , useMemo , 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" ;
2026-03-15 18:59:37 -05:00
import { ConfirmActionDialog } from "../../components/ConfirmActionDialog" ;
2026-03-15 11:12:58 -05:00
import { emptyCompletionInput , emptyMaterialIssueInput , workOrderStatusOptions } from "./config" ;
import { WorkOrderStatusBadge } from "./WorkOrderStatusBadge" ;
export function WorkOrderDetailPage() {
const { token , user } = useAuth ( ) ;
const { workOrderId } = useParams ( ) ;
const [ workOrder , setWorkOrder ] = useState < WorkOrderDetailDto | null > ( null ) ;
const [ locationOptions , setLocationOptions ] = useState < WarehouseLocationOptionDto [ ] > ( [ ] ) ;
const [ issueForm , setIssueForm ] = useState < WorkOrderMaterialIssueInput > ( emptyMaterialIssueInput ) ;
const [ completionForm , setCompletionForm ] = useState < WorkOrderCompletionInput > ( emptyCompletionInput ) ;
const [ status , setStatus ] = useState ( "Loading work order..." ) ;
const [ isUpdatingStatus , setIsUpdatingStatus ] = useState ( false ) ;
const [ isPostingIssue , setIsPostingIssue ] = useState ( false ) ;
const [ isPostingCompletion , setIsPostingCompletion ] = useState ( false ) ;
2026-03-15 18:59:37 -05:00
const [ pendingConfirmation , setPendingConfirmation ] = useState <
| {
kind : "status" | "issue" | "completion" ;
title : string ;
description : string ;
impact : string ;
recovery : string ;
confirmLabel : string ;
confirmationLabel? : string ;
confirmationValue? : string ;
nextStatus? : WorkOrderStatus ;
}
| null
> ( null ) ;
2026-03-15 11:12:58 -05:00
const canManage = user ? . permissions . includes ( permissions . manufacturingWrite ) ? ? false ;
useEffect ( ( ) = > {
if ( ! token || ! workOrderId ) {
return ;
}
api . getWorkOrder ( token , workOrderId )
. then ( ( nextWorkOrder ) = > {
setWorkOrder ( nextWorkOrder ) ;
setIssueForm ( {
. . . emptyMaterialIssueInput ,
warehouseId : nextWorkOrder.warehouseId ,
locationId : nextWorkOrder.locationId ,
} ) ;
setCompletionForm ( {
. . . emptyCompletionInput ,
quantity : Math.max ( nextWorkOrder . dueQuantity , 1 ) ,
} ) ;
setStatus ( "Work order loaded." ) ;
} )
. catch ( ( error : unknown ) = > {
const message = error instanceof ApiError ? error . message : "Unable to load work order." ;
setStatus ( message ) ;
} ) ;
api . getWarehouseLocationOptions ( token ) . then ( setLocationOptions ) . catch ( ( ) = > setLocationOptions ( [ ] ) ) ;
} , [ token , workOrderId ] ) ;
const filteredLocationOptions = useMemo (
( ) = > locationOptions . filter ( ( option ) = > option . warehouseId === issueForm . warehouseId ) ,
[ issueForm . warehouseId , locationOptions ]
) ;
2026-03-15 18:59:37 -05:00
async function applyStatusChange ( nextStatus : WorkOrderStatus ) {
2026-03-15 11:12:58 -05:00
if ( ! token || ! workOrder ) {
return ;
}
setIsUpdatingStatus ( true ) ;
setStatus ( "Updating work-order status..." ) ;
try {
const nextWorkOrder = await api . updateWorkOrderStatus ( token , workOrder . id , nextStatus ) ;
setWorkOrder ( nextWorkOrder ) ;
2026-03-15 18:59:37 -05:00
setStatus ( "Work-order status updated. Review downstream planning and shipment readiness if this change affects execution timing." ) ;
2026-03-15 11:12:58 -05:00
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to update work-order status." ;
setStatus ( message ) ;
} finally {
setIsUpdatingStatus ( false ) ;
}
}
2026-03-15 18:59:37 -05:00
async function submitIssue() {
2026-03-15 11:12:58 -05:00
if ( ! token || ! workOrder ) {
return ;
}
setIsPostingIssue ( true ) ;
setStatus ( "Posting material issue..." ) ;
try {
const nextWorkOrder = await api . issueWorkOrderMaterial ( token , workOrder . id , issueForm ) ;
setWorkOrder ( nextWorkOrder ) ;
setIssueForm ( {
. . . emptyMaterialIssueInput ,
warehouseId : nextWorkOrder.warehouseId ,
locationId : nextWorkOrder.locationId ,
} ) ;
2026-03-15 18:59:37 -05:00
setStatus ( "Material issue posted. This consumed inventory immediately; post a correcting stock movement if the issue quantity was wrong." ) ;
2026-03-15 11:12:58 -05:00
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to post material issue." ;
setStatus ( message ) ;
} finally {
setIsPostingIssue ( false ) ;
}
}
2026-03-15 18:59:37 -05:00
async function submitCompletion() {
2026-03-15 11:12:58 -05:00
if ( ! token || ! workOrder ) {
return ;
}
setIsPostingCompletion ( true ) ;
setStatus ( "Posting completion..." ) ;
try {
const nextWorkOrder = await api . recordWorkOrderCompletion ( token , workOrder . id , completionForm ) ;
setWorkOrder ( nextWorkOrder ) ;
setCompletionForm ( {
. . . emptyCompletionInput ,
quantity : Math.max ( nextWorkOrder . dueQuantity , 1 ) ,
} ) ;
2026-03-15 18:59:37 -05:00
setStatus ( "Completion posted. Finished-goods stock has been received; verify the remaining quantity and post a correcting transaction if this completion was overstated." ) ;
2026-03-15 11:12:58 -05:00
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to post completion." ;
setStatus ( message ) ;
} finally {
setIsPostingCompletion ( false ) ;
}
}
2026-03-15 18:59:37 -05:00
function handleStatusChange ( nextStatus : WorkOrderStatus ) {
if ( ! workOrder ) {
return ;
}
const option = workOrderStatusOptions . find ( ( entry ) = > entry . value === nextStatus ) ;
setPendingConfirmation ( {
kind : "status" ,
title : ` Change status to ${ option ? . label ? ? nextStatus } ` ,
description : ` Update work order ${ workOrder . workOrderNumber } from ${ workOrder . status } to ${ nextStatus } . ` ,
impact :
nextStatus === "CANCELLED"
? "Cancelling a work order can invalidate planning assumptions, reservations, and operator expectations."
: nextStatus === "COMPLETE"
? "Completing the work order signals execution closure and can change readiness views across the system."
: "This changes the execution state used by planning, dashboards, and downstream operational review." ,
recovery : "If this status was selected in error, set the work order back to the correct state immediately after review." ,
confirmLabel : ` Set ${ option ? . label ? ? nextStatus } ` ,
confirmationLabel : nextStatus === "CANCELLED" ? "Type work-order number to confirm:" : undefined ,
confirmationValue : nextStatus === "CANCELLED" ? workOrder.workOrderNumber : undefined ,
nextStatus ,
} ) ;
}
function handleIssueSubmit ( event : React.FormEvent < HTMLFormElement > ) {
event . preventDefault ( ) ;
if ( ! workOrder ) {
return ;
}
const component = workOrder . materialRequirements . find ( ( requirement ) = > requirement . componentItemId === issueForm . componentItemId ) ;
setPendingConfirmation ( {
kind : "issue" ,
title : "Post material issue" ,
description : ` Issue ${ issueForm . quantity } units of ${ component ? . componentSku ? ? "the selected component" } to work order ${ workOrder . workOrderNumber } . ` ,
impact : "This consumes component inventory immediately and updates work-order material history." ,
recovery : "If the wrong quantity was issued, post a correcting stock transaction and note the reason on the work order." ,
confirmLabel : "Post issue" ,
confirmationLabel : "Type work-order number to confirm:" ,
confirmationValue : workOrder.workOrderNumber ,
} ) ;
}
function handleCompletionSubmit ( event : React.FormEvent < HTMLFormElement > ) {
event . preventDefault ( ) ;
if ( ! workOrder ) {
return ;
}
setPendingConfirmation ( {
kind : "completion" ,
title : "Post production completion" ,
description : ` Receive ${ completionForm . quantity } finished units into ${ workOrder . warehouseCode } / ${ workOrder . locationCode } . ` ,
impact : "This increases finished-goods inventory immediately and advances the execution history for this work order." ,
recovery : "If the completion quantity is wrong, post the correcting inventory movement and verify the work-order remaining quantity." ,
confirmLabel : "Post completion" ,
confirmationLabel : completionForm.quantity >= workOrder . dueQuantity ? "Type work-order number to confirm:" : undefined ,
confirmationValue : completionForm.quantity >= workOrder . dueQuantity ? workOrder.workOrderNumber : undefined ,
} ) ;
}
2026-03-15 11:12:58 -05:00
if ( ! workOrder ) {
return < div className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel" > { status } < / div > ;
}
return (
< section className = "space-y-4" >
< div className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< div className = "flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between" >
< div >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Work Order < / p >
< h3 className = "mt-2 text-xl font-bold text-text" > { workOrder . workOrderNumber } < / h3 >
< p className = "mt-1 text-sm text-text" > { workOrder . itemSku } - { workOrder . itemName } < / p >
< div className = "mt-3" > < WorkOrderStatusBadge status = { workOrder . status } / > < / div >
< / div >
< div className = "flex flex-wrap gap-3" >
< Link to = "/manufacturing/work-orders" 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 work orders < / Link >
{ workOrder . projectId ? < Link to = { ` /projects/ ${ workOrder . projectId } ` } className = "inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text" > Open project < / Link > : null }
2026-03-15 16:40:25 -05:00
{ workOrder . salesOrderId ? < Link to = { ` /sales/orders/ ${ workOrder . 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 > : null }
2026-03-15 11:12:58 -05:00
< Link to = { ` /inventory/items/ ${ workOrder . itemId } ` } className = "inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text" > Open item < / Link >
{ canManage ? < Link to = { ` /manufacturing/work-orders/ ${ workOrder . id } /edit ` } className = "inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white" > Edit work order < / Link > : null }
< / div >
< / div >
< / div >
{ canManage ? (
< section className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< div className = "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between" >
< div >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Quick Actions < / p >
< p className = "mt-2 text-sm text-muted" > Release , hold , or close administrative status from the work - order record . < / p >
< / div >
< div className = "flex flex-wrap gap-2" >
{ workOrderStatusOptions . map ( ( option ) = > (
< button key = { option . value } type = "button" onClick = { ( ) = > handleStatusChange ( option . value ) } disabled = { isUpdatingStatus || workOrder . status === option . value } className = "rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60" >
{ option . label }
< / button >
) ) }
< / div >
< / div >
< / section >
) : null }
2026-03-15 12:11:46 -05:00
< section className = "grid gap-3 xl:grid-cols-6" >
2026-03-15 11:12:58 -05:00
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Planned < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . quantity } < / div > < / article >
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Completed < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . completedQuantity } < / div > < / article >
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Remaining < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . dueQuantity } < / div > < / article >
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Project < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . projectNumber || "Unlinked" } < / div > < / article >
2026-03-15 12:11:46 -05:00
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Operations < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . operations . length } < / div > < / article >
2026-03-15 11:12:58 -05:00
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Due Date < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . dueDate ? new Date ( workOrder . dueDate ) . toLocaleDateString ( ) : "Not set" } < / div > < / article >
2026-03-15 16:40:25 -05:00
< article className = "rounded-[24px] border border-line/70 bg-surface/90 px-3 py-3 shadow-panel" > < p className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Material Shortage < / p > < div className = "mt-2 text-base font-bold text-text" > { workOrder . materialRequirements . reduce ( ( sum , requirement ) = > sum + requirement . shortageQuantity , 0 ) } < / div > < / article >
2026-03-15 11:12:58 -05:00
< / section >
< div className = "grid gap-3 xl:grid-cols-[minmax(0,1fr)_minmax(360px,0.9fr)]" >
< article className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Execution Context < / p >
< dl className = "mt-5 grid gap-3" >
< div > < dt className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Build item < / dt > < dd className = "mt-1 text-sm text-text" > { workOrder . itemSku } - { workOrder . itemName } < / dd > < / div >
< div > < dt className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Item type < / dt > < dd className = "mt-1 text-sm text-text" > { workOrder . itemType } < / dd > < / div >
< div > < dt className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Output location < / dt > < dd className = "mt-1 text-sm text-text" > { workOrder . warehouseCode } / { workOrder . locationCode } < / dd > < / div >
< div > < dt className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Project customer < / dt > < dd className = "mt-1 text-sm text-text" > { workOrder . projectCustomerName || "Not linked" } < / dd > < / div >
2026-03-15 16:40:25 -05:00
< div > < dt className = "text-xs font-semibold uppercase tracking-[0.18em] text-muted" > Demand source < / dt > < dd className = "mt-1 text-sm text-text" > { workOrder . salesOrderNumber ? ? "Not linked" } < / dd > < / div >
2026-03-15 11:12:58 -05:00
< / dl >
< / article >
< article className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Work Instructions < / p >
< p className = "mt-3 whitespace-pre-line text-sm leading-6 text-text" > { workOrder . notes || "No work-order notes recorded." } < / p >
< / article >
< / div >
2026-03-15 12:11:46 -05:00
< section className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Operation Plan < / p >
{ workOrder . operations . length === 0 ? (
< div className = "mt-6 rounded-3xl border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted" > This work order has no inherited station operations . Add routing steps on the item record to automate planning . < / div >
) : (
< div className = "mt-5 overflow-hidden rounded-3xl border border-line/70" >
< table className = "min-w-full divide-y divide-line/70 text-sm" >
< thead className = "bg-page/70" >
< tr className = "text-left text-xs font-semibold uppercase tracking-[0.18em] text-muted" >
< th className = "px-3 py-3" > Seq < / th >
< th className = "px-3 py-3" > Station < / th >
< th className = "px-3 py-3" > Start < / th >
< th className = "px-3 py-3" > End < / th >
< th className = "px-3 py-3" > Minutes < / th >
< / tr >
< / thead >
< tbody className = "divide-y divide-line/70" >
{ workOrder . operations . map ( ( operation ) = > (
< tr key = { operation . id } className = "bg-surface/70" >
< td className = "px-3 py-3 text-text" > { operation . sequence } < / td >
< td className = "px-3 py-3" >
< div className = "font-semibold text-text" > { operation . stationCode } < / div >
< div className = "mt-1 text-xs text-muted" > { operation . stationName } < / div >
< / td >
< td className = "px-3 py-3 text-text" > { new Date ( operation . plannedStart ) . toLocaleString ( ) } < / td >
< td className = "px-3 py-3 text-text" > { new Date ( operation . plannedEnd ) . toLocaleString ( ) } < / td >
< td className = "px-3 py-3 text-text" > { operation . plannedMinutes } < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / div >
) }
< / section >
2026-03-15 11:12:58 -05:00
{ canManage ? (
< section className = "grid gap-3 xl:grid-cols-2" >
< form onSubmit = { handleIssueSubmit } className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Material Issue < / p >
< div className = "mt-4 grid gap-3" >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Component < / span >
< select value = { issueForm . componentItemId } onChange = { ( event ) = > setIssueForm ( ( current ) = > ( { . . . current , componentItemId : event.target.value } ) ) } className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" >
< option value = "" > Select component < / option >
{ workOrder . materialRequirements . map ( ( requirement ) = > (
< option key = { requirement . componentItemId } value = { requirement . componentItemId } > { requirement . componentSku } - { requirement . componentName } < / option >
) ) }
< / select >
< / label >
< div className = "grid gap-3 sm:grid-cols-3" >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Warehouse < / span >
< select value = { issueForm . warehouseId } onChange = { ( event ) = > setIssueForm ( ( current ) = > ( { . . . current , warehouseId : event.target.value , locationId : "" } ) ) } className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" >
{ [ . . . new Map ( locationOptions . map ( ( option ) = > [ option . warehouseId , option ] ) ) . values ( ) ] . map ( ( option ) = > (
< option key = { option . warehouseId } value = { option . warehouseId } > { option . warehouseCode } - { option . warehouseName } < / option >
) ) }
< / select >
< / label >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Location < / span >
< select value = { issueForm . locationId } onChange = { ( event ) = > setIssueForm ( ( current ) = > ( { . . . current , locationId : event.target.value } ) ) } className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" >
< option value = "" > Select location < / option >
{ filteredLocationOptions . map ( ( option ) = > (
< option key = { option . locationId } value = { option . locationId } > { option . locationCode } - { option . locationName } < / option >
) ) }
< / select >
< / label >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Quantity < / span >
< input type = "number" min = { 1 } step = { 1 } value = { issueForm . quantity } onChange = { ( event ) = > setIssueForm ( ( current ) = > ( { . . . current , quantity : Number.parseInt ( event . target . value , 10 ) || 1 } ) ) } className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" / >
< / label >
< / div >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Notes < / span >
< textarea value = { issueForm . notes } onChange = { ( event ) = > setIssueForm ( ( current ) = > ( { . . . current , notes : event.target.value } ) ) } rows = { 3 } className = "w-full rounded-3xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" / >
< / label >
< button type = "submit" disabled = { isPostingIssue } className = "rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-60" >
{ isPostingIssue ? "Posting issue..." : "Post material issue" }
< / button >
< / div >
< / form >
< form onSubmit = { handleCompletionSubmit } className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Production Completion < / p >
< div className = "mt-4 grid gap-3" >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Quantity < / span >
< input type = "number" min = { 1 } step = { 1 } value = { completionForm . quantity } onChange = { ( event ) = > setCompletionForm ( ( current ) = > ( { . . . current , quantity : Number.parseInt ( event . target . value , 10 ) || 1 } ) ) } className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" / >
< / label >
< label className = "block" >
< span className = "mb-2 block text-sm font-semibold text-text" > Notes < / span >
< textarea value = { completionForm . notes } onChange = { ( event ) = > setCompletionForm ( ( current ) = > ( { . . . current , notes : event.target.value } ) ) } rows = { 3 } className = "w-full rounded-3xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" / >
< / label >
< div className = "rounded-2xl border border-line/70 bg-page/70 px-2 py-2 text-sm text-muted" > Finished goods receipt posts back to { workOrder . warehouseCode } / { workOrder . locationCode } . < / div >
< button type = "submit" disabled = { isPostingCompletion } className = "rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-60" >
{ isPostingCompletion ? "Posting completion..." : "Post completion" }
< / button >
< / div >
< / form >
< / section >
) : null }
< section className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Material Requirements < / p >
{ workOrder . materialRequirements . length === 0 ? (
< div className = "mt-6 rounded-3xl border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted" > This build item does not currently have BOM material requirements . < / div >
) : (
< div className = "mt-5 overflow-hidden rounded-3xl border border-line/70" >
< table className = "min-w-full divide-y divide-line/70 text-sm" >
< thead className = "bg-page/70" >
< tr className = "text-left text-xs font-semibold uppercase tracking-[0.18em] text-muted" >
< th className = "px-3 py-3" > Component < / th >
< th className = "px-3 py-3" > Per < / th >
< th className = "px-3 py-3" > Required < / th >
< th className = "px-3 py-3" > Issued < / th >
< th className = "px-3 py-3" > Remaining < / th >
2026-03-15 16:40:25 -05:00
< th className = "px-3 py-3" > Available < / th >
< th className = "px-3 py-3" > Shortage < / th >
2026-03-15 11:12:58 -05:00
< / tr >
< / thead >
< tbody className = "divide-y divide-line/70" >
{ workOrder . materialRequirements . map ( ( requirement ) = > (
< tr key = { requirement . componentItemId } className = "bg-surface/70" >
< td className = "px-3 py-3" > < div className = "font-semibold text-text" > { requirement . componentSku } < / div > < div className = "mt-1 text-xs text-muted" > { requirement . componentName } < / div > < / td >
< td className = "px-3 py-3 text-text" > { requirement . quantityPer } { requirement . unitOfMeasure } < / td >
< td className = "px-3 py-3 text-text" > { requirement . requiredQuantity } < / td >
< td className = "px-3 py-3 text-text" > { requirement . issuedQuantity } < / td >
< td className = "px-3 py-3 text-text" > { requirement . remainingQuantity } < / td >
2026-03-15 16:40:25 -05:00
< td className = "px-3 py-3 text-text" > { requirement . availableQuantity } < / td >
< td className = "px-3 py-3 text-text" > { requirement . shortageQuantity } < / td >
2026-03-15 11:12:58 -05:00
< / tr >
) ) }
< / tbody >
< / table >
< / div >
) }
< / section >
< section className = "grid gap-3 xl:grid-cols-2" >
< article className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Issue History < / p >
{ workOrder . materialIssues . length === 0 ? (
< div className = "mt-6 rounded-3xl border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted" > No material issues have been posted yet . < / div >
) : (
< div className = "mt-5 space-y-3" >
{ workOrder . materialIssues . map ( ( issue ) = > (
< div key = { issue . id } className = "rounded-3xl border border-line/70 bg-page/60 px-3 py-3" >
< div className = "flex flex-wrap items-center justify-between gap-3" >
< div >
< div className = "font-semibold text-text" > { issue . componentSku } - { issue . componentName } < / div >
< div className = "mt-1 text-xs text-muted" > { issue . warehouseCode } / { issue . locationCode } · { issue . createdByName } < / div >
< / div >
< div className = "text-sm font-semibold text-text" > { issue . quantity } < / div >
< / div >
< div className = "mt-2 text-xs text-muted" > { new Date ( issue . createdAt ) . toLocaleString ( ) } < / div >
< div className = "mt-2 text-sm text-text" > { issue . notes || "No notes recorded." } < / div >
< / div >
) ) }
< / div >
) }
< / article >
< article className = "rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Completion History < / p >
{ workOrder . completions . length === 0 ? (
< div className = "mt-6 rounded-3xl border border-dashed border-line/70 bg-page/60 px-4 py-8 text-center text-sm text-muted" > No production completions have been posted yet . < / div >
) : (
< div className = "mt-5 space-y-3" >
{ workOrder . completions . map ( ( completion ) = > (
< div key = { completion . id } className = "rounded-3xl border border-line/70 bg-page/60 px-3 py-3" >
< div className = "flex flex-wrap items-center justify-between gap-3" >
< div className = "font-semibold text-text" > { completion . quantity } completed < / div >
< div className = "text-xs text-muted" > { completion . createdByName } < / div >
< / div >
< div className = "mt-2 text-xs text-muted" > { new Date ( completion . createdAt ) . toLocaleString ( ) } < / div >
< div className = "mt-2 text-sm text-text" > { completion . notes || "No notes recorded." } < / div >
< / div >
) ) }
< / div >
) }
< / article >
< / section >
< FileAttachmentsPanel
ownerType = "WORK_ORDER"
ownerId = { workOrder . id }
eyebrow = "Manufacturing Documents"
title = "Work-order files"
description = "Store travelers, build instructions, inspection records, and support documents directly on the work order."
emptyMessage = "No manufacturing attachments have been uploaded for this work order yet."
/ >
< div className = "rounded-2xl border border-line/70 bg-page/60 px-2 py-2 text-sm text-muted" > { status } < / div >
2026-03-15 18:59:37 -05:00
< ConfirmActionDialog
open = { pendingConfirmation != null }
title = { pendingConfirmation ? . title ? ? "Confirm manufacturing action" }
description = { pendingConfirmation ? . description ? ? "" }
impact = { pendingConfirmation ? . impact }
recovery = { pendingConfirmation ? . recovery }
confirmLabel = { pendingConfirmation ? . confirmLabel ? ? "Confirm" }
confirmationLabel = { pendingConfirmation ? . confirmationLabel }
confirmationValue = { pendingConfirmation ? . confirmationValue }
isConfirming = {
( pendingConfirmation ? . kind === "status" && isUpdatingStatus ) ||
( pendingConfirmation ? . kind === "issue" && isPostingIssue ) ||
( pendingConfirmation ? . kind === "completion" && isPostingCompletion )
}
onClose = { ( ) = > {
if ( ! isUpdatingStatus && ! isPostingIssue && ! isPostingCompletion ) {
setPendingConfirmation ( null ) ;
}
} }
onConfirm = { async ( ) = > {
if ( ! pendingConfirmation ) {
return ;
}
if ( pendingConfirmation . kind === "status" && pendingConfirmation . nextStatus ) {
await applyStatusChange ( pendingConfirmation . nextStatus ) ;
} else if ( pendingConfirmation . kind === "issue" ) {
await submitIssue ( ) ;
} else if ( pendingConfirmation . kind === "completion" ) {
await submitCompletion ( ) ;
}
setPendingConfirmation ( null ) ;
} }
/ >
2026-03-15 11:12:58 -05:00
< / section >
) ;
}