2026-03-15 11:12:58 -05:00
import { permissions } from "@mrp/shared" ;
2026-03-18 06:22:37 -05:00
import type {
2026-03-18 06:39:38 -05:00
ManufacturingUserOptionDto ,
2026-03-18 06:22:37 -05:00
WorkOrderCompletionInput ,
WorkOrderDetailDto ,
WorkOrderMaterialIssueInput ,
2026-03-18 06:39:38 -05:00
WorkOrderOperationAssignmentInput ,
2026-03-18 06:22:37 -05:00
WorkOrderOperationExecutionInput ,
WorkOrderOperationLaborEntryInput ,
WorkOrderOperationScheduleInput ,
2026-03-18 06:39:38 -05:00
WorkOrderOperationTimerInput ,
2026-03-18 06:22:37 -05:00
WorkOrderStatus ,
} from "@mrp/shared" ;
2026-03-15 11:12:58 -05:00
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 [ ] > ( [ ] ) ;
2026-03-18 06:39:38 -05:00
const [ operatorOptions , setOperatorOptions ] = useState < ManufacturingUserOptionDto [ ] > ( [ ] ) ;
2026-03-15 11:12:58 -05:00
const [ issueForm , setIssueForm ] = useState < WorkOrderMaterialIssueInput > ( emptyMaterialIssueInput ) ;
const [ completionForm , setCompletionForm ] = useState < WorkOrderCompletionInput > ( emptyCompletionInput ) ;
2026-03-18 12:05:28 -05:00
const [ holdReasonDraft , setHoldReasonDraft ] = useState ( "" ) ;
2026-03-15 11:12:58 -05:00
const [ status , setStatus ] = useState ( "Loading work order..." ) ;
const [ isUpdatingStatus , setIsUpdatingStatus ] = useState ( false ) ;
const [ isPostingIssue , setIsPostingIssue ] = useState ( false ) ;
const [ isPostingCompletion , setIsPostingCompletion ] = useState ( false ) ;
2026-03-18 00:10:15 -05:00
const [ operationScheduleForm , setOperationScheduleForm ] = useState < Record < string , WorkOrderOperationScheduleInput > > ( { } ) ;
2026-03-18 06:22:37 -05:00
const [ operationLaborForm , setOperationLaborForm ] = useState < Record < string , WorkOrderOperationLaborEntryInput > > ( { } ) ;
2026-03-18 06:39:38 -05:00
const [ operationAssignmentForm , setOperationAssignmentForm ] = useState < Record < string , WorkOrderOperationAssignmentInput > > ( { } ) ;
const [ operationTimerForm , setOperationTimerForm ] = useState < Record < string , WorkOrderOperationTimerInput > > ( { } ) ;
2026-03-18 00:10:15 -05:00
const [ reschedulingOperationId , setReschedulingOperationId ] = useState < string | null > ( null ) ;
2026-03-18 06:22:37 -05:00
const [ executingOperationId , setExecutingOperationId ] = useState < string | null > ( null ) ;
const [ postingLaborOperationId , setPostingLaborOperationId ] = useState < string | null > ( null ) ;
2026-03-18 06:39:38 -05:00
const [ assigningOperationId , setAssigningOperationId ] = useState < string | null > ( null ) ;
const [ timerOperationId , setTimerOperationId ] = useState < string | null > ( null ) ;
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 ) ,
} ) ;
2026-03-18 00:10:15 -05:00
setOperationScheduleForm (
Object . fromEntries (
nextWorkOrder . operations . map ( ( operation ) = > [ operation . id , { plannedStart : operation.plannedStart } ] )
)
) ;
2026-03-18 06:22:37 -05:00
setOperationLaborForm (
Object . fromEntries (
nextWorkOrder . operations . map ( ( operation ) = > [ operation . id , { minutes : Math.max ( Math . round ( operation . plannedMinutes / 4 ) , 15 ) , notes : "" } ] )
)
) ;
2026-03-18 06:39:38 -05:00
setOperationAssignmentForm (
Object . fromEntries (
nextWorkOrder . operations . map ( ( operation ) = > [ operation . id , { assignedOperatorId : operation.assignedOperatorId } ] )
)
) ;
setOperationTimerForm (
Object . fromEntries (
nextWorkOrder . operations . map ( ( operation ) = > [ operation . id , { action : operation.activeTimerStartedAt ? "STOP" : "START" , notes : "" } ] )
)
) ;
2026-03-15 11:12:58 -05:00
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 ( [ ] ) ) ;
2026-03-18 06:39:38 -05:00
api . getManufacturingUserOptions ( token ) . then ( setOperatorOptions ) . catch ( ( ) = > setOperatorOptions ( [ ] ) ) ;
2026-03-15 11:12:58 -05:00
} , [ 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 {
2026-03-18 12:05:28 -05:00
const nextWorkOrder = await api . updateWorkOrderStatus ( token , workOrder . id , {
status : nextStatus ,
reason : nextStatus === "ON_HOLD" ? holdReasonDraft : null ,
} ) ;
2026-03-15 11:12:58 -05:00
setWorkOrder ( nextWorkOrder ) ;
2026-03-18 12:05:28 -05:00
setHoldReasonDraft ( "" ) ;
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-18 00:10:15 -05:00
async function submitOperationReschedule ( operationId : string ) {
if ( ! token || ! workOrder ) {
return ;
}
const payload = operationScheduleForm [ operationId ] ;
if ( ! payload ? . plannedStart ) {
return ;
}
setReschedulingOperationId ( operationId ) ;
setStatus ( "Rebuilding operation schedule..." ) ;
try {
const nextWorkOrder = await api . updateWorkOrderOperationSchedule ( token , workOrder . id , operationId , payload ) ;
setWorkOrder ( nextWorkOrder ) ;
setOperationScheduleForm (
Object . fromEntries (
nextWorkOrder . operations . map ( ( operation ) = > [ operation . id , { plannedStart : operation.plannedStart } ] )
)
) ;
setStatus ( "Operation schedule updated with station calendar constraints." ) ;
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to reschedule operation." ;
setStatus ( message ) ;
} finally {
setReschedulingOperationId ( null ) ;
}
}
2026-03-18 06:22:37 -05:00
async function submitOperationExecution ( operationId : string , action : WorkOrderOperationExecutionInput [ "action" ] ) {
if ( ! token || ! workOrder ) {
return ;
}
setExecutingOperationId ( operationId ) ;
setStatus ( "Updating operation execution..." ) ;
try {
const nextWorkOrder = await api . updateWorkOrderOperationExecution ( token , workOrder . id , operationId , {
action ,
notes : ` ${ action } from work-order detail ` ,
} ) ;
setWorkOrder ( nextWorkOrder ) ;
setOperationScheduleForm (
Object . fromEntries (
nextWorkOrder . operations . map ( ( operation ) = > [ operation . id , { plannedStart : operation.plannedStart } ] )
)
) ;
setStatus ( "Operation execution updated." ) ;
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to update operation execution." ;
setStatus ( message ) ;
} finally {
setExecutingOperationId ( null ) ;
}
}
async function submitOperationLabor ( operationId : string ) {
if ( ! token || ! workOrder ) {
return ;
}
const payload = operationLaborForm [ operationId ] ;
if ( ! payload ? . minutes ) {
return ;
}
setPostingLaborOperationId ( operationId ) ;
setStatus ( "Posting labor entry..." ) ;
try {
const nextWorkOrder = await api . recordWorkOrderOperationLabor ( token , workOrder . id , operationId , payload ) ;
setWorkOrder ( nextWorkOrder ) ;
setOperationLaborForm ( ( current ) = > ( {
. . . current ,
[ operationId ] : {
minutes : Math.max ( Math . round ( ( nextWorkOrder . operations . find ( ( operation ) = > operation . id === operationId ) ? . plannedMinutes ? ? 60 ) / 4 ) , 15 ) ,
notes : "" ,
} ,
} ) ) ;
setStatus ( "Labor entry posted." ) ;
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to post operation labor." ;
setStatus ( message ) ;
} finally {
setPostingLaborOperationId ( null ) ;
}
}
2026-03-18 06:39:38 -05:00
async function submitOperationAssignment ( operationId : string ) {
if ( ! token || ! workOrder ) {
return ;
}
const payload = operationAssignmentForm [ operationId ] ;
if ( ! payload ) {
return ;
}
setAssigningOperationId ( operationId ) ;
setStatus ( "Updating operator assignment..." ) ;
try {
const nextWorkOrder = await api . updateWorkOrderOperationAssignment ( token , workOrder . id , operationId , payload ) ;
setWorkOrder ( nextWorkOrder ) ;
setStatus ( "Operator assignment updated." ) ;
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to update operator assignment." ;
setStatus ( message ) ;
} finally {
setAssigningOperationId ( null ) ;
}
}
async function submitOperationTimer ( operationId : string , action : WorkOrderOperationTimerInput [ "action" ] ) {
if ( ! token || ! workOrder ) {
return ;
}
const payload = operationTimerForm [ operationId ] ? ? { action , notes : "" } ;
setTimerOperationId ( operationId ) ;
setStatus ( action === "START" ? "Starting timer..." : "Stopping timer..." ) ;
try {
const nextWorkOrder = await api . updateWorkOrderOperationTimer ( token , workOrder . id , operationId , {
action ,
notes : payload.notes ,
} ) ;
setWorkOrder ( nextWorkOrder ) ;
setOperationTimerForm ( ( current ) = > ( {
. . . current ,
[ operationId ] : {
action : action === "START" ? "STOP" : "START" ,
notes : "" ,
} ,
} ) ) ;
setStatus ( action === "START" ? "Operation timer started." : "Operation timer stopped and labor posted." ) ;
} catch ( error : unknown ) {
const message = error instanceof ApiError ? error . message : "Unable to update operation timer." ;
setStatus ( message ) ;
} finally {
setTimerOperationId ( null ) ;
}
}
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."
2026-03-18 12:05:28 -05:00
: nextStatus === "ON_HOLD"
? "Putting a work order on hold pauses expected execution and should capture the exact blocker so planning and shop-floor review stay aligned."
2026-03-15 18:59:37 -05:00
: 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 ,
} ) ;
2026-03-18 12:05:28 -05:00
setHoldReasonDraft ( nextStatus === "ON_HOLD" ? workOrder . holdReason ? ? "" : "" ) ;
2026-03-15 18:59:37 -05:00
}
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 ) {
2026-03-15 20:07:48 -05:00
return < div className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel" > { status } < / div > ;
2026-03-15 11:12:58 -05:00
}
return (
2026-03-18 20:36:30 -05:00
< section className = "page-stack" >
< div className = "surface-panel" >
2026-03-15 11:12:58 -05:00
< div className = "flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between" >
< div >
2026-03-18 20:36:30 -05:00
< p className = "section-kicker" > WORK ORDER < / p >
< h3 className = "module-title" > { workOrder . workOrderNumber } < / h3 >
2026-03-15 11:12:58 -05:00
< p className = "mt-1 text-sm text-text" > { workOrder . itemSku } - { workOrder . itemName } < / p >
2026-03-18 20:36:30 -05:00
< div className = "mt-2.5" > < WorkOrderStatusBadge status = { workOrder . status } / > < / div >
2026-03-18 12:05:28 -05:00
{ workOrder . status === "ON_HOLD" && workOrder . holdReason ? (
2026-03-18 20:36:30 -05:00
< div className = "mt-2.5 max-w-2xl rounded-[16px] border border-amber-300/60 bg-amber-50 px-3 py-2.5 text-sm text-amber-900" >
< div className = "metric-kicker text-amber-900" > Current Hold Reason < / div >
2026-03-18 12:05:28 -05:00
< div className = "mt-2 whitespace-pre-line" > { workOrder . holdReason } < / div >
< / div >
) : null }
2026-03-15 11:12:58 -05:00
< / 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 ? (
2026-03-18 20:36:30 -05:00
< section className = "surface-panel" >
2026-03-15 11:12:58 -05:00
< div className = "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between" >
< div >
2026-03-18 20:36:30 -05:00
< p className = "section-kicker" > QUICK ACTIONS < / p >
2026-03-15 11:12:58 -05:00
< / 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-18 22:51:17 -05:00
< section className = "grid gap-2 xl:grid-cols-6" >
< article className = "surface-panel-tight" > < 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 >
2026-03-15 20:07:48 -05:00
< article className = "rounded-[18px] 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-[18px] 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-[18px] 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 >
< article className = "rounded-[18px] 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 >
< article className = "rounded-[18px] 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 >
< article className = "rounded-[18px] 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-18 06:22:37 -05:00
< article className = "rounded-[18px] 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" > Actual Hours < / p > < div className = "mt-2 text-base font-bold text-text" > { ( workOrder . totalActualMinutes / 60 ) . toFixed ( 1 ) } < / 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)]" >
2026-03-15 20:07:48 -05:00
< article className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< 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 >
2026-03-15 20:07:48 -05:00
< article className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< 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 20:07:48 -05:00
< section className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 12:11:46 -05:00
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Operation Plan < / p >
{ workOrder . operations . length === 0 ? (
2026-03-15 20:07:48 -05:00
< div className = "mt-6 rounded-[18px] 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 >
2026-03-15 12:11:46 -05:00
) : (
2026-03-15 20:07:48 -05:00
< div className = "mt-5 overflow-hidden rounded-[18px] border border-line/70" >
2026-03-15 12:11:46 -05:00
< 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 >
2026-03-18 06:22:37 -05:00
< th className = "px-3 py-3" > Execution < / th >
2026-03-18 00:10:15 -05:00
< th className = "px-3 py-3" > Capacity < / th >
2026-03-15 12:11:46 -05:00
< th className = "px-3 py-3" > Start < / th >
< th className = "px-3 py-3" > End < / th >
2026-03-18 06:22:37 -05:00
< th className = "px-3 py-3" > Planned / Actual < / th >
{ canManage ? < th className = "px-3 py-3" > Execution Controls < / th > : null }
2026-03-15 12:11:46 -05:00
< / 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 >
2026-03-18 06:22:37 -05:00
< td className = "px-3 py-3 text-xs text-muted" >
< div className = "font-semibold text-text" > { operation . status . replaceAll ( "_" , " " ) } < / div >
< div className = "mt-1" > Start { operation . actualStart ? new Date ( operation . actualStart ) . toLocaleString ( ) : "Not started" } < / div >
< div > End { operation . actualEnd ? new Date ( operation . actualEnd ) . toLocaleString ( ) : "Open" } < / div >
2026-03-18 06:39:38 -05:00
< div > Operator { operation . assignedOperatorName ? ? "Unassigned" } < / div >
< div > { operation . activeTimerStartedAt ? ` Timer running since ${ new Date ( operation . activeTimerStartedAt ) . toLocaleTimeString ( ) } ` : "Timer stopped" } < / div >
2026-03-18 06:22:37 -05:00
< div > { operation . laborEntryCount } labor entr { operation . laborEntryCount === 1 ? "y" : "ies" } < / div >
< / td >
2026-03-18 00:10:15 -05:00
< td className = "px-3 py-3 text-xs text-muted" >
< div > { operation . stationDailyCapacityMinutes } min / day x { operation . stationParallelCapacity } < / div >
< div > { operation . stationWorkingDays . join ( "," ) } < / div >
< / td >
2026-03-15 12:11:46 -05:00
< 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 >
2026-03-18 06:22:37 -05:00
< td className = "px-3 py-3 text-xs text-text" >
< div > { operation . plannedMinutes } planned < / div >
< div className = "mt-1" > { operation . actualMinutes } actual < / div >
< / td >
2026-03-18 00:10:15 -05:00
{ canManage ? (
< td className = "px-3 py-3" >
2026-03-18 06:22:37 -05:00
< div className = "min-w-[320px] space-y-2" >
< div className = "flex flex-wrap gap-2" >
{ operation . status === "PENDING" ? (
< button type = "button" onClick = { ( ) = > void submitOperationExecution ( operation . id , "START" ) } disabled = { executingOperationId === operation . id } className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60" > Start < / button >
) : null }
{ ( operation . status === "PENDING" || operation . status === "PAUSED" ) ? (
< button type = "button" onClick = { ( ) = > void submitOperationExecution ( operation . id , "RESUME" ) } disabled = { executingOperationId === operation . id } className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60" > Resume < / button >
) : null }
{ operation . status === "IN_PROGRESS" ? (
< button type = "button" onClick = { ( ) = > void submitOperationExecution ( operation . id , "PAUSE" ) } disabled = { executingOperationId === operation . id } className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60" > Pause < / button >
) : null }
{ operation . status !== "COMPLETE" ? (
< button type = "button" onClick = { ( ) = > void submitOperationExecution ( operation . id , "COMPLETE" ) } disabled = { executingOperationId === operation . id } className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60" > Complete < / button >
) : null }
< / div >
2026-03-18 06:39:38 -05:00
< div className = "flex items-center gap-2" >
< select
value = { operationAssignmentForm [ operation . id ] ? . assignedOperatorId ? ? "" }
onChange = { ( event ) = >
setOperationAssignmentForm ( ( current ) = > ( {
. . . current ,
[ operation . id ] : {
assignedOperatorId : event.target.value || null ,
} ,
} ) )
}
className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-xs text-text outline-none transition focus:border-brand"
>
< option value = "" > Unassigned operator < / option >
{ operatorOptions . map ( ( operator ) = > (
< option key = { operator . id } value = { operator . id } >
{ operator . name } ( { operator . email } )
< / option >
) ) }
< / select >
< button
type = "button"
onClick = { ( ) = > void submitOperationAssignment ( operation . id ) }
disabled = { assigningOperationId === operation . id }
className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60"
>
{ assigningOperationId === operation . id ? "Saving..." : "Assign" }
< / button >
< / div >
2026-03-18 00:10:15 -05:00
< input
type = "datetime-local"
value = { ( operationScheduleForm [ operation . id ] ? . plannedStart ? ? operation . plannedStart ) . slice ( 0 , 16 ) }
onChange = { ( event ) = >
setOperationScheduleForm ( ( current ) = > ( {
. . . current ,
[ operation . id ] : { plannedStart : new Date ( event . target . value ) . toISOString ( ) } ,
} ) )
}
className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-xs text-text outline-none transition focus:border-brand"
/ >
2026-03-18 06:22:37 -05:00
< div className = "flex items-center gap-2" >
< input
type = "number"
min = { 1 }
step = { 1 }
value = { operationLaborForm [ operation . id ] ? . minutes ? ? 15 }
onChange = { ( event ) = >
setOperationLaborForm ( ( current ) = > ( {
. . . current ,
[ operation . id ] : {
. . . ( current [ operation . id ] ? ? { notes : "" } ) ,
minutes : Number.parseInt ( event . target . value , 10 ) || 1 ,
} ,
} ) )
}
className = "w-24 rounded-2xl border border-line/70 bg-page px-2 py-2 text-xs text-text outline-none transition focus:border-brand"
/ >
< input
type = "text"
placeholder = "Labor note"
value = { operationLaborForm [ operation . id ] ? . notes ? ? "" }
onChange = { ( event ) = >
setOperationLaborForm ( ( current ) = > ( {
. . . current ,
[ operation . id ] : {
. . . ( current [ operation . id ] ? ? { minutes : 15 } ) ,
notes : event.target.value ,
} ,
} ) )
}
className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-xs text-text outline-none transition focus:border-brand"
/ >
< / div >
2026-03-18 06:39:38 -05:00
< div className = "flex items-center gap-2" >
< input
type = "text"
placeholder = { operation . activeTimerStartedAt ? "Stop timer note" : "Start timer note" }
value = { operationTimerForm [ operation . id ] ? . notes ? ? "" }
onChange = { ( event ) = >
setOperationTimerForm ( ( current ) = > ( {
. . . current ,
[ operation . id ] : {
action : operation.activeTimerStartedAt ? "STOP" : "START" ,
notes : event.target.value ,
} ,
} ) )
}
className = "w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-xs text-text outline-none transition focus:border-brand"
/ >
< button
type = "button"
onClick = { ( ) = > void submitOperationTimer ( operation . id , operation . activeTimerStartedAt ? "STOP" : "START" ) }
disabled = { timerOperationId === operation . id || operation . status === "COMPLETE" }
className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60"
>
{ timerOperationId === operation . id ? "Saving..." : operation . activeTimerStartedAt ? "Stop timer" : "Start timer" }
< / button >
< / div >
2026-03-18 06:22:37 -05:00
< div className = "flex gap-2" >
< button
type = "button"
onClick = { ( ) = > void submitOperationReschedule ( operation . id ) }
disabled = { reschedulingOperationId === operation . id }
className = "rounded-2xl border border-line/70 px-2 py-2 text-xs font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60"
>
{ reschedulingOperationId === operation . id ? "Saving..." : "Apply plan" }
< / button >
< button
type = "button"
onClick = { ( ) = > void submitOperationLabor ( operation . id ) }
disabled = { postingLaborOperationId === operation . id || operation . status === "COMPLETE" }
className = "rounded-2xl bg-brand px-2 py-2 text-xs font-semibold text-white disabled:cursor-not-allowed disabled:opacity-60"
>
{ postingLaborOperationId === operation . id ? "Posting..." : "Post labor" }
< / button >
< / div >
2026-03-18 00:10:15 -05:00
< / div >
< / td >
) : null }
2026-03-15 12:11:46 -05:00
< / tr >
) ) }
< / tbody >
< / table >
< / div >
) }
< / section >
2026-03-15 11:12:58 -05:00
{ canManage ? (
< section className = "grid gap-3 xl:grid-cols-2" >
2026-03-15 20:07:48 -05:00
< form onSubmit = { handleIssueSubmit } className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< 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 >
2026-03-15 20:07:48 -05:00
< textarea value = { issueForm . notes } onChange = { ( event ) = > setIssueForm ( ( current ) = > ( { . . . current , notes : event.target.value } ) ) } rows = { 3 } className = "w-full rounded-[18px] border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" / >
2026-03-15 11:12:58 -05:00
< / 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 >
2026-03-15 20:07:48 -05:00
< form onSubmit = { handleCompletionSubmit } className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< 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 >
2026-03-15 20:07:48 -05:00
< textarea value = { completionForm . notes } onChange = { ( event ) = > setCompletionForm ( ( current ) = > ( { . . . current , notes : event.target.value } ) ) } rows = { 3 } className = "w-full rounded-[18px] border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" / >
2026-03-15 11:12:58 -05:00
< / 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 }
2026-03-15 20:07:48 -05:00
< section className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Material Requirements < / p >
{ workOrder . materialRequirements . length === 0 ? (
2026-03-15 20:07:48 -05:00
< div className = "mt-6 rounded-[18px] 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 >
2026-03-15 11:12:58 -05:00
) : (
2026-03-15 20:07:48 -05:00
< div className = "mt-5 overflow-hidden rounded-[18px] border border-line/70" >
2026-03-15 11:12:58 -05:00
< 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" >
2026-03-15 20:07:48 -05:00
< article className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Issue History < / p >
{ workOrder . materialIssues . length === 0 ? (
2026-03-15 20:07:48 -05:00
< div className = "mt-6 rounded-[18px] 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 >
2026-03-15 11:12:58 -05:00
) : (
< div className = "mt-5 space-y-3" >
{ workOrder . materialIssues . map ( ( issue ) = > (
2026-03-15 20:07:48 -05:00
< div key = { issue . id } className = "rounded-[18px] border border-line/70 bg-page/60 px-3 py-3" >
2026-03-15 11:12:58 -05:00
< 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 >
2026-03-15 20:07:48 -05:00
< article className = "rounded-[20px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5" >
2026-03-15 11:12:58 -05:00
< p className = "text-xs font-semibold uppercase tracking-[0.24em] text-muted" > Completion History < / p >
{ workOrder . completions . length === 0 ? (
2026-03-15 20:07:48 -05:00
< div className = "mt-6 rounded-[18px] 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 >
2026-03-15 11:12:58 -05:00
) : (
< div className = "mt-5 space-y-3" >
{ workOrder . completions . map ( ( completion ) = > (
2026-03-15 20:07:48 -05:00
< div key = { completion . id } className = "rounded-[18px] border border-line/70 bg-page/60 px-3 py-3" >
2026-03-15 11:12:58 -05:00
< 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 }
2026-03-18 12:05:28 -05:00
extraFieldLabel = { pendingConfirmation ? . kind === "status" && pendingConfirmation ? . nextStatus === "ON_HOLD" ? "Hold reason" : undefined }
extraFieldPlaceholder = { pendingConfirmation ? . kind === "status" && pendingConfirmation ? . nextStatus === "ON_HOLD" ? "Explain the blocker forcing this work order onto hold." : undefined }
extraFieldValue = { pendingConfirmation ? . kind === "status" && pendingConfirmation ? . nextStatus === "ON_HOLD" ? holdReasonDraft : undefined }
extraFieldRequired = { pendingConfirmation ? . kind === "status" && pendingConfirmation ? . nextStatus === "ON_HOLD" }
extraFieldMultiline = { pendingConfirmation ? . kind === "status" && pendingConfirmation ? . nextStatus === "ON_HOLD" }
onExtraFieldChange = { pendingConfirmation ? . kind === "status" && pendingConfirmation ? . nextStatus === "ON_HOLD" ? setHoldReasonDraft : undefined }
2026-03-15 18:59:37 -05:00
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 >
) ;
}
2026-03-15 20:07:48 -05:00