"use client"; import React, { useState, useEffect, useCallback, useMemo } from "react"; import { Search, Plus, Trash2, Edit, ListOrdered, Package } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { cn } from "@/lib/utils"; import { useToast } from "@/hooks/use-toast"; import { ItemRoutingConfig, ItemRoutingComponentProps } from "./types"; import { defaultConfig } from "./config"; import { useItemRouting } from "./hooks/useItemRouting"; export function ItemRoutingComponent({ config: configProp, isPreview, }: ItemRoutingComponentProps) { const { toast } = useToast(); const { config, items, versions, details, loading, selectedItemCode, selectedItemName, selectedVersionId, fetchItems, selectItem, selectVersion, refreshVersions, refreshDetails, deleteDetail, deleteVersion, } = useItemRouting(configProp || {}); const [searchText, setSearchText] = useState(""); const [deleteTarget, setDeleteTarget] = useState<{ type: "version" | "detail"; id: string; name: string; } | null>(null); // 초기 로딩 (마운트 시 1회만) const mountedRef = React.useRef(false); useEffect(() => { if (!mountedRef.current) { mountedRef.current = true; fetchItems(); } }, [fetchItems]); // 모달 저장 성공 감지 -> 데이터 새로고침 useEffect(() => { const handleSaveSuccess = () => { refreshVersions(); refreshDetails(); }; window.addEventListener("saveSuccessInModal", handleSaveSuccess); return () => { window.removeEventListener("saveSuccessInModal", handleSaveSuccess); }; }, [refreshVersions, refreshDetails]); // 품목 검색 const handleSearch = useCallback(() => { fetchItems(searchText || undefined); }, [fetchItems, searchText]); const handleSearchKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") handleSearch(); }, [handleSearch] ); // 버전 추가 모달 const handleAddVersion = useCallback(() => { if (!selectedItemCode) { toast({ title: "품목을 먼저 선택해주세요", variant: "destructive" }); return; } const screenId = config.modals.versionAddScreenId; if (!screenId) return; window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId, urlParams: { mode: "add", tableName: config.dataSource.routingVersionTable }, splitPanelParentData: { [config.dataSource.routingVersionFkColumn]: selectedItemCode, }, }, }) ); }, [selectedItemCode, config, toast]); // 공정 추가 모달 const handleAddProcess = useCallback(() => { if (!selectedVersionId) { toast({ title: "라우팅 버전을 먼저 선택해주세요", variant: "destructive" }); return; } const screenId = config.modals.processAddScreenId; if (!screenId) return; window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId, urlParams: { mode: "add", tableName: config.dataSource.routingDetailTable }, splitPanelParentData: { [config.dataSource.routingDetailFkColumn]: selectedVersionId, }, }, }) ); }, [selectedVersionId, config, toast]); // 공정 수정 모달 const handleEditProcess = useCallback( (detail: Record) => { const screenId = config.modals.processEditScreenId; if (!screenId) return; window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId, urlParams: { mode: "edit", tableName: config.dataSource.routingDetailTable }, editData: detail, }, }) ); }, [config] ); // 삭제 확인 const handleConfirmDelete = useCallback(async () => { if (!deleteTarget) return; let success = false; if (deleteTarget.type === "version") { success = await deleteVersion(deleteTarget.id); } else { success = await deleteDetail(deleteTarget.id); } if (success) { toast({ title: `${deleteTarget.name} 삭제 완료` }); } else { toast({ title: "삭제 실패", variant: "destructive" }); } setDeleteTarget(null); }, [deleteTarget, deleteVersion, deleteDetail, toast]); // entity join으로 가져온 공정명 컬럼 이름 추정 const processNameKey = useMemo(() => { const ds = config.dataSource; return `${ds.processTable}_${ds.processNameColumn}`; }, [config.dataSource]); const splitRatio = config.splitRatio || 40; if (isPreview) { return (

품목별 라우팅 관리

품목 선택 - 라우팅 버전 - 공정 순서

); } return (
{/* 좌측 패널: 품목 목록 */}

{config.leftPanelTitle || "품목 목록"}

{/* 검색 */}
setSearchText(e.target.value)} onKeyDown={handleSearchKeyDown} placeholder="품목명/품번 검색" className="h-8 text-xs" />
{/* 품목 리스트 */}
{items.length === 0 ? (

{loading ? "로딩 중..." : "품목이 없습니다"}

) : (
{items.map((item) => { const itemCode = item[config.dataSource.itemCodeColumn] || item.item_code || item.item_number; const itemName = item[config.dataSource.itemNameColumn] || item.item_name; const isSelected = selectedItemCode === itemCode; return ( ); })}
)}
{/* 우측 패널: 버전 + 공정 */}
{selectedItemCode ? ( <> {/* 헤더: 선택된 품목 + 버전 추가 */}

{selectedItemName}

{selectedItemCode}

{!config.readonly && ( )}
{/* 버전 선택 버튼들 */} {versions.length > 0 ? (
버전: {versions.map((ver) => { const isActive = selectedVersionId === ver.id; return (
selectVersion(ver.id)} > {ver[config.dataSource.routingVersionNameColumn] || ver.version_name || ver.id} {!config.readonly && ( )}
); })}
) : (

라우팅 버전이 없습니다. 버전을 추가해주세요.

)} {/* 공정 테이블 */} {selectedVersionId ? (
{/* 공정 테이블 헤더 */}

{config.rightPanelTitle || "공정 순서"} ({details.length}건)

{!config.readonly && ( )}
{/* 테이블 */}
{details.length === 0 ? (

{loading ? "로딩 중..." : "등록된 공정이 없습니다"}

) : ( {config.processColumns.map((col) => ( {col.label} ))} {!config.readonly && ( 관리 )} {details.map((detail) => ( {config.processColumns.map((col) => { let cellValue = detail[col.name]; if ( col.name === "process_code" && detail[processNameKey] ) { cellValue = `${detail[col.name]} (${detail[processNameKey]})`; } return ( {cellValue ?? "-"} ); })} {!config.readonly && (
)}
))}
)}
) : ( versions.length > 0 && (

라우팅 버전을 선택해주세요

) )} ) : (

좌측에서 품목을 선택하세요

품목을 선택하면 라우팅 버전별 공정 순서를 관리할 수 있습니다

)}
{/* 삭제 확인 다이얼로그 */} setDeleteTarget(null)}> 삭제 확인 {deleteTarget?.name}을(를) 삭제하시겠습니까? {deleteTarget?.type === "version" && ( <>
해당 버전에 포함된 모든 공정 정보도 함께 삭제됩니다. )}
취소 삭제
); }