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
- {/* ─── 2단계: 상세 유형 옵션 (Collapsible + 접이식 카드) ─── */}
+ {/* 상세 유형 */}
-
+
상세 유형
-
+
{config.detailTypes.length}개
-
-
작업 항목의 상세 유형 드롭다운 옵션
+
+
+ 작업 항목의 상세 유형 옵션
+
{config.detailTypes.map((dt, idx) => (
@@ -225,18 +386,30 @@ export const V2ProcessWorkStandardConfigPanel: React.FC
-
- #{idx + 1}
- {dt.label}
- {dt.value}
+
+
+ #{idx + 1}
+
+
+ {dt.label}
+
+
+ {dt.value}
+
{ e.stopPropagation(); removeDetailType(idx); }}
- className="h-5 w-5 p-0 text-muted-foreground hover:text-destructive shrink-0"
+ onClick={(e) => {
+ e.stopPropagation();
+ removeDetailType(idx);
+ }}
+ className="text-muted-foreground hover:text-destructive h-5 w-5 shrink-0 p-0"
disabled={config.detailTypes.length <= 1}
>
@@ -246,21 +419,27 @@ export const V2ProcessWorkStandardConfigPanel: React.FC
@@ -272,7 +451,7 @@ export const V2ProcessWorkStandardConfigPanel: React.FC
@@ -282,179 +461,102 @@ export const V2ProcessWorkStandardConfigPanel: React.FC
- {/* ─── 3단계: 고급 설정 (데이터 소스 + 레이아웃 통합) ─── */}
-
+ {/* 데이터 소스 (테이블만) */}
+
-
- 고급 설정
+
+ 테이블 설정
-
+
+
+ 테이블만 선택하면 컬럼 정보는 엔티티 설정에서 자동으로 가져옵니다.
+
+
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) */}
-
-
-
-
-
- 데이터 소스
- {config.dataSource.itemTable && (
-
- {config.dataSource.itemTable}
-
- )}
-
-
-
-
-
-
- 품목 테이블
- updateDataSource("itemTable", e.target.value)}
- className="h-7 w-full text-xs"
- />
-
-
-
- 라우팅 버전 테이블
- updateDataSource("routingVersionTable", e.target.value)}
- className="h-7 w-full 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"
- />
-
-
-
-
-
@@ -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 (
+
+
+
+ {loading ? "로딩 중..." : selected ? selected.displayName || selected.tableName : "테이블 선택"}
+
+
+
+
+
+
+
+ 테이블을 찾을 수 없습니다.
+
+ {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 (
+
+
+
+
+ {loading ? "로딩..." : !tableName ? "테이블 먼저 선택" : selected ? selected.displayName || selected.columnName : placeholder || "컬럼 선택"}
+
+
+
+
+
+
+
+
+ 컬럼을 찾을 수 없습니다.
+
+ {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("routingFkColumn", e.target.value)}
- className="mt-1 h-8 text-xs"
- />
+ updateDataSource("routingFkColumn", v)} tableName={config.dataSource.routingVersionTable} placeholder="FK 컬럼" />