"use client"; /** * V2 품목별 라우팅 설정 패널 */ import React, { useState, useEffect } from "react"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Badge } from "@/components/ui/badge"; import { Settings, ChevronDown, ChevronRight, Plus, Trash2, Check, ChevronsUpDown, Database, Monitor, Columns, List, Filter, Eye, } from "lucide-react"; import { cn } from "@/lib/utils"; import type { ItemRoutingConfig, ProcessColumnDef, ColumnDef, ItemFilterCondition } from "@/lib/registry/components/v2-item-routing/types"; import { defaultConfig } from "@/lib/registry/components/v2-item-routing/config"; interface V2ItemRoutingConfigPanelProps { config: Partial; onChange: (config: Partial) => void; } interface TableInfo { tableName: string; displayName?: string; } interface ColumnInfo { columnName: string; displayName?: string; dataType?: string; } interface ScreenInfo { screenId: number; screenName: string; screenCode: string; } // ─── 공용: 테이블 Combobox ─── function TableCombobox({ value, onChange, tables, loading }: { value: string; onChange: (v: string) => void; tables: TableInfo[]; loading: boolean; }) { const [open, setOpen] = useState(false); const selected = tables.find((t) => t.tableName === value); return ( 테이블을 찾을 수 없습니다. {tables.map((t) => ( { onChange(t.tableName); setOpen(false); }} className="text-xs">
{t.displayName || t.tableName} {t.displayName && {t.tableName}}
))}
); } // ─── 공용: 컬럼 Combobox ─── function ColumnCombobox({ value, onChange, tableName, placeholder }: { value: string; onChange: (v: string, displayName?: string) => void; tableName: string; placeholder?: string; }) { const [open, setOpen] = useState(false); const [columns, setColumns] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { if (!tableName) { setColumns([]); return; } const load = async () => { setLoading(true); try { const { tableManagementApi } = await import("@/lib/api/tableManagement"); const res = await tableManagementApi.getColumnList(tableName); if (res.success && res.data?.columns) setColumns(res.data.columns); } catch { /* ignore */ } finally { setLoading(false); } }; load(); }, [tableName]); const selected = columns.find((c) => c.columnName === value); return ( 컬럼을 찾을 수 없습니다. {columns.map((c) => ( { onChange(c.columnName, c.displayName); setOpen(false); }} className="text-xs">
{c.displayName || c.columnName} {c.displayName && {c.columnName}}
))}
); } // ─── 공용: 화면 Combobox ─── function ScreenCombobox({ value, onChange }: { value?: number; onChange: (v?: number) => void; }) { const [open, setOpen] = useState(false); const [screens, setScreens] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { const load = async () => { setLoading(true); try { const { screenApi } = await import("@/lib/api/screen"); const res = await screenApi.getScreens({ page: 1, size: 1000 }); if (res.data) { setScreens(res.data.map((s: any) => ({ screenId: s.screenId, screenName: s.screenName || `화면 ${s.screenId}`, screenCode: s.screenCode || "", }))); } } catch { /* ignore */ } finally { setLoading(false); } }; load(); }, []); const selected = screens.find((s) => s.screenId === value); return ( 화면을 찾을 수 없습니다. {screens.map((s) => ( { onChange(s.screenId); setOpen(false); }} className="text-xs">
{s.screenName} ID: {s.screenId}
))}
); } // ─── 컬럼 편집 카드 (품목/모달/공정 공용) ─── function ColumnEditor({ columns, onChange, tableName, title, icon }: { columns: ColumnDef[]; onChange: (cols: ColumnDef[]) => void; tableName: string; title: string; icon: React.ReactNode; }) { const [open, setOpen] = useState(false); const addColumn = () => onChange([...columns, { name: "", label: "새 컬럼", width: 100, align: "left" }]); const removeColumn = (idx: number) => onChange(columns.filter((_, i) => i !== idx)); const updateColumn = (idx: number, field: keyof ColumnDef, value: string | number) => { const next = [...columns]; next[idx] = { ...next[idx], [field]: value }; onChange(next); }; return (
{columns.map((col, idx) => (
컬럼 { updateColumn(idx, "name", v); if (!col.label || col.label === "새 컬럼" || col.label === col.name) updateColumn(idx, "label", displayName || v); }} tableName={tableName} placeholder="컬럼 선택" />
표시명 updateColumn(idx, "label", e.target.value)} className="h-7 text-xs" />
너비 updateColumn(idx, "width", parseInt(e.target.value) || 100)} className="h-7 text-xs" />
정렬
))}
); } // ─── 메인 컴포넌트 ─── export const V2ItemRoutingConfigPanel: React.FC = ({ config: configProp, onChange }) => { const [tables, setTables] = useState([]); const [loadingTables, setLoadingTables] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [dataSourceOpen, setDataSourceOpen] = useState(false); const [layoutOpen, setLayoutOpen] = useState(false); const [filterOpen, setFilterOpen] = useState(false); const config: ItemRoutingConfig = { ...defaultConfig, ...configProp, dataSource: { ...defaultConfig.dataSource, ...configProp?.dataSource }, modals: { ...defaultConfig.modals, ...configProp?.modals }, processColumns: configProp?.processColumns?.length ? configProp.processColumns : defaultConfig.processColumns, itemDisplayColumns: configProp?.itemDisplayColumns?.length ? configProp.itemDisplayColumns : defaultConfig.itemDisplayColumns, modalDisplayColumns: configProp?.modalDisplayColumns?.length ? configProp.modalDisplayColumns : defaultConfig.modalDisplayColumns, itemFilterConditions: configProp?.itemFilterConditions || [], }; useEffect(() => { const loadTables = async () => { setLoadingTables(true); try { const { tableManagementApi } = await import("@/lib/api/tableManagement"); const res = await tableManagementApi.getTableList(); if (res.success && res.data) { setTables(res.data.map((t: any) => ({ tableName: t.tableName, displayName: t.displayName || t.tableName }))); } } catch { /* ignore */ } finally { setLoadingTables(false); } }; loadTables(); }, []); const dispatchConfigEvent = (newConfig: Partial) => { if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("componentConfigChanged", { detail: { config: { ...config, ...newConfig } } })); } }; const update = (partial: Partial) => { const merged = { ...configProp, ...partial }; onChange(merged); dispatchConfigEvent(partial); }; const updateDataSource = (field: string, value: string) => { const newDS = { ...config.dataSource, [field]: value }; onChange({ ...configProp, dataSource: newDS }); dispatchConfigEvent({ dataSource: newDS }); }; const updateModals = (field: string, value?: number) => { const newM = { ...config.modals, [field]: value }; onChange({ ...configProp, modals: newM }); dispatchConfigEvent({ modals: newM }); }; // 필터 조건 관리 const filters = config.itemFilterConditions || []; const addFilter = () => update({ itemFilterConditions: [...filters, { column: "", operator: "equals", value: "" }] }); const removeFilter = (idx: number) => update({ itemFilterConditions: filters.filter((_, i) => i !== idx) }); const updateFilter = (idx: number, field: keyof ItemFilterCondition, val: string) => { const next = [...filters]; next[idx] = { ...next[idx], [field]: val }; update({ itemFilterConditions: next }); }; return (
{/* ─── 품목 목록 모드 ─── */}
품목 목록 모드

좌측 품목 목록에 표시할 방식을 선택하세요

{config.itemListMode === "registered" && (

현재 화면 ID를 기준으로 품목 목록이 자동 관리됩니다.

)}
{/* ─── 품목 표시 컬럼 ─── */} update({ itemDisplayColumns: cols })} tableName={config.dataSource.itemTable} title="품목 목록 컬럼" icon={} /> {/* ─── 모달 표시 컬럼 (등록 모드에서만 의미 있지만 항상 설정 가능) ─── */} update({ modalDisplayColumns: cols })} tableName={config.dataSource.itemTable} title="품목 추가 모달 컬럼" icon={} /> {/* ─── 품목 필터 조건 ─── */}

품목 조회 시 자동으로 적용되는 필터 조건입니다

{filters.map((f, idx) => (
컬럼 updateFilter(idx, "column", v)} tableName={config.dataSource.itemTable} placeholder="필터 컬럼" />
조건
updateFilter(idx, "value", e.target.value)} placeholder="필터값" className="h-7 text-xs" />
))}
{/* ─── 모달 연동 ─── */}

버전 추가/공정 추가·수정 시 열리는 화면

버전 추가 updateModals("versionAddScreenId", v)} />
공정 추가 updateModals("processAddScreenId", v)} />
공정 수정 updateModals("processEditScreenId", v)} />
{/* ─── 공정 테이블 컬럼 ─── */} update({ processColumns: cols })} tableName={config.dataSource.routingDetailTable} title="공정 테이블 컬럼" icon={} /> {/* ─── 데이터 소스 ─── */}
품목 테이블 updateDataSource("itemTable", v)} tables={tables} loading={loadingTables} />
품목명 컬럼 updateDataSource("itemNameColumn", v)} tableName={config.dataSource.itemTable} placeholder="품목명" />
품목코드 컬럼 updateDataSource("itemCodeColumn", v)} tableName={config.dataSource.itemTable} placeholder="품목코드" />
라우팅 버전 테이블 updateDataSource("routingVersionTable", v)} tables={tables} loading={loadingTables} />
품목 FK 컬럼 updateDataSource("routingVersionFkColumn", v)} tableName={config.dataSource.routingVersionTable} placeholder="FK 컬럼" />
버전명 컬럼 updateDataSource("routingVersionNameColumn", v)} tableName={config.dataSource.routingVersionTable} placeholder="버전명" />
라우팅 상세 테이블 updateDataSource("routingDetailTable", v)} tables={tables} loading={loadingTables} />
버전 FK 컬럼 updateDataSource("routingDetailFkColumn", v)} tableName={config.dataSource.routingDetailTable} placeholder="FK 컬럼" />
공정 마스터 테이블 updateDataSource("processTable", v)} tables={tables} loading={loadingTables} />
공정명 컬럼 updateDataSource("processNameColumn", v)} tableName={config.dataSource.processTable} placeholder="공정명" />
공정코드 컬럼 updateDataSource("processCodeColumn", v)} tableName={config.dataSource.processTable} placeholder="공정코드" />
{/* ─── 레이아웃 & 기타 ─── */}
좌측 패널 비율 (%)

품목 목록 패널의 너비

update({ splitRatio: parseInt(e.target.value) || 40 })} className="h-7 w-[80px] text-xs" />
좌측 패널 제목 update({ leftPanelTitle: e.target.value })} placeholder="품목 목록" className="h-7 w-[140px] text-xs" />
우측 패널 제목 update({ rightPanelTitle: e.target.value })} placeholder="공정 순서" className="h-7 w-[140px] text-xs" />
버전 추가 버튼 텍스트 update({ versionAddButtonText: e.target.value })} placeholder="+ 라우팅 버전 추가" className="h-7 w-[140px] text-xs" />
공정 추가 버튼 텍스트 update({ processAddButtonText: e.target.value })} placeholder="+ 공정 추가" className="h-7 w-[140px] text-xs" />

첫 번째 버전 자동 선택

품목 선택 시 첫 버전을 자동으로 선택해요

update({ autoSelectFirstVersion: checked })} />

읽기 전용

추가/수정/삭제 버튼을 숨겨요

update({ readonly: checked })} />
); }; V2ItemRoutingConfigPanel.displayName = "V2ItemRoutingConfigPanel"; export default V2ItemRoutingConfigPanel;