From 8fcb305980c5d14ac22126f40065e8fa55b382db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 22 Mar 2026 13:18:48 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[=EC=9E=90=EC=9E=AC=ED=88=AC=EC=9E=85]?= =?UTF-8?q?=20=ED=92=88=EB=AA=A9=20=EA=B2=80=EC=83=89=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20-=20=EC=9E=AC=EA=B3=A0=20=EC=97=86=EB=8A=94=20=ED=92=88?= =?UTF-8?q?=EB=AA=A9=EB=8F=84=20=EA=B2=80=EC=83=89=20+=20=EA=B0=9C?= =?UTF-8?q?=EB=B3=84=20=EA=B0=95=EC=A0=9C=EC=9E=85=EA=B3=A0=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WorkerScreen/MaterialInputModal.tsx | 61 +++++++++++++------ .../production/WorkerScreen/actions.ts | 32 ++++++++++ 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/src/components/production/WorkerScreen/MaterialInputModal.tsx b/src/components/production/WorkerScreen/MaterialInputModal.tsx index ab57c70f..cbc1c85e 100644 --- a/src/components/production/WorkerScreen/MaterialInputModal.tsx +++ b/src/components/production/WorkerScreen/MaterialInputModal.tsx @@ -34,7 +34,7 @@ import { } from '@/components/ui/table'; import { toast } from 'sonner'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; -import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, searchStockByCode, forceCreateReceiving, type MaterialForInput, type MaterialForItemInput, type StockSearchResult } from './actions'; +import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, searchStockByCode, forceCreateReceiving, searchItems, type MaterialForInput, type MaterialForItemInput, type StockSearchResult, type ItemSearchResult } from './actions'; import type { WorkOrder } from '../ProductionDashboard/types'; import type { MaterialInput } from './types'; import { formatNumber } from '@/lib/utils/amount'; @@ -95,11 +95,22 @@ export function MaterialInputModal({ const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); + // 품목 검색 결과 (재고 없는 품목 포함) + const [itemSearchResults, setItemSearchResults] = useState([]); + const handleStockSearch = useCallback(async (query: string) => { - if (!query.trim()) { setSearchResults([]); return; } + if (!query.trim()) { setSearchResults([]); setItemSearchResults([]); return; } setIsSearching(true); - const result = await searchStockByCode(query.trim()); - if (result.success) setSearchResults(result.data); + const [stockResult, itemResult] = await Promise.all([ + searchStockByCode(query.trim()), + searchItems(query.trim()), + ]); + if (stockResult.success) setSearchResults(stockResult.data); + if (itemResult.success) { + // 재고 검색 결과에 이미 있는 품목은 제외 + const stockItemIds = new Set(stockResult.data?.map(s => s.itemId) || []); + setItemSearchResults(itemResult.data.filter(i => !stockItemIds.has(i.itemId))); + } setIsSearching(false); }, []); @@ -591,23 +602,33 @@ export function MaterialInputModal({ ) : isSearching ? (

검색 중...

- ) : searchQuery && ( + ) : searchQuery && searchResults.length === 0 && itemSearchResults.length === 0 ? (

검색 결과 없음

- )} - {searchQuery && ( - + ) : null} + {/* 재고 없는 품목 목록 (items 테이블에만 존재) */} + {itemSearchResults.length > 0 && ( +
+

재고 없는 품목 {itemSearchResults.length}건:

+ {itemSearchResults.map((item, idx) => ( +
+
+ {item.itemCode} + {item.itemName} + ({item.itemType}) +
+ +
+ ))} +
)} diff --git a/src/components/production/WorkerScreen/actions.ts b/src/components/production/WorkerScreen/actions.ts index 97570586..ae83e3f2 100644 --- a/src/components/production/WorkerScreen/actions.ts +++ b/src/components/production/WorkerScreen/actions.ts @@ -939,4 +939,36 @@ export async function searchStockByCode( })), })), }; +} + +// ===== 품목(items) 검색 (재고 없는 품목도 검색) ===== +export interface ItemSearchResult { + itemId: number; + itemCode: string; + itemName: string; + itemType: string; + unit: string; +} + +export async function searchItems( + search: string +): Promise<{ success: boolean; data: ItemSearchResult[]; error?: string }> { + const result = await executeServerAction<{ + data: Array<{ id: number; code: string; name: string; item_type: string; unit: string }>; + }>({ + url: buildApiUrl('/api/v1/items', { search, per_page: 10 }), + errorMessage: '품목 검색에 실패했습니다.', + }); + if (!result.success || !result.data) return { success: false, data: [], error: result.error }; + const items = Array.isArray(result.data) ? result.data : (result.data.data || []); + return { + success: true, + data: items.map((i: { id: number; code: string; name: string; item_type: string; unit: string }) => ({ + itemId: i.id, + itemCode: i.code, + itemName: i.name, + itemType: i.item_type, + unit: i.unit, + })), + }; } \ No newline at end of file