From a3ebed48d0f5d0de2812188a593b83a6c9754e71 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Thu, 12 Mar 2026 01:29:07 +0900 Subject: [PATCH] [agent-pipeline] rollback to 0277b6ba --- .../V2ApprovalStepConfigPanel.tsx | 313 --------- .../config-panels/V2PivotGridConfigPanel.tsx | 618 ------------------ .../components/v2-approval-step/index.ts | 2 +- .../components/v2-pivot-grid/index.ts | 2 +- 4 files changed, 2 insertions(+), 933 deletions(-) delete mode 100644 frontend/components/v2/config-panels/V2ApprovalStepConfigPanel.tsx delete mode 100644 frontend/components/v2/config-panels/V2PivotGridConfigPanel.tsx diff --git a/frontend/components/v2/config-panels/V2ApprovalStepConfigPanel.tsx b/frontend/components/v2/config-panels/V2ApprovalStepConfigPanel.tsx deleted file mode 100644 index 2952b611..00000000 --- a/frontend/components/v2/config-panels/V2ApprovalStepConfigPanel.tsx +++ /dev/null @@ -1,313 +0,0 @@ -"use client"; - -/** - * V2 결재 단계 설정 패널 - * 토스식 단계별 UX: 데이터 소스(Combobox) -> 레코드 식별(Combobox) -> 표시 설정(Switch+설명, Collapsible) - */ - -import React, { useState, useEffect } from "react"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; -import { Button } from "@/components/ui/button"; -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 { Check, ChevronsUpDown, Database, ChevronDown, Settings } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { tableTypeApi } from "@/lib/api/screen"; -import { tableManagementApi } from "@/lib/api/tableManagement"; -import type { ApprovalStepConfig } from "@/lib/registry/components/v2-approval-step/types"; - -interface V2ApprovalStepConfigPanelProps { - config: ApprovalStepConfig; - onChange: (config: Partial) => void; - screenTableName?: string; -} - -export const V2ApprovalStepConfigPanel: React.FC = ({ - config, - onChange, - screenTableName, -}) => { - const [availableTables, setAvailableTables] = useState>([]); - const [loadingTables, setLoadingTables] = useState(false); - const [tableOpen, setTableOpen] = useState(false); - - const [availableColumns, setAvailableColumns] = useState>([]); - const [loadingColumns, setLoadingColumns] = useState(false); - const [columnOpen, setColumnOpen] = useState(false); - - const [displayOpen, setDisplayOpen] = useState(false); - - const targetTableName = config.targetTable || screenTableName; - - const handleChange = (key: keyof ApprovalStepConfig, value: any) => { - onChange({ [key]: value }); - }; - - useEffect(() => { - const fetchTables = async () => { - setLoadingTables(true); - try { - const response = await tableTypeApi.getTables(); - setAvailableTables( - response.map((table: any) => ({ - tableName: table.tableName, - displayName: table.displayName || table.tableName, - })) - ); - } catch { /* ignore */ } finally { setLoadingTables(false); } - }; - fetchTables(); - }, []); - - useEffect(() => { - if (!targetTableName) { setAvailableColumns([]); return; } - const fetchColumns = async () => { - setLoadingColumns(true); - try { - const result = await tableManagementApi.getColumnList(targetTableName); - if (result.success && result.data) { - const columns = Array.isArray(result.data) ? result.data : result.data.columns; - if (columns && Array.isArray(columns)) { - setAvailableColumns( - columns.map((col: any) => ({ - columnName: col.columnName || col.column_name || col.name, - label: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name || col.name, - })) - ); - } - } - } catch { setAvailableColumns([]); } finally { setLoadingColumns(false); } - }; - fetchColumns(); - }, [targetTableName]); - - const handleTableChange = (newTableName: string) => { - if (newTableName === targetTableName) return; - handleChange("targetTable", newTableName); - handleChange("targetRecordIdField", ""); - setTableOpen(false); - }; - - return ( -
- {/* ─── 1단계: 데이터 소스 ─── */} -
-
- -

데이터 소스

-
-

결재 상태를 조회할 대상 테이블을 설정해요

-
- -
-
- 대상 테이블 - - - - - - - - - 테이블을 찾을 수 없습니다. - - {availableTables.map((table) => ( - handleTableChange(table.tableName)} - className="text-xs" - > - -
- {table.displayName} - {table.displayName !== table.tableName && ( - {table.tableName} - )} -
-
- ))} -
-
-
-
-
- - {screenTableName && targetTableName !== screenTableName && ( -
- - 화면 기본 테이블({screenTableName})과 다른 테이블 사용 중 - - -
- )} -
- - {/* 레코드 ID 필드 */} -
- 레코드 ID 필드 - {targetTableName ? ( - - - - - - - - - 컬럼을 찾을 수 없습니다. - - {availableColumns.map((col) => ( - { handleChange("targetRecordIdField", col.columnName); setColumnOpen(false); }} - className="text-xs" - > - -
- {col.label} - {col.label !== col.columnName && ( - {col.columnName} - )} -
-
- ))} -
-
-
-
-
- ) : ( -

대상 테이블을 먼저 선택하세요

- )} -

결재 대상 레코드를 식별할 PK 컬럼

-
-
- - {/* ─── 2단계: 표시 모드 ─── */} -
-

표시 모드

-

결재 단계의 방향을 설정해요

-
- -
- -
- - {/* ─── 3단계: 표시 옵션 (Collapsible) ─── */} - - - - - -
-
-
-

부서/직급 표시

-

결재자의 부서와 직급을 보여줘요

-
- handleChange("showDept", checked)} - /> -
- -
-
-

결재 코멘트

-

결재자가 남긴 의견을 표시해요

-
- handleChange("showComment", checked)} - /> -
- -
-
-

처리 시각

-

각 단계의 처리 일시를 보여줘요

-
- handleChange("showTimestamp", checked)} - /> -
- -
-
-

콤팩트 모드

-

좁은 공간에 맞게 작게 표시해요

-
- handleChange("compact", checked)} - /> -
-
-
-
-
- ); -}; - -V2ApprovalStepConfigPanel.displayName = "V2ApprovalStepConfigPanel"; - -export default V2ApprovalStepConfigPanel; diff --git a/frontend/components/v2/config-panels/V2PivotGridConfigPanel.tsx b/frontend/components/v2/config-panels/V2PivotGridConfigPanel.tsx deleted file mode 100644 index b57db0d1..00000000 --- a/frontend/components/v2/config-panels/V2PivotGridConfigPanel.tsx +++ /dev/null @@ -1,618 +0,0 @@ -"use client"; - -/** - * V2 피벗 그리드 설정 패널 - * 토스식 단계별 UX: 테이블 선택(Combobox) -> 필드 배치(영역 드롭존) -> 고급 설정(Collapsible) - */ - -import React, { useState, useEffect, useCallback } from "react"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Switch } from "@/components/ui/switch"; -import { Badge } from "@/components/ui/badge"; -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 { - Rows, Columns, Calculator, X, Plus, GripVertical, - Check, ChevronsUpDown, ChevronDown, ChevronUp, Settings, Database, Info, -} from "lucide-react"; -import { cn } from "@/lib/utils"; -import { tableTypeApi } from "@/lib/api/screen"; -import type { - PivotGridComponentConfig, - PivotFieldConfig, - PivotAreaType, - AggregationType, - FieldDataType, -} from "@/lib/registry/components/v2-pivot-grid/types"; - -interface TableInfo { - tableName: string; - displayName: string; -} - -interface ColumnInfo { - column_name: string; - data_type: string; - column_comment?: string; -} - -interface V2PivotGridConfigPanelProps { - config: PivotGridComponentConfig; - onChange: (config: PivotGridComponentConfig) => void; -} - -function mapDbTypeToFieldType(dbType: string): FieldDataType { - const type = dbType.toLowerCase(); - if (type.includes("int") || type.includes("numeric") || type.includes("decimal") || type.includes("float")) return "number"; - if (type.includes("date") || type.includes("time") || type.includes("timestamp")) return "date"; - if (type.includes("bool")) return "boolean"; - return "string"; -} - -// ─── 영역 드롭존 (토스식) ─── - -interface AreaDropZoneProps { - area: PivotAreaType; - label: string; - description: string; - icon: React.ReactNode; - fields: PivotFieldConfig[]; - columns: ColumnInfo[]; - onAddField: (column: ColumnInfo) => void; - onRemoveField: (index: number) => void; - onUpdateField: (index: number, updates: Partial) => void; - borderClass: string; -} - -const AreaDropZone: React.FC = ({ - area, label, description, icon, fields, columns, - onAddField, onRemoveField, onUpdateField, borderClass, -}) => { - const [isExpanded, setIsExpanded] = useState(true); - const availableColumns = columns.filter((col) => !fields.some((f) => f.field === col.column_name)); - - return ( -
-
setIsExpanded(!isExpanded)}> -
- {icon} - {label} - {fields.length} -
- {isExpanded ? : } -
-

{description}

- - {isExpanded && ( -
- {fields.length > 0 ? ( -
- {fields.map((field, idx) => ( -
- - {field.caption || field.field} - {area === "data" && ( - - )} - -
- ))} -
- ) : ( -
- 아래에서 컬럼을 선택하세요 -
- )} - - {availableColumns.length > 0 && ( - - )} -
- )} -
- ); -}; - -// ─── 메인 패널 ─── - -export const V2PivotGridConfigPanel: React.FC = ({ config, onChange }) => { - const [tables, setTables] = useState([]); - const [columns, setColumns] = useState([]); - const [loadingTables, setLoadingTables] = useState(false); - const [loadingColumns, setLoadingColumns] = useState(false); - const [tableOpen, setTableOpen] = useState(false); - const [advancedOpen, setAdvancedOpen] = useState(false); - - useEffect(() => { - const loadTables = async () => { - setLoadingTables(true); - try { - const tableList = await tableTypeApi.getTables(); - setTables( - tableList.map((t: any) => ({ - tableName: t.tableName, - displayName: t.tableLabel || t.displayName || t.tableName, - })) - ); - } catch { /* ignore */ } finally { setLoadingTables(false); } - }; - loadTables(); - }, []); - - useEffect(() => { - const loadColumns = async () => { - if (!config.dataSource?.tableName) { setColumns([]); return; } - setLoadingColumns(true); - try { - const columnList = await tableTypeApi.getColumns(config.dataSource.tableName); - setColumns( - columnList.map((c: any) => ({ - column_name: c.columnName || c.column_name, - data_type: c.dataType || c.data_type || "text", - column_comment: c.columnLabel || c.column_label || c.columnName || c.column_name, - })) - ); - } catch { /* ignore */ } finally { setLoadingColumns(false); } - }; - loadColumns(); - }, [config.dataSource?.tableName]); - - const updateConfig = useCallback( - (updates: Partial) => { onChange({ ...config, ...updates }); }, - [config, onChange] - ); - - const handleAddField = (area: PivotAreaType, column: ColumnInfo) => { - const currentFields = config.fields || []; - const areaFields = currentFields.filter((f) => f.area === area); - const newField: PivotFieldConfig = { - field: column.column_name, - caption: column.column_comment || column.column_name, - area, - areaIndex: areaFields.length, - dataType: mapDbTypeToFieldType(column.data_type), - visible: true, - }; - if (area === "data") newField.summaryType = "sum"; - updateConfig({ fields: [...currentFields, newField] }); - }; - - const handleRemoveField = (area: PivotAreaType, index: number) => { - const currentFields = config.fields || []; - const newFields = currentFields.filter((f) => !(f.area === area && f.areaIndex === index)); - let idx = 0; - newFields.forEach((f) => { if (f.area === area) f.areaIndex = idx++; }); - updateConfig({ fields: newFields }); - }; - - const handleUpdateField = (area: PivotAreaType, index: number, updates: Partial) => { - const currentFields = config.fields || []; - const newFields = currentFields.map((f) => - f.area === area && f.areaIndex === index ? { ...f, ...updates } : f - ); - updateConfig({ fields: newFields }); - }; - - const getFieldsByArea = (area: PivotAreaType) => - (config.fields || []).filter((f) => f.area === area).sort((a, b) => (a.areaIndex || 0) - (b.areaIndex || 0)); - - const selectedTable = tables.find((t) => t.tableName === config.dataSource?.tableName); - - return ( -
- {/* ─── 안내 ─── */} -
-
- -
-

피벗 테이블 설정

-
    -
  1. 테이블을 선택하세요
  2. -
  3. 행/열/값 영역에 컬럼을 배치하세요
  4. -
-
-
-
- - {/* ─── 1단계: 테이블 선택 ─── */} -
-
- -

테이블 선택

-
- - - - - - - - - 테이블을 찾을 수 없습니다. - - {tables.map((table) => ( - { - updateConfig({ - dataSource: { ...config.dataSource, type: "table", tableName: table.tableName }, - fields: [], - }); - setTableOpen(false); - }} - className="text-xs" - > - -
- {table.displayName} - {table.displayName !== table.tableName && ( - {table.tableName} - )} -
-
- ))} -
-
-
-
-
-

피벗 분석할 데이터가 있는 테이블을 선택해요

-
- - {/* ─── 2단계: 필드 배치 ─── */} - {config.dataSource?.tableName && ( -
-

- 필드 배치 - {loadingColumns && (컬럼 로딩 중...)} -

- -
- } - fields={getFieldsByArea("row")} - columns={columns} - onAddField={(col) => handleAddField("row", col)} - onRemoveField={(idx) => handleRemoveField("row", idx)} - onUpdateField={(idx, u) => handleUpdateField("row", idx, u)} - borderClass="border-emerald-200 bg-emerald-50/50" - /> - } - fields={getFieldsByArea("column")} - columns={columns} - onAddField={(col) => handleAddField("column", col)} - onRemoveField={(idx) => handleRemoveField("column", idx)} - onUpdateField={(idx, u) => handleUpdateField("column", idx, u)} - borderClass="border-primary/20 bg-primary/5" - /> - } - fields={getFieldsByArea("data")} - columns={columns} - onAddField={(col) => handleAddField("data", col)} - onRemoveField={(idx) => handleRemoveField("data", idx)} - onUpdateField={(idx, u) => handleUpdateField("data", idx, u)} - borderClass="border-amber-200 bg-amber-50/50" - /> -
-
- )} - - {/* ─── 3단계: 고급 설정 (Collapsible) ─── */} - - - - - -
- {/* 총계 설정 */} -
-

총계 설정

-
-
- 행 총계 - updateConfig({ totals: { ...config.totals, showRowGrandTotals: v } })} - /> -
-
- 열 총계 - updateConfig({ totals: { ...config.totals, showColumnGrandTotals: v } })} - /> -
-
- 행 총계 위치 - -
-
- 열 총계 위치 - -
-
- 행 소계 - updateConfig({ totals: { ...config.totals, showRowTotals: v } })} - /> -
-
- 열 소계 - updateConfig({ totals: { ...config.totals, showColumnTotals: v } })} - /> -
-
-
- - {/* 스타일 설정 */} -
-

스타일

-
-
- 줄무늬 - updateConfig({ style: { ...config.style, alternateRowColors: v } })} - /> -
-
- 셀 병합 - updateConfig({ style: { ...config.style, mergeCells: v } })} - /> -
-
- CSV 내보내기 - updateConfig({ exportConfig: { ...config.exportConfig, excel: v } })} - /> -
-
- 상태 저장 - updateConfig({ ...(config as any), saveState: v })} - /> -
-
-
- - {/* 크기 설정 */} -
-

크기

-
-
- 높이 - updateConfig({ height: e.target.value })} - placeholder="400px" - className="h-7 text-xs" - /> -
-
- 최대 높이 - updateConfig({ maxHeight: e.target.value })} - placeholder="600px" - className="h-7 text-xs" - /> -
-
-
- - {/* 조건부 서식 */} -
-

조건부 서식

-
- {(config.style?.conditionalFormats || []).map((rule, index) => ( -
- - - {rule.type === "colorScale" && ( -
- { - const newFormats = [...(config.style?.conditionalFormats || [])]; - newFormats[index] = { ...rule, colorScale: { ...rule.colorScale, minColor: e.target.value, maxColor: rule.colorScale?.maxColor || "#00ff00" } }; - updateConfig({ style: { ...config.style, conditionalFormats: newFormats } }); - }} - className="h-6 w-6 cursor-pointer rounded" - title="최소값 색상" - /> - - { - const newFormats = [...(config.style?.conditionalFormats || [])]; - newFormats[index] = { ...rule, colorScale: { ...rule.colorScale, minColor: rule.colorScale?.minColor || "#ff0000", maxColor: e.target.value } }; - updateConfig({ style: { ...config.style, conditionalFormats: newFormats } }); - }} - className="h-6 w-6 cursor-pointer rounded" - title="최대값 색상" - /> -
- )} - - {rule.type === "dataBar" && ( - { - const newFormats = [...(config.style?.conditionalFormats || [])]; - newFormats[index] = { ...rule, dataBar: { color: e.target.value } }; - updateConfig({ style: { ...config.style, conditionalFormats: newFormats } }); - }} - className="h-6 w-6 cursor-pointer rounded" - title="바 색상" - /> - )} - - {rule.type === "iconSet" && ( - - )} - - -
- ))} - - -
-
-
-
-
-
- ); -}; - -V2PivotGridConfigPanel.displayName = "V2PivotGridConfigPanel"; - -export default V2PivotGridConfigPanel; diff --git a/frontend/lib/registry/components/v2-approval-step/index.ts b/frontend/lib/registry/components/v2-approval-step/index.ts index 84475b90..01225a23 100644 --- a/frontend/lib/registry/components/v2-approval-step/index.ts +++ b/frontend/lib/registry/components/v2-approval-step/index.ts @@ -5,7 +5,7 @@ import { createComponentDefinition } from "../../utils/createComponentDefinition import { ComponentCategory } from "@/types/component"; import type { WebType } from "@/types/screen"; import { ApprovalStepWrapper } from "./ApprovalStepComponent"; -import { V2ApprovalStepConfigPanel as ApprovalStepConfigPanel } from "@/components/v2/config-panels/V2ApprovalStepConfigPanel"; +import { ApprovalStepConfigPanel } from "./ApprovalStepConfigPanel"; import { ApprovalStepConfig } from "./types"; /** diff --git a/frontend/lib/registry/components/v2-pivot-grid/index.ts b/frontend/lib/registry/components/v2-pivot-grid/index.ts index 50d5691e..b1bbe99b 100644 --- a/frontend/lib/registry/components/v2-pivot-grid/index.ts +++ b/frontend/lib/registry/components/v2-pivot-grid/index.ts @@ -43,7 +43,7 @@ export type { // 컴포넌트 내보내기 export { PivotGridComponent } from "./PivotGridComponent"; -export { V2PivotGridConfigPanel as PivotGridConfigPanel } from "@/components/v2/config-panels/V2PivotGridConfigPanel"; +export { PivotGridConfigPanel } from "./PivotGridConfigPanel"; // 유틸리티 export {