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 && (
+
+
+
+
+
+ )}
+
+ {/* 메모 */}
+
+
+
+
+
+
),
- []
+ [prodName, specName, lengthName, isSmokeBarrier, item]
);
return (
@@ -343,7 +343,7 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) {
itemId={orderId}
isLoading={loading}
onCancel={() => router.push(basePath)}
- headerActions={headerActions}
+ headerActions={null}
headerActionItems={headerActionItems}
renderView={(data) => renderViewContent(data as unknown as StockOrder)}
/>