From af573b0ed43497c92c6dbd3206eeb535abebb321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Tue, 17 Mar 2026 14:50:12 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[stocks]=20=EB=B2=A4=EB=94=A9=20LOT=20?= =?UTF-8?q?=ED=8F=BC=20=EA=B0=9C=EC=84=A0=20+=20package.json=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - src/components/stocks/BendingLotForm.tsx | 229 ++++++++++++++++-- .../stocks/StockProductionDetail.tsx | 14 +- src/components/stocks/actions.ts | 32 +++ 4 files changed, 245 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 1457b5f6..11f0149c 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "date-fns": "^4.1.0", "dompurify": "^3.3.1", "immer": "^11.0.1", - "jspdf": "^4.0.0", "lucide-react": "^0.552.0", "next": "^15.5.9", "next-intl": "^4.4.0", diff --git a/src/components/stocks/BendingLotForm.tsx b/src/components/stocks/BendingLotForm.tsx index 7ca1ad0b..35e66ba5 100644 --- a/src/components/stocks/BendingLotForm.tsx +++ b/src/components/stocks/BendingLotForm.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { NumberInput } from '@/components/ui/number-input'; @@ -14,7 +15,21 @@ import { SelectItem, SelectValue, } from '@/components/ui/select'; -import { Package, ClipboardList, MessageSquare, Tag, Layers } from 'lucide-react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Package, ClipboardList, MessageSquare, Tag, Layers, Search, X, Loader2 } from 'lucide-react'; import { toast } from 'sonner'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { FormSection } from '@/components/organisms/FormSection'; @@ -23,8 +38,10 @@ import { resolveBendingItem, generateBendingLot, createBendingStockOrder, + getMaterialLots, type BendingCodeMap, type BendingResolvedItem, + type MaterialLot, } from './actions'; import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types'; @@ -93,6 +110,105 @@ function getInitialForm(): BendingFormState { }; } +// ============================================================================ +// 원자재 LOT 선택 모달 +// ============================================================================ + +function MaterialLotModal({ + open, + onOpenChange, + material, + title, + onSelect, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + material: string; + title: string; + onSelect: (lotNo: string) => void; +}) { + const [lots, setLots] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (!open || !material) return; + setIsLoading(true); + getMaterialLots(material) + .then((result) => { + if (result.__authError) { + toast.error('인증이 만료되었습니다.'); + return; + } + if (result.success && result.data) { + setLots(result.data); + } else { + toast.error(result.error || 'LOT 목록 조회에 실패했습니다.'); + } + }) + .finally(() => setIsLoading(false)); + }, [open, material]); + + return ( + + + + {title} + +

+ 재질: {material} +

+
+ {isLoading ? ( +
+ +
+ ) : lots.length === 0 ? ( +
+ 해당 재질의 입고 LOT가 없습니다. +
+ ) : ( + + + + LOT번호 + 품목명 + 규격 + 입고수량 + 입고일 + 공급업체 + + + + {lots.map((lot) => ( + { + onSelect(lot.lot_no); + onOpenChange(false); + }} + > + + + {lot.lot_no} + + + {lot.item_name} + {lot.specification} + {lot.receiving_qty} + {lot.receiving_date} + {lot.supplier} + + ))} + +
+ )} +
+
+
+ ); +} + // ============================================================================ // Component // ============================================================================ @@ -109,6 +225,8 @@ export function BendingLotForm() { const [resolveError, setResolveError] = useState(''); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); + const [rawLotModalOpen, setRawLotModalOpen] = useState(false); + const [fabricLotModalOpen, setFabricLotModalOpen] = useState(false); // 코드맵 로드 useEffect(() => { @@ -451,24 +569,67 @@ export function BendingLotForm() {
- - setForm((prev) => ({ ...prev, rawLotNo: e.target.value })) - } - /> +
+ + {form.rawLotNo && ( + + )} + +
{isSmokeBarrier && (
- - setForm((prev) => ({ ...prev, fabricLotNo: e.target.value })) - } - /> +
+ + {form.fabricLotNo && ( + + )} + +
)}
@@ -507,13 +668,35 @@ export function BendingLotForm() { ); return ( - + <> + + + {/* 원자재 LOT 선택 모달 */} + setForm((prev) => ({ ...prev, rawLotNo: lotNo }))} + /> + + {/* 원단 LOT 선택 모달 (연기차단재 전용) */} + {isSmokeBarrier && ( + setForm((prev) => ({ ...prev, fabricLotNo: lotNo }))} + /> + )} + ); } diff --git a/src/components/stocks/StockProductionDetail.tsx b/src/components/stocks/StockProductionDetail.tsx index 82115a80..0fa63a6d 100644 --- a/src/components/stocks/StockProductionDetail.tsx +++ b/src/components/stocks/StockProductionDetail.tsx @@ -29,6 +29,7 @@ import { ClipboardList, MessageSquare, Tag, + RotateCcw, } from 'lucide-react'; import { toast } from 'sonner'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; @@ -263,15 +264,14 @@ export function StockProductionDetail({ orderId }: StockProductionDetailProps) { }); } - // cancelled → 수정 불가 안내 + // cancelled → 복원 버튼 if (order.status === 'cancelled') { items.push({ - icon: Pencil, - label: '수정', - onClick: () => toast.warning('취소 상태에서는 수정이 불가합니다.'), - variant: 'outline', - disabled: false, - className: 'opacity-50', + icon: RotateCcw, + label: '복원', + onClick: () => handleStatusChange('draft'), + className: 'bg-blue-600 hover:bg-blue-500 text-white', + disabled: isProcessing, }); } diff --git a/src/components/stocks/actions.ts b/src/components/stocks/actions.ts index 6bc0b470..b5bbec38 100644 --- a/src/components/stocks/actions.ts +++ b/src/components/stocks/actions.ts @@ -563,6 +563,21 @@ export interface BendingLotFormData { material?: string; } +export interface MaterialLot { + id: number; + lot_no: string; + supplier_lot: string; + item_name: string; + specification: string; + receiving_qty: string; + receiving_date: string; + supplier: string; + options?: { + inspection_status?: string; + inspection_result?: string; + }; +} + // ============================================================================ // 절곡품 LOT API 함수 // ============================================================================ @@ -708,3 +723,20 @@ export async function createBendingStockOrder(params: { if (result.__authError) return { success: false, __authError: true }; return { success: result.success, data: result.data, error: result.error }; } + +/** + * 원자재 LOT 목록 조회 (수입검사 완료 입고 건) + */ +export async function getMaterialLots(material: string): Promise<{ + success: boolean; + data?: MaterialLot[]; + error?: string; + __authError?: boolean; +}> { + const result = await executeServerAction({ + url: buildApiUrl('/api/v1/bending/material-lots', { material }), + errorMessage: '원자재 LOT 목록 조회에 실패했습니다.', + }); + if (result.__authError) return { success: false, __authError: true }; + return { success: result.success, data: result.data, error: result.error }; +}