From 3f8e995c8718c6bcece433c2a338b88f7cb699c1 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Wed, 11 Mar 2026 15:57:02 +0900 Subject: [PATCH] [agent-pipeline] pipe-20260311064710-y8yf round-8 --- .../config-panels/ButtonConfigPanel.tsx | 5491 +---------------- .../config-panels/button-config/ActionTab.tsx | 4540 ++++++++++++++ 2 files changed, 4581 insertions(+), 5450 deletions(-) create mode 100644 frontend/components/screen/config-panels/button-config/ActionTab.tsx diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index 295371c0..b59d87e5 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -1,5473 +1,64 @@ "use client"; -import React, { useState, useEffect, useMemo, useCallback } from "react"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Switch } from "@/components/ui/switch"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { - Check, - ChevronsUpDown, - Search, - Plus, - X, - ChevronUp, - ChevronDown, - Type, - Database, - Info, - RotateCcw, -} from "lucide-react"; -import { cn } from "@/lib/utils"; +import React from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ComponentData } from "@/types/screen"; -import { apiClient } from "@/lib/api/client"; -import { ButtonDataflowConfigPanel } from "./ButtonDataflowConfigPanel"; -import { ImprovedButtonControlConfigPanel } from "./ImprovedButtonControlConfigPanel"; -import { FlowVisibilityConfigPanel } from "./FlowVisibilityConfigPanel"; -import { QuickInsertConfigSection } from "./QuickInsertConfigSection"; -import { getApprovalDefinitions, type ApprovalDefinition } from "@/lib/api/approval"; -import DOMPurify from "isomorphic-dompurify"; -import { ColorPickerWithTransparent } from "../common/ColorPickerWithTransparent"; -import { icons as allLucideIcons } from "lucide-react"; -import { - actionIconMap, - noIconActions, - NO_ICON_MESSAGE, - iconSizePresets, - getLucideIcon, - addToIconMap, - getDefaultIconForAction, -} from "@/lib/button-icon-map"; - -// πŸ†• 제λͺ© 블둝 νƒ€μž… -interface TitleBlock { - id: string; - type: "text" | "field"; - value: string; // text: ν…μŠ€νŠΈ λ‚΄μš©, field: 컬럼λͺ… - tableName?: string; // field일 λ•Œ ν…Œμ΄λΈ”λͺ… - label?: string; // field일 λ•Œ ν‘œμ‹œμš© 라벨 -} +import { BasicTab } from "./button-config/BasicTab"; +import { ActionTab } from "./button-config/ActionTab"; +import { DataflowTab } from "./button-config/DataflowTab"; +import { AdvancedTab } from "./button-config/AdvancedTab"; interface ButtonConfigPanelProps { component: ComponentData; onUpdateProperty: (path: string, value: any) => void; - allComponents?: ComponentData[]; // πŸ†• ν”Œλ‘œμš° μœ„μ ― κ°μ§€μš© - currentTableName?: string; // ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… (μžλ™ κ°μ§€μš©) - currentScreenCompanyCode?: string; // ν˜„μž¬ νŽΈμ§‘ 쀑인 ν™”λ©΄μ˜ νšŒμ‚¬ μ½”λ“œ -} - -interface ScreenOption { - id: number; - name: string; - description?: string; + allComponents?: ComponentData[]; + currentTableName?: string; + currentScreenCompanyCode?: string; } export const ButtonConfigPanel: React.FC = ({ component, onUpdateProperty, - allComponents = [], // πŸ†• κΈ°λ³Έκ°’ 빈 λ°°μ—΄ - currentTableName, // ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… - currentScreenCompanyCode, // ν˜„μž¬ νŽΈμ§‘ 쀑인 ν™”λ©΄μ˜ νšŒμ‚¬ μ½”λ“œ + allComponents = [], + currentTableName, + currentScreenCompanyCode, }) => { - // πŸ”§ componentκ°€ μ—†λŠ” 경우 λ°©μ–΄ 처리 if (!component) { return
μ»΄ν¬λ„ŒνŠΈ 정보λ₯Ό 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€.
; } - // πŸ”§ componentμ—μ„œ 직접 읽기 (useMemo 제거) - const config = component.componentConfig || {}; - const currentAction = component.componentConfig?.action || {}; - - // 둜컬 μƒνƒœ 관리 (μ‹€μ‹œκ°„ μž…λ ₯ 반영) - const [localInputs, setLocalInputs] = useState({ - text: config.text !== undefined ? config.text : "λ²„νŠΌ", - actionType: String(config.action?.type || "save"), - modalTitle: String(config.action?.modalTitle || ""), - modalDescription: String(config.action?.modalDescription || ""), - editModalTitle: String(config.action?.editModalTitle || ""), - editModalDescription: String(config.action?.editModalDescription || ""), - targetUrl: String(config.action?.targetUrl || ""), - groupByColumn: String(config.action?.groupByColumns?.[0] || ""), - }); - - // μ•„μ΄μ½˜ μ„€μ • μƒνƒœ - const [displayMode, setDisplayMode] = useState<"text" | "icon" | "icon-text">(config.displayMode || "text"); - const [selectedIcon, setSelectedIcon] = useState(config.icon?.name || ""); - const [selectedIconType, setSelectedIconType] = useState<"lucide" | "svg">(config.icon?.type || "lucide"); - const [iconSize, setIconSize] = useState(config.icon?.size || "보톡"); - const [iconColor, setIconColor] = useState(config.icon?.color || ""); - const [iconGap, setIconGap] = useState(config.iconGap ?? 6); - const [iconTextPosition, setIconTextPosition] = useState<"right" | "left" | "bottom" | "top">( - config.iconTextPosition || "right", - ); - - // μ»€μŠ€ν…€ μ•„μ΄μ½˜ UI μƒνƒœ - const [lucideSearchOpen, setLucideSearchOpen] = useState(false); - const [lucideSearchTerm, setLucideSearchTerm] = useState(""); - const [svgPasteOpen, setSvgPasteOpen] = useState(false); - const [svgInput, setSvgInput] = useState(""); - const [svgName, setSvgName] = useState(""); - const [svgError, setSvgError] = useState(""); - - const [screens, setScreens] = useState([]); - const [screensLoading, setScreensLoading] = useState(false); - const [modalScreenOpen, setModalScreenOpen] = useState(false); - const [navScreenOpen, setNavScreenOpen] = useState(false); - const [modalSearchTerm, setModalSearchTerm] = useState(""); - const [navSearchTerm, setNavSearchTerm] = useState(""); - - // ν…Œμ΄λΈ” 컬럼 λͺ©λ‘ μƒνƒœ - const [tableColumns, setTableColumns] = useState([]); - const [columnsLoading, setColumnsLoading] = useState(false); - const [displayColumnOpen, setDisplayColumnOpen] = useState(false); - const [displayColumnSearch, setDisplayColumnSearch] = useState(""); - - // πŸ†• 제λͺ© 블둝 λΉŒλ” μƒνƒœ - const [titleBlocks, setTitleBlocks] = useState([]); - const [availableTables, setAvailableTables] = useState>([]); // μ‹œμŠ€ν…œμ˜ λͺ¨λ“  ν…Œμ΄λΈ” λͺ©λ‘ - const [tableColumnsMap, setTableColumnsMap] = useState>>({}); - const [blockTableSearches, setBlockTableSearches] = useState>({}); // 블둝별 ν…Œμ΄λΈ” 검색어 - const [blockColumnSearches, setBlockColumnSearches] = useState>({}); // 블둝별 컬럼 검색어 - const [blockTablePopoverOpen, setBlockTablePopoverOpen] = useState>({}); // 블둝별 ν…Œμ΄λΈ” Popover μ—΄λ¦Ό μƒνƒœ - const [blockColumnPopoverOpen, setBlockColumnPopoverOpen] = useState>({}); // 블둝별 컬럼 Popover μ—΄λ¦Ό μƒνƒœ - - // πŸ†• 데이터 전달 ν•„λ“œ λ§€ν•‘μš© μƒνƒœ (λ©€ν‹° ν…Œμ΄λΈ” λ§€ν•‘ 지원) - const [mappingSourceColumnsMap, setMappingSourceColumnsMap] = useState< - Record> - >({}); - const [mappingTargetColumns, setMappingTargetColumns] = useState>([]); - const [mappingSourcePopoverOpen, setMappingSourcePopoverOpen] = useState>({}); - const [mappingTargetPopoverOpen, setMappingTargetPopoverOpen] = useState>({}); - const [mappingSourceSearch, setMappingSourceSearch] = useState>({}); - const [mappingTargetSearch, setMappingTargetSearch] = useState>({}); - const [activeMappingGroupIndex, setActiveMappingGroupIndex] = useState(0); - - // πŸ†• openModalWithData μ „μš© ν•„λ“œ λ§€ν•‘ μƒνƒœ - const [modalSourceColumns, setModalSourceColumns] = useState>([]); - const [modalTargetColumns, setModalTargetColumns] = useState>([]); - const [modalSourcePopoverOpen, setModalSourcePopoverOpen] = useState>({}); - const [modalTargetPopoverOpen, setModalTargetPopoverOpen] = useState>({}); - - // 결재 μœ ν˜• λͺ©λ‘ μƒνƒœ - const [approvalDefinitions, setApprovalDefinitions] = useState([]); - const [approvalDefinitionsLoading, setApprovalDefinitionsLoading] = useState(false); - - // πŸ†• κ·Έλ£Ήν™” 컬럼 μ„ νƒμš© μƒνƒœ - const [currentTableColumns, setCurrentTableColumns] = useState>([]); - const [groupByColumnOpen, setGroupByColumnOpen] = useState(false); - const [groupByColumnSearch, setGroupByColumnSearch] = useState(""); - const [modalSourceSearch, setModalSourceSearch] = useState>({}); - const [modalTargetSearch, setModalTargetSearch] = useState>({}); - - // πŸ†• modal μ•‘μ…˜μš© ν•„λ“œ λ§€ν•‘ μƒνƒœ - const [modalActionSourceTable, setModalActionSourceTable] = useState(null); - const [modalActionTargetTable, setModalActionTargetTable] = useState(null); - const [modalActionSourceColumns, setModalActionSourceColumns] = useState>([]); - const [modalActionTargetColumns, setModalActionTargetColumns] = useState>([]); - const [modalActionFieldMappings, setModalActionFieldMappings] = useState< - Array<{ sourceField: string; targetField: string }> - >([]); - const [modalFieldMappingSourceOpen, setModalFieldMappingSourceOpen] = useState>({}); - const [modalFieldMappingTargetOpen, setModalFieldMappingTargetOpen] = useState>({}); - const [modalFieldMappingSourceSearch, setModalFieldMappingSourceSearch] = useState>({}); - const [modalFieldMappingTargetSearch, setModalFieldMappingTargetSearch] = useState>({}); - - // 🎯 ν”Œλ‘œμš° μœ„μ ―μ΄ 화면에 μžˆλŠ”μ§€ 확인 - const hasFlowWidget = useMemo(() => { - const found = allComponents.some((comp: any) => { - // ScreenDesignerμ—μ„œ μ €μž₯ν•˜λŠ” componentType 속성 확인! - const compType = comp.componentType || comp.widgetType || ""; - - // "flow-widget" 체크 - const isFlow = compType === "flow-widget" || compType?.toLowerCase().includes("flow"); - return isFlow; - }); - return found; - }, [allComponents]); - - // μ»΄ν¬λ„ŒνŠΈ prop λ³€κ²½ μ‹œ 둜컬 μƒνƒœ 동기화 (Input만) - useEffect(() => { - const latestConfig = component.componentConfig || {}; - const latestAction = latestConfig.action || {}; - - setLocalInputs({ - text: latestConfig.text !== undefined ? latestConfig.text : "λ²„νŠΌ", - actionType: String(latestAction.type || "save"), - modalTitle: String(latestAction.modalTitle || ""), - modalDescription: String(latestAction.modalDescription || ""), - editModalTitle: String(latestAction.editModalTitle || ""), - editModalDescription: String(latestAction.editModalDescription || ""), - targetUrl: String(latestAction.targetUrl || ""), - groupByColumn: String(latestAction.groupByColumns?.[0] || ""), - }); - - // πŸ†• 제λͺ© 블둝 μ΄ˆκΈ°ν™” - if (latestAction.modalTitleBlocks && latestAction.modalTitleBlocks.length > 0) { - setTitleBlocks(latestAction.modalTitleBlocks); - } else { - // κΈ°λ³Έκ°’: 빈 λ°°μ—΄ - setTitleBlocks([]); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [component.id, component.componentConfig?.action?.type]); - - // πŸ†• 제λͺ© 블둝 ν•Έλ“€λŸ¬ - const addTextBlock = () => { - const newBlock: TitleBlock = { - id: `text-${Date.now()}`, - type: "text", - value: "", - }; - const updatedBlocks = [...titleBlocks, newBlock]; - setTitleBlocks(updatedBlocks); - onUpdateProperty("componentConfig.action.modalTitleBlocks", updatedBlocks); - }; - - const addFieldBlock = () => { - const newBlock: TitleBlock = { - id: `field-${Date.now()}`, - type: "field", - value: "", - tableName: "", - label: "", - }; - const updatedBlocks = [...titleBlocks, newBlock]; - setTitleBlocks(updatedBlocks); - onUpdateProperty("componentConfig.action.modalTitleBlocks", updatedBlocks); - }; - - const updateBlock = (id: string, updates: Partial) => { - const updatedBlocks = titleBlocks.map((block) => (block.id === id ? { ...block, ...updates } : block)); - setTitleBlocks(updatedBlocks); - onUpdateProperty("componentConfig.action.modalTitleBlocks", updatedBlocks); - }; - - const removeBlock = (id: string) => { - const updatedBlocks = titleBlocks.filter((block) => block.id !== id); - setTitleBlocks(updatedBlocks); - onUpdateProperty("componentConfig.action.modalTitleBlocks", updatedBlocks); - }; - - const moveBlockUp = (id: string) => { - const index = titleBlocks.findIndex((b) => b.id === id); - if (index <= 0) return; - const newBlocks = [...titleBlocks]; - [newBlocks[index - 1], newBlocks[index]] = [newBlocks[index], newBlocks[index - 1]]; - setTitleBlocks(newBlocks); - onUpdateProperty("componentConfig.action.modalTitleBlocks", newBlocks); - }; - - const moveBlockDown = (id: string) => { - const index = titleBlocks.findIndex((b) => b.id === id); - if (index < 0 || index >= titleBlocks.length - 1) return; - const newBlocks = [...titleBlocks]; - [newBlocks[index], newBlocks[index + 1]] = [newBlocks[index + 1], newBlocks[index]]; - setTitleBlocks(newBlocks); - onUpdateProperty("componentConfig.action.modalTitleBlocks", newBlocks); - }; - - // πŸ†• 제λͺ© 미리보기 생성 - const generateTitlePreview = (): string => { - if (titleBlocks.length === 0) return "(제λͺ© μ—†μŒ)"; - return titleBlocks - .map((block) => { - if (block.type === "text") { - return block.value || "(ν…μŠ€νŠΈ)"; - } else { - return block.label || block.value || "(ν•„λ“œ)"; - } - }) - .join(""); - }; - - // πŸ†• μ‹œμŠ€ν…œμ˜ λͺ¨λ“  ν…Œμ΄λΈ” λͺ©λ‘ λ‘œλ“œ - useEffect(() => { - const fetchAllTables = async () => { - try { - const response = await apiClient.get("/table-management/tables"); - - if (response.data.success && response.data.data) { - const tables = response.data.data.map((table: any) => ({ - name: table.tableName, - label: table.displayName || table.tableName, - })); - setAvailableTables(tables); - } - } catch (error) { - console.error("ν…Œμ΄λΈ” λͺ©λ‘ λ‘œλ“œ μ‹€νŒ¨:", error); - } - }; - - fetchAllTables(); - }, []); - - // πŸ†• νŠΉμ • ν…Œμ΄λΈ”μ˜ 컬럼 λ‘œλ“œ - const loadTableColumns = async (tableName: string) => { - if (!tableName || tableColumnsMap[tableName]) return; - - try { - const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); - console.log(`πŸ“₯ ν…Œμ΄λΈ” ${tableName} 컬럼 응닡:`, response.data); - - if (response.data.success) { - // dataκ°€ 배열인지 확인 - let columnData = response.data.data; - - // data.columns ν˜•νƒœμΌ μˆ˜λ„ 있음 - if (!Array.isArray(columnData) && columnData?.columns) { - columnData = columnData.columns; - } - - // data.data ν˜•νƒœμΌ μˆ˜λ„ 있음 - if (!Array.isArray(columnData) && columnData?.data) { - columnData = columnData.data; - } - - if (Array.isArray(columnData)) { - const columns = columnData.map((col: any) => { - const name = col.name || col.columnName; - const label = col.displayName || col.label || col.columnLabel || name; - console.log(` - 컬럼: ${name} β†’ "${label}"`); - return { name, label }; - }); - setTableColumnsMap((prev) => ({ ...prev, [tableName]: columns })); - console.log(`βœ… ν…Œμ΄λΈ” ${tableName} 컬럼 λ‘œλ“œ 성곡:`, columns.length, "개"); - } else { - console.error("❌ 컬럼 데이터가 배열이 μ•„λ‹™λ‹ˆλ‹€:", columnData); - } - } - } catch (error) { - console.error("컬럼 λ‘œλ“œ μ‹€νŒ¨:", error); - } - }; - - // λ©€ν‹° ν…Œμ΄λΈ” λ§€ν•‘: μ†ŒμŠ€/νƒ€κ²Ÿ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ - const loadMappingColumns = useCallback(async (tableName: string): Promise> => { - try { - const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); - if (response.data.success) { - let columnData = response.data.data; - if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; - if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; - - if (Array.isArray(columnData)) { - return columnData.map((col: any) => ({ - name: col.name || col.columnName, - label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, - })); - } - } - } catch (error) { - console.error(`ν…Œμ΄λΈ” ${tableName} 컬럼 λ‘œλ“œ μ‹€νŒ¨:`, error); - } - return []; - }, []); - - useEffect(() => { - const multiTableMappings: Array<{ sourceTable: string }> = config.action?.dataTransfer?.multiTableMappings || []; - const legacySourceTable = config.action?.dataTransfer?.sourceTable; - const targetTable = config.action?.dataTransfer?.targetTable; - - const loadAll = async () => { - const sourceTableNames = multiTableMappings.map((m) => m.sourceTable).filter(Boolean); - if (legacySourceTable && !sourceTableNames.includes(legacySourceTable)) { - sourceTableNames.push(legacySourceTable); - } - - const newMap: Record> = {}; - for (const tbl of sourceTableNames) { - if (!mappingSourceColumnsMap[tbl]) { - newMap[tbl] = await loadMappingColumns(tbl); - } - } - if (Object.keys(newMap).length > 0) { - setMappingSourceColumnsMap((prev) => ({ ...prev, ...newMap })); - } - - if (targetTable && mappingTargetColumns.length === 0) { - const cols = await loadMappingColumns(targetTable); - setMappingTargetColumns(cols); - } - }; - - loadAll(); - }, [ - config.action?.dataTransfer?.multiTableMappings, - config.action?.dataTransfer?.sourceTable, - config.action?.dataTransfer?.targetTable, - loadMappingColumns, - ]); - - // πŸ†• modal μ•‘μ…˜: λŒ€μƒ ν™”λ©΄ ν…Œμ΄λΈ” 쑰회 및 ν•„λ“œ λ§€ν•‘ λ‘œλ“œ - useEffect(() => { - const actionType = config.action?.type; - if (actionType !== "modal") return; - - const autoDetect = config.action?.autoDetectDataSource; - if (!autoDetect) { - // 데이터 전달이 λΉ„ν™œμ„±ν™”λ˜λ©΄ μƒνƒœ μ΄ˆκΈ°ν™” - setModalActionSourceTable(null); - setModalActionTargetTable(null); - setModalActionSourceColumns([]); - setModalActionTargetColumns([]); - return; - } - - const targetScreenId = config.action?.targetScreenId; - if (!targetScreenId) return; - - const loadModalActionMappingData = async () => { - // 1. μ†ŒμŠ€ ν…Œμ΄λΈ” 감지 (ν˜„μž¬ ν™”λ©΄) - let sourceTableName: string | null = currentTableName || null; - - // allComponentsμ—μ„œ λΆ„ν• νŒ¨λ„/ν…Œμ΄λΈ”λ¦¬μŠ€νŠΈ/톡합λͺ©λ‘ 감지 - for (const comp of allComponents) { - const compType = comp.componentType || (comp as any).componentConfig?.type; - const compConfig = (comp as any).componentConfig || {}; - - if (compType === "split-panel-layout" || compType === "screen-split-panel") { - sourceTableName = compConfig.leftPanel?.tableName || compConfig.tableName || null; - if (sourceTableName) break; - } - if (compType === "table-list") { - sourceTableName = compConfig.tableName || compConfig.selectedTable || null; - if (sourceTableName) break; - } - if (compType === "v2-list") { - sourceTableName = compConfig.dataSource?.table || compConfig.tableName || null; - if (sourceTableName) break; - } - } - - setModalActionSourceTable(sourceTableName); - - // 2. λŒ€μƒ ν™”λ©΄μ˜ ν…Œμ΄λΈ” 쑰회 - let targetTableName: string | null = null; - try { - const screenResponse = await apiClient.get(`/screen-management/screens/${targetScreenId}`); - if (screenResponse.data.success && screenResponse.data.data) { - targetTableName = screenResponse.data.data.tableName || null; - } else if (screenResponse.data?.tableName) { - // 직접 데이터 λ°˜ν™˜ ν˜•μ‹μΈ 경우 - targetTableName = screenResponse.data.tableName || null; - } - } catch (error) { - console.error("λŒ€μƒ ν™”λ©΄ 정보 λ‘œλ“œ μ‹€νŒ¨:", error); - } - - setModalActionTargetTable(targetTableName); - - // 3. μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ - if (sourceTableName) { - try { - const response = await apiClient.get(`/table-management/tables/${sourceTableName}/columns`); - if (response.data.success) { - let columnData = response.data.data; - if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; - if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; - - if (Array.isArray(columnData)) { - const columns = columnData.map((col: any) => ({ - name: col.name || col.columnName, - label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, - })); - setModalActionSourceColumns(columns); - } - } - } catch (error) { - console.error("μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ μ‹€νŒ¨:", error); - } - } - - // 4. λŒ€μƒ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ - if (targetTableName) { - try { - const response = await apiClient.get(`/table-management/tables/${targetTableName}/columns`); - if (response.data.success) { - let columnData = response.data.data; - if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; - if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; - - if (Array.isArray(columnData)) { - const columns = columnData.map((col: any) => ({ - name: col.name || col.columnName, - label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, - })); - setModalActionTargetColumns(columns); - } - } - } catch (error) { - console.error("λŒ€μƒ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ μ‹€νŒ¨:", error); - } - } - - // 5. κΈ°μ‘΄ ν•„λ“œ λ§€ν•‘ λ‘œλ“œ λ˜λŠ” μžλ™ λ§€ν•‘ 생성 - const existingMappings = config.action?.fieldMappings || []; - if (existingMappings.length > 0) { - setModalActionFieldMappings(existingMappings); - } else if (sourceTableName && targetTableName && sourceTableName === targetTableName) { - // ν…Œμ΄λΈ”μ΄ κ°™μœΌλ©΄ μžλ™ λ§€ν•‘ (동일 컬럼λͺ…) - setModalActionFieldMappings([]); // 빈 λ°°μ—΄ = μžλ™ λ§€ν•‘ - } - }; - - loadModalActionMappingData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - config.action?.type, - config.action?.autoDetectDataSource, - config.action?.targetScreenId, - currentTableName, + const tabProps = { + component, + onUpdateProperty, allComponents, - ]); - - // πŸ†• ν˜„μž¬ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ (κ·Έλ£Ήν™” 컬럼 μ„ νƒμš©) - useEffect(() => { - if (!currentTableName) return; - - const loadCurrentTableColumns = async () => { - try { - const response = await apiClient.get(`/table-management/tables/${currentTableName}/columns`); - if (response.data.success) { - let columnData = response.data.data; - if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; - if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; - - if (Array.isArray(columnData)) { - const columns = columnData.map((col: any) => ({ - name: col.name || col.columnName, - label: col.displayName || col.label || col.columnLabel || col.name || col.columnName, - })); - setCurrentTableColumns(columns); - } - } - } catch (error) { - console.error("ν˜„μž¬ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ μ‹€νŒ¨:", error); - } - }; - - loadCurrentTableColumns(); - }, [currentTableName]); - - // πŸ†• openModalWithData μ†ŒμŠ€/νƒ€κ²Ÿ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ - useEffect(() => { - const actionType = config.action?.type; - if (actionType !== "openModalWithData") return; - - const loadModalMappingColumns = async () => { - // μ†ŒμŠ€ ν…Œμ΄λΈ”: ν˜„μž¬ ν™”λ©΄μ˜ λΆ„ν•  νŒ¨λ„ λ˜λŠ” ν…Œμ΄λΈ”μ—μ„œ 감지 - let sourceTableName: string | null = null; - - console.log("[openModalWithData] 컬럼 λ‘œλ“œ μ‹œμž‘:", { - allComponentsCount: allComponents.length, - currentTableName, - targetScreenId: config.action?.targetScreenId, - }); - - // λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈ νƒ€μž… 둜그 - allComponents.forEach((comp, idx) => { - const compType = comp.componentType || (comp as any).componentConfig?.type; - console.log( - ` [${idx}] componentType: ${compType}, tableName: ${(comp as any).componentConfig?.tableName || (comp as any).componentConfig?.leftPanel?.tableName || "N/A"}`, - ); - }); - - for (const comp of allComponents) { - const compType = comp.componentType || (comp as any).componentConfig?.type; - const compConfig = (comp as any).componentConfig || {}; - - // λΆ„ν•  νŒ¨λ„ νƒ€μž…λ“€ (λ‹€μ–‘ν•œ κ²½λ‘œμ—μ„œ ν…Œμ΄λΈ”λͺ… μΆ”μΆœ) - if (compType === "split-panel-layout" || compType === "screen-split-panel") { - sourceTableName = compConfig?.leftPanel?.tableName || compConfig?.leftTableName || compConfig?.tableName; - if (sourceTableName) { - console.log(`βœ… [openModalWithData] split-panel-layoutμ—μ„œ μ†ŒμŠ€ ν…Œμ΄λΈ” 감지: ${sourceTableName}`); - break; - } - } - - // split-panel-layout2 νƒ€μž… (μƒˆλ‘œμš΄ λΆ„ν•  νŒ¨λ„) - if (compType === "split-panel-layout2") { - sourceTableName = compConfig?.leftPanel?.tableName || compConfig?.tableName || compConfig?.leftTableName; - if (sourceTableName) { - console.log(`βœ… [openModalWithData] split-panel-layout2μ—μ„œ μ†ŒμŠ€ ν…Œμ΄λΈ” 감지: ${sourceTableName}`); - break; - } - } - - // ν…Œμ΄λΈ” 리슀트 νƒ€μž… - if (compType === "table-list") { - sourceTableName = compConfig?.tableName; - if (sourceTableName) { - console.log(`βœ… [openModalWithData] table-listμ—μ„œ μ†ŒμŠ€ ν…Œμ΄λΈ” 감지: ${sourceTableName}`); - break; - } - } - - // πŸ†• λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈμ—μ„œ tableName μ°ΎκΈ° (폴백) - if (!sourceTableName && compConfig?.tableName) { - sourceTableName = compConfig.tableName; - console.log(`βœ… [openModalWithData] ${compType}μ—μ„œ μ†ŒμŠ€ ν…Œμ΄λΈ” 감지 (폴백): ${sourceTableName}`); - break; - } - } - - // μ—¬μ „νžˆ μ—†μœΌλ©΄ currentTableName μ‚¬μš© (ν™”λ©΄ 레벨 ν…Œμ΄λΈ”λͺ…) - if (!sourceTableName && currentTableName) { - sourceTableName = currentTableName; - console.log(`βœ… [openModalWithData] currentTableNameμ—μ„œ μ†ŒμŠ€ ν…Œμ΄λΈ” μ‚¬μš©: ${sourceTableName}`); - } - - if (!sourceTableName) { - console.warn("[openModalWithData] μ†ŒμŠ€ ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); - } - - // μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ - if (sourceTableName) { - try { - const response = await apiClient.get(`/table-management/tables/${sourceTableName}/columns`); - if (response.data.success) { - let columnData = response.data.data; - if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; - if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; - - if (Array.isArray(columnData)) { - const columns = columnData.map((col: any) => ({ - name: col.name || col.columnName || col.column_name, - label: - col.displayName || - col.label || - col.columnLabel || - col.display_name || - col.name || - col.columnName || - col.column_name, - })); - setModalSourceColumns(columns); - console.log(`βœ… [openModalWithData] μ†ŒμŠ€ ν…Œμ΄λΈ”(${sourceTableName}) 컬럼 λ‘œλ“œ μ™„λ£Œ:`, columns.length); - } - } - } catch (error) { - console.error("μ†ŒμŠ€ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ μ‹€νŒ¨:", error); - } - } - - // νƒ€κ²Ÿ ν™”λ©΄μ˜ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ - const targetScreenId = config.action?.targetScreenId; - if (targetScreenId) { - try { - // νƒ€κ²Ÿ ν™”λ©΄ 정보 κ°€μ Έμ˜€κΈ° - const screenResponse = await apiClient.get(`/screen-management/screens/${targetScreenId}`); - console.log("[openModalWithData] νƒ€κ²Ÿ ν™”λ©΄ 응닡:", screenResponse.data); - - if (screenResponse.data.success && screenResponse.data.data) { - const targetTableName = screenResponse.data.data.tableName; - console.log("[openModalWithData] νƒ€κ²Ÿ ν™”λ©΄ ν…Œμ΄λΈ”λͺ…:", targetTableName); - - if (targetTableName) { - const columnResponse = await apiClient.get(`/table-management/tables/${targetTableName}/columns`); - if (columnResponse.data.success) { - let columnData = columnResponse.data.data; - if (!Array.isArray(columnData) && columnData?.columns) columnData = columnData.columns; - if (!Array.isArray(columnData) && columnData?.data) columnData = columnData.data; - - if (Array.isArray(columnData)) { - const columns = columnData.map((col: any) => ({ - name: col.name || col.columnName || col.column_name, - label: - col.displayName || - col.label || - col.columnLabel || - col.display_name || - col.name || - col.columnName || - col.column_name, - })); - setModalTargetColumns(columns); - console.log(`βœ… [openModalWithData] νƒ€κ²Ÿ ν…Œμ΄λΈ”(${targetTableName}) 컬럼 λ‘œλ“œ μ™„λ£Œ:`, columns.length); - } - } - } else { - console.warn("[openModalWithData] νƒ€κ²Ÿ 화면에 ν…Œμ΄λΈ”λͺ…이 μ—†μŠ΅λ‹ˆλ‹€."); - } - } - } catch (error) { - console.error("νƒ€κ²Ÿ ν™”λ©΄ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ μ‹€νŒ¨:", error); - } - } else { - console.warn("[openModalWithData] νƒ€κ²Ÿ ν™”λ©΄ IDκ°€ μ—†μŠ΅λ‹ˆλ‹€."); - } - }; - - loadModalMappingColumns(); - }, [config.action?.type, config.action?.targetScreenId, allComponents, currentTableName]); - - // ν™”λ©΄ λͺ©λ‘ κ°€μ Έμ˜€κΈ° (ν˜„μž¬ νŽΈμ§‘ 쀑인 ν™”λ©΄μ˜ νšŒμ‚¬ μ½”λ“œ κΈ°μ€€) - useEffect(() => { - const fetchScreens = async () => { - try { - setScreensLoading(true); - // ν˜„μž¬ νŽΈμ§‘ 쀑인 ν™”λ©΄μ˜ νšŒμ‚¬ μ½”λ“œ κΈ°μ€€μœΌλ‘œ ν™”λ©΄ λͺ©λ‘ 쑰회 - const params: any = { - page: 1, - size: 9999, // 맀우 큰 κ°’μœΌλ‘œ μ„€μ •ν•˜μ—¬ 전체 λͺ©λ‘ κ°€μ Έμ˜€κΈ° - }; - - // ν˜„μž¬ ν™”λ©΄μ˜ νšŒμ‚¬ μ½”λ“œκ°€ 있으면 필터링 νŒŒλΌλ―Έν„°λ‘œ 전달 - if (currentScreenCompanyCode) { - params.companyCode = currentScreenCompanyCode; - } - - const response = await apiClient.get("/screen-management/screens", { - params, - }); - - if (response.data.success && Array.isArray(response.data.data)) { - const screenList = response.data.data.map((screen: any) => ({ - id: screen.screenId, - name: screen.screenName, - description: screen.description, - })); - setScreens(screenList); - } - } catch (error) { - // console.error("❌ ν™”λ©΄ λͺ©λ‘ λ‘œλ”© μ‹€νŒ¨:", error); - } finally { - setScreensLoading(false); - } - }; - - fetchScreens(); - }, [currentScreenCompanyCode]); - - // 결재 μœ ν˜• λͺ©λ‘ κ°€μ Έμ˜€κΈ° (approval μ•‘μ…˜μΌ λ•Œ) - useEffect(() => { - if (localInputs.actionType !== "approval") return; - const fetchApprovalDefinitions = async () => { - setApprovalDefinitionsLoading(true); - try { - const res = await getApprovalDefinitions({ is_active: "Y" }); - if (res.success && res.data) { - setApprovalDefinitions(res.data); - } - } catch { - // 쑰용히 μ‹€νŒ¨ - } finally { - setApprovalDefinitionsLoading(false); - } - }; - fetchApprovalDefinitions(); - }, [localInputs.actionType]); - - // ν…Œμ΄λΈ” 컬럼 λͺ©λ‘ κ°€μ Έμ˜€κΈ° (ν…Œμ΄λΈ” 이λ ₯ 보기 μ•‘μ…˜μΌ λ•Œ) - useEffect(() => { - const fetchTableColumns = async () => { - // ν…Œμ΄λΈ” 이λ ₯ 보기 μ•‘μ…˜μ΄ μ•„λ‹ˆλ©΄ μŠ€ν‚΅ - if (config.action?.type !== "view_table_history") { - return; - } - - // 1. μˆ˜λ™ μž…λ ₯된 ν…Œμ΄λΈ”λͺ… μš°μ„  - // 2. μ—†μœΌλ©΄ ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… μ‚¬μš© - const tableName = config.action?.historyTableName || currentTableName; - - // ν…Œμ΄λΈ”λͺ…이 μ—†μœΌλ©΄ μŠ€ν‚΅ - if (!tableName) { - return; - } - - try { - setColumnsLoading(true); - const response = await apiClient.get(`/table-management/tables/${tableName}/columns`, { - params: { - page: 1, - size: 9999, // 전체 컬럼 κ°€μ Έμ˜€κΈ° - }, - }); - - // API 응닡 ꡬ쑰: { success, data: { columns: [...], total, page, totalPages } } - const columnData = response.data.data?.columns; - - if (!columnData || !Array.isArray(columnData)) { - console.error("❌ 컬럼 데이터가 배열이 μ•„λ‹™λ‹ˆλ‹€:", columnData); - setTableColumns([]); - return; - } - - if (response.data.success) { - // ID 컬럼과 λ‚ μ§œ κ΄€λ ¨ 컬럼 μ œμ™Έ - const filteredColumns = columnData - .filter((col: any) => { - const colName = col.columnName.toLowerCase(); - const dataType = col.dataType?.toLowerCase() || ""; - - // ID 컬럼 μ œμ™Έ (id, _id둜 λλ‚˜λŠ” 컬럼) - if (colName === "id" || colName.endsWith("_id")) { - return false; - } - - // λ‚ μ§œ/μ‹œκ°„ νƒ€μž… μ œμ™Έ (데이터 νƒ€μž… κΈ°μ€€) - if (dataType.includes("date") || dataType.includes("time") || dataType.includes("timestamp")) { - return false; - } - - // λ‚ μ§œ/μ‹œκ°„ κ΄€λ ¨ 컬럼λͺ… μ œμ™Έ (컬럼λͺ…에 date, time, at 포함) - if ( - colName.includes("date") || - colName.includes("time") || - colName.endsWith("_at") || - colName.startsWith("created") || - colName.startsWith("updated") - ) { - return false; - } - - return true; - }) - .map((col: any) => col.columnName); - - setTableColumns(filteredColumns); - } - } catch (error) { - console.error("❌ ν…Œμ΄λΈ” 컬럼 λ‘œλ”© μ‹€νŒ¨:", error); - } finally { - setColumnsLoading(false); - } - }; - - fetchTableColumns(); - }, [config.action?.type, config.action?.historyTableName, currentTableName]); - - // 검색 필터링 ν•¨μˆ˜ - const filterScreens = (searchTerm: string) => { - if (!searchTerm.trim()) return screens; - return screens.filter( - (screen) => - screen.name.toLowerCase().includes(searchTerm.toLowerCase()) || - (screen.description && screen.description.toLowerCase().includes(searchTerm.toLowerCase())), - ); + currentTableName, + currentScreenCompanyCode, }; - // μ•„μ΄μ½˜ 선택 ν•Έλ“€λŸ¬ - const handleSelectIcon = (iconName: string, iconType: "lucide" | "svg" = "lucide") => { - setSelectedIcon(iconName); - setSelectedIconType(iconType); - onUpdateProperty("componentConfig.icon", { - name: iconName, - type: iconType, - size: iconSize, - ...(iconColor ? { color: iconColor } : {}), - }); - }; - - // 선택 쀑인 μ•„μ΄μ½˜μ΄ μ‚­μ œλ˜μ—ˆμ„ λ•Œ λ””ν΄νŠΈ μ•„μ΄μ½˜μœΌλ‘œ 볡귀 - const revertToDefaultIcon = () => { - const def = getDefaultIconForAction(localInputs.actionType); - setSelectedIcon(def.name); - setSelectedIconType(def.type); - handleSelectIcon(def.name, def.type); - }; - - // ν‘œμ‹œ λͺ¨λ“œ λ³€κ²½ ν•Έλ“€λŸ¬ β€” icon/icon-text μ „ν™˜ μ‹œ μ•„μ΄μ½˜ 미선택이면 λ””ν΄νŠΈ λΆ€μ—¬ - const handleDisplayModeChange = (mode: "text" | "icon" | "icon-text") => { - setDisplayMode(mode); - onUpdateProperty("componentConfig.displayMode", mode); - - if ((mode === "icon" || mode === "icon-text") && !selectedIcon) { - revertToDefaultIcon(); - } - }; - - // μ•„μ΄μ½˜ 크기 프리셋 λ³€κ²½ (μ•„μ΄μ½˜ 미선택 μ‹œ 둜컬만 μ—…λ°μ΄νŠΈ) - const handleIconSizePreset = (preset: string) => { - setIconSize(preset); - if (selectedIcon) { - onUpdateProperty("componentConfig.icon.size", preset); - } - }; - - // μ•„μ΄μ½˜ 색상 λ³€κ²½ - const handleIconColorChange = (color: string | undefined) => { - const val = color || ""; - setIconColor(val); - if (selectedIcon) { - if (val) { - onUpdateProperty("componentConfig.icon.color", val); - } else { - onUpdateProperty("componentConfig.icon.color", undefined); - } - } - }; - - // ν˜„μž¬ μ•‘μ…˜μ˜ μΆ”μ²œ μ•„μ΄μ½˜ λͺ©λ‘ - const currentActionIcons = actionIconMap[localInputs.actionType] || []; - const isNoIconAction = noIconActions.has(localInputs.actionType); - const customIcons: string[] = config.customIcons || []; - const customSvgIcons: Array<{ name: string; svg: string }> = config.customSvgIcons || []; - const showIconSettings = displayMode === "icon" || displayMode === "icon-text"; - return ( -
- {/* ν‘œμ‹œ λͺ¨λ“œ 선택 */} -
- -
- {( - [ - { value: "text", label: "ν…μŠ€νŠΈ" }, - { value: "icon", label: "μ•„μ΄μ½˜" }, - { value: "icon-text", label: "μ•„μ΄μ½˜+ν…μŠ€νŠΈ" }, - ] as const - ).map((opt) => ( - - ))} -
-
- - {/* μ•„μ΄μ½˜ λͺ¨λ“œ λ ˆμ΄μ•„μ›ƒ μ•ˆλ‚΄ */} - {displayMode === "icon" && ( -
- - μ•„μ΄μ½˜λ§Œ ν‘œμ‹œν•  λ•ŒλŠ” λ²„νŠΌ μ˜μ—­μ˜ κ°€λ‘œ 폭을 쀄여 μ •μ‚¬κ°ν˜•μ— κ°€κΉκ²Œ λ§Œλ“€λ©΄ 더 κΉ”λ”ν•©λ‹ˆλ‹€. -
- )} - - {/* λ²„νŠΌ ν…μŠ€νŠΈ (ν…μŠ€νŠΈ / μ•„μ΄μ½˜+ν…μŠ€νŠΈ λͺ¨λ“œμ—μ„œ ν‘œμ‹œ) */} - {(displayMode === "text" || displayMode === "icon-text") && ( -
- - { - const newValue = e.target.value; - setLocalInputs((prev) => ({ ...prev, text: newValue })); - onUpdateProperty("componentConfig.text", newValue); - }} - placeholder="λ²„νŠΌ ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”" - /> -
- )} - -
- - -
- - {/* ────────────────── μ•„μ΄μ½˜ μ„€μ • μ˜μ—­ ────────────────── */} - {showIconSettings && ( -
- {/* μΆ”μ²œ μ•„μ΄μ½˜ / μ•ˆλ‚΄ 문ꡬ */} - {isNoIconAction ? ( -
-
- {NO_ICON_MESSAGE} -
- - {/* μ»€μŠ€ν…€ μ•„μ΄μ½˜μ΄ 있으면 ν‘œμ‹œ */} - {(customIcons.length > 0 || customSvgIcons.length > 0) && ( - <> -
-
- μ»€μŠ€ν…€ μ•„μ΄μ½˜ -
-
-
- {customIcons.map((iconName) => { - const Icon = getLucideIcon(iconName); - if (!Icon) return null; - return ( -
- - -
- ); - })} - {customSvgIcons.map((svgIcon) => ( -
- - -
- ))} -
- - )} - - {/* μ»€μŠ€ν…€ μ•„μ΄μ½˜ μΆ”κ°€ λ²„νŠΌ */} -
- - - - - - - - - μ•„μ΄μ½˜μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. - - {Object.keys(allLucideIcons) - .filter((name) => name.toLowerCase().includes(lucideSearchTerm.toLowerCase())) - .slice(0, 30) - .map((iconName) => { - const Icon = allLucideIcons[iconName as keyof typeof allLucideIcons]; - return ( - { - const next = [...customIcons]; - if (!next.includes(iconName)) { - next.push(iconName); - onUpdateProperty("componentConfig.customIcons", next); - if (Icon) addToIconMap(iconName, Icon); - } - setLucideSearchOpen(false); - setLucideSearchTerm(""); - }} - className="flex items-center gap-2 text-xs" - > - {Icon ? : } - {iconName} - {customIcons.includes(iconName) && } - - ); - })} - - - - - - - - - - - - - setSvgName(e.target.value)} - placeholder="예: νšŒμ‚¬λ‘œκ³ " - className="h-7 text-xs" - /> - -