// Work-order detail screen -- read-only header + start/complete // action verbs that drive the v2 state machine. The shop-floor // dashboard at /shop-floor handles the per-operation walk for v3 // routing-equipped orders. v1 SPA keeps this screen simple: start a // DRAFT, complete an IN_PROGRESS into a warehouse. import { useCallback, useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { inventory, production } from '@/api/client' import type { Location, WorkOrder } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' import { useT } from '@/i18n/LocaleContext' export function WorkOrderDetailPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() const t = useT() const [order, setOrder] = useState(null) const [locations, setLocations] = useState([]) const [outputLocation, setOutputLocation] = useState('') const [loading, setLoading] = useState(true) const [acting, setActing] = useState(false) const [error, setError] = useState(null) const [actionMessage, setActionMessage] = useState(null) const refresh = useCallback(async () => { const o = await production.getWorkOrder(id) setOrder(o) }, [id]) useEffect(() => { let active = true setLoading(true) Promise.all([production.getWorkOrder(id), inventory.listLocations()]) .then(([o, locs]: [WorkOrder, Location[]]) => { if (!active) return setOrder(o) setLocations(locs.filter((l) => l.active)) const firstWarehouse = locs.find((l) => l.active && l.type === 'WAREHOUSE') setOutputLocation(firstWarehouse?.code ?? locs[0]?.code ?? '') }) .catch((e: unknown) => { if (active) setError(e instanceof Error ? e : new Error(String(e))) }) .finally(() => active && setLoading(false)) return () => { active = false } }, [id]) if (loading) return if (error) return if (!order) return const onStart = async () => { setActing(true) setError(null) setActionMessage(null) try { await production.startWorkOrder(order.id) await refresh() setActionMessage(t('page.workOrderDetail.startMsg')) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { setActing(false) } } const onComplete = async () => { if (!outputLocation) { setError(new Error(t('page.workOrderDetail.pickLocation'))) return } setActing(true) setError(null) setActionMessage(null) try { await production.completeWorkOrder(order.id, outputLocation) await refresh() setActionMessage( t('page.workOrderDetail.completeMsg').replace('{location}', outputLocation), ) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { setActing(false) } } const canStart = order.status === 'DRAFT' const canComplete = order.status === 'IN_PROGRESS' return (
navigate('/work-orders')}> {t('action.back')} } />
{t('label.status')}:
{actionMessage && (
{actionMessage}
)}

{t('label.actions')}

{t('label.bomInputs')}

{order.inputs.length === 0 ? (
{t('label.noBomLines')}
) : ( {order.inputs.map((i) => ( ))}
{t('label.lineNo')} {t('label.item')} {t('label.qtyPerUnit')} {t('label.sourceLoc')}
{i.lineNo} {i.itemCode} {String(i.quantityPerUnit)} {i.sourceLocationCode}
)}

{t('label.routingOperations')}

{order.operations.length === 0 ? (
{t('label.noRouting')}
) : ( {order.operations.map((o) => ( ))}
{t('label.lineNo')} {t('label.operation')} {t('label.workCenter')} {t('label.stdMin')} {t('label.status')}
{o.lineNo} {o.operationCode} {o.workCenter} {String(o.standardMinutes)}
)}
) }