"use client"; import React, { useState, useMemo, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Search, RotateCcw, Package, ClipboardList, Factory, MapPin, AlertTriangle, CheckCircle2, Loader2, } from "lucide-react"; import { cn } from "@/lib/utils"; import { getWorkOrders, getMaterialStatus, getWarehouses, type WorkOrder, type MaterialData, type WarehouseData, } from "@/lib/api/materialStatus"; const formatDate = (date: Date) => { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, "0"); const d = String(date.getDate()).padStart(2, "0"); return `${y}-${m}-${d}`; }; const getStatusLabel = (status: string) => { const map: Record = { planned: "계획", in_progress: "진행중", completed: "완료", pending: "대기", cancelled: "취소", }; return map[status] || status; }; const getStatusStyle = (status: string) => { const map: Record = { planned: "bg-amber-100 text-amber-700 border-amber-200", pending: "bg-amber-100 text-amber-700 border-amber-200", in_progress: "bg-blue-100 text-blue-700 border-blue-200", completed: "bg-emerald-100 text-emerald-700 border-emerald-200", cancelled: "bg-gray-100 text-gray-500 border-gray-200", }; return map[status] || "bg-gray-100 text-gray-500 border-gray-200"; }; export default function MaterialStatusPage() { const today = new Date(); const monthAgo = new Date(today); monthAgo.setMonth(today.getMonth() - 1); const [searchDateFrom, setSearchDateFrom] = useState(formatDate(monthAgo)); const [searchDateTo, setSearchDateTo] = useState(formatDate(today)); const [searchItemCode, setSearchItemCode] = useState(""); const [searchItemName, setSearchItemName] = useState(""); const [workOrders, setWorkOrders] = useState([]); const [workOrdersLoading, setWorkOrdersLoading] = useState(false); const [checkedWoIds, setCheckedWoIds] = useState([]); const [selectedWoId, setSelectedWoId] = useState(null); const [warehouses, setWarehouses] = useState([]); const [warehouse, setWarehouse] = useState(""); const [materialSearch, setMaterialSearch] = useState(""); const [showShortageOnly, setShowShortageOnly] = useState(false); const [materials, setMaterials] = useState([]); const [materialsLoading, setMaterialsLoading] = useState(false); // 창고 목록 초기 로드 useEffect(() => { (async () => { const res = await getWarehouses(); if (res.success && res.data) { setWarehouses(res.data); } })(); }, []); // 작업지시 검색 const handleSearch = useCallback(async () => { setWorkOrdersLoading(true); try { const res = await getWorkOrders({ dateFrom: searchDateFrom, dateTo: searchDateTo, itemCode: searchItemCode || undefined, itemName: searchItemName || undefined, }); if (res.success && res.data) { setWorkOrders(res.data); setCheckedWoIds([]); setSelectedWoId(null); setMaterials([]); } } finally { setWorkOrdersLoading(false); } }, [searchDateFrom, searchDateTo, searchItemCode, searchItemName]); // 초기 로드 useEffect(() => { handleSearch(); }, []); const isAllChecked = workOrders.length > 0 && checkedWoIds.length === workOrders.length; const handleCheckAll = useCallback( (checked: boolean) => { setCheckedWoIds(checked ? workOrders.map((wo) => wo.id) : []); }, [workOrders] ); const handleCheckWo = useCallback((id: number, checked: boolean) => { setCheckedWoIds((prev) => checked ? [...prev, id] : prev.filter((i) => i !== id) ); }, []); const handleSelectWo = useCallback((id: number) => { setSelectedWoId((prev) => (prev === id ? null : id)); }, []); // 선택된 작업지시의 자재 조회 const handleLoadSelectedMaterials = useCallback(async () => { if (checkedWoIds.length === 0) { alert("자재를 조회할 작업지시를 선택해주세요."); return; } setMaterialsLoading(true); try { const res = await getMaterialStatus({ planIds: checkedWoIds, warehouseCode: warehouse || undefined, }); if (res.success && res.data) { setMaterials(res.data); } } finally { setMaterialsLoading(false); } }, [checkedWoIds, warehouse]); const handleResetSearch = useCallback(() => { const t = new Date(); const m = new Date(t); m.setMonth(t.getMonth() - 1); setSearchDateFrom(formatDate(m)); setSearchDateTo(formatDate(t)); setSearchItemCode(""); setSearchItemName(""); setMaterialSearch(""); setShowShortageOnly(false); }, []); const filteredMaterials = useMemo(() => { return materials.filter((m) => { const searchLower = materialSearch.toLowerCase(); const matchesSearch = !materialSearch || m.code.toLowerCase().includes(searchLower) || m.name.toLowerCase().includes(searchLower); const matchesShortage = !showShortageOnly || m.current < m.required; return matchesSearch && matchesShortage; }); }, [materials, materialSearch, showShortageOnly]); return (
{/* 헤더 */}

자재현황

작업지시 대비 원자재 재고 현황

{/* 검색 영역 */}
setSearchDateFrom(e.target.value)} /> ~ setSearchDateTo(e.target.value)} />
setSearchItemCode(e.target.value)} />
setSearchItemName(e.target.value)} />
{/* 메인 콘텐츠 (좌우 분할) */}
{/* 왼쪽: 작업지시 리스트 */}
{/* 패널 헤더 */}
작업지시 리스트
{workOrders.length}
{/* 작업지시 목록 */}
{workOrdersLoading ? (

작업지시를 조회하고 있습니다...

) : workOrders.length === 0 ? (

작업지시가 없습니다

) : ( workOrders.map((wo) => (
handleSelectWo(wo.id)} >
e.stopPropagation()} > handleCheckWo(wo.id, c as boolean) } />
{wo.plan_no || wo.work_order_no || `WO-${wo.id}`} {getStatusLabel(wo.status)}
{wo.item_name} ({wo.item_code})
수량: {Number(wo.plan_qty).toLocaleString()}개 | 일자: {wo.plan_date ? new Date(wo.plan_date) .toISOString() .slice(0, 10) : "-"}
)) )}
{/* 오른쪽: 원자재 현황 */}
{/* 패널 헤더 */}
원자재 재고 현황
{/* 필터 */}
setMaterialSearch(e.target.value)} /> {filteredMaterials.length}개 품목
{/* 원자재 목록 */}
{materialsLoading ? (

자재현황을 조회하고 있습니다...

) : materials.length === 0 ? (

작업지시를 선택하고 자재조회 버튼을 클릭해주세요

) : filteredMaterials.length === 0 ? (

조회된 원자재가 없습니다

) : ( filteredMaterials.map((material) => { const shortage = material.required - material.current; const isShortage = shortage > 0; const percentage = material.required > 0 ? Math.min( (material.current / material.required) * 100, 100 ) : 100; return (
{/* 메인 정보 라인 */}
{material.name} ({material.code}) | 필요: {material.required.toLocaleString()} {material.unit} | 현재: {material.current.toLocaleString()} {material.unit} | {isShortage ? "부족:" : "여유:"} {Math.abs(shortage).toLocaleString()} {material.unit} ({percentage.toFixed(0)}%) {isShortage ? ( 부족 ) : ( 충분 )}
{/* 위치별 재고 */} {material.locations.length > 0 && (
{material.locations.map((loc, idx) => ( {loc.location || loc.warehouse} {loc.qty.toLocaleString()} {material.unit} ))}
)}
); }) )}
); }