feat: [자재투입] 재고 없는 자재에 재고 검색 기능 추가
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { Loader2, Check, Zap, Info } from 'lucide-react';
|
||||
import { Loader2, Check, Zap, Info, Search, X } from 'lucide-react';
|
||||
import { ContentSkeleton } from '@/components/ui/skeleton';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -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, type MaterialForInput, type MaterialForItemInput } from './actions';
|
||||
import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, searchStockByCode, type MaterialForInput, type MaterialForItemInput, type StockSearchResult } from './actions';
|
||||
import type { WorkOrder } from '../ProductionDashboard/types';
|
||||
import type { MaterialInput } from './types';
|
||||
import { formatNumber } from '@/lib/utils/amount';
|
||||
@@ -89,6 +89,20 @@ export function MaterialInputModal({
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const materialsLoadedRef = useRef(false);
|
||||
|
||||
// 재고 검색 상태
|
||||
const [searchOpenGroup, setSearchOpenGroup] = useState<string | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<StockSearchResult[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
const handleStockSearch = useCallback(async (query: string) => {
|
||||
if (!query.trim()) { setSearchResults([]); return; }
|
||||
setIsSearching(true);
|
||||
const result = await searchStockByCode(query.trim());
|
||||
if (result.success) setSearchResults(result.data);
|
||||
setIsSearching(false);
|
||||
}, []);
|
||||
|
||||
// 목업 자재 데이터 (개발용)
|
||||
const MOCK_MATERIALS: MaterialForInput[] = Array.from({ length: 5 }, (_, i) => ({
|
||||
stockLotId: 100 + i,
|
||||
@@ -742,6 +756,75 @@ export function MaterialInputModal({
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* 재고 검색 패널 */}
|
||||
{!group.lots.some(l => l.stockLotId !== null) && searchOpenGroup !== group.groupKey && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpenGroup(group.groupKey);
|
||||
setSearchQuery(group.materialCode || group.materialName);
|
||||
setSearchResults([]);
|
||||
handleStockSearch(group.materialCode || group.materialName);
|
||||
}}
|
||||
className="w-full px-4 py-2 text-xs text-red-600 bg-red-50 border-t flex items-center gap-1.5 hover:bg-red-100 transition-colors"
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
매칭 가능한 입고 재고 없음 — 클릭하여 재고 검색
|
||||
</button>
|
||||
)}
|
||||
{searchOpenGroup === group.groupKey && (
|
||||
<div className="px-4 py-3 bg-blue-50 border-t space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-3.5 w-3.5 text-blue-500 shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleStockSearch(searchQuery)}
|
||||
placeholder="품목코드 또는 자재명 검색"
|
||||
className="flex-1 text-xs px-2 py-1.5 border rounded bg-white"
|
||||
/>
|
||||
<Button size="sm" variant="outline" className="h-7 text-xs" onClick={() => handleStockSearch(searchQuery)}>
|
||||
{isSearching ? <Loader2 className="h-3 w-3 animate-spin" /> : '검색'}
|
||||
</Button>
|
||||
<button onClick={() => setSearchOpenGroup(null)} className="text-gray-400 hover:text-gray-600">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{searchResults.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
<p className="text-[11px] text-blue-600 font-medium">{searchResults.length}건 검색됨:</p>
|
||||
{searchResults.map((s) => (
|
||||
<div key={s.itemId} className={cn(
|
||||
"text-[11px] px-2 py-1.5 rounded border",
|
||||
s.availableQty > 0 ? "bg-white border-emerald-200" : "bg-gray-50 border-gray-200"
|
||||
)}>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">{s.itemCode}</span>
|
||||
<span className={s.availableQty > 0 ? "text-emerald-600 font-semibold" : "text-gray-400"}>
|
||||
가용 {formatNumber(s.availableQty)} EA
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-gray-500">{s.itemName}</div>
|
||||
{s.lots.length > 0 && (
|
||||
<div className="mt-1 space-y-0.5">
|
||||
{s.lots.slice(0, 3).map((l, li) => (
|
||||
<div key={li} className="text-[10px] text-gray-400 ml-2">
|
||||
LOT {l.lotNo} | {formatNumber(l.availableQty)} EA | FIFO #{l.fifoOrder}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : isSearching ? (
|
||||
<p className="text-[11px] text-gray-400 flex items-center gap-1"><Loader2 className="h-3 w-3 animate-spin" />검색 중...</p>
|
||||
) : searchQuery && (
|
||||
<p className="text-[11px] text-red-500">검색 결과 없음 — 해당 품목의 입고 처리가 필요합니다</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user