diff --git a/src/components/production/ProductionDashboard/types.ts b/src/components/production/ProductionDashboard/types.ts index 1fa899b0..dccfdbc1 100644 --- a/src/components/production/ProductionDashboard/types.ts +++ b/src/components/production/ProductionDashboard/types.ts @@ -58,6 +58,7 @@ export interface WorkOrderNodeGroup { // 개소 내 개별 아이템 export interface WorkOrderNodeItem { id: number; + itemCode?: string | null; itemName: string; quantity: number; specification?: string | null; diff --git a/src/components/production/WorkerScreen/WorkItemCard.tsx b/src/components/production/WorkerScreen/WorkItemCard.tsx index afa0b427..4127639c 100644 --- a/src/components/production/WorkerScreen/WorkItemCard.tsx +++ b/src/components/production/WorkerScreen/WorkItemCard.tsx @@ -76,9 +76,16 @@ export const WorkItemCard = memo(function WorkItemCard({ {item.itemNo} - - {item.itemCode} - {item.itemName} - +
+ + {item.itemCode} - {item.itemName} + + {item.bdCode && ( + + {item.bdCode} + + )} +
{!item.isWip && ( diff --git a/src/components/production/WorkerScreen/actions.ts b/src/components/production/WorkerScreen/actions.ts index 99664948..9024f3f2 100644 --- a/src/components/production/WorkerScreen/actions.ts +++ b/src/components/production/WorkerScreen/actions.ts @@ -157,6 +157,7 @@ function transformToWorkerScreenFormat(api: WorkOrderApiItem): WorkOrder { nodeName: g.nodeName, items: (g.items || []).map((it) => ({ id: it.id, + itemCode: it.item?.code || null, itemName: it.item_name, quantity: Number(it.quantity), specification: it.specification, diff --git a/src/components/production/WorkerScreen/index.tsx b/src/components/production/WorkerScreen/index.tsx index c10a32db..28be4f41 100644 --- a/src/components/production/WorkerScreen/index.tsx +++ b/src/components/production/WorkerScreen/index.tsx @@ -13,6 +13,13 @@ * - 하단 고정 버튼 (작업일지보기 / 중간검사하기) */ +/** 품목명에서 길이(mm) 추출: "가이드레일(측면) 본체(철재) 2438mm" → 2438 */ +function extractLengthFromName(name?: string | null): number { + if (!name) return 0; + const m = name.match(/(\d{3,5})\s*mm/i); + return m ? parseInt(m[1], 10) : 0; +} + import { useState, useMemo, useCallback, useEffect } from 'react'; import dynamic from 'next/dynamic'; import { useSidebarCollapsed } from '@/stores/menuStore'; @@ -47,6 +54,7 @@ import type { InspectionTemplateData } from './types'; import { getProcessList } from '@/components/process-management/actions'; import type { InspectionSetting, InspectionScope, Process } from '@/types/process'; import type { WorkOrder } from '../ProductionDashboard/types'; +import { BENDING_STEP_MAP, extractBendingTypeCode } from '../WorkOrders/types'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import type { WorkerStats, @@ -90,6 +98,7 @@ interface SidebarOrder { shutterCount: number; priority: 'urgent' | 'priority' | 'normal'; subType?: 'slat' | 'jointbar' | 'bending' | 'wip'; + bdCode?: string; // 재공품 BD- 코드 (예: BD-ST-24) } const SUB_TYPE_TAGS: Record = { @@ -363,14 +372,23 @@ export default function WorkerScreen() { // ===== API WorkOrders → SidebarOrder 변환 ===== const apiSidebarOrders: SidebarOrder[] = useMemo(() => { - return filteredWorkOrders.map((wo) => ({ - id: wo.id, - siteName: wo.projectName || wo.client || '-', - date: wo.dueDate || (wo.createdAt ? wo.createdAt.slice(0, 10) : '-'), - quantity: wo.quantity, - shutterCount: wo.shutterCount || 0, - priority: (wo.isUrgent ? 'urgent' : (wo.priority <= 3 ? 'priority' : 'normal')) as SidebarOrder['priority'], - })); + return filteredWorkOrders.map((wo) => { + const isWip = wo.projectName === '재고생산' || wo.salesOrderNo?.startsWith('STK'); + // 재공품: 첫 번째 item의 BD- 코드 추출 + const bdCode = isWip + ? wo.nodeGroups?.flatMap(g => g.items).find(it => it.itemCode?.startsWith('BD-'))?.itemCode ?? undefined + : undefined; + return { + id: wo.id, + siteName: wo.projectName || wo.client || '-', + date: wo.dueDate || (wo.createdAt ? wo.createdAt.slice(0, 10) : '-'), + quantity: wo.quantity, + shutterCount: wo.shutterCount || 0, + priority: (wo.isUrgent ? 'urgent' : (wo.priority <= 3 ? 'priority' : 'normal')) as SidebarOrder['priority'], + subType: isWip ? 'wip' as const : undefined, + bdCode, + }; + }); }, [filteredWorkOrders]); // ===== 탭 변경/데이터 로드 시 최상위 우선순위 작업 자동 선택 ===== @@ -480,7 +498,42 @@ export default function WorkerScreen() { const nodeKey = group.nodeId != null ? String(group.nodeId) : `unassigned-${index}`; const firstItem = group.items[0]; const firstItemId = firstItem?.id as number | undefined; - const steps: WorkStepData[] = stepsTemplate.map((st, si) => { + + // 절곡 공정: BD 코드에 따라 필요한 단계만 필터링 + let itemStepsTemplate = stepsTemplate; + if (activeProcessTabKey === 'bending') { + const itemCodes = group.items.map((it) => it.itemCode).filter(Boolean) as string[]; + if (itemCodes.length > 0) { + const neededKeys = new Set(); + let hasNonBd = false; + for (const code of itemCodes) { + const typeCode = extractBendingTypeCode(code); + if (typeCode && BENDING_STEP_MAP[typeCode]) { + BENDING_STEP_MAP[typeCode].forEach((k) => neededKeys.add(k)); + } else { + hasNonBd = true; + } + } + if (!hasNonBd && neededKeys.size > 0) { + // 단계명 매핑: guide_rail→가이드레일 제작, case→케이스 제작, bottom_finish→하단마감재 제작, inspection→검사/중간검사 + const stepNameMap: Record = { + guide_rail: ['가이드레일 제작', '가이드레일'], + case: ['케이스 제작', '케이스'], + bottom_finish: ['하단마감재 제작', '하단마감재'], + inspection: ['검사', '중간검사'], + }; + const allowedNames = new Set(); + // 자재투입은 항상 포함 + allowedNames.add('자재투입'); + for (const key of neededKeys) { + (stepNameMap[key] || []).forEach((n) => allowedNames.add(n)); + } + itemStepsTemplate = stepsTemplate.filter((st) => allowedNames.has(st.name)); + } + } + } + + const steps: WorkStepData[] = itemStepsTemplate.map((st, si) => { const stepKey = `${selectedOrder.id}-${nodeKey}-${st.name}`; return enrichStep(st, `${selectedOrder.id}-${nodeKey}-step-${si}`, stepKey, firstItemId); }); @@ -529,8 +582,8 @@ export default function WorkerScreen() { itemName: selectedOrder.productCode !== '-' ? selectedOrder.productCode : itemSummary, floor: (opts.floor as string) || '-', code: (opts.code as string) || '-', - width: (opts.width as number) || 0, - height: (opts.height as number) || 0, + width: (opts.bending_width as number) || (opts.width as number) || 0, + height: (opts.height as number) || extractLengthFromName(firstItem?.itemName) || 0, quantity: group.totalQuantity, processType: activeProcessTabKey, steps, @@ -556,6 +609,11 @@ export default function WorkerScreen() { detailParts: (bi.detail_parts || []).map(dp => ({ partName: dp.part_name, material: dp.material, barcyInfo: dp.barcy_info })), }; } + // BD- 코드 추출 (재공품/절곡품 참조용) + const bdItemCode = firstItem?.itemCode; + if (bdItemCode?.startsWith('BD-')) { + workItem.bdCode = bdItemCode; + } if (opts.is_wip) { workItem.isWip = true; const wi = opts.wip_info as { specification: string; length_quantity: string } | undefined; @@ -1617,6 +1675,27 @@ function SidebarContent({ onSelectOrder, apiOrders, }: SidebarContentProps) { + const [sidebarTab, setSidebarTab] = useState<'orders' | 'wip'>('orders'); + const [searchTerm, setSearchTerm] = useState(''); + + // 수주목록 / 재공품 분리 + const regularOrders = useMemo(() => + apiOrders.filter((o) => o.subType !== 'wip'), + [apiOrders]); + const wipOrders = useMemo(() => + apiOrders.filter((o) => o.subType === 'wip'), + [apiOrders]); + + // 검색 필터링 + const displayOrders = useMemo(() => { + const source = sidebarTab === 'orders' ? regularOrders : wipOrders; + if (!searchTerm.trim()) return source; + const q = searchTerm.toLowerCase(); + return source.filter((o) => + o.siteName.toLowerCase().includes(q) || o.date.includes(q) + ); + }, [sidebarTab, regularOrders, wipOrders, searchTerm]); + const renderOrders = (orders: SidebarOrder[]) => ( <> {PRIORITY_GROUPS.map((group) => { @@ -1648,6 +1727,9 @@ function SidebarContent({ )} + {order.bdCode && ( +

{order.bdCode}

+ )}
{order.date} {order.shutterCount}개소 @@ -1664,12 +1746,48 @@ function SidebarContent({ return (
-

수주 목록

+ {/* 탭: 수주목록 / 재공품 */} +
+ + +
- {apiOrders.length > 0 ? ( - renderOrders(apiOrders) + {/* 검색 */} + setSearchTerm(e.target.value)} + className="h-8 text-xs" + /> + + {displayOrders.length > 0 ? ( + renderOrders(displayOrders) ) : ( -

수주 데이터가 없습니다.

+

+ {searchTerm ? '검색 결과가 없습니다.' : '데이터가 없습니다.'} +

)}
); diff --git a/src/components/production/WorkerScreen/types.ts b/src/components/production/WorkerScreen/types.ts index a9d11a99..e302ec8c 100644 --- a/src/components/production/WorkerScreen/types.ts +++ b/src/components/production/WorkerScreen/types.ts @@ -45,6 +45,7 @@ export interface WorkItemData { processType: ProcessTab; // 공정 타입 steps: WorkStepData[]; // 공정 단계들 isWip?: boolean; // 재공품 여부 + bdCode?: string; // 절곡품 BD- 코드 (예: BD-ST-24) isJointBar?: boolean; // 조인트바 여부 // 스크린 전용 cuttingInfo?: CuttingInfo;