diff --git a/src/components/stocks/StockProductionDetail.tsx b/src/components/stocks/StockProductionDetail.tsx index 92506dc1..e42e871c 100644 --- a/src/components/stocks/StockProductionDetail.tsx +++ b/src/components/stocks/StockProductionDetail.tsx @@ -3,41 +3,36 @@ /** * 재고생산 상세 보기 컴포넌트 * - * - 기본 정보 (생산번호, 상태, 생산사유, 목표재고수량, 메모, 비고) - * - 품목 내역 테이블 - * - 상태 변경 / 수정 / 생산지시 생성 버튼 + * - BendingLotForm(등록/수정)과 동일한 레이아웃 (읽기 전용) + * - 기본 정보, 품목 선택, LOT 정보, 메모 섹션 */ import { useState, useEffect, useMemo, useCallback } from 'react'; import { useRouter, useParams } from 'next/navigation'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Label } from '@/components/ui/label'; import { Package, ClipboardList, MessageSquare, Tag, + Layers, RotateCcw, } from 'lucide-react'; import { toast } from 'sonner'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; +import { FormSection } from '@/components/organisms/FormSection'; import { BadgeSm } from '@/components/atoms/BadgeSm'; -import { formatAmount } from '@/lib/utils/amount'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import { getStockOrderById, + getBendingCodeMap, updateStockOrderStatus, deleteStockOrder, type StockOrder, type StockStatus, + type BendingCodeMap, } from './actions'; import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types'; import type { ActionItem } from '@/components/templates/IntegratedDetailTemplate/types'; @@ -47,7 +42,7 @@ import type { ActionItem } from '@/components/templates/IntegratedDetailTemplate // ============================================================================ const stockDetailConfig: DetailConfig = { - title: '재고생산', + title: '절곡품 재고생산', description: '재고생산 정보를 조회합니다', icon: Package, basePath: '/sales/stocks', @@ -79,16 +74,6 @@ function getStatusBadge(status: string) { return {config.label}; } -// 정보 표시 컴포넌트 -function InfoItem({ label, value }: { label: string; value: string | number }) { - return ( -
-

{label}

-

{value || '-'}

-
- ); -} - // ============================================================================ // Props // ============================================================================ @@ -108,32 +93,63 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) { const basePath = `/${locale}/sales/stocks`; const [order, setOrder] = useState(null); + const [codeMap, setCodeMap] = useState(null); const [loading, setLoading] = useState(true); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isProcessing, setIsProcessing] = useState(false); // 데이터 로드 useEffect(() => { - async function loadOrder() { + async function load() { try { setLoading(true); - const result = await getStockOrderById(orderId); - if (result.__authError) { + const [orderResult, codeMapResult] = await Promise.all([ + getStockOrderById(orderId), + getBendingCodeMap(), + ]); + + if (orderResult.__authError) { toast.error('인증이 만료되었습니다.'); return; } - if (result.success && result.data) { - setOrder(result.data); + if (orderResult.success && orderResult.data) { + setOrder(orderResult.data); } else { - toast.error(result.error || '재고생산 정보를 불러오는데 실패했습니다.'); + toast.error(orderResult.error || '재고생산 정보를 불러오는데 실패했습니다.'); + } + + if (codeMapResult.success && codeMapResult.data) { + setCodeMap(codeMapResult.data); } } finally { setLoading(false); } } - loadOrder(); + load(); }, [orderId]); + // 코드 → 이름 변환 + const prodName = useMemo(() => { + if (!codeMap || !order?.bendingLot?.prodCode) return order?.bendingLot?.prodCode || '-'; + return codeMap.products.find((p) => p.code === order.bendingLot!.prodCode)?.name || order.bendingLot.prodCode; + }, [codeMap, order]); + + const specName = useMemo(() => { + if (!codeMap || !order?.bendingLot?.specCode) return order?.bendingLot?.specCode || '-'; + return codeMap.specs.find((s) => s.code === order.bendingLot!.specCode)?.name || order.bendingLot.specCode; + }, [codeMap, order]); + + const lengthName = useMemo(() => { + if (!codeMap || !order?.bendingLot?.prodCode || !order?.bendingLot?.lengthCode) return order?.bendingLot?.lengthCode || '-'; + const lengths = order.bendingLot.prodCode === 'G' + ? codeMap.lengths.smoke_barrier + : codeMap.lengths.general; + return lengths.find((l) => l.code === order.bendingLot!.lengthCode)?.name || order.bendingLot.lengthCode; + }, [codeMap, order]); + + // 연기차단재 여부 + const isSmokeBarrier = order?.bendingLot?.prodCode === 'G'; + // 상태 변경 const handleStatusChange = useCallback(async (newStatus: StockStatus) => { if (!order) return; @@ -178,12 +194,10 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) { }, [order, router, basePath]); // 헤더 액션 버튼 - // 재고생산: 저장 즉시 IN_PROGRESS (확정/생산지시 자동). draft/confirmed 분기 불필요. const headerActionItems = useMemo((): ActionItem[] => { if (!order) return []; const items: ActionItem[] = []; - // cancelled → 복원 버튼 if (order.status === 'cancelled') { items.push({ icon: RotateCcw, @@ -197,141 +211,127 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) { return items; }, [order, isProcessing, handleStatusChange]); - // 상태 뱃지 — 기본 정보 카드에 이미 표시되므로 하단 바에는 미표시 - const headerActions = useMemo(() => { - return null; - }, []); + // 품목 정보 (첫 번째 아이템) + const item = order?.items?.[0]; - // renderView + // renderView — 등록 화면(BendingLotForm)과 동일한 레이아웃 const renderViewContent = useMemo( () => (data: StockOrder) => (
{/* 기본 정보 */} - - - - - 기본 정보 - - - -
- -
-

상태

-
{getStatusBadge(data.status)}
-
- - - - + +
+
+ +
- - +
+ +
+ {getStatusBadge(data.status)} +
+
+
+ + +
+
+ + +
+
+
- {/* LOT 정보 (절곡품) */} + {/* 품목 선택 (캐스케이딩 — 읽기 전용) */} {data.bendingLot && ( - - - - - LOT 정보 - - - -
- - - - {data.bendingLot.prodCode === 'G' && ( - - )} + +
+
+ +
- - - )} +
+ + +
+
+ + +
+
- {/* 비고 */} - {(data.memo || data.remarks) && ( - - - - - 비고 - - - -
- {data.memo && } - {data.remarks && } -
-
-
- )} - - {/* 품목 내역 */} - - - - - 품목 내역 ({data.items.length}건) - - - - {data.items.length === 0 ? ( -
- 품목이 없습니다. -
- ) : ( -
- - - - No - 품목코드 - 품목명 - 규격 - 수량 - 단위 - 단가 - 금액 - - - - {data.items.map((item, index) => ( - - {index + 1} - - {item.itemCode ? ( - - {item.itemCode} - - ) : '-'} - - {item.itemName} - - {item.specification || '-'} - - {item.quantity} - {item.unit} - - {formatAmount(item.unitPrice)} - - - {formatAmount(item.totalAmount)} - - - ))} - -
+ {/* 매핑된 품목 표시 */} + {item && item.itemCode && ( +
+

매핑된 품목

+
+
+ 품목코드: + {item.itemCode} +
+
+ 품목명: + {item.itemName} +
+
+ 규격: + {item.specification || '-'} +
+
+ 단위: + {item.unit} +
+
)} - - + + )} + + {/* LOT 정보 */} + {data.bendingLot && ( + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ {isSmokeBarrier && ( +
+ + +
+ )} +
+
+ )} + + {/* 메모 */} + +
+ +