From 5e6261f51ad1927fe7876ef7e1a43a11ce68f004 Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 17 Mar 2026 18:19:08 +0900 Subject: [PATCH] feat: enhance V2 process work standard configuration panel - Introduced a new TableCombobox component for selecting tables, improving user experience by allowing table searches and selections. - Added a ColumnCombobox component to facilitate column selection based on the chosen table, enhancing the configurability of the process work standard settings. - Updated the V2ProcessWorkStandardConfigPanel to utilize the new combobox components, streamlining the configuration process for item tables and columns. - Removed the deprecated mcp.json file and updated .gitignore to reflect recent changes. These enhancements aim to improve the usability and flexibility of the configuration panel, making it easier for users to manage their process work standards. --- .cursor/mcp.json | 8 - .gitignore | 3 + .../V2ProcessWorkStandardConfigPanel.tsx | 549 +++++++++++------- .../ProcessWorkStandardConfigPanel.tsx | 167 ++++-- 4 files changed, 454 insertions(+), 273 deletions(-) delete mode 100644 .cursor/mcp.json diff --git a/.cursor/mcp.json b/.cursor/mcp.json deleted file mode 100644 index d5e0ca4b..00000000 --- a/.cursor/mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "Framelink Figma MCP": { - "command": "npx", - "args": ["-y", "figma-developer-mcp", "--figma-api-key=figd_NrYdIWf-CnC23NyH6eMym7sBdfbZTuXyS91tI3VS", "--stdio"] - } - } -} diff --git a/.gitignore b/.gitignore index b6114eb7..197ad216 100644 --- a/.gitignore +++ b/.gitignore @@ -185,6 +185,9 @@ popdocs/ # 멀티 에이전트 MCP 태스크 큐 mcp-task-queue/ +.cursor/mcp.json .cursor/rules/multi-agent-pm.mdc .cursor/rules/multi-agent-worker.mdc .cursor/rules/multi-agent-tester.mdc +.cursor/rules/multi-agent-reviewer.mdc +.cursor/rules/multi-agent-knowledge.mdc diff --git a/frontend/components/v2/config-panels/V2ProcessWorkStandardConfigPanel.tsx b/frontend/components/v2/config-panels/V2ProcessWorkStandardConfigPanel.tsx index 33a7ae33..a2b7abdc 100644 --- a/frontend/components/v2/config-panels/V2ProcessWorkStandardConfigPanel.tsx +++ b/frontend/components/v2/config-panels/V2ProcessWorkStandardConfigPanel.tsx @@ -1,17 +1,33 @@ "use client"; /** - * V2 공정 작업기준 설정 패널 - * Progressive Disclosure: 작업 단계 -> 상세 유형 -> 고급 설정(접힘) + * V2 공정 작업기준 설정 패널 (간소화) */ -import React, { useState } from "react"; +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 { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +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, Database, Layers, List } from "lucide-react"; +import { + Settings, + ChevronDown, + ChevronRight, + Plus, + Trash2, + Check, + ChevronsUpDown, + Database, + Layers, + List, +} from "lucide-react"; import { cn } from "@/lib/utils"; import type { ProcessWorkStandardConfig, @@ -20,26 +36,87 @@ import type { } from "@/lib/registry/components/v2-process-work-standard/types"; import { defaultConfig } from "@/lib/registry/components/v2-process-work-standard/config"; +interface TableInfo { tableName: string; displayName?: string; } + +function TableCombobox({ value, onChange, tables, loading, label }: { + value: string; onChange: (v: string) => void; tables: TableInfo[]; loading: boolean; label: string; +}) { + const [open, setOpen] = useState(false); + const selected = tables.find((t) => t.tableName === value); + return ( +
+ {label} + + + + + + + + + 테이블을 찾을 수 없습니다. + + {tables.map((t) => ( + { onChange(t.tableName); setOpen(false); }} className="text-xs"> + +
+ {t.displayName || t.tableName} + {t.displayName && {t.tableName}} +
+
+ ))} +
+
+
+
+
+
+ ); +} + interface V2ProcessWorkStandardConfigPanelProps { config: Partial; onChange: (config: Partial) => void; } -export const V2ProcessWorkStandardConfigPanel: React.FC = ({ - config: configProp, - onChange, -}) => { +export const V2ProcessWorkStandardConfigPanel: React.FC< + V2ProcessWorkStandardConfigPanelProps +> = ({ config: configProp, onChange }) => { const [phasesOpen, setPhasesOpen] = useState(false); const [detailTypesOpen, setDetailTypesOpen] = useState(false); - const [advancedOpen, setAdvancedOpen] = useState(false); + const [layoutOpen, setLayoutOpen] = useState(false); const [dataSourceOpen, setDataSourceOpen] = useState(false); + const [tables, setTables] = useState([]); + const [loadingTables, setLoadingTables] = useState(false); + + 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 config: ProcessWorkStandardConfig = { ...defaultConfig, ...configProp, dataSource: { ...defaultConfig.dataSource, ...configProp?.dataSource }, - phases: configProp?.phases?.length ? configProp.phases : defaultConfig.phases, - detailTypes: configProp?.detailTypes?.length ? configProp.detailTypes : defaultConfig.detailTypes, + phases: configProp?.phases?.length + ? configProp.phases + : defaultConfig.phases, + detailTypes: configProp?.detailTypes?.length + ? configProp.detailTypes + : defaultConfig.detailTypes, }; const update = (partial: Partial) => { @@ -50,13 +127,16 @@ export const V2ProcessWorkStandardConfigPanel: React.FC { const nextOrder = config.phases.length + 1; update({ phases: [ ...config.phases, - { key: `PHASE_${nextOrder}`, label: `단계 ${nextOrder}`, sortOrder: nextOrder }, + { + key: `PHASE_${nextOrder}`, + label: `단계 ${nextOrder}`, + sortOrder: nextOrder, + }, ], }); }; @@ -65,18 +145,24 @@ export const V2ProcessWorkStandardConfigPanel: React.FC i !== idx) }); }; - const updatePhase = (idx: number, field: keyof WorkPhaseDefinition, value: string | number) => { + const updatePhase = ( + idx: number, + field: keyof WorkPhaseDefinition, + value: string | number, + ) => { const next = [...config.phases]; next[idx] = { ...next[idx], [field]: value }; update({ phases: next }); }; - // ─── 상세 유형 관리 ─── const addDetailType = () => { update({ detailTypes: [ ...config.detailTypes, - { value: `TYPE_${config.detailTypes.length + 1}`, label: "신규 유형" }, + { + value: `TYPE_${config.detailTypes.length + 1}`, + label: "신규 유형", + }, ], }); }; @@ -85,7 +171,11 @@ export const V2ProcessWorkStandardConfigPanel: React.FC i !== idx) }); }; - const updateDetailType = (idx: number, field: keyof DetailTypeDefinition, value: string) => { + const updateDetailType = ( + idx: number, + field: keyof DetailTypeDefinition, + value: string, + ) => { const next = [...config.detailTypes]; next[idx] = { ...next[idx], [field]: value }; update({ detailTypes: next }); @@ -93,31 +183,75 @@ export const V2ProcessWorkStandardConfigPanel: React.FC - {/* ─── 1단계: 작업 단계 설정 (Collapsible + 접이식 카드) ─── */} + {/* 품목 목록 모드 */} +
+ 품목 목록 모드 +
+ + +
+ {config.itemListMode === "registered" && ( +

+ 품목별 라우팅 탭에서 등록한 품목만 표시됩니다. +

+ )} +
+ + {/* 작업 단계 */} -
-

공정별 작업 단계(Phase)를 정의

+
+

+ 공정별 작업 단계를 정의 +

{config.phases.map((phase, idx) => ( @@ -125,18 +259,30 @@ export const V2ProcessWorkStandardConfigPanel: React.FC -
-

작업 항목의 상세 유형 드롭다운 옵션

+
+

+ 작업 항목의 상세 유형 옵션 +

{config.detailTypes.map((dt, idx) => ( @@ -225,18 +386,30 @@ export const V2ProcessWorkStandardConfigPanel: React.FC -
+
+

+ 테이블만 선택하면 컬럼 정보는 엔티티 설정에서 자동으로 가져옵니다. +

+ updateDataSource("itemTable", v)} tables={tables} loading={loadingTables} /> + updateDataSource("routingVersionTable", v)} tables={tables} loading={loadingTables} /> + updateDataSource("routingDetailTable", v)} tables={tables} loading={loadingTables} /> + updateDataSource("processTable", v)} tables={tables} loading={loadingTables} /> +
+ + - {/* 레이아웃 기본 설정 */} -
-
-
- 좌측 패널 비율 (%) -

품목/공정 선택 패널의 너비

-
- update({ splitRatio: parseInt(e.target.value) || 30 })} - className="h-7 w-[80px] text-xs" - /> -
-
- 좌측 패널 제목 - update({ leftPanelTitle: e.target.value })} - placeholder="품목 및 공정 선택" - className="h-7 w-[140px] text-xs" - /> -
-
-
-

읽기 전용

-

수정/삭제 버튼을 숨겨요

-
- update({ readonly: checked })} - /> -
+ {/* 레이아웃 & 기타 */} + + + + + +
+
+
+ + 좌측 패널 비율 (%) + +

+ 품목/공정 선택 패널의 너비 +

+
+ + update({ splitRatio: parseInt(e.target.value) || 30 }) + } + className="h-7 w-[80px] text-xs" + /> +
+
+ + 좌측 패널 제목 + + update({ leftPanelTitle: e.target.value })} + placeholder="품목 및 공정 선택" + className="h-7 w-[140px] text-xs" + /> +
+
+
+

읽기 전용

+

+ 수정/삭제 버튼을 숨겨요 +

+
+ update({ readonly: checked })} + />
- - {/* 데이터 소스 (서브 Collapsible) */} - - - - - -
- 품목 테이블 - updateDataSource("itemTable", e.target.value)} - className="h-7 w-full text-xs" - /> -
-
-
- 품목명 컬럼 - updateDataSource("itemNameColumn", e.target.value)} - className="h-7 text-xs" - /> -
-
- 품목코드 컬럼 - updateDataSource("itemCodeColumn", e.target.value)} - className="h-7 text-xs" - /> -
-
-
- 라우팅 버전 테이블 - updateDataSource("routingVersionTable", e.target.value)} - className="h-7 w-full text-xs" - /> -
-
-
- 품목 연결 FK - updateDataSource("routingFkColumn", e.target.value)} - className="h-7 text-xs" - /> -
-
- 버전명 컬럼 - updateDataSource("routingVersionNameColumn", e.target.value)} - className="h-7 text-xs" - /> -
-
-
- 라우팅 상세 테이블 - updateDataSource("routingDetailTable", e.target.value)} - className="h-7 w-full text-xs" - /> -
-
- 공정 마스터 테이블 - updateDataSource("processTable", e.target.value)} - className="h-7 w-full text-xs" - /> -
-
-
- 공정명 컬럼 - updateDataSource("processNameColumn", e.target.value)} - className="h-7 text-xs" - /> -
-
- 공정코드 컬럼 - updateDataSource("processCodeColumn", e.target.value)} - className="h-7 text-xs" - /> -
-
-
-
-
@@ -462,6 +564,7 @@ export const V2ProcessWorkStandardConfigPanel: React.FC 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}} +
+
+ ))} +
+
+
+
+
+ ); +} + +function ColumnCombobox({ value, onChange, tableName, placeholder }: { + value: string; onChange: (v: 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); setOpen(false); }} className="text-xs"> + +
+ {c.displayName || c.columnName} + {c.displayName && {c.columnName}} +
+
+ ))} +
+
+
+
+
+ ); +} + interface ConfigPanelProps { config: Partial; onChange: (config: Partial) => void; @@ -19,6 +117,9 @@ export function ProcessWorkStandardConfigPanel({ config: configProp, onChange, }: ConfigPanelProps) { + const [tables, setTables] = useState([]); + const [loadingTables, setLoadingTables] = useState(false); + const config: ProcessWorkStandardConfig = { ...defaultConfig, ...configProp, @@ -27,6 +128,20 @@ export function ProcessWorkStandardConfigPanel({ detailTypes: configProp?.detailTypes?.length ? configProp.detailTypes : defaultConfig.detailTypes, }; + 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 update = (partial: Partial) => { onChange({ ...configProp, ...partial }); }; @@ -112,72 +227,40 @@ export function ProcessWorkStandardConfigPanel({
- updateDataSource("itemTable", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("itemTable", v)} tables={tables} loading={loadingTables} />
- updateDataSource("itemNameColumn", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("itemNameColumn", v)} tableName={config.dataSource.itemTable} placeholder="품목명" />
- updateDataSource("itemCodeColumn", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("itemCodeColumn", v)} tableName={config.dataSource.itemTable} placeholder="품목코드" />
- updateDataSource("routingVersionTable", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("routingVersionTable", v)} tables={tables} loading={loadingTables} />
- updateDataSource("routingFkColumn", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("routingFkColumn", v)} tableName={config.dataSource.routingVersionTable} placeholder="FK 컬럼" />
- updateDataSource("processTable", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("processTable", v)} tables={tables} loading={loadingTables} />
- updateDataSource("processNameColumn", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("processNameColumn", v)} tableName={config.dataSource.processTable} placeholder="공정명" />
- updateDataSource("processCodeColumn", e.target.value)} - className="mt-1 h-8 text-xs" - /> + updateDataSource("processCodeColumn", v)} tableName={config.dataSource.processTable} placeholder="공정코드" />