From 7bd4bd38dae80ec35650c435238a09476e90c24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Mon, 9 Mar 2026 17:43:28 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[quality]=20=EC=88=98=EC=A3=BC=EC=B2=98?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20UI=20+=20client=5Fid=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20+=20=EC=88=98=EC=A0=95=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수주처를 텍스트 입력에서 거래처 검색 선택으로 변경 - 수주 선택 시 거래처+모델 필터 연동 (양방향) - ProductInspection/Api에 clientId 매핑 추가 - 수정 시 새 개소 locations 필터 (NaN ID 에러 해결) - SupplierSearchModal 콜백에 id 반환 추가 --- .../SupplierSearchModal.tsx | 4 +- .../InspectionManagement/InspectionCreate.tsx | 92 ++++++++++++++----- .../InspectionManagement/InspectionDetail.tsx | 85 +++++++++++++---- .../quality/InspectionManagement/actions.ts | 19 ++-- .../quality/InspectionManagement/types.ts | 1 + 5 files changed, 154 insertions(+), 47 deletions(-) diff --git a/src/components/material/ReceivingManagement/SupplierSearchModal.tsx b/src/components/material/ReceivingManagement/SupplierSearchModal.tsx index 7268981b..747407cc 100644 --- a/src/components/material/ReceivingManagement/SupplierSearchModal.tsx +++ b/src/components/material/ReceivingManagement/SupplierSearchModal.tsx @@ -27,7 +27,7 @@ interface SupplierItem { interface SupplierSearchModalProps { open: boolean; onOpenChange: (open: boolean) => void; - onSelectSupplier: (supplier: { name: string; code?: string }) => void; + onSelectSupplier: (supplier: { id: number | string; name: string; code?: string }) => void; } // ============================================================================= @@ -115,7 +115,7 @@ export function SupplierSearchModal({ }, []); const handleSelect = useCallback((supplier: SupplierItem) => { - onSelectSupplier({ name: supplier.name, code: supplier.clientCode }); + onSelectSupplier({ id: supplier.id, name: supplier.name, code: supplier.clientCode }); }, [onSelectSupplier]); return ( diff --git a/src/components/quality/InspectionManagement/InspectionCreate.tsx b/src/components/quality/InspectionManagement/InspectionCreate.tsx index 83e34b88..a13cd301 100644 --- a/src/components/quality/InspectionManagement/InspectionCreate.tsx +++ b/src/components/quality/InspectionManagement/InspectionCreate.tsx @@ -48,6 +48,9 @@ const OrderSelectModal = dynamic( const ProductInspectionInputModal = dynamic( () => import('./ProductInspectionInputModal').then(mod => ({ default: mod.ProductInspectionInputModal })), ); +const SupplierSearchModal = dynamic( + () => import('@/components/material/ReceivingManagement/SupplierSearchModal').then(mod => ({ default: mod.SupplierSearchModal })), +); import type { InspectionFormData, OrderSettingItem, OrderSelectItem, OrderGroup, ProductInspectionData } from './types'; import { emptyConstructionSite, @@ -77,6 +80,7 @@ export function InspectionCreate() { const [isSubmitting, setIsSubmitting] = useState(false); const [orderModalOpen, setOrderModalOpen] = useState(false); + const [clientModalOpen, setClientModalOpen] = useState(false); // 제품검사 입력 모달 const [inspectionInputOpen, setInspectionInputOpen] = useState(false); @@ -123,10 +127,15 @@ export function InspectionCreate() { changeReason: '', }] ); - setFormData((prev) => ({ - ...prev, - orderItems: [...prev.orderItems, ...newOrderItems], - })); + setFormData((prev) => { + const updated = { ...prev, orderItems: [...prev.orderItems, ...newOrderItems] }; + // 수주처 미선택 상태에서 수주 선택 시 → 수주처 자동 채움 + if (!prev.clientId && items.length > 0 && items[0].clientId) { + updated.clientId = items[0].clientId ?? undefined; + updated.client = items[0].clientName || ''; + } + return updated; + }); }, []); // ===== 수주 항목 삭제 ===== @@ -238,9 +247,9 @@ export function InspectionCreate() { toast.error('현장명은 필수 입력 항목입니다.'); return { success: false, error: '현장명을 입력해주세요.' }; } - if (!formData.client.trim()) { - toast.error('수주처는 필수 입력 항목입니다.'); - return { success: false, error: '수주처를 입력해주세요.' }; + if (!formData.clientId) { + toast.error('수주처는 필수 선택 항목입니다.'); + return { success: false, error: '수주처를 선택해주세요.' }; } setIsSubmitting(true); @@ -400,11 +409,29 @@ export function InspectionCreate() {
- updateField('client', e.target.value)} - placeholder="수주처 입력" - /> +
+ setClientModalOpen(true)} + /> + {formData.clientId && ( + + )} +
@@ -691,16 +718,28 @@ export function InspectionCreate() { [formData.orderItems] ); - // 이미 선택된 수주가 있으면 같은 거래처+모델만 필터 + // 수주 선택 필터: 기본정보 수주처 또는 이미 선택된 수주 기준 const orderFilter = useMemo(() => { - if (formData.orderItems.length === 0) return { clientId: undefined, itemId: undefined, label: undefined }; - const first = formData.orderItems[0]; - return { - clientId: first.clientId ?? undefined, - itemId: first.itemId ?? undefined, - label: [first.clientName, first.itemName].filter(Boolean).join(' / ') || undefined, - }; - }, [formData.orderItems]); + // 기본정보에서 수주처가 선택된 경우 → 해당 거래처 필터 + if (formData.clientId) { + const firstItem = formData.orderItems[0]; + return { + clientId: formData.clientId, + itemId: firstItem?.itemId ?? undefined, + label: [formData.client, firstItem?.itemName].filter(Boolean).join(' / ') || undefined, + }; + } + // 수주가 선택된 경우 → 첫 수주 기준 필터 + if (formData.orderItems.length > 0) { + const first = formData.orderItems[0]; + return { + clientId: first.clientId ?? undefined, + itemId: first.itemId ?? undefined, + label: [first.clientName, first.itemName].filter(Boolean).join(' / ') || undefined, + }; + } + return { clientId: undefined, itemId: undefined, label: undefined }; + }, [formData.clientId, formData.client, formData.orderItems]); return ( <> @@ -725,6 +764,17 @@ export function InspectionCreate() { filterLabel={orderFilter.label} /> + {/* 거래처(수주처) 검색 모달 */} + { + updateField('clientId', Number(supplier.id)); + updateField('client', supplier.name); + setClientModalOpen(false); + }} + /> + {/* 제품검사 입력 모달 */} import('./OrderSelectModal').then(mod => ({ default: mod.OrderSelectModal })), ); +const SupplierSearchModal = dynamic( + () => import('@/components/material/ReceivingManagement/SupplierSearchModal').then(mod => ({ default: mod.SupplierSearchModal })), +); import type { ProductInspection, InspectionFormData, @@ -137,6 +140,7 @@ export function InspectionDetail({ id }: InspectionDetailProps) { // 수주 선택 모달 const [orderModalOpen, setOrderModalOpen] = useState(false); + const [clientModalOpen, setClientModalOpen] = useState(false); // 문서 모달 const [requestDocOpen, setRequestDocOpen] = useState(false); @@ -213,6 +217,7 @@ export function InspectionDetail({ id }: InspectionDetailProps) { qualityDocNumber: result.data.qualityDocNumber, siteName: result.data.siteName, client: result.data.client, + clientId: result.data.clientId, manager: result.data.manager, managerContact: result.data.managerContact, constructionSite: { ...result.data.constructionSite }, @@ -374,10 +379,15 @@ export function InspectionDetail({ id }: InspectionDetailProps) { changeReason: '', }] ); - setFormData((prev) => ({ - ...prev, - orderItems: [...prev.orderItems, ...newOrderItems], - })); + setFormData((prev) => { + const updated = { ...prev, orderItems: [...prev.orderItems, ...newOrderItems] }; + // 수주처 미선택 상태에서 수주 선택 시 → 수주처 자동 채움 + if (!prev.clientId && items.length > 0 && items[0].clientId) { + updated.clientId = items[0].clientId ?? undefined; + updated.client = items[0].clientName || ''; + } + return updated; + }); }, []); const handleRemoveOrderItem = useCallback((itemId: string) => { @@ -393,17 +403,29 @@ export function InspectionDetail({ id }: InspectionDetailProps) { [formData.orderItems] ); - // 이미 선택된 수주가 있으면 같은 거래처+모델만 필터 + // 수주 선택 필터: 기본정보 수주처 또는 이미 선택된 수주 기준 const orderFilter = useMemo(() => { const items = isEditMode ? formData.orderItems : (inspection?.orderItems || []); - if (items.length === 0) return { clientId: undefined, itemId: undefined, label: undefined }; - const first = items[0]; - return { - clientId: first.clientId ?? undefined, - itemId: first.itemId ?? undefined, - label: [first.clientName, first.itemName].filter(Boolean).join(' / ') || undefined, - }; - }, [isEditMode, formData.orderItems, inspection?.orderItems]); + // 기본정보에서 수주처가 선택된 경우 → 해당 거래처 필터 + if (formData.clientId) { + const firstItem = items[0]; + return { + clientId: formData.clientId, + itemId: firstItem?.itemId ?? undefined, + label: [formData.client, firstItem?.itemName].filter(Boolean).join(' / ') || undefined, + }; + } + // 수주가 선택된 경우 → 첫 수주 기준 필터 + if (items.length > 0) { + const first = items[0]; + return { + clientId: first.clientId ?? undefined, + itemId: first.itemId ?? undefined, + label: [first.clientName, first.itemName].filter(Boolean).join(' / ') || undefined, + }; + } + return { clientId: undefined, itemId: undefined, label: undefined }; + }, [isEditMode, formData.clientId, formData.client, formData.orderItems, inspection?.orderItems]); // ===== 수주 설정 요약 ===== const orderSummary = useMemo(() => { @@ -976,10 +998,28 @@ export function InspectionDetail({ id }: InspectionDetailProps) {
- updateField('client', e.target.value)} - /> +
+ setClientModalOpen(true)} + /> + {formData.clientId && ( + + )} +
@@ -1334,6 +1374,17 @@ export function InspectionDetail({ id }: InspectionDetailProps) { filterLabel={orderFilter.label} /> + {/* 거래처(수주처) 검색 모달 */} + { + updateField('clientId', Number(supplier.id)); + updateField('client', supplier.name); + setClientModalOpen(false); + }} + /> + {/* 제품검사요청서 모달 */} ({ - id: Number(item.id), - post_width: item.constructionWidth || null, - post_height: item.constructionHeight || null, - change_reason: item.changeReason || null, - inspection_data: item.inspectionData || null, - })); + // 새로 추가된 항목(id가 "orderId-nodeId" 형태)은 order_ids 동기화로 생성되므로 제외 + apiData.locations = data.orderItems + .filter((item) => !String(item.id).includes('-') && !isNaN(Number(item.id))) + .map((item) => ({ + id: Number(item.id), + post_width: item.constructionWidth || null, + post_height: item.constructionHeight || null, + change_reason: item.changeReason || null, + inspection_data: item.inspectionData || null, + })); } const result = await executeServerAction({ diff --git a/src/components/quality/InspectionManagement/types.ts b/src/components/quality/InspectionManagement/types.ts index 3c69238c..200437e8 100644 --- a/src/components/quality/InspectionManagement/types.ts +++ b/src/components/quality/InspectionManagement/types.ts @@ -152,6 +152,7 @@ export interface ProductInspection { qualityDocNumber: string; // 품질관리서 번호 siteName: string; // 현장명 client: string; // 수주처 + clientId?: number; // 수주처 ID locationCount: number; // 개소 requiredInfo: string; // 필수정보 (완료 / N건 누락) inspectionPeriod: string; // 검사기간 (2026-01-01 또는 2026-01-01~2026-01-02)