"use client"; import React, { useState, useCallback, useEffect, useMemo, useRef } from "react"; import { ComponentRendererProps } from "../../types"; import { SplitPanelLayoutConfig } from "./types"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save, ChevronRight, Pencil, Trash2, Settings, Move, } from "lucide-react"; import { dataApi } from "@/lib/api/data"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { useToast } from "@/hooks/use-toast"; import { tableTypeApi } from "@/lib/api/screen"; import { apiClient } from "@/lib/api/client"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { useTableOptions } from "@/contexts/TableOptionsContext"; import { TableFilter, ColumnVisibility, GroupSumConfig } from "@/types/table-options"; import { useAuth } from "@/hooks/useAuth"; import { useSplitPanel } from "./SplitPanelContext"; import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer"; import { PanelInlineComponent } from "./types"; import { cn } from "@/lib/utils"; export interface SplitPanelLayoutComponentProps extends ComponentRendererProps { // ์ถ”๊ฐ€ props onUpdateComponent?: (component: any) => void; // ๐Ÿ†• ํŒจ๋„ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ์ฝœ๋ฐฑ (ํƒญ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผ ๊ตฌ์กฐ) onSelectPanelComponent?: (panelSide: "left" | "right", compId: string, comp: PanelInlineComponent) => void; selectedPanelComponentId?: string; } /** * SplitPanelLayout ์ปดํฌ๋„ŒํŠธ * ๋งˆ์Šคํ„ฐ-๋””ํ…Œ์ผ ํŒจํ„ด์˜ ์ขŒ์šฐ ๋ถ„ํ•  ๋ ˆ์ด์•„์›ƒ */ export const SplitPanelLayoutComponent: React.FC = ({ component, isDesignMode = false, isSelected = false, isPreview = false, onClick, onUpdateComponent, onSelectPanelComponent, selectedPanelComponentId: externalSelectedPanelComponentId, ...props }) => { const componentConfig = (component.componentConfig || {}) as SplitPanelLayoutConfig; // ๐Ÿ› ๋””๋ฒ„๊น…: ๋กœ๋“œ ์‹œ rightPanel.components ํ™•์ธ const rightComps = componentConfig.rightPanel?.components || []; const finishedTimeline = rightComps.find((c: any) => c.id === "finished_timeline"); if (finishedTimeline) { const fm = finishedTimeline.componentConfig?.fieldMapping; console.log("๐Ÿ” [SplitPanelLayout] finished_timeline fieldMapping:", { componentId: finishedTimeline.id, fieldMapping: fm ? JSON.stringify(fm) : "undefined", fieldMappingKeys: fm ? Object.keys(fm) : [], fieldMappingId: fm?.id, fullComponentConfig: JSON.stringify(finishedTimeline.componentConfig || {}, null, 2), }); } // ๐Ÿ†• ํ”„๋ฆฌ๋ทฐ์šฉ ํšŒ์‚ฌ ์ฝ”๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ (์ตœ๊ณ  ๊ด€๋ฆฌ์ž๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) const companyCode = (props as any).companyCode as string | undefined; // ๊ธฐ๋ณธ ์„ค์ •๊ฐ’ const splitRatio = componentConfig.splitRatio || 30; const resizable = componentConfig.resizable ?? true; const minLeftWidth = componentConfig.minLeftWidth || 200; const minRightWidth = componentConfig.minRightWidth || 300; // ํ•„๋“œ ํ‘œ์‹œ ์œ ํ‹ธ๋ฆฌํ‹ฐ (ํ•˜๋“œ์ฝ”๋”ฉ ์ œ๊ฑฐ, ๋™์ ์œผ๋กœ ์ž‘๋™) const shouldShowField = (fieldName: string): boolean => { const lower = fieldName.toLowerCase(); // ๊ธฐ๋ณธ ์ œ์™ธ: id, ๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ, ํšŒ์‚ฌ์ฝ”๋“œ if (lower === "id" || lower === "company_code" || lower === "company_name") return false; if (lower.includes("password") || lower.includes("token")) return false; // ๋‚˜๋จธ์ง€๋Š” ๋ชจ๋‘ ํ‘œ์‹œ! return true; }; // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ปฌ๋Ÿผ๋ช… ๋ณ€ํ™˜ ํ—ฌํผ // "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•์‹์„ "์›๋ณธ์ปฌ๋Ÿผ_์กฐ์ธ์ปฌ๋Ÿผ๋ช…" ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ const getEntityJoinValue = useCallback( (item: any, columnName: string, entityColumnMap?: Record): any => { // ๐Ÿ†• ๋ฐฑ์—”๋“œ๊ฐ€ ์ œ๊ณตํ•˜๋Š” _label ํ•„๋“œ ์šฐ์„  ์‚ฌ์šฉ // ๋ฐฑ์—”๋“œ๋Š” "ํ‘œ์‹œ ์ปฌ๋Ÿผ"์ด ์„ค์ •๋œ ๊ฒฝ์šฐ columnName_label์„ ์ž๋™ ์ƒ์„ฑ const labelKey = `${columnName}_label`; if (item[labelKey] !== undefined && item[labelKey] !== "" && item[labelKey] !== null) { return item[labelKey]; } // ์ง์ ‘ ๋งค์นญ ์‹œ๋„ (JOIN๋œ ๊ฐ’์ด ์—†์œผ๋ฉด ์›๋ณธ ๊ฐ’ ๋ฐ˜ํ™˜) if (item[columnName] !== undefined) { return item[columnName]; } // "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•์‹์ธ ๊ฒฝ์šฐ (์˜ˆ: item_info.item_name) if (columnName.includes(".")) { const [tableName, fieldName] = columnName.split("."); // ๐Ÿ” ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ปฌ๋Ÿผ ๊ฐ’ ์ถ”์ถœ // ์˜ˆ: item_info.item_name, item_info.standard, item_info.unit // 1๏ธโƒฃ ์†Œ์Šค ์ปฌ๋Ÿผ ์ถ”๋ก  (item_info โ†’ item_code, warehouse_info โ†’ warehouse_id ๋“ฑ) const inferredSourceColumn = tableName.replace("_info", "_code").replace("_mng", "_id"); // 2๏ธโƒฃ ์ •ํ™•ํ•œ ํ‚ค ๋งคํ•‘ ์‹œ๋„: ์†Œ์Šค์ปฌ๋Ÿผ_ํ•„๋“œ๋ช… // ์˜ˆ: item_code_item_name, item_code_standard, item_code_unit const exactKey = `${inferredSourceColumn}_${fieldName}`; if (item[exactKey] !== undefined) { return item[exactKey]; } // ๐Ÿ†• 2-1๏ธโƒฃ item_id ํŒจํ„ด ์‹œ๋„ (๋ฐฑ์—”๋“œ๊ฐ€ item_id_xxx ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ) // ์˜ˆ: item_info.item_name โ†’ item_id_item_name const idPatternKey = `${tableName.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`; if (item[idPatternKey] !== undefined) { return item[idPatternKey]; } // 3๏ธโƒฃ ๋ณ„์นญ ํŒจํ„ด: ์†Œ์Šค์ปฌ๋Ÿผ_name (๊ธฐ๋ณธ ํ‘œ์‹œ ์ปฌ๋Ÿผ์šฉ) // ์˜ˆ: item_code_name (item_name์˜ ๋ณ„์นญ) if (fieldName === "item_name" || fieldName === "name") { const aliasKey = `${inferredSourceColumn}_name`; if (item[aliasKey] !== undefined) { return item[aliasKey]; } // ๐Ÿ†• item_id_name ํŒจํ„ด๋„ ์‹œ๋„ const idAliasKey = `${tableName.replace("_info", "_id").replace("_mng", "_id")}_name`; if (item[idAliasKey] !== undefined) { return item[idAliasKey]; } } // 4๏ธโƒฃ entityColumnMap์—์„œ ๋งคํ•‘ ์ฐพ๊ธฐ (ํ™”๋ฉด ์„ค์ •์—์„œ ์ง€์ •๋œ ๊ฒฝ์šฐ) if (entityColumnMap && entityColumnMap[tableName]) { const sourceColumn = entityColumnMap[tableName]; const joinedColumnName = `${sourceColumn}_${fieldName}`; if (item[joinedColumnName] !== undefined) { return item[joinedColumnName]; } } // 5๏ธโƒฃ ํ…Œ์ด๋ธ”๋ช…_์ปฌ๋Ÿผ๋ช… ํ˜•์‹์œผ๋กœ ์‹œ๋„ const underscoreKey = `${tableName}_${fieldName}`; if (item[underscoreKey] !== undefined) { return item[underscoreKey]; } } return undefined; }, [], ); // TableOptions Context const { registerTable, unregisterTable } = useTableOptions(); const [leftFilters, setLeftFilters] = useState([]); const [leftGrouping, setLeftGrouping] = useState([]); const [leftColumnVisibility, setLeftColumnVisibility] = useState([]); const [leftColumnOrder, setLeftColumnOrder] = useState([]); // ๐Ÿ”ง ์ปฌ๋Ÿผ ์ˆœ์„œ const [leftGroupSumConfig, setLeftGroupSumConfig] = useState(null); // ๐Ÿ†• ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ ์„ค์ • const [rightFilters, setRightFilters] = useState([]); const [rightGrouping, setRightGrouping] = useState([]); const [rightColumnVisibility, setRightColumnVisibility] = useState([]); // ๋ฐ์ดํ„ฐ ์ƒํƒœ const [leftData, setLeftData] = useState([]); const [rightData, setRightData] = useState(null); // ์กฐ์ธ ๋ชจ๋“œ๋Š” ๋ฐฐ์—ด, ์ƒ์„ธ ๋ชจ๋“œ๋Š” ๊ฐ์ฒด const [selectedLeftItem, setSelectedLeftItem] = useState(null); const [expandedRightItems, setExpandedRightItems] = useState>(new Set()); // ํ™•์žฅ๋œ ์šฐ์ธก ์•„์ดํ…œ const [customLeftSelectedData, setCustomLeftSelectedData] = useState>({}); // ์ปค์Šคํ…€ ๋ชจ๋“œ: ์ขŒ์ธก ์„ ํƒ ๋ฐ์ดํ„ฐ // ์ปค์Šคํ…€ ๋ชจ๋“œ: ํƒญ/๋ฒ„ํŠผ ๊ฐ„ ๊ณต์œ ํ•  selectedRowsData ์ž์ฒด ๊ด€๋ฆฌ (ํ•ญ์ƒ ๋กœ์ปฌ ์ƒํƒœ ์‚ฌ์šฉ) const [localSelectedRowsData, setLocalSelectedRowsData] = useState([]); const handleLocalSelectedRowsChange = useCallback( (selectedRows: any[], selectedRowsDataNew: any[], sortBy?: string, sortOrder?: "asc" | "desc", columnOrder?: string[]) => { setLocalSelectedRowsData(selectedRowsDataNew); if ((props as any).onSelectedRowsChange) { (props as any).onSelectedRowsChange(selectedRows, selectedRowsDataNew, sortBy, sortOrder, columnOrder); } }, [(props as any).onSelectedRowsChange], ); const [leftSearchQuery, setLeftSearchQuery] = useState(""); const [rightSearchQuery, setRightSearchQuery] = useState(""); const [isLoadingLeft, setIsLoadingLeft] = useState(false); const [isLoadingRight, setIsLoadingRight] = useState(false); const [rightTableColumns, setRightTableColumns] = useState([]); // ์šฐ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด const [expandedItems, setExpandedItems] = useState>(new Set()); // ํŽผ์ณ์ง„ ํ•ญ๋ชฉ๋“ค // ์ถ”๊ฐ€ ํƒญ ๊ด€๋ จ ์ƒํƒœ const [activeTabIndex, setActiveTabIndex] = useState(0); // 0 = ๊ธฐ๋ณธ ํƒญ, 1+ = ์ถ”๊ฐ€ ํƒญ const [tabsData, setTabsData] = useState>({}); // ํƒญ๋ณ„ ๋ฐ์ดํ„ฐ const [tabsLoading, setTabsLoading] = useState>({}); // ํƒญ๋ณ„ ๋กœ๋”ฉ ์ƒํƒœ const [leftColumnLabels, setLeftColumnLabels] = useState>({}); // ์ขŒ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ const [rightColumnLabels, setRightColumnLabels] = useState>({}); // ์šฐ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ const [leftCategoryMappings, setLeftCategoryMappings] = useState< Record> >({}); // ์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ const [rightCategoryMappings, setRightCategoryMappings] = useState< Record> >({}); // ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋“œ๋ž˜๊ทธ/๋ฆฌ์‚ฌ์ด์ฆˆ ์ƒํƒœ const [draggingCompId, setDraggingCompId] = useState(null); const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null); const [resizingCompId, setResizingCompId] = useState(null); const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null); // ๐Ÿ†• ์™ธ๋ถ€์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์„ ํƒ ์ƒํƒœ ์‚ฌ์šฉ (ํƒญ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผ ๊ตฌ์กฐ) const selectedPanelComponentId = externalSelectedPanelComponentId || null; // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋ถ„ํ• ํŒจ๋„ ๋‚ด ํƒญ ์ปดํฌ๋„ŒํŠธ์˜ ์„ ํƒ ์ƒํƒœ ๊ด€๋ฆฌ const [nestedTabSelectedCompId, setNestedTabSelectedCompId] = useState(undefined); const rafRef = useRef(null); // ๐Ÿ†• 10px ๋‹จ์œ„ ์Šค๋ƒ… ํ•จ์ˆ˜ const snapTo10 = useCallback((value: number) => Math.round(value / 10) * 10, []); // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ์ปดํฌ๋„ŒํŠธ ์‚ญ์ œ ํ•ธ๋“ค๋Ÿฌ const handleRemovePanelComponent = useCallback( (panelSide: "left" | "right", compId: string) => { if (!onUpdateComponent) return; const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel"; const panelConfig = componentConfig[panelKey] || {}; const updatedComponents = (panelConfig.components || []).filter( (c: PanelInlineComponent) => c.id !== compId ); onUpdateComponent({ ...component, componentConfig: { ...componentConfig, [panelKey]: { ...panelConfig, components: updatedComponents, }, }, }); }, [component, componentConfig, onUpdateComponent] ); // ๐Ÿ†• ์ค‘์ฒฉ๋œ ์ปดํฌ๋„ŒํŠธ ์—…๋ฐ์ดํŠธ ํ•ธ๋“ค๋Ÿฌ (ํƒญ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€ ์œ„์น˜ ๋ณ€๊ฒฝ ๋“ฑ) const handleNestedComponentUpdate = useCallback( (panelSide: "left" | "right", compId: string, updatedNestedComponent: any) => { if (!onUpdateComponent) return; const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel"; const panelConfig = componentConfig[panelKey] || {}; const panelComponents = panelConfig.components || []; const updatedComponents = panelComponents.map((c: PanelInlineComponent) => c.id === compId ? { ...c, ...updatedNestedComponent, id: c.id } : c ); onUpdateComponent({ ...component, componentConfig: { ...componentConfig, [panelKey]: { ...panelConfig, components: updatedComponents, }, }, }); }, [component, componentConfig, onUpdateComponent] ); // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ํ•ธ๋“ค๋Ÿฌ const handlePanelDragStart = useCallback( (e: React.MouseEvent, panelSide: "left" | "right", comp: PanelInlineComponent) => { e.stopPropagation(); e.preventDefault(); const startMouseX = e.clientX; const startMouseY = e.clientY; const startLeft = comp.position?.x || 0; const startTop = comp.position?.y || 0; setDraggingCompId(comp.id); setDragPosition({ x: startLeft, y: startTop }); const handleMouseMove = (moveEvent: MouseEvent) => { if (rafRef.current) { cancelAnimationFrame(rafRef.current); } rafRef.current = requestAnimationFrame(() => { const deltaX = moveEvent.clientX - startMouseX; const deltaY = moveEvent.clientY - startMouseY; // 10px ๋‹จ์œ„ ์Šค๋ƒ… ์ ์šฉ const newX = snapTo10(Math.max(0, startLeft + deltaX)); const newY = snapTo10(Math.max(0, startTop + deltaY)); setDragPosition({ x: newX, y: newY }); }); }; const handleMouseUp = (upEvent: MouseEvent) => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } const deltaX = upEvent.clientX - startMouseX; const deltaY = upEvent.clientY - startMouseY; // 10px ๋‹จ์œ„ ์Šค๋ƒ… ์ ์šฉ const newX = snapTo10(Math.max(0, startLeft + deltaX)); const newY = snapTo10(Math.max(0, startTop + deltaY)); setDraggingCompId(null); setDragPosition(null); if (onUpdateComponent) { const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel"; const panelConfig = componentConfig[panelKey] || {}; const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) => c.id === comp.id ? { ...c, position: { x: newX, y: newY } } : c ); onUpdateComponent({ ...component, componentConfig: { ...componentConfig, [panelKey]: { ...panelConfig, components: updatedComponents, }, }, }); } }; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }, [component, componentConfig, onUpdateComponent, snapTo10] ); // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋ฆฌ์‚ฌ์ด์ฆˆ ์‹œ์ž‘ ํ•ธ๋“ค๋Ÿฌ const handlePanelResizeStart = useCallback( (e: React.MouseEvent, panelSide: "left" | "right", comp: PanelInlineComponent, direction: "e" | "s" | "se") => { e.stopPropagation(); e.preventDefault(); const startMouseX = e.clientX; const startMouseY = e.clientY; const startWidth = comp.size?.width || 200; const startHeight = comp.size?.height || 100; setResizingCompId(comp.id); setResizeSize({ width: startWidth, height: startHeight }); const handleMouseMove = (moveEvent: MouseEvent) => { if (rafRef.current) { cancelAnimationFrame(rafRef.current); } rafRef.current = requestAnimationFrame(() => { const deltaX = moveEvent.clientX - startMouseX; const deltaY = moveEvent.clientY - startMouseY; let newWidth = startWidth; let newHeight = startHeight; if (direction === "e" || direction === "se") { newWidth = snapTo10(Math.max(50, startWidth + deltaX)); } if (direction === "s" || direction === "se") { newHeight = snapTo10(Math.max(30, startHeight + deltaY)); } setResizeSize({ width: newWidth, height: newHeight }); }); }; const handleMouseUp = (upEvent: MouseEvent) => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } const deltaX = upEvent.clientX - startMouseX; const deltaY = upEvent.clientY - startMouseY; let newWidth = startWidth; let newHeight = startHeight; if (direction === "e" || direction === "se") { newWidth = snapTo10(Math.max(50, startWidth + deltaX)); } if (direction === "s" || direction === "se") { newHeight = snapTo10(Math.max(30, startHeight + deltaY)); } setResizingCompId(null); setResizeSize(null); if (onUpdateComponent) { const panelKey = panelSide === "left" ? "leftPanel" : "rightPanel"; const panelConfig = componentConfig[panelKey] || {}; const updatedComponents = (panelConfig.components || []).map((c: PanelInlineComponent) => c.id === comp.id ? { ...c, size: { width: newWidth, height: newHeight } } : c ); onUpdateComponent({ ...component, componentConfig: { ...componentConfig, [panelKey]: { ...panelConfig, components: updatedComponents, }, }, }); } }; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }, [component, componentConfig, onUpdateComponent, snapTo10] ); const { toast } = useToast(); // ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ์ƒํƒœ const [showAddModal, setShowAddModal] = useState(false); const [addModalPanel, setAddModalPanel] = useState<"left" | "right" | "left-item" | null>(null); const [addModalFormData, setAddModalFormData] = useState>({}); // ์ˆ˜์ • ๋ชจ๋‹ฌ ์ƒํƒœ const [showEditModal, setShowEditModal] = useState(false); const [editModalPanel, setEditModalPanel] = useState<"left" | "right" | null>(null); const [editModalItem, setEditModalItem] = useState(null); const [editModalFormData, setEditModalFormData] = useState>({}); // ์‚ญ์ œ ํ™•์ธ ๋ชจ๋‹ฌ ์ƒํƒœ const [showDeleteModal, setShowDeleteModal] = useState(false); const [deleteModalPanel, setDeleteModalPanel] = useState<"left" | "right" | null>(null); const [deleteModalItem, setDeleteModalItem] = useState(null); const [deleteModalTableName, setDeleteModalTableName] = useState(null); // ์ถ”๊ฐ€ ํƒญ ์‚ญ์ œ ์‹œ ํ…Œ์ด๋ธ”๋ช… // ๋ฆฌ์‚ฌ์ด์ € ๋“œ๋ž˜๊ทธ ์ƒํƒœ const [isDragging, setIsDragging] = useState(false); const [leftWidth, setLeftWidth] = useState(splitRatio); const containerRef = React.useRef(null); // ๐Ÿ†• SplitPanel Resize Context ์—ฐ๋™ (๋ฒ„ํŠผ ๋“ฑ ์™ธ๋ถ€ ์ปดํฌ๋„ŒํŠธ์™€ ๋“œ๋ž˜๊ทธ ๋ฆฌ์‚ฌ์ด์ฆˆ ์ƒํƒœ ๊ณต์œ ) const splitPanelContext = useSplitPanel(); const { registerSplitPanel: ctxRegisterSplitPanel, unregisterSplitPanel: ctxUnregisterSplitPanel, updateSplitPanel: ctxUpdateSplitPanel, } = splitPanelContext; const splitPanelId = `split-panel-${component.id}`; // ๋””๋ฒ„๊น…: Context ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ console.log("๐Ÿ”— [SplitPanelLayout] Context ์—ฐ๊ฒฐ ์ƒํƒœ:", { componentId: component.id, splitPanelId, hasRegisterFunc: typeof ctxRegisterSplitPanel === "function", splitPanelsSize: splitPanelContext.splitPanels?.size ?? "์—†์Œ", }); // Context์— ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ๋ก (์ขŒํ‘œ ์ •๋ณด ํฌํ•จ) - ๋งˆ์šดํŠธ ์‹œ 1ํšŒ๋งŒ ์‹คํ–‰ const ctxRegisterRef = useRef(ctxRegisterSplitPanel); const ctxUnregisterRef = useRef(ctxUnregisterSplitPanel); ctxRegisterRef.current = ctxRegisterSplitPanel; ctxUnregisterRef.current = ctxUnregisterSplitPanel; useEffect(() => { // ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜์™€ ํฌ๊ธฐ ์ •๋ณด const panelX = component.position?.x || 0; const panelY = component.position?.y || 0; const panelWidth = component.size?.width || component.style?.width || 800; const panelHeight = component.size?.height || component.style?.height || 600; const panelInfo = { x: panelX, y: panelY, width: typeof panelWidth === "number" ? panelWidth : parseInt(String(panelWidth)) || 800, height: typeof panelHeight === "number" ? panelHeight : parseInt(String(panelHeight)) || 600, leftWidthPercent: splitRatio, // ์ดˆ๊ธฐ๊ฐ’์€ splitRatio ์‚ฌ์šฉ initialLeftWidthPercent: splitRatio, isDragging: false, }; console.log("๐Ÿ“ฆ [SplitPanelLayout] Context์— ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ๋ก:", { splitPanelId, panelInfo, }); ctxRegisterRef.current(splitPanelId, panelInfo); return () => { console.log("๐Ÿ“ฆ [SplitPanelLayout] Context์—์„œ ๋ถ„ํ•  ํŒจ๋„ ํ•ด์ œ:", splitPanelId); ctxUnregisterRef.current(splitPanelId); }; // ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์‹คํ–‰, ์œ„์น˜/ํฌ๊ธฐ ๋ณ€๊ฒฝ์€ ๋ณ„๋„ ์—…๋ฐ์ดํŠธ๋กœ ์ฒ˜๋ฆฌ // eslint-disable-next-line react-hooks/exhaustive-deps }, [splitPanelId]); // ์œ„์น˜/ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ Context ์—…๋ฐ์ดํŠธ (๋“ฑ๋ก ํ›„) const ctxUpdateRef = useRef(ctxUpdateSplitPanel); ctxUpdateRef.current = ctxUpdateSplitPanel; useEffect(() => { const panelX = component.position?.x || 0; const panelY = component.position?.y || 0; const panelWidth = component.size?.width || component.style?.width || 800; const panelHeight = component.size?.height || component.style?.height || 600; ctxUpdateRef.current(splitPanelId, { x: panelX, y: panelY, width: typeof panelWidth === "number" ? panelWidth : parseInt(String(panelWidth)) || 800, height: typeof panelHeight === "number" ? panelHeight : parseInt(String(panelHeight)) || 600, }); }, [ splitPanelId, component.position?.x, component.position?.y, component.size?.width, component.size?.height, component.style?.width, component.style?.height, ]); // leftWidth ๋ณ€๊ฒฝ ์‹œ Context ์—…๋ฐ์ดํŠธ useEffect(() => { ctxUpdateRef.current(splitPanelId, { leftWidthPercent: leftWidth }); }, [leftWidth, splitPanelId]); // ๋“œ๋ž˜๊ทธ ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ Context ์—…๋ฐ์ดํŠธ // ์ด์ „ ๋“œ๋ž˜๊ทธ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜์—ฌ ๋“œ๋ž˜๊ทธ ์ข…๋ฃŒ ์‹œ์ ์„ ๊ฐ์ง€ const prevIsDraggingRef = useRef(false); useEffect(() => { const wasJustDragging = prevIsDraggingRef.current && !isDragging; if (isDragging) { // ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ์‹œ: ํ˜„์žฌ ๋น„์œจ์„ ์ดˆ๊ธฐ ๋น„์œจ๋กœ ์ €์žฅ ctxUpdateRef.current(splitPanelId, { isDragging: true, initialLeftWidthPercent: leftWidth, }); } else if (wasJustDragging) { // ๋“œ๋ž˜๊ทธ ์ข…๋ฃŒ ์‹œ: ์ตœ์ข… ๋น„์œจ์„ ์ดˆ๊ธฐ ๋น„์œจ๋กœ ์—…๋ฐ์ดํŠธ (๋ฒ„ํŠผ ์œ„์น˜ ๊ณ ์ •) ctxUpdateRef.current(splitPanelId, { isDragging: false, initialLeftWidthPercent: leftWidth, }); console.log("๐Ÿ›‘ [SplitPanelLayout] ๋“œ๋ž˜๊ทธ ์ข…๋ฃŒ - ๋ฒ„ํŠผ ์œ„์น˜ ๊ณ ์ •:", { splitPanelId, finalLeftWidthPercent: leftWidth, }); } prevIsDraggingRef.current = isDragging; }, [isDragging, splitPanelId, leftWidth]); // ๐Ÿ†• ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ๋œ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐ const summedLeftData = useMemo(() => { console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] leftGroupSumConfig:", leftGroupSumConfig); // ๊ทธ๋ฃนํ•‘์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ๊ฑฐ๋‚˜ ๊ทธ๋ฃน ๊ธฐ์ค€ ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ if (!leftGroupSumConfig?.enabled || !leftGroupSumConfig?.groupByColumn) { console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] ๊ทธ๋ฃนํ•‘ ๋น„ํ™œ์„ฑํ™” - ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜"); return leftData; } const groupByColumn = leftGroupSumConfig.groupByColumn; const groupMap = new Map(); // ์กฐ์ธ ์ปฌ๋Ÿผ์ธ์ง€ ํ™•์ธํ•˜๊ณ  ์‹ค์ œ ํ‚ค ์ถ”๋ก  const getActualKey = (columnName: string, item: any): string => { if (columnName.includes(".")) { const [refTable, fieldName] = columnName.split("."); const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id"); const exactKey = `${inferredSourceColumn}_${fieldName}`; console.log("๐Ÿ” [๊ทธ๋ฃนํ•ฉ์‚ฐ] ์กฐ์ธ ์ปฌ๋Ÿผ ํ‚ค ๋ณ€ํ™˜:", { columnName, exactKey, hasKey: item[exactKey] !== undefined }); if (item[exactKey] !== undefined) return exactKey; if (fieldName === "item_name" || fieldName === "name") { const aliasKey = `${inferredSourceColumn}_name`; if (item[aliasKey] !== undefined) return aliasKey; } } return columnName; }; // ์ˆซ์ž ํƒ€์ž…์ธ์ง€ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜ const isNumericValue = (value: any): boolean => { if (value === null || value === undefined || value === "") return false; const num = parseFloat(String(value)); return !isNaN(num) && isFinite(num); }; // ๊ทธ๋ฃนํ•‘ ์ˆ˜ํ–‰ leftData.forEach((item) => { const actualKey = getActualKey(groupByColumn, item); const groupValue = String(item[actualKey] || item[groupByColumn] || ""); // ์›๋ณธ ID ์ถ”์ถœ (id, ID, ๋˜๋Š” ์ฒซ ๋ฒˆ์งธ ๊ฐ’) const originalId = item.id || item.ID || Object.values(item)[0]; if (!groupMap.has(groupValue)) { // ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์„ ๊ธฐ์ค€์œผ๋กœ ์ดˆ๊ธฐํ™” + ์›๋ณธ ID ๋ฐฐ์—ด + ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด groupMap.set(groupValue, { ...item, _groupCount: 1, _originalIds: [originalId], _originalItems: [item], // ๐Ÿ†• ์›๋ณธ ๋ฐ์ดํ„ฐ ์ „์ฒด ์ €์žฅ }); } else { const existing = groupMap.get(groupValue); existing._groupCount += 1; existing._originalIds.push(originalId); existing._originalItems.push(item); // ๐Ÿ†• ์›๋ณธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ // ๋ชจ๋“  ํ‚ค์— ๋Œ€ํ•ด ์ˆซ์ž๋ฉด ํ•ฉ์‚ฐ Object.keys(item).forEach((key) => { const value = item[key]; if (isNumericValue(value) && key !== groupByColumn && !key.endsWith("_id") && !key.includes("code")) { const numValue = parseFloat(String(value)); const existingValue = parseFloat(String(existing[key] || 0)); existing[key] = existingValue + numValue; } }); groupMap.set(groupValue, existing); } }); const result = Array.from(groupMap.values()); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ ๊ฒฐ๊ณผ:", { ์›๋ณธ๊ฐœ์ˆ˜: leftData.length, ๊ทธ๋ฃน๊ฐœ์ˆ˜: result.length, ๊ทธ๋ฃน๊ธฐ์ค€: groupByColumn, }); return result; }, [leftData, leftGroupSumConfig]); // ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ // height ์ฒ˜๋ฆฌ: ์ด๋ฏธ px ๋‹จ์œ„๋ฉด ๊ทธ๋Œ€๋กœ, ์ˆซ์ž๋ฉด px ์ถ”๊ฐ€ const getHeightValue = () => { const height = component.style?.height; if (!height) return "600px"; if (typeof height === "string") return height; // ์ด๋ฏธ '540px' ํ˜•ํƒœ return `${height}px`; // ์ˆซ์ž๋ฉด px ์ถ”๊ฐ€ }; const componentStyle: React.CSSProperties = isPreview ? { // ๋ฐ˜์‘ํ˜• ๋ชจ๋“œ: position relative, ๊ทธ๋ฆฌ๋“œ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํฌ๊ธฐ ์‚ฌ์šฉ position: "relative", width: "100%", // ๐Ÿ†• ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ ๋„ˆ๋น„์— ๋งž์ถค height: getHeightValue(), border: "1px solid #e5e7eb", } : { // ๋””์ž์ด๋„ˆ ๋ชจ๋“œ: position absolute position: "absolute", left: `${component.style?.positionX || 0}px`, top: `${component.style?.positionY || 0}px`, width: "100%", // ๐Ÿ†• ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ ๋„ˆ๋น„์— ๋งž์ถค (๊ทธ๋ฆฌ๋“œ ๊ธฐ๋ฐ˜) height: getHeightValue(), zIndex: component.style?.positionZ || 1, cursor: isDesignMode ? "pointer" : "default", border: isSelected ? "2px solid #3b82f6" : "1px solid #e5e7eb", }; // ๊ณ„์ธต ๊ตฌ์กฐ ๋นŒ๋“œ ํ•จ์ˆ˜ (ํŠธ๋ฆฌ ๊ตฌ์กฐ ์œ ์ง€) const buildHierarchy = useCallback( (items: any[]): any[] => { if (!items || items.length === 0) return []; const itemAddConfig = componentConfig.leftPanel?.itemAddConfig; if (!itemAddConfig) return items.map((item) => ({ ...item, children: [] })); // ๊ณ„์ธต ์„ค์ •์ด ์—†์œผ๋ฉด ํ‰๋ฉด ๋ชฉ๋ก const { sourceColumn, parentColumn } = itemAddConfig; if (!sourceColumn || !parentColumn) return items.map((item) => ({ ...item, children: [] })); // ID๋ฅผ ํ‚ค๋กœ ํ•˜๋Š” ๋งต ์ƒ์„ฑ const itemMap = new Map(); const rootItems: any[] = []; // ๋ชจ๋“  ํ•ญ๋ชฉ์„ ๋งต์— ์ถ”๊ฐ€ํ•˜๊ณ  children ๋ฐฐ์—ด ์ดˆ๊ธฐํ™” items.forEach((item) => { const id = item[sourceColumn]; itemMap.set(id, { ...item, children: [], level: 0 }); }); // ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„ ์„ค์ • items.forEach((item) => { const id = item[sourceColumn]; const parentId = item[parentColumn]; const currentItem = itemMap.get(id); if (!currentItem) return; if (!parentId || parentId === null || parentId === "") { // ์ตœ์ƒ์œ„ ํ•ญ๋ชฉ rootItems.push(currentItem); } else { // ๋ถ€๋ชจ๊ฐ€ ์žˆ๋Š” ํ•ญ๋ชฉ const parentItem = itemMap.get(parentId); if (parentItem) { currentItem.level = parentItem.level + 1; parentItem.children.push(currentItem); } else { // ๋ถ€๋ชจ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์œผ๋ฉด ์ตœ์ƒ์œ„๋กœ ์ฒ˜๋ฆฌ rootItems.push(currentItem); } } }); return rootItems; }, [componentConfig.leftPanel?.itemAddConfig], ); // ๐Ÿ”ง ์‚ฌ์šฉ์ž ID ๊ฐ€์ ธ์˜ค๊ธฐ const { userId: currentUserId } = useAuth(); // ๐Ÿ”„ ํ•„ํ„ฐ๋ฅผ searchValues ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ const searchValues = useMemo(() => { if (!leftFilters || leftFilters.length === 0) return {}; const values: Record = {}; leftFilters.forEach((filter) => { if (filter.value !== undefined && filter.value !== null && filter.value !== "") { values[filter.columnName] = { value: filter.value, operator: filter.operator || "contains", }; } }); return values; }, [leftFilters]); // ๐Ÿ”„ ์ปฌ๋Ÿผ ๊ฐ€์‹œ์„ฑ ๋ฐ ์ˆœ์„œ ์ฒ˜๋ฆฌ const visibleLeftColumns = useMemo(() => { const displayColumns = componentConfig.leftPanel?.columns || []; if (displayColumns.length === 0) return []; let columns = displayColumns; // columnVisibility๊ฐ€ ์žˆ์œผ๋ฉด ๊ฐ€์‹œ์„ฑ ์ ์šฉ if (leftColumnVisibility.length > 0) { const visibilityMap = new Map(leftColumnVisibility.map((cv) => [cv.columnName, cv.visible])); columns = columns.filter((col: any) => { const colName = typeof col === "string" ? col : col.name || col.columnName; return visibilityMap.get(colName) !== false; }); } // ๐Ÿ”ง ์ปฌ๋Ÿผ ์ˆœ์„œ ์ ์šฉ if (leftColumnOrder.length > 0) { const orderMap = new Map(leftColumnOrder.map((name, index) => [name, index])); columns = [...columns].sort((a, b) => { const aName = typeof a === "string" ? a : a.name || a.columnName; const bName = typeof b === "string" ? b : b.name || b.columnName; const aIndex = orderMap.get(aName) ?? 999; const bIndex = orderMap.get(bName) ?? 999; return aIndex - bIndex; }); } return columns; }, [componentConfig.leftPanel?.columns, leftColumnVisibility, leftColumnOrder]); // ๐Ÿ”„ ๋ฐ์ดํ„ฐ ๊ทธ๋ฃนํ™” const groupedLeftData = useMemo(() => { if (!leftGrouping || leftGrouping.length === 0 || leftData.length === 0) return []; const grouped = new Map(); leftData.forEach((item) => { // ๊ฐ ๊ทธ๋ฃน ์ปฌ๋Ÿผ์˜ ๊ฐ’์„ ์กฐํ•ฉํ•˜์—ฌ ๊ทธ๋ฃน ํ‚ค ์ƒ์„ฑ const groupKey = leftGrouping .map((col) => { const value = item[col]; // null/undefined ์ฒ˜๋ฆฌ return value === null || value === undefined ? "(๋น„์–ด์žˆ์Œ)" : String(value); }) .join(" > "); if (!grouped.has(groupKey)) { grouped.set(groupKey, []); } grouped.get(groupKey)!.push(item); }); return Array.from(grouped.entries()).map(([key, items]) => ({ groupKey: key, items, count: items.length, })); }, [leftData, leftGrouping]); // ๋‚ ์งœ ํฌ๋งทํŒ… ํ—ฌํผ ํ•จ์ˆ˜ const formatDateValue = useCallback((value: any, dateFormat: string): string => { if (!value) return "-"; const date = new Date(value); if (isNaN(date.getTime())) return String(value); if (dateFormat === "relative") { // ์ƒ๋Œ€ ์‹œ๊ฐ„ (์˜ˆ: 3์ผ ์ „, 2์‹œ๊ฐ„ ์ „) const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHour = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHour / 24); const diffMonth = Math.floor(diffDay / 30); const diffYear = Math.floor(diffMonth / 12); if (diffYear > 0) return `${diffYear}๋…„ ์ „`; if (diffMonth > 0) return `${diffMonth}๊ฐœ์›” ์ „`; if (diffDay > 0) return `${diffDay}์ผ ์ „`; if (diffHour > 0) return `${diffHour}์‹œ๊ฐ„ ์ „`; if (diffMin > 0) return `${diffMin}๋ถ„ ์ „`; return "๋ฐฉ๊ธˆ ์ „"; } // ํฌ๋งท ๋ฌธ์ž์—ด ์น˜ํ™˜ return dateFormat .replace("YYYY", String(date.getFullYear())) .replace("MM", String(date.getMonth() + 1).padStart(2, "0")) .replace("DD", String(date.getDate()).padStart(2, "0")) .replace("HH", String(date.getHours()).padStart(2, "0")) .replace("mm", String(date.getMinutes()).padStart(2, "0")) .replace("ss", String(date.getSeconds()).padStart(2, "0")); }, []); // ์ˆซ์ž ํฌ๋งทํŒ… ํ—ฌํผ ํ•จ์ˆ˜ const formatNumberValue = useCallback((value: any, format: any): string => { if (value === null || value === undefined || value === "") return "-"; const num = typeof value === "number" ? value : parseFloat(String(value)); if (isNaN(num)) return String(value); const options: Intl.NumberFormatOptions = { minimumFractionDigits: format?.decimalPlaces ?? 0, maximumFractionDigits: format?.decimalPlaces ?? 10, useGrouping: format?.thousandSeparator ?? false, }; let result = num.toLocaleString("ko-KR", options); if (format?.prefix) result = format.prefix + result; if (format?.suffix) result = result + format.suffix; return result; }, []); // ์…€ ๊ฐ’ ํฌ๋งทํŒ… ํ•จ์ˆ˜ (์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ์ฒ˜๋ฆฌ + ๋‚ ์งœ/์ˆซ์ž ํฌ๋งท) const formatCellValue = useCallback( ( columnName: string, value: any, categoryMappings: Record>, format?: { type?: "number" | "currency" | "date" | "text"; thousandSeparator?: boolean; decimalPlaces?: number; prefix?: string; suffix?: string; dateFormat?: string; }, ) => { if (value === null || value === undefined) return "-"; // ๐Ÿ†• ๋‚ ์งœ ํฌ๋งท ์ ์šฉ if (format?.type === "date" || format?.dateFormat) { return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD"); } // ๐Ÿ†• ์ˆซ์ž ํฌ๋งท ์ ์šฉ if ( format?.type === "number" || format?.type === "currency" || format?.thousandSeparator || format?.decimalPlaces !== undefined ) { return formatNumberValue(value, format); } // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ฐพ๊ธฐ (์—ฌ๋Ÿฌ ํ‚ค ํ˜•ํƒœ ์‹œ๋„) // 1. ์ „์ฒด ์ปฌ๋Ÿผ๋ช… (์˜ˆ: "item_info.material") // 2. ์ปฌ๋Ÿผ๋ช…๋งŒ (์˜ˆ: "material") let mapping = categoryMappings[columnName]; if (!mapping && columnName.includes(".")) { // ์กฐ์ธ๋œ ์ปฌ๋Ÿผ์˜ ๊ฒฝ์šฐ ์ปฌ๋Ÿผ๋ช…๋งŒ์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ const simpleColumnName = columnName.split(".").pop() || columnName; mapping = categoryMappings[simpleColumnName]; } if (mapping && mapping[String(value)]) { const categoryData = mapping[String(value)]; const displayLabel = categoryData.label || String(value); const displayColor = categoryData.color || "#64748b"; // ๋ฐฐ์ง€๋กœ ํ‘œ์‹œ return ( {displayLabel} ); } // ๐Ÿ†• ์ž๋™ ๋‚ ์งœ ๊ฐ์ง€ (ISO 8601 ํ˜•์‹ ๋˜๋Š” Date ๊ฐ์ฒด) if (typeof value === "string" && value.match(/^\d{4}-\d{2}-\d{2}(T|\s)/)) { return formatDateValue(value, "YYYY-MM-DD"); } // ๐Ÿ†• ์ž๋™ ์ˆซ์ž ๊ฐ์ง€ (์ˆซ์ž ๋˜๋Š” ์ˆซ์ž ๋ฌธ์ž์—ด) - ์†Œ์ˆ˜์  ์žˆ์œผ๋ฉด ์ •์ˆ˜๋กœ ๋ณ€ํ™˜ if (typeof value === "number") { // ์ˆซ์ž์ธ ๊ฒฝ์šฐ ์ •์ˆ˜๋กœ ํ‘œ์‹œ (์†Œ์ˆ˜์  ์ œ๊ฑฐ) return Number.isInteger(value) ? String(value) : String(Math.round(value * 100) / 100); } if (typeof value === "string" && /^-?\d+\.?\d*$/.test(value.trim())) { // ์ˆซ์ž ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ (์˜ˆ: "5.00" โ†’ "5") const num = parseFloat(value); if (!isNaN(num)) { return Number.isInteger(num) ? String(num) : String(Math.round(num * 100) / 100); } } // ์ผ๋ฐ˜ ๊ฐ’ return String(value); }, [formatDateValue, formatNumberValue], ); // ๐Ÿ†• ํŒจ๋„ config์˜ columns์—์„œ additionalJoinColumns ์ถ”์ถœํ•˜๋Š” ํ—ฌํผ const extractAdditionalJoinColumns = useCallback((columns: any[] | undefined, tableName: string) => { if (!columns || columns.length === 0) return undefined; const joinColumns: Array<{ sourceTable: string; sourceColumn: string; referenceTable: string; joinAlias: string; }> = []; columns.forEach((col: any) => { // ๋ฐฉ๋ฒ• 1: isEntityJoin ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ (์„ค์ • ํŒจ๋„์—์„œ Entity ์กฐ์ธ ์ปฌ๋Ÿผ์œผ๋กœ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ) if (col.isEntityJoin && col.joinInfo) { const existing = joinColumns.find( (j) => j.referenceTable === col.joinInfo.referenceTable && j.joinAlias === col.joinInfo.joinAlias ); if (!existing) { joinColumns.push({ sourceTable: col.joinInfo.sourceTable || tableName, sourceColumn: col.joinInfo.sourceColumn, referenceTable: col.joinInfo.referenceTable, joinAlias: col.joinInfo.joinAlias, }); } return; } // ๋ฐฉ๋ฒ• 2: "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•์‹ (๊ธฐ์กด ์ขŒ์ธก ํŒจ๋„ ๋ฐฉ์‹) const colName = typeof col === "string" ? col : col.name || col.columnName; if (colName && colName.includes(".")) { const [refTable, refColumn] = colName.split("."); const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id"); const existing = joinColumns.find( (j) => j.referenceTable === refTable && j.sourceColumn === inferredSourceColumn ); if (!existing) { joinColumns.push({ sourceTable: tableName, sourceColumn: inferredSourceColumn, referenceTable: refTable, joinAlias: `${inferredSourceColumn}_${refColumn}`, }); } else { // ์ด๋ฏธ ์ถ”๊ฐ€๋œ ํ…Œ์ด๋ธ”์ด๋ฉด ๋ณ„์นญ๋งŒ ์ถ”๊ฐ€ const newAlias = `${inferredSourceColumn}_${refColumn}`; if (!joinColumns.find((j) => j.joinAlias === newAlias)) { joinColumns.push({ sourceTable: tableName, sourceColumn: inferredSourceColumn, referenceTable: refTable, joinAlias: newAlias, }); } } } }); return joinColumns.length > 0 ? joinColumns : undefined; }, []); // ์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ const loadLeftData = useCallback(async () => { const leftTableName = componentConfig.leftPanel?.tableName; if (!leftTableName || isDesignMode) return; setIsLoadingLeft(true); try { // ๐ŸŽฏ ํ•„ํ„ฐ ์กฐ๊ฑด์„ API์— ์ „๋‹ฌ (entityJoinApi ์‚ฌ์šฉ) const filters = Object.keys(searchValues).length > 0 ? searchValues : undefined; // ๐Ÿ†• ์ขŒ์ธก ํŒจ๋„ config์˜ Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ถ”์ถœ (ํ—ฌํผ ํ•จ์ˆ˜ ์‚ฌ์šฉ) const leftJoinColumns = extractAdditionalJoinColumns( componentConfig.leftPanel?.columns, leftTableName, ); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ์ขŒ์ธก additionalJoinColumns:", leftJoinColumns); const result = await entityJoinApi.getTableDataWithJoins(leftTableName, { page: 1, size: 100, search: filters, enableEntityJoin: true, dataFilter: componentConfig.leftPanel?.dataFilter, additionalJoinColumns: leftJoinColumns, companyCodeOverride: companyCode, }); // ๐Ÿ” ๋””๋ฒ„๊น…: API ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ํ‚ค ํ™•์ธ if (result.data && result.data.length > 0) { console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] API ์‘๋‹ต ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ํ‚ค:", Object.keys(result.data[0])); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] API ์‘๋‹ต ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ:", result.data[0]); } // ๊ฐ€๋‚˜๋‹ค์ˆœ ์ •๋ ฌ (์ขŒ์ธก ํŒจ๋„์˜ ํ‘œ์‹œ ์ปฌ๋Ÿผ ๊ธฐ์ค€) const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; if (leftColumn && result.data.length > 0) { result.data.sort((a, b) => { const aValue = String(a[leftColumn] || ""); const bValue = String(b[leftColumn] || ""); return aValue.localeCompare(bValue, "ko-KR"); }); } // ๊ณ„์ธต ๊ตฌ์กฐ ๋นŒ๋“œ const hierarchicalData = buildHierarchy(result.data); setLeftData(hierarchicalData); } catch (error) { console.error("์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", error); toast({ title: "๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ", description: "์ขŒ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } finally { setIsLoadingLeft(false); } }, [ componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, componentConfig.leftPanel?.dataFilter, componentConfig.rightPanel?.relation?.leftColumn, isDesignMode, toast, buildHierarchy, searchValues, ]); // ์šฐ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ (leftItem์ด null์ด๋ฉด ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ) const loadRightData = useCallback( async (leftItem: any) => { const relationshipType = componentConfig.rightPanel?.relation?.type || "detail"; const rightTableName = componentConfig.rightPanel?.tableName; if (!rightTableName || isDesignMode) return; // ์ขŒ์ธก ๋ฏธ์„ ํƒ ์‹œ: ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ (dataFilter ์ ์šฉ) if (!leftItem && relationshipType === "join") { setIsLoadingRight(true); try { const rightJoinColumns = extractAdditionalJoinColumns( componentConfig.rightPanel?.columns, rightTableName, ); const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: rightJoinColumns, dataFilter: componentConfig.rightPanel?.dataFilter, }); // dataFilter ์ ์šฉ let filteredData = result.data || []; const dataFilter = componentConfig.rightPanel?.dataFilter; if (dataFilter?.enabled && dataFilter.filters?.length > 0) { filteredData = filteredData.filter((item: any) => { return dataFilter.filters.every((cond: any) => { const value = item[cond.columnName]; switch (cond.operator) { case "equals": return value === cond.value; case "notEquals": return value !== cond.value; case "contains": return String(value || "").includes(String(cond.value)); case "is_null": return value === null || value === undefined || value === ""; case "is_not_null": return value !== null && value !== undefined && value !== ""; default: return true; } }); }); } // conditions ํ˜•์‹ dataFilter๋„ ์ง€์› (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) const dataFilterConditions = componentConfig.rightPanel?.dataFilter; if (dataFilterConditions?.enabled && dataFilterConditions.conditions?.length > 0) { filteredData = filteredData.filter((item: any) => { return dataFilterConditions.conditions.every((cond: any) => { const value = item[cond.column]; switch (cond.operator) { case "equals": return value === cond.value; case "notEquals": return value !== cond.value; case "contains": return String(value || "").includes(String(cond.value)); default: return true; } }); }); } setRightData(filteredData); } catch (error) { console.error("์šฐ์ธก ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", error); } finally { setIsLoadingRight(false); } return; } // leftItem์ด null์ด๋ฉด join ๋ชจ๋“œ ์ด์™ธ์—๋Š” ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋ถˆ๊ฐ€ if (!leftItem) return; setIsLoadingRight(true); try { if (relationshipType === "detail") { // ์ƒ์„ธ ๋ชจ๋“œ: ๋™์ผ ํ…Œ์ด๋ธ”์˜ ์ƒ์„ธ ์ •๋ณด (์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ํ™œ์„ฑํ™”) const primaryKey = leftItem.id || leftItem.ID || Object.values(leftItem)[0]; // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ API ์‚ฌ์šฉ const { entityJoinApi } = await import("@/lib/api/entityJoin"); const rightDetailJoinColumns = extractAdditionalJoinColumns( componentConfig.rightPanel?.columns, rightTableName, ); const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { search: { id: primaryKey }, enableEntityJoin: true, size: 1, companyCodeOverride: companyCode, additionalJoinColumns: rightDetailJoinColumns, // ๐Ÿ†• Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ „๋‹ฌ }); const detail = result.items && result.items.length > 0 ? result.items[0] : null; setRightData(detail); } else if (relationshipType === "join") { // ์กฐ์ธ ๋ชจ๋“œ: ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ ๊ด€๋ จ ๋ฐ์ดํ„ฐ (์—ฌ๋Ÿฌ ๊ฐœ) const keys = componentConfig.rightPanel?.relation?.keys; const leftTable = componentConfig.leftPanel?.tableName; // ๐Ÿ†• ๊ทธ๋ฃน ํ•ฉ์‚ฐ๋œ ํ•ญ๋ชฉ์ธ ๊ฒฝ์šฐ: ์›๋ณธ ๋ฐ์ดํ„ฐ๋“ค๋กœ ์šฐ์ธก ํŒจ๋„ ํ‘œ์‹œ if (leftItem._originalItems && leftItem._originalItems.length > 0) { console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๊ทธ๋ฃน ํ•ฉ์‚ฐ ํ•ญ๋ชฉ - ์›๋ณธ ๊ฐœ์ˆ˜:", leftItem._originalItems.length); // ์ •๋ ฌ ๊ธฐ์ค€ ์ปฌ๋Ÿผ (๋ณตํ•ฉํ‚ค์˜ leftColumn๋“ค) const sortColumns = keys?.map((k: any) => k.leftColumn).filter(Boolean) || []; console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ์ •๋ ฌ ๊ธฐ์ค€ ์ปฌ๋Ÿผ:", sortColumns); // ์ •๋ ฌ ํ•จ์ˆ˜ const sortByKeys = (data: any[]) => { if (sortColumns.length === 0) return data; return [...data].sort((a, b) => { for (const col of sortColumns) { const aVal = String(a[col] || ""); const bVal = String(b[col] || ""); const cmp = aVal.localeCompare(bVal, "ko-KR"); if (cmp !== 0) return cmp; } return 0; }); }; // ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์šฐ์ธก ํŒจ๋„์— ํ‘œ์‹œ (์ด๋ ฅ ํ…Œ์ด๋ธ”๊ณผ ๋™์ผ ํ…Œ์ด๋ธ”์ธ ๊ฒฝ์šฐ) if (leftTable === rightTableName) { const sortedData = sortByKeys(leftItem._originalItems); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋™์ผ ํ…Œ์ด๋ธ” - ์ •๋ ฌ๋œ ์›๋ณธ ๋ฐ์ดํ„ฐ:", sortedData.length); setRightData(sortedData); return; } // ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์ธ ๊ฒฝ์šฐ: ์›๋ณธ ID๋“ค๋กœ ์กฐํšŒ const { entityJoinApi } = await import("@/lib/api/entityJoin"); const allResults: any[] = []; // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ถ”์ถœ (๊ทธ๋ฃน ํ•ฉ์‚ฐ์šฉ) const rightJoinColumnsForGroup = extractAdditionalJoinColumns( componentConfig.rightPanel?.columns, rightTableName, ); // ๊ฐ ์›๋ณธ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์กฐํšŒ for (const originalItem of leftItem._originalItems) { const searchConditions: Record = {}; keys?.forEach((key: any) => { if (key.leftColumn && key.rightColumn && originalItem[key.leftColumn] !== undefined) { searchConditions[key.rightColumn] = originalItem[key.leftColumn]; } }); if (Object.keys(searchConditions).length > 0) { const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { search: searchConditions, enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: rightJoinColumnsForGroup, // ๐Ÿ†• Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ „๋‹ฌ }); if (result.data) { allResults.push(...result.data); } } } // ์ •๋ ฌ ์ ์šฉ const sortedResults = sortByKeys(allResults); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๊ทธ๋ฃน ํ•ฉ์‚ฐ - ์šฐ์ธก ํŒจ๋„ ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ:", sortedResults.length); setRightData(sortedResults); return; } // ๐Ÿ†• ๋ณตํ•ฉํ‚ค ์ง€์› if (keys && keys.length > 0 && leftTable) { // ๋ณตํ•ฉํ‚ค: ์—ฌ๋Ÿฌ ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋ง const { entityJoinApi } = await import("@/lib/api/entityJoin"); // ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด ์ƒ์„ฑ const searchConditions: Record = {}; keys.forEach((key) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { searchConditions[key.rightColumn] = leftItem[key.leftColumn]; } }); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋ณตํ•ฉํ‚ค ์กฐ๊ฑด:", searchConditions); // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ config์˜ Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ถ”์ถœ const rightJoinColumns = extractAdditionalJoinColumns( componentConfig.rightPanel?.columns, rightTableName, ); if (rightJoinColumns) { console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ์šฐ์ธก ํŒจ๋„ additionalJoinColumns:", rightJoinColumns); } // ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ API๋กœ ๋ฐ์ดํ„ฐ ์กฐํšŒ const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { search: searchConditions, enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: rightJoinColumns, // ๐Ÿ†• Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ „๋‹ฌ }); console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋ณตํ•ฉํ‚ค ์กฐํšŒ ๊ฒฐ๊ณผ:", result); // ์ถ”๊ฐ€ dataFilter ์ ์šฉ let filteredData = result.data || []; const dataFilter = componentConfig.rightPanel?.dataFilter; if (dataFilter?.enabled && dataFilter.conditions?.length > 0) { filteredData = filteredData.filter((item: any) => { return dataFilter.conditions.every((cond: any) => { const value = item[cond.column]; const condValue = cond.value; switch (cond.operator) { case "equals": return value === condValue; case "notEquals": return value !== condValue; case "contains": return String(value).includes(String(condValue)); default: return true; } }); }); } setRightData(filteredData); } else { // ๋‹จ์ผํ‚ค (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) โ†’ entityJoinApi ์‚ฌ์šฉ์œผ๋กœ ์ „ํ™˜ (entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ง€์›) const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; const rightColumn = componentConfig.rightPanel?.relation?.foreignKey; if (leftColumn && rightColumn && leftTable) { const leftValue = leftItem[leftColumn]; const { entityJoinApi } = await import("@/lib/api/entityJoin"); // ๋‹จ์ผํ‚ค๋ฅผ ๋ณตํ•ฉํ‚ค ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ const searchConditions: Record = {}; searchConditions[rightColumn] = leftValue; // Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ถ”์ถœ const rightJoinColumnsLegacy = extractAdditionalJoinColumns( componentConfig.rightPanel?.columns, rightTableName, ); if (rightJoinColumnsLegacy) { console.log("๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ๋‹จ์ผํ‚ค ๋ชจ๋“œ additionalJoinColumns:", rightJoinColumnsLegacy); } const result = await entityJoinApi.getTableDataWithJoins(rightTableName, { search: searchConditions, enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: rightJoinColumnsLegacy, }); let filteredDataLegacy = result.data || []; // ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ ์šฉ const dataFilterLegacy = componentConfig.rightPanel?.dataFilter; if (dataFilterLegacy?.enabled && dataFilterLegacy.conditions?.length > 0) { filteredDataLegacy = filteredDataLegacy.filter((item: any) => { return dataFilterLegacy.conditions.every((cond: any) => { const value = item[cond.column]; const condValue = cond.value; switch (cond.operator) { case "equals": return value === condValue; case "notEquals": return value !== condValue; case "contains": return String(value).includes(String(condValue)); default: return true; } }); }); } setRightData(filteredDataLegacy || []); } } } } catch (error) { console.error("์šฐ์ธก ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", error); toast({ title: "๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ", description: "์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } finally { setIsLoadingRight(false); } }, [ componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.relation, componentConfig.leftPanel?.tableName, isDesignMode, toast, ], ); // ์ถ”๊ฐ€ ํƒญ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ํ•จ์ˆ˜ (leftItem์ด null์ด๋ฉด ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ) const loadTabData = useCallback( async (tabIndex: number, leftItem: any) => { const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1]; if (!tabConfig || isDesignMode) return; const tabTableName = tabConfig.tableName; if (!tabTableName) return; setTabsLoading((prev) => ({ ...prev, [tabIndex]: true })); try { const keys = tabConfig.relation?.keys; const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn; const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn; // ํƒญ config์˜ Entity ์กฐ์ธ ์ปฌ๋Ÿผ ์ถ”์ถœ const tabJoinColumns = extractAdditionalJoinColumns(tabConfig.columns, tabTableName); if (tabJoinColumns) { console.log(`๐Ÿ”— [๋ถ„ํ• ํŒจ๋„] ํƒญ ${tabIndex} additionalJoinColumns:`, tabJoinColumns); } let resultData: any[] = []; // ํƒญ์˜ dataFilter (API ์ „๋‹ฌ์šฉ) const tabDataFilterForApi = (tabConfig as any).dataFilter; if (!leftItem) { // ์ขŒ์ธก ๋ฏธ์„ ํƒ: ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ (dataFilter๋Š” API์— ์ „๋‹ฌ) const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: tabJoinColumns, dataFilter: tabDataFilterForApi, }); resultData = result.data || []; } else if (leftColumn && rightColumn) { const searchConditions: Record = {}; if (keys && keys.length > 0) { keys.forEach((key: any) => { if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) { searchConditions[key.rightColumn] = { value: leftItem[key.leftColumn], operator: "equals", }; } }); } else { const leftValue = leftItem[leftColumn]; if (leftValue !== undefined) { searchConditions[rightColumn] = { value: leftValue, operator: "equals", }; } } const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { search: searchConditions, enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: tabJoinColumns, dataFilter: tabDataFilterForApi, }); resultData = result.data || []; } else { const result = await entityJoinApi.getTableDataWithJoins(tabTableName, { enableEntityJoin: true, size: 1000, companyCodeOverride: companyCode, additionalJoinColumns: tabJoinColumns, dataFilter: tabDataFilterForApi, }); resultData = result.data || []; } // ํƒญ๋ณ„ dataFilter ์ ์šฉ const tabDataFilter = (tabConfig as any).dataFilter; if (tabDataFilter?.enabled && tabDataFilter.filters?.length > 0) { resultData = resultData.filter((item: any) => { return tabDataFilter.filters.every((cond: any) => { const value = item[cond.columnName]; switch (cond.operator) { case "equals": return value === cond.value; case "notEquals": return value !== cond.value; case "contains": return String(value || "").includes(String(cond.value)); case "is_null": return value === null || value === undefined || value === ""; case "is_not_null": return value !== null && value !== undefined && value !== ""; default: return true; } }); }); } setTabsData((prev) => ({ ...prev, [tabIndex]: resultData })); } catch (error) { console.error(`์ถ”๊ฐ€ํƒญ ${tabIndex} ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:`, error); toast({ title: "๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ", description: `ํƒญ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.`, variant: "destructive", }); } finally { setTabsLoading((prev) => ({ ...prev, [tabIndex]: false })); } }, [componentConfig.rightPanel?.additionalTabs, isDesignMode, toast], ); // ํƒญ ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ (์ขŒ์ธก ๋ฏธ์„ ํƒ ์‹œ์—๋„ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ) const handleTabChange = useCallback( (newTabIndex: number) => { setActiveTabIndex(newTabIndex); if (newTabIndex === 0) { if (!rightData || (Array.isArray(rightData) && rightData.length === 0)) { loadRightData(selectedLeftItem); } } else { if (!tabsData[newTabIndex]) { loadTabData(newTabIndex, selectedLeftItem); } } }, [selectedLeftItem, rightData, tabsData, loadRightData, loadTabData], ); // ์ขŒ์ธก ํ•ญ๋ชฉ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ (๋™์ผ ํ•ญ๋ชฉ ์žฌํด๋ฆญ ์‹œ ์„ ํƒ ํ•ด์ œ โ†’ ์ „์ฒด ๋ฐ์ดํ„ฐ ํ‘œ์‹œ) const handleLeftItemSelect = useCallback( (item: any) => { // ๋™์ผ ํ•ญ๋ชฉ ํด๋ฆญ ์‹œ ์„ ํƒ ํ•ด์ œ (์ „์ฒด ๋ณด๊ธฐ๋กœ ๋ณต๊ท€) const leftPk = componentConfig.rightPanel?.relation?.leftColumn || componentConfig.rightPanel?.relation?.keys?.[0]?.leftColumn; const isSameItem = selectedLeftItem && leftPk && selectedLeftItem[leftPk] === item[leftPk]; if (isSameItem) { // ์„ ํƒ ํ•ด์ œ โ†’ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ setSelectedLeftItem(null); setCustomLeftSelectedData({}); // ์ปค์Šคํ…€ ๋ชจ๋“œ ์šฐ์ธก ํผ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” setExpandedRightItems(new Set()); setTabsData({}); if (activeTabIndex === 0) { loadRightData(null); } else { loadTabData(activeTabIndex, null); } // ์ถ”๊ฐ€ ํƒญ๋“ค๋„ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ const tabs = componentConfig.rightPanel?.additionalTabs; if (tabs && tabs.length > 0) { tabs.forEach((_: any, idx: number) => { if (idx + 1 !== activeTabIndex) { loadTabData(idx + 1, null); } }); } return; } setSelectedLeftItem(item); setCustomLeftSelectedData(item); // ์ปค์Šคํ…€ ๋ชจ๋“œ ์šฐ์ธก ํผ์— ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ setExpandedRightItems(new Set()); // ์ขŒ์ธก ํ•ญ๋ชฉ ๋ณ€๊ฒฝ ์‹œ ์šฐ์ธก ํ™•์žฅ ์ดˆ๊ธฐํ™” setTabsData({}); // ๋ชจ๋“  ํƒญ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” // ํ˜„์žฌ ํ™œ์„ฑ ํƒญ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ if (activeTabIndex === 0) { loadRightData(item); } else { loadTabData(activeTabIndex, item); } // modalDataStore์— ์„ ํƒ๋œ ์ขŒ์ธก ํ•ญ๋ชฉ ์ €์žฅ (๋‹จ์ผ ์„ ํƒ) const leftTableName = componentConfig.leftPanel?.tableName; if (leftTableName && !isDesignMode) { import("@/stores/modalDataStore").then(({ useModalDataStore }) => { useModalDataStore.getState().setData(leftTableName, [item]); console.log(`โœ… ๋ถ„ํ•  ํŒจ๋„ ์ขŒ์ธก ์„ ํƒ: ${leftTableName}`, item); }); } }, [loadRightData, loadTabData, activeTabIndex, componentConfig.leftPanel?.tableName, componentConfig.rightPanel?.relation, componentConfig.rightPanel?.additionalTabs, isDesignMode, selectedLeftItem], ); // ์šฐ์ธก ํ•ญ๋ชฉ ํ™•์žฅ/์ถ•์†Œ ํ† ๊ธ€ const toggleRightItemExpansion = useCallback((itemId: string | number) => { setExpandedRightItems((prev) => { const newSet = new Set(prev); if (newSet.has(itemId)) { newSet.delete(itemId); } else { newSet.add(itemId); } return newSet; }); }, []); // ์ปฌ๋Ÿผ๋ช…์„ ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜ const getColumnLabel = useCallback( (columnName: string) => { const column = rightTableColumns.find((col) => col.columnName === columnName || col.column_name === columnName); return column?.columnLabel || column?.column_label || column?.displayName || columnName; }, [rightTableColumns], ); // ๐Ÿ”ง ์ปฌ๋Ÿผ์˜ ๊ณ ์œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜ const getLeftColumnUniqueValues = useCallback( async (columnName: string) => { const leftTableName = componentConfig.leftPanel?.tableName; if (!leftTableName || leftData.length === 0) return []; // ํ˜„์žฌ ๋กœ๋“œ๋œ ๋ฐ์ดํ„ฐ์—์„œ ๊ณ ์œ ๊ฐ’ ์ถ”์ถœ const uniqueValues = new Set(); leftData.forEach((item) => { // ๐Ÿ†• ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ (item_info.standard โ†’ item_code_standard ๋˜๋Š” item_id_standard) let value: any; if (columnName.includes(".")) { // ์กฐ์ธ ์ปฌ๋Ÿผ: getEntityJoinValue์™€ ๋™์ผํ•œ ๋กœ์ง ์ ์šฉ const [refTable, fieldName] = columnName.split("."); const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id"); // ์ •ํ™•ํ•œ ํ‚ค๋กœ ๋จผ์ € ์‹œ๋„ const exactKey = `${inferredSourceColumn}_${fieldName}`; value = item[exactKey]; // ๐Ÿ†• item_id ํŒจํ„ด ์‹œ๋„ if (value === undefined) { const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`; value = item[idPatternKey]; } // ๊ธฐ๋ณธ ๋ณ„์นญ ํŒจํ„ด ์‹œ๋„ (item_code_name ๋˜๋Š” item_id_name) if (value === undefined && (fieldName === "item_name" || fieldName === "name")) { const aliasKey = `${inferredSourceColumn}_name`; value = item[aliasKey]; // item_id_name ํŒจํ„ด๋„ ์‹œ๋„ if (value === undefined) { const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`; value = item[idAliasKey]; } } } else { // ์ผ๋ฐ˜ ์ปฌ๋Ÿผ value = item[columnName]; } if (value !== null && value !== undefined && value !== "") { // _name ํ•„๋“œ ์šฐ์„  ์‚ฌ์šฉ (category/entity type) const displayValue = item[`${columnName}_name`] || value; uniqueValues.add(String(displayValue)); } }); return Array.from(uniqueValues).map((value) => ({ value: value, label: value, })); }, [componentConfig.leftPanel?.tableName, leftData], ); // ์ขŒ์ธก ํ…Œ์ด๋ธ” ๋“ฑ๋ก (Context์— ๋“ฑ๋ก) useEffect(() => { const leftTableName = componentConfig.leftPanel?.tableName; if (!leftTableName || isDesignMode) return; const leftTableId = `split-panel-left-${component.id}`; // ๐Ÿ”ง ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” ์ปฌ๋Ÿผ ์‚ฌ์šฉ (columns ์†์„ฑ) const configuredColumns = componentConfig.leftPanel?.columns || []; // ๐Ÿ†• ์„ค์ •์—์„œ ์ง€์ •ํ•œ ๋ผ๋ฒจ ๋งต ์ƒ์„ฑ const configuredLabels: Record = {}; configuredColumns.forEach((col: any) => { if (typeof col === "object" && col.name && col.label) { configuredLabels[col.name] = col.label; } }); const displayColumns = configuredColumns .map((col: any) => { if (typeof col === "string") return col; return col.columnName || col.name || col; }) .filter(Boolean); // ํ™”๋ฉด์— ์„ค์ •๋œ ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ๋“ฑ๋กํ•˜์ง€ ์•Š์Œ if (displayColumns.length === 0) return; // ํ…Œ์ด๋ธ”๋ช…์ด ์žˆ์œผ๋ฉด ๋“ฑ๋ก registerTable({ tableId: leftTableId, label: `${component.title || "๋ถ„ํ•  ํŒจ๋„"} (์ขŒ์ธก)`, tableName: leftTableName, columns: displayColumns.map((col: string) => ({ columnName: col, // ๐Ÿ†• ์šฐ์„ ์ˆœ์œ„: 1) ์„ค์ •์—์„œ ์ง€์ •ํ•œ ๋ผ๋ฒจ 2) DB ๋ผ๋ฒจ 3) ์ปฌ๋Ÿผ๋ช… columnLabel: configuredLabels[col] || leftColumnLabels[col] || col, inputType: "text", visible: true, width: 150, sortable: true, filterable: true, })), onFilterChange: setLeftFilters, onGroupChange: setLeftGrouping, onColumnVisibilityChange: setLeftColumnVisibility, onColumnOrderChange: setLeftColumnOrder, // ๐Ÿ”ง ์ปฌ๋Ÿผ ์ˆœ์„œ ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ ์ถ”๊ฐ€ getColumnUniqueValues: getLeftColumnUniqueValues, // ๐Ÿ”ง ๊ณ ์œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜ ์ถ”๊ฐ€ onGroupSumChange: setLeftGroupSumConfig, // ๐Ÿ†• ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ ์„ค์ • ์ฝœ๋ฐฑ }); return () => unregisterTable(leftTableId); }, [ component.id, componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, leftColumnLabels, component.title, isDesignMode, getLeftColumnUniqueValues, ]); // ์šฐ์ธก ํ…Œ์ด๋ธ”์€ ๊ฒ€์ƒ‰ ์ปดํฌ๋„ŒํŠธ ๋“ฑ๋ก ์ œ์™ธ (์ขŒ์ธก ๋งˆ์Šคํ„ฐ ํ…Œ์ด๋ธ”๋งŒ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ) // useEffect(() => { // const rightTableName = componentConfig.rightPanel?.tableName; // if (!rightTableName || isDesignMode) return; // // const rightTableId = `split-panel-right-${component.id}`; // // ๐Ÿ”ง ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š” ์ปฌ๋Ÿผ๋งŒ ๋“ฑ๋ก (displayColumns ๋˜๋Š” columns) // const displayColumns = componentConfig.rightPanel?.columns || []; // const rightColumns = displayColumns.map((col: any) => col.columnName || col.name || col).filter(Boolean); // // if (rightColumns.length > 0) { // registerTable({ // tableId: rightTableId, // label: `${component.title || "๋ถ„ํ•  ํŒจ๋„"} (์šฐ์ธก)`, // tableName: rightTableName, // columns: rightColumns.map((col: string) => ({ // columnName: col, // columnLabel: rightColumnLabels[col] || col, // inputType: "text", // visible: true, // width: 150, // sortable: true, // filterable: true, // })), // onFilterChange: setRightFilters, // onGroupChange: setRightGrouping, // onColumnVisibilityChange: setRightColumnVisibility, // }); // // return () => unregisterTable(rightTableId); // } // }, [component.id, componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.columns, rightColumnLabels, component.title, isDesignMode]); // ์ขŒ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ useEffect(() => { const loadLeftColumnLabels = async () => { const leftTableName = componentConfig.leftPanel?.tableName; if (!leftTableName || isDesignMode) return; try { const columnsResponse = await tableTypeApi.getColumns(leftTableName); const labels: Record = {}; columnsResponse.forEach((col: any) => { const columnName = col.columnName || col.column_name; const label = col.columnLabel || col.column_label || col.displayName || columnName; if (columnName) { labels[columnName] = label; } }); setLeftColumnLabels(labels); console.log("โœ… ์ขŒ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ:", labels); } catch (error) { console.error("์ขŒ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ ์‹คํŒจ:", error); } }; loadLeftColumnLabels(); }, [componentConfig.leftPanel?.tableName, isDesignMode]); // ์šฐ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ useEffect(() => { const loadRightTableColumns = async () => { const rightTableName = componentConfig.rightPanel?.tableName; if (!rightTableName || isDesignMode) return; try { const columnsResponse = await tableTypeApi.getColumns(rightTableName); setRightTableColumns(columnsResponse || []); // ์šฐ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ๋„ ํ•จ๊ป˜ ๋กœ๋“œ const labels: Record = {}; columnsResponse.forEach((col: any) => { const columnName = col.columnName || col.column_name; const label = col.columnLabel || col.column_label || col.displayName || columnName; if (columnName) { labels[columnName] = label; } }); setRightColumnLabels(labels); console.log("โœ… ์šฐ์ธก ์ปฌ๋Ÿผ ๋ผ๋ฒจ ๋กœ๋“œ:", labels); } catch (error) { console.error("์šฐ์ธก ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ ์‹คํŒจ:", error); } }; loadRightTableColumns(); }, [componentConfig.rightPanel?.tableName, isDesignMode]); // ์ขŒ์ธก ํ…Œ์ด๋ธ” ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ useEffect(() => { const loadLeftCategoryMappings = async () => { const leftTableName = componentConfig.leftPanel?.tableName; if (!leftTableName || isDesignMode) return; try { // 1. ์ปฌ๋Ÿผ ๋ฉ”ํƒ€ ์ •๋ณด ์กฐํšŒ const columnsResponse = await tableTypeApi.getColumns(leftTableName); const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category"); if (categoryColumns.length === 0) { setLeftCategoryMappings({}); return; } // 2. ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์— ๋Œ€ํ•œ ๊ฐ’ ์กฐํšŒ const mappings: Record> = {}; for (const col of categoryColumns) { const columnName = col.columnName || col.column_name; try { const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values`); if (response.data.success && response.data.data) { const valueMap: Record = {}; response.data.data.forEach((item: any) => { valueMap[item.value_code || item.valueCode] = { label: item.value_label || item.valueLabel, color: item.color, }; }); mappings[columnName] = valueMap; console.log(`โœ… ์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${columnName}]:`, valueMap); } } catch (error) { console.error(`์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ ์กฐํšŒ ์‹คํŒจ [${columnName}]:`, error); } } setLeftCategoryMappings(mappings); } catch (error) { console.error("์ขŒ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ ์‹คํŒจ:", error); } }; loadLeftCategoryMappings(); }, [componentConfig.leftPanel?.tableName, isDesignMode]); // ์šฐ์ธก ํ…Œ์ด๋ธ” ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ (์กฐ์ธ๋œ ํ…Œ์ด๋ธ” ํฌํ•จ) useEffect(() => { const loadRightCategoryMappings = async () => { const rightTableName = componentConfig.rightPanel?.tableName; if (!rightTableName || isDesignMode) return; try { const mappings: Record> = {}; // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ปฌ๋Ÿผ ์„ค์ •์—์„œ ์กฐ์ธ๋œ ํ…Œ์ด๋ธ” ์ถ”์ถœ const rightColumns = componentConfig.rightPanel?.columns || []; const tablesToLoad = new Set([rightTableName]); // ์ปฌ๋Ÿผ๋ช…์—์„œ ํ…Œ์ด๋ธ”๋ช… ์ถ”์ถœ (์˜ˆ: "item_info.material" -> "item_info") rightColumns.forEach((col: any) => { const colName = col.name || col.columnName; if (colName && colName.includes(".")) { const joinTableName = colName.split(".")[0]; tablesToLoad.add(joinTableName); } }); // ๐Ÿ†• ์ถ”๊ฐ€ ํƒญ์˜ ํ…Œ์ด๋ธ”๋„ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋“œ ๋Œ€์ƒ์— ํฌํ•จ const additionalTabs = componentConfig.rightPanel?.additionalTabs || []; additionalTabs.forEach((tab: any) => { if (tab.tableName) { tablesToLoad.add(tab.tableName); } // ์ถ”๊ฐ€ ํƒญ ์ปฌ๋Ÿผ์—์„œ ์กฐ์ธ๋œ ํ…Œ์ด๋ธ” ์ถ”์ถœ (tab.columns || []).forEach((col: any) => { const colName = col.name || col.columnName; if (colName && colName.includes(".")) { const joinTableName = colName.split(".")[0]; tablesToLoad.add(joinTableName); } }); }); console.log("๐Ÿ” ์šฐ์ธก ํŒจ๋„ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋“œ ๋Œ€์ƒ ํ…Œ์ด๋ธ”:", Array.from(tablesToLoad)); // ๊ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ for (const tableName of tablesToLoad) { try { // 1. ์ปฌ๋Ÿผ ๋ฉ”ํƒ€ ์ •๋ณด ์กฐํšŒ const columnsResponse = await tableTypeApi.getColumns(tableName); const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category"); // 2. ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์— ๋Œ€ํ•œ ๊ฐ’ ์กฐํšŒ for (const col of categoryColumns) { const columnName = col.columnName || col.column_name; try { const response = await apiClient.get(`/table-categories/${tableName}/${columnName}/values`); if (response.data.success && response.data.data) { const valueMap: Record = {}; response.data.data.forEach((item: any) => { valueMap[item.value_code || item.valueCode] = { label: item.value_label || item.valueLabel, color: item.color, }; }); // ์กฐ์ธ๋œ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•ํƒœ๋กœ ์ €์žฅ const mappingKey = tableName === rightTableName ? columnName : `${tableName}.${columnName}`; mappings[mappingKey] = valueMap; // ๐Ÿ†• ์ปฌ๋Ÿผ๋ช…๋งŒ์œผ๋กœ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”๊ฐ€ ์ €์žฅ (๋ชจ๋“  ํ…Œ์ด๋ธ”) // ๊ธฐ์กด ๋งคํ•‘์ด ์žˆ์œผ๋ฉด ๋ณ‘ํ•ฉ, ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap }; console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${mappingKey}]:`, valueMap); console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ (์ปฌ๋Ÿผ๋ช…๋งŒ) [${columnName}]:`, mappings[columnName]); } } catch (error) { console.error(`์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ ์กฐํšŒ ์‹คํŒจ [${tableName}.${columnName}]:`, error); } } } catch (error) { console.error(`ํ…Œ์ด๋ธ” ${tableName} ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:`, error); } } setRightCategoryMappings(mappings); } catch (error) { console.error("์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ ์‹คํŒจ:", error); } }; loadRightCategoryMappings(); }, [componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.columns, componentConfig.rightPanel?.additionalTabs, isDesignMode]); // ํ•ญ๋ชฉ ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ ํ† ๊ธ€ const toggleExpand = useCallback((itemId: any) => { setExpandedItems((prev) => { const newSet = new Set(prev); if (newSet.has(itemId)) { newSet.delete(itemId); } else { newSet.add(itemId); } return newSet; }); }, []); // ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ const handleAddClick = useCallback( (panel: "left" | "right") => { // ์ขŒ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ์‹œ, addButton ๋ชจ๋‹ฌ ๋ชจ๋“œ ํ™•์ธ if (panel === "left") { const addButtonConfig = componentConfig.leftPanel?.addButton; if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) { const leftTableName = componentConfig.leftPanel?.tableName || ""; // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId: addButtonConfig.modalScreenId, urlParams: { mode: "add", tableName: leftTableName, }, }, }), ); console.log("โœ… [SplitPanel] ์ขŒ์ธก ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ:", { screenId: addButtonConfig.modalScreenId, tableName: leftTableName, }); return; } } // ์šฐ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ์‹œ, addButton ๋ชจ๋‹ฌ ๋ชจ๋“œ ํ™•์ธ if (panel === "right") { const addButtonConfig = activeTabIndex === 0 ? componentConfig.rightPanel?.addButton : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.addButton; if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) { // ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ const currentTableName = activeTabIndex === 0 ? componentConfig.rightPanel?.tableName || "" : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || ""; // ์ขŒ์ธก ์„ ํƒ ๋ฐ์ดํ„ฐ๋ฅผ modalDataStore์— ์ €์žฅ (์ถ”๊ฐ€ ํ™”๋ฉด์—์„œ ์ฐธ์กฐ ๊ฐ€๋Šฅ) if (selectedLeftItem && componentConfig.leftPanel?.tableName) { import("@/stores/modalDataStore").then(({ useModalDataStore }) => { useModalDataStore.getState().setData(componentConfig.leftPanel!.tableName!, [selectedLeftItem]); }); } // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId: addButtonConfig.modalScreenId, urlParams: { mode: "add", tableName: currentTableName, // ์ขŒ์ธก ์„ ํƒ ํ•ญ๋ชฉ์˜ ์—ฐ๊ฒฐ ํ‚ค ๊ฐ’ ์ „๋‹ฌ ...(selectedLeftItem && (() => { const relation = activeTabIndex === 0 ? componentConfig.rightPanel?.relation : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.relation; const leftColumn = relation?.keys?.[0]?.leftColumn || relation?.leftColumn; const rightColumn = relation?.keys?.[0]?.rightColumn || relation?.foreignKey; if (leftColumn && rightColumn && selectedLeftItem[leftColumn] !== undefined) { return { [rightColumn]: selectedLeftItem[leftColumn] }; } return {}; })()), }, }, }), ); console.log("โœ… [SplitPanel] ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ:", { screenId: addButtonConfig.modalScreenId, tableName: currentTableName, }); return; } } // ๊ธฐ์กด ๋‚ด์žฅ ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ๋กœ์ง setAddModalPanel(panel); // ์šฐ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ์‹œ, ์ขŒ์ธก์—์„œ ์„ ํƒ๋œ ํ•ญ๋ชฉ์˜ ์กฐ์ธ ์ปฌ๋Ÿผ ๊ฐ’์„ ์ž๋™์œผ๋กœ ์ฑ„์›€ if ( panel === "right" && selectedLeftItem && componentConfig.leftPanel?.leftColumn && componentConfig.rightPanel?.rightColumn ) { const leftColumnValue = selectedLeftItem[componentConfig.leftPanel.leftColumn]; setAddModalFormData({ [componentConfig.rightPanel.rightColumn]: leftColumnValue, }); } else { setAddModalFormData({}); } setShowAddModal(true); }, [selectedLeftItem, componentConfig, activeTabIndex], ); // ์ˆ˜์ • ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ const handleEditClick = useCallback( (panel: "left" | "right", item: any) => { // ์ขŒ์ธก ํŒจ๋„ ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • ํ™•์ธ (๋ชจ๋‹ฌ ๋ชจ๋“œ) if (panel === "left") { const editButtonConfig = componentConfig.leftPanel?.editButton; if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) { const leftTableName = componentConfig.leftPanel?.tableName || ""; // Primary Key ์ฐพ๊ธฐ - ์‹ค์ œ DB์˜ id ์ปฌ๋Ÿผ ๊ฐ’์„ ์šฐ์„  ์‚ฌ์šฉ let primaryKeyValue = item.id || item.ID; if (primaryKeyValue === undefined || primaryKeyValue === null) { // id๊ฐ€ ์—†์œผ๋ฉด sourceColumn ์‹œ๋„, ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ํ‚ค const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; primaryKeyValue = item[sourceColumn]; if (primaryKeyValue === undefined || primaryKeyValue === null) { const firstKey = Object.keys(item)[0]; primaryKeyValue = item[firstKey]; } } // modalDataStore์— ์ €์žฅ import("@/stores/modalDataStore").then(({ useModalDataStore }) => { useModalDataStore.getState().setData(leftTableName, [item]); }); // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId: editButtonConfig.modalScreenId, urlParams: { mode: "edit", editId: primaryKeyValue, tableName: leftTableName, }, }, }), ); console.log("โœ… [SplitPanel] ์ขŒ์ธก ์ˆ˜์ • ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ:", { screenId: editButtonConfig.modalScreenId, tableName: leftTableName, primaryKeyValue, }); return; } } // ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ • ๋ฒ„ํŠผ ์„ค์ • ํ™•์ธ (ํƒญ๋ณ„ ์„ค์ • ์ง€์›) if (panel === "right") { const editButtonConfig = activeTabIndex === 0 ? componentConfig.rightPanel?.editButton : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.editButton; const currentTableName = activeTabIndex === 0 ? componentConfig.rightPanel?.tableName || "" : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || ""; if (editButtonConfig?.mode === "modal") { const modalScreenId = editButtonConfig?.modalScreenId; if (modalScreenId) { // ์ปค์Šคํ…€ ๋ชจ๋‹ฌ ํ™”๋ฉด ์—ด๊ธฐ const rightTableName = currentTableName; // Primary Key ์ฐพ๊ธฐ (์šฐ์„ ์ˆœ์œ„: id > ID > user_id > {table}_id > ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ) let primaryKeyName = "id"; let primaryKeyValue: any; if (item.id !== undefined && item.id !== null) { primaryKeyName = "id"; primaryKeyValue = item.id; } else if (item.ID !== undefined && item.ID !== null) { primaryKeyName = "ID"; primaryKeyValue = item.ID; } else if (item.user_id !== undefined && item.user_id !== null) { // user_info ํ…Œ์ด๋ธ” ๋“ฑ user_id๋ฅผ Primary Key๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ primaryKeyName = "user_id"; primaryKeyValue = item.user_id; } else { // ํ…Œ์ด๋ธ”๋ช…_id ํŒจํ„ด ํ™•์ธ (์˜ˆ: dept_id, item_id ๋“ฑ) const tableIdKey = rightTableName ? `${rightTableName.replace(/_info$/, "")}_id` : ""; if (tableIdKey && item[tableIdKey] !== undefined && item[tableIdKey] !== null) { primaryKeyName = tableIdKey; primaryKeyValue = item[tableIdKey]; } else { // ๋งˆ์ง€๋ง‰์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ๋ฅผ Primary Key๋กœ ๊ฐ„์ฃผ const firstKey = Object.keys(item)[0]; primaryKeyName = firstKey; primaryKeyValue = item[firstKey]; } } console.log("โœ… ์ˆ˜์ • ๋ชจ๋‹ฌ ์—ด๊ธฐ:", { tableName: rightTableName, primaryKeyName, primaryKeyValue, screenId: modalScreenId, fullItem: item, }); // modalDataStore์—๋„ ์ €์žฅ (ํ˜ธํ™˜์„ฑ ์œ ์ง€) import("@/stores/modalDataStore").then(({ useModalDataStore }) => { useModalDataStore.getState().setData(rightTableName, [item]); }); // ๐Ÿ†• groupByColumns ์ถ”์ถœ const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || []; console.log("๐Ÿ”ง [SplitPanel] ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ - groupByColumns ํ™•์ธ:", { groupByColumns, editButtonConfig: componentConfig.rightPanel?.editButton, hasGroupByColumns: groupByColumns.length > 0, }); // ScreenModal ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ID + groupByColumns ์ „๋‹ฌ) window.dispatchEvent( new CustomEvent("openScreenModal", { detail: { screenId: modalScreenId, urlParams: { mode: "edit", editId: primaryKeyValue, tableName: rightTableName, ...(groupByColumns.length > 0 && { groupByColumns: JSON.stringify(groupByColumns), }), }, }, }), ); console.log("โœ… [SplitPanel] openScreenModal ์ด๋ฒคํŠธ ๋ฐœ์ƒ:", { screenId: modalScreenId, editId: primaryKeyValue, tableName: rightTableName, groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "์—†์Œ", }); return; } } } // ๊ธฐ์กด ์ž๋™ ํŽธ์ง‘ ๋ชจ๋“œ (์ธ๋ผ์ธ ํŽธ์ง‘ ๋ชจ๋‹ฌ) setEditModalPanel(panel); setEditModalItem(item); setEditModalFormData({ ...item }); setShowEditModal(true); }, [componentConfig, activeTabIndex], ); // ์ปค์Šคํ…€ ๋ชจ๋“œ ์šฐ์ธก ํŒจ๋„ ์ €์žฅ (์ธ๋ผ์ธ ํŽธ์ง‘ ๋ฐ์ดํ„ฐ) const handleCustomRightSave = useCallback(async () => { if (!selectedLeftItem || !customLeftSelectedData || Object.keys(customLeftSelectedData).length === 0) { toast({ title: "์ €์žฅ ์‹คํŒจ", description: "์ €์žฅํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.", variant: "destructive", }); return; } const tableName = componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName; if (!tableName) { toast({ title: "์ €์žฅ ์‹คํŒจ", description: "ํ…Œ์ด๋ธ” ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); return; } // Primary Key ์ฐพ๊ธฐ - ์‹ค์ œ DB์˜ id ์ปฌ๋Ÿผ ๊ฐ’์„ ์‚ฌ์šฉ (sourceColumn์€ ๊ด€๊ณ„ ์—ฐ๊ฒฐ์šฉ์ด๋ฏ€๋กœ PK๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ) const primaryKey = selectedLeftItem.id || selectedLeftItem.ID; if (!primaryKey) { toast({ title: "์ €์žฅ ์‹คํŒจ", description: "Primary Key๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); return; } try { // ํ”„๋ก ํŠธ์—”๋“œ ์ „์šฉ ํ•„๋“œ ์ œ๊ฑฐ const cleanData = { ...customLeftSelectedData }; delete cleanData.children; delete cleanData.level; delete cleanData._originalItems; // company_code ์ž๋™ ์ถ”๊ฐ€ if (companyCode && !cleanData.company_code) { cleanData.company_code = companyCode; } console.log("๐Ÿ“ [SplitPanel] ์ปค์Šคํ…€ ์šฐ์ธก ํŒจ๋„ ์ €์žฅ:", { tableName, primaryKey, data: cleanData }); const response = await dataApi.updateRecord(tableName, primaryKey, cleanData); if (response.success) { toast({ title: "์ €์žฅ ์™„๋ฃŒ", description: "๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", }); // ์ขŒ์ธก ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ (๋ณ€๊ฒฝ๋œ ํ•ญ๋ชฉ ๋ฐ˜์˜) loadLeftData(); // selectedLeftItem๋„ ์—…๋ฐ์ดํŠธ setSelectedLeftItem(customLeftSelectedData); } else { toast({ title: "์ €์žฅ ์‹คํŒจ", description: response.error || "๋ฐ์ดํ„ฐ ์ €์žฅ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } } catch (error) { console.error("์ปค์Šคํ…€ ์šฐ์ธก ํŒจ๋„ ์ €์žฅ ์˜ค๋ฅ˜:", error); toast({ title: "์ €์žฅ ์˜ค๋ฅ˜", description: "๋ฐ์ดํ„ฐ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } }, [selectedLeftItem, customLeftSelectedData, componentConfig, companyCode, toast, loadLeftData]); // ์ˆ˜์ • ๋ชจ๋‹ฌ ์ €์žฅ const handleEditModalSave = useCallback(async () => { const tableName = editModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName; const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; const primaryKey = editModalItem[sourceColumn] || editModalItem.id || editModalItem.ID; if (!tableName || !primaryKey) { toast({ title: "์ˆ˜์ • ์˜ค๋ฅ˜", description: "ํ…Œ์ด๋ธ”๋ช… ๋˜๋Š” Primary Key๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); return; } try { console.log("๐Ÿ“ ๋ฐ์ดํ„ฐ ์ˆ˜์ •:", { tableName, primaryKey, data: editModalFormData }); // ํ”„๋ก ํŠธ์—”๋“œ ์ „์šฉ ํ•„๋“œ ์ œ๊ฑฐ (children, level ๋“ฑ) const cleanData = { ...editModalFormData }; delete cleanData.children; delete cleanData.level; // ์ขŒ์ธก ํŒจ๋„ ์ˆ˜์ • ์‹œ, ์กฐ์ธ ๊ด€๊ณ„ ์ •๋ณด ํฌํ•จ const updatePayload: any = cleanData; if (editModalPanel === "left" && componentConfig.rightPanel?.relation?.type === "join") { // ์กฐ์ธ ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ๊ด€๊ณ„ ์ •๋ณด๋ฅผ ํŽ˜์ด๋กœ๋“œ์— ์ถ”๊ฐ€ updatePayload._relationInfo = { rightTable: componentConfig.rightPanel.tableName, leftColumn: componentConfig.rightPanel.relation.leftColumn, rightColumn: componentConfig.rightPanel.relation.rightColumn, oldLeftValue: editModalItem[componentConfig.rightPanel.relation.leftColumn], }; console.log("๐Ÿ”— ์กฐ์ธ ๊ด€๊ณ„ ์ •๋ณด ์ถ”๊ฐ€:", updatePayload._relationInfo); } const result = await dataApi.updateRecord(tableName, primaryKey, updatePayload); if (result.success) { toast({ title: "์„ฑ๊ณต", description: "๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", }); // ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ setShowEditModal(false); setEditModalFormData({}); setEditModalItem(null); // ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ if (editModalPanel === "left") { loadLeftData(); // ์šฐ์ธก ํŒจ๋„๋„ ์ƒˆ๋กœ๊ณ ์นจ (FK๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Œ) loadRightData(selectedLeftItem); } else if (editModalPanel === "right") { loadRightData(selectedLeftItem); } } else { toast({ title: "์ˆ˜์ • ์‹คํŒจ", description: result.message || "๋ฐ์ดํ„ฐ ์ˆ˜์ •์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } } catch (error: any) { console.error("๋ฐ์ดํ„ฐ ์ˆ˜์ • ์˜ค๋ฅ˜:", error); toast({ title: "์˜ค๋ฅ˜", description: error?.response?.data?.message || "๋ฐ์ดํ„ฐ ์ˆ˜์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } }, [ editModalPanel, componentConfig, editModalItem, editModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData, ]); // ์‚ญ์ œ ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ (tableName: ์ถ”๊ฐ€ ํƒญ ๋“ฑ ํŠน์ • ํ…Œ์ด๋ธ” ์ง€์ • ์‹œ ์‚ฌ์šฉ) const handleDeleteClick = useCallback((panel: "left" | "right", item: any, tableName?: string) => { setDeleteModalPanel(panel); setDeleteModalItem(item); setDeleteModalTableName(tableName || null); setShowDeleteModal(true); }, []); // ์‚ญ์ œ ํ™•์ธ const handleDeleteConfirm = useCallback(async () => { // 1. ํ…Œ์ด๋ธ”๋ช… ๊ฒฐ์ •: deleteModalTableName์ด ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ (์ถ”๊ฐ€ ํƒญ ๋“ฑ) let tableName = deleteModalTableName; if (!tableName) { tableName = deleteModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName; // ์šฐ์ธก ํŒจ๋„ + ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ๋ชจ๋“œ์ธ ๊ฒฝ์šฐ if (deleteModalPanel === "right" && componentConfig.rightPanel?.addConfig?.targetTable) { tableName = componentConfig.rightPanel.addConfig.targetTable; console.log("๐Ÿ”— ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ๋ชจ๋“œ: ์‚ญ์ œ ๋Œ€์ƒ ํ…Œ์ด๋ธ” =", tableName); } } // 2. Primary Key ์ถ”์ถœ: id ํ•„๋“œ๋ฅผ ์šฐ์„  ์‚ฌ์šฉ, ์—†์œผ๋ฉด ์ „์ฒด ๊ฐ์ฒด ์ „๋‹ฌ (๋ณตํ•ฉํ‚ค) let primaryKey: any = deleteModalItem?.id || deleteModalItem?.ID; if (!primaryKey && deleteModalItem && typeof deleteModalItem === "object") { // id๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ „์ฒด ๊ฐ์ฒด ์ „๋‹ฌ (๋ณตํ•ฉํ‚ค ํ…Œ์ด๋ธ”) primaryKey = deleteModalItem; console.log("๐Ÿ”‘ ๋ณตํ•ฉํ‚ค: ์ „์ฒด ๊ฐ์ฒด ์ „๋‹ฌ", Object.keys(primaryKey)); } else { console.log("๐Ÿ”‘ ๋‹จ์ผํ‚ค ์‚ญ์ œ: id =", primaryKey, "ํ…Œ์ด๋ธ” =", tableName); } if (!tableName || !primaryKey) { toast({ title: "์‚ญ์ œ ์˜ค๋ฅ˜", description: "ํ…Œ์ด๋ธ”๋ช… ๋˜๋Š” Primary Key๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); return; } try { console.log("๐Ÿ—‘๏ธ ๋ฐ์ดํ„ฐ ์‚ญ์ œ:", { tableName, primaryKey }); // ๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ๋””๋ฒ„๊น… console.log("๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ๋””๋ฒ„๊น…:", { panel: deleteModalPanel, dataFilter: componentConfig.rightPanel?.dataFilter, deduplication: componentConfig.rightPanel?.dataFilter?.deduplication, enabled: componentConfig.rightPanel?.dataFilter?.deduplication?.enabled, }); let result; // ๐Ÿ”ง ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, groupByColumn ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ if (deleteModalPanel === "right" && componentConfig.rightPanel?.dataFilter?.deduplication?.enabled) { const deduplication = componentConfig.rightPanel.dataFilter.deduplication; const groupByColumn = deduplication.groupByColumn; if (groupByColumn && deleteModalItem[groupByColumn]) { const groupValue = deleteModalItem[groupByColumn]; console.log(`๐Ÿ”— ์ค‘๋ณต ์ œ๊ฑฐ ํ™œ์„ฑํ™”: ${groupByColumn} = ${groupValue} ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ`); // groupByColumn ๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์‚ญ์ œ const filterConditions: Record = { [groupByColumn]: groupValue, }; // ์ขŒ์ธก ํŒจ๋„์˜ ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด๋„ ํฌํ•จ (customer_id ๋“ฑ) if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { const leftColumn = componentConfig.rightPanel.join.leftColumn; const rightColumn = componentConfig.rightPanel.join.rightColumn; filterConditions[rightColumn] = selectedLeftItem[leftColumn]; } console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); // ๊ทธ๋ฃน ์‚ญ์ œ API ํ˜ธ์ถœ result = await dataApi.deleteGroupRecords(tableName, filterConditions); } else { // ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ result = await dataApi.deleteRecord(tableName, primaryKey); } } else { // ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ result = await dataApi.deleteRecord(tableName, primaryKey); } if (result.success) { toast({ title: "์„ฑ๊ณต", description: "๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", }); // ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ setShowDeleteModal(false); setDeleteModalItem(null); setDeleteModalTableName(null); // ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ if (deleteModalPanel === "left") { loadLeftData(); // ์‚ญ์ œ๋œ ํ•ญ๋ชฉ์ด ์„ ํƒ๋˜์–ด ์žˆ์—ˆ์œผ๋ฉด ์„ ํƒ ํ•ด์ œ const deletedId = deleteModalItem?.id || deleteModalItem?.ID; if (selectedLeftItem && (selectedLeftItem.id === deletedId || selectedLeftItem.ID === deletedId)) { setSelectedLeftItem(null); setRightData(null); } } else if (deleteModalPanel === "right") { // ์ถ”๊ฐ€ ํƒญ์—์„œ ์‚ญ์ œํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ํƒญ ๋ฐ์ดํ„ฐ ๋ฆฌ๋กœ๋“œ if (deleteModalTableName && activeTabIndex > 0) { loadTabData(activeTabIndex, selectedLeftItem); } else { loadRightData(selectedLeftItem); } } } else { toast({ title: "์‚ญ์ œ ์‹คํŒจ", description: result.message || "๋ฐ์ดํ„ฐ ์‚ญ์ œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } } catch (error: any) { console.error("๋ฐ์ดํ„ฐ ์‚ญ์ œ ์˜ค๋ฅ˜:", error); // ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ์กฐ๊ฑด ์—๋Ÿฌ ์ฒ˜๋ฆฌ let errorMessage = "๋ฐ์ดํ„ฐ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."; if (error?.response?.data?.error?.includes("foreign key")) { errorMessage = "์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์–ด ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."; } toast({ title: "์˜ค๋ฅ˜", description: errorMessage, variant: "destructive", }); } }, [deleteModalPanel, deleteModalTableName, componentConfig, deleteModalItem, toast, selectedLeftItem, loadLeftData, loadRightData, loadTabData, activeTabIndex]); // ํ•ญ๋ชฉ๋ณ„ ์ถ”๊ฐ€ ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ (์ขŒ์ธก ํ•ญ๋ชฉ์˜ + ๋ฒ„ํŠผ - ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€) const handleItemAddClick = useCallback( (item: any) => { const itemAddConfig = componentConfig.leftPanel?.itemAddConfig; if (!itemAddConfig) { toast({ title: "์„ค์ • ์˜ค๋ฅ˜", description: "ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€ ์„ค์ •์ด ์—†์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); return; } const { sourceColumn, parentColumn } = itemAddConfig; if (!sourceColumn || !parentColumn) { toast({ title: "์„ค์ • ์˜ค๋ฅ˜", description: "ํ˜„์žฌ ํ•ญ๋ชฉ ID ์ปฌ๋Ÿผ๊ณผ ์ƒ์œ„ ํ•ญ๋ชฉ ์ €์žฅ ์ปฌ๋Ÿผ์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.", variant: "destructive", }); return; } // ์„ ํƒ๋œ ํ•ญ๋ชฉ์˜ sourceColumn ๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ parentColumn์— ๋งคํ•‘ const sourceValue = item[sourceColumn]; if (!sourceValue) { toast({ title: "๋ฐ์ดํ„ฐ ์˜ค๋ฅ˜", description: `์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ${sourceColumn} ๊ฐ’์ด ์—†์Šต๋‹ˆ๋‹ค.`, variant: "destructive", }); return; } // ์ขŒ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ์—ด๊ธฐ (parentColumn ๊ฐ’ ๋ฏธ๋ฆฌ ์ฑ„์šฐ๊ธฐ) setAddModalPanel("left-item"); setAddModalFormData({ [parentColumn]: sourceValue }); setShowAddModal(true); }, [componentConfig, toast], ); // ์ถ”๊ฐ€ ๋ชจ๋‹ฌ ์ €์žฅ const handleAddModalSave = useCallback(async () => { // ํ…Œ์ด๋ธ”๋ช…๊ณผ ๋ชจ๋‹ฌ ์ปฌ๋Ÿผ ๊ฒฐ์ • let tableName: string | undefined; let modalColumns: Array<{ name: string; label: string; required?: boolean }> | undefined; const finalData = { ...addModalFormData }; if (addModalPanel === "left") { tableName = componentConfig.leftPanel?.tableName; modalColumns = componentConfig.leftPanel?.addModalColumns; } else if (addModalPanel === "right") { // ์šฐ์ธก ํŒจ๋„: ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ์„ค์ •์ด ์žˆ๋Š”์ง€ ํ™•์ธ const addConfig = componentConfig.rightPanel?.addConfig; if (addConfig?.targetTable) { // ์ค‘๊ณ„ ํ…Œ์ด๋ธ” ๋ชจ๋“œ tableName = addConfig.targetTable; modalColumns = componentConfig.rightPanel?.addModalColumns; // ์ขŒ์ธก ํŒจ๋„์—์„œ ์„ ํƒ๋œ ๊ฐ’ ์ž๋™ ์ฑ„์šฐ๊ธฐ if (addConfig.leftPanelColumn && addConfig.targetColumn && selectedLeftItem) { const leftValue = selectedLeftItem[addConfig.leftPanelColumn]; finalData[addConfig.targetColumn] = leftValue; console.log(`๐Ÿ”— ์ขŒ์ธก ํŒจ๋„ ๊ฐ’ ์ž๋™ ์ฑ„์›€: ${addConfig.targetColumn} = ${leftValue}`); } // ์ž๋™ ์ฑ„์›€ ์ปฌ๋Ÿผ ์ถ”๊ฐ€ if (addConfig.autoFillColumns) { Object.entries(addConfig.autoFillColumns).forEach(([key, value]) => { finalData[key] = value; }); console.log("๐Ÿ”ง ์ž๋™ ์ฑ„์›€ ์ปฌ๋Ÿผ:", addConfig.autoFillColumns); } } else { // ์ผ๋ฐ˜ ํ…Œ์ด๋ธ” ๋ชจ๋“œ tableName = componentConfig.rightPanel?.tableName; modalColumns = componentConfig.rightPanel?.addModalColumns; } } else if (addModalPanel === "left-item") { // ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€ (์ขŒ์ธก ํ…Œ์ด๋ธ”์— ์ถ”๊ฐ€) tableName = componentConfig.leftPanel?.tableName; modalColumns = componentConfig.leftPanel?.itemAddConfig?.addModalColumns; } if (!tableName) { toast({ title: "ํ…Œ์ด๋ธ” ์˜ค๋ฅ˜", description: "ํ…Œ์ด๋ธ”๋ช…์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); return; } // ํ•„์ˆ˜ ํ•„๋“œ ๊ฒ€์ฆ const requiredFields = (modalColumns || []).filter((col) => col.required); for (const field of requiredFields) { if (!addModalFormData[field.name]) { toast({ title: "์ž…๋ ฅ ์˜ค๋ฅ˜", description: `${field.label}์€(๋Š”) ํ•„์ˆ˜ ์ž…๋ ฅ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.`, variant: "destructive", }); return; } } try { console.log("๐Ÿ“ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€:", { tableName, data: finalData }); const result = await dataApi.createRecord(tableName, finalData); if (result.success) { toast({ title: "์„ฑ๊ณต", description: "๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", }); // ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ setShowAddModal(false); setAddModalFormData({}); // ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ if (addModalPanel === "left" || addModalPanel === "left-item") { // ์ขŒ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ (์ผ๋ฐ˜ ์ถ”๊ฐ€ ๋˜๋Š” ํ•˜์œ„ ํ•ญ๋ชฉ ์ถ”๊ฐ€) loadLeftData(); } else if (addModalPanel === "right") { // ์šฐ์ธก ํŒจ๋„ ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ loadRightData(selectedLeftItem); } } else { toast({ title: "์ €์žฅ ์‹คํŒจ", description: result.message || "๋ฐ์ดํ„ฐ ์ถ”๊ฐ€์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } } catch (error: any) { console.error("๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ ์˜ค๋ฅ˜:", error); // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถ”์ถœ let errorMessage = "๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."; if (error?.response?.data) { const responseData = error.response.data; // ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ˜ํ™˜ํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ™•์ธ if (responseData.error) { // ์ค‘๋ณต ํ‚ค ์—๋Ÿฌ ์ฒ˜๋ฆฌ if (responseData.error.includes("duplicate key")) { errorMessage = "์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๊ฐ’์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."; } // NOT NULL ์ œ์•ฝ์กฐ๊ฑด ์—๋Ÿฌ else if (responseData.error.includes("null value")) { const match = responseData.error.match(/column "(\w+)"/); const columnName = match ? match[1] : "ํ•„์ˆ˜"; errorMessage = `${columnName} ํ•„๋“œ๋Š” ํ•„์ˆ˜ ์ž…๋ ฅ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.`; } // ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ์กฐ๊ฑด ์—๋Ÿฌ else if (responseData.error.includes("foreign key")) { errorMessage = "์ฐธ์กฐํ•˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."; } // ๊ธฐํƒ€ ์—๋Ÿฌ else { errorMessage = responseData.message || responseData.error; } } else if (responseData.message) { errorMessage = responseData.message; } } toast({ title: "์˜ค๋ฅ˜", description: errorMessage, variant: "destructive", }); } }, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]); // ๐Ÿ”ง ์ขŒ์ธก ์ปฌ๋Ÿผ ๊ฐ€์‹œ์„ฑ ์„ค์ • ์ €์žฅ ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ useEffect(() => { const leftTableName = componentConfig.leftPanel?.tableName; if (leftTableName && currentUserId) { // localStorage์—์„œ ์ €์žฅ๋œ ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`; const savedSettings = localStorage.getItem(storageKey); if (savedSettings) { try { const parsed = JSON.parse(savedSettings) as ColumnVisibility[]; setLeftColumnVisibility(parsed); } catch (error) { console.error("์ €์žฅ๋œ ์ปฌ๋Ÿผ ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ:", error); } } } }, [componentConfig.leftPanel?.tableName, currentUserId]); // ๐Ÿ”ง ์ปฌ๋Ÿผ ๊ฐ€์‹œ์„ฑ ๋ณ€๊ฒฝ ์‹œ localStorage์— ์ €์žฅ ๋ฐ ์ˆœ์„œ ์—…๋ฐ์ดํŠธ useEffect(() => { const leftTableName = componentConfig.leftPanel?.tableName; if (leftColumnVisibility.length > 0 && leftTableName && currentUserId) { // ์ˆœ์„œ ์—…๋ฐ์ดํŠธ const newOrder = leftColumnVisibility.map((cv) => cv.columnName).filter((name) => name !== "__checkbox__"); // ์ฒดํฌ๋ฐ•์Šค ์ œ์™ธ setLeftColumnOrder(newOrder); // localStorage์— ์ €์žฅ const storageKey = `table_column_visibility_${leftTableName}_${currentUserId}`; localStorage.setItem(storageKey, JSON.stringify(leftColumnVisibility)); } }, [leftColumnVisibility, componentConfig.leftPanel?.tableName, currentUserId]); // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (์ขŒ์ธก + ์šฐ์ธก ์ „์ฒด ๋ฐ์ดํ„ฐ) useEffect(() => { if (!isDesignMode && componentConfig.autoLoad !== false) { loadLeftData(); // ์ขŒ์ธก ๋ฏธ์„ ํƒ ์ƒํƒœ์—์„œ ์šฐ์ธก ์ „์ฒด ๋ฐ์ดํ„ฐ ๊ธฐ๋ณธ ๋กœ๋“œ const relationshipType = componentConfig.rightPanel?.relation?.type || "detail"; if (relationshipType === "join") { loadRightData(null); // ์ถ”๊ฐ€ ํƒญ๋„ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ const tabs = componentConfig.rightPanel?.additionalTabs; if (tabs && tabs.length > 0) { tabs.forEach((_: any, idx: number) => { loadTabData(idx + 1, null); }); } } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDesignMode, componentConfig.autoLoad]); // ๐Ÿ”„ ํ•„ํ„ฐ ๋ณ€๊ฒฝ ์‹œ ๋ฐ์ดํ„ฐ ๋‹ค์‹œ ๋กœ๋“œ useEffect(() => { if (!isDesignMode && componentConfig.autoLoad !== false) { loadLeftData(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [leftFilters]); // ์ „์—ญ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ useEffect(() => { const handleRefreshTable = () => { if (!isDesignMode) { console.log("๐Ÿ”„ [SplitPanel] refreshTable ์ด๋ฒคํŠธ ์ˆ˜์‹  - ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ"); loadLeftData(); // ํ˜„์žฌ ํ™œ์„ฑ ํƒญ ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ (์ขŒ์ธก ๋ฏธ์„ ํƒ ์‹œ์—๋„ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋กœ๋“œ) if (activeTabIndex === 0) { loadRightData(selectedLeftItem); } else { loadTabData(activeTabIndex, selectedLeftItem); } } }; window.addEventListener("refreshTable", handleRefreshTable); return () => { window.removeEventListener("refreshTable", handleRefreshTable); }; }, [isDesignMode, loadLeftData, loadRightData, loadTabData, activeTabIndex, selectedLeftItem]); // ๋ฆฌ์‚ฌ์ด์ € ๋“œ๋ž˜๊ทธ ํ•ธ๋“ค๋Ÿฌ const handleMouseDown = (e: React.MouseEvent) => { if (!resizable) return; setIsDragging(true); e.preventDefault(); }; const handleMouseMove = useCallback( (e: MouseEvent) => { if (!isDragging || !containerRef.current) return; const containerRect = containerRef.current.getBoundingClientRect(); const containerWidth = containerRect.width; const relativeX = e.clientX - containerRect.left; const newLeftWidth = (relativeX / containerWidth) * 100; // ์ตœ์†Œ/์ตœ๋Œ€ ๋„ˆ๋น„ ์ œํ•œ (20% ~ 80%) if (newLeftWidth >= 20 && newLeftWidth <= 80) { setLeftWidth(newLeftWidth); } }, [isDragging], ); const handleMouseUp = useCallback(() => { setIsDragging(false); }, []); React.useEffect(() => { if (isDragging) { // ๋“œ๋ž˜๊ทธ ์ค‘์—๋Š” ํ…์ŠคํŠธ ์„ ํƒ ๋ฐฉ์ง€ document.body.style.userSelect = "none"; document.body.style.cursor = "col-resize"; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); return () => { document.body.style.userSelect = ""; document.body.style.cursor = ""; document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; } }, [isDragging, handleMouseMove, handleMouseUp]); return (
{ if (isDesignMode) { e.stopPropagation(); onClick?.(e); } }} className="w-full overflow-hidden rounded-lg bg-white shadow-sm" > {/* ์ขŒ์ธก ํŒจ๋„ */}
{componentConfig.leftPanel?.title || "์ขŒ์ธก ํŒจ๋„"} {!isDesignMode && componentConfig.leftPanel?.showAdd && ( )}
{componentConfig.leftPanel?.showSearch && (
setLeftSearchQuery(e.target.value)} className="pl-9" />
)} {/* ์ขŒ์ธก ๋ฐ์ดํ„ฐ ๋ชฉ๋ก/ํ…Œ์ด๋ธ”/์ปค์Šคํ…€ */} {console.log("๐Ÿ” [SplitPanel] ์™ผ์ชฝ ํŒจ๋„ displayMode:", componentConfig.leftPanel?.displayMode, "isDesignMode:", isDesignMode)} {componentConfig.leftPanel?.displayMode === "custom" ? ( // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ํŒจ๋„ ์•ˆ์— ์ž์œ ๋กญ๊ฒŒ ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์น˜
{/* ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋””์ž์ธ/์‹คํ–‰ ๋ชจ๋“œ ํ†ตํ•ฉ ๋ Œ๋”๋ง */} {componentConfig.leftPanel?.components && componentConfig.leftPanel.components.length > 0 ? (
{componentConfig.leftPanel.components.map((comp: PanelInlineComponent) => { const isSelectedComp = selectedPanelComponentId === comp.id; const isDraggingComp = draggingCompId === comp.id; const isResizingComp = resizingCompId === comp.id; // ๋“œ๋ž˜๊ทธ/๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘ ํ‘œ์‹œํ•  ํฌ๊ธฐ/์œ„์น˜ const displayX = isDraggingComp && dragPosition ? dragPosition.x : (comp.position?.x || 0); const displayY = isDraggingComp && dragPosition ? dragPosition.y : (comp.position?.y || 0); const displayWidth = isResizingComp && resizeSize ? resizeSize.width : (comp.size?.width || 200); const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100); // ์ปดํฌ๋„ŒํŠธ ๋ฐ์ดํ„ฐ๋ฅผ DynamicComponentRenderer ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ // componentConfig์˜ ์ฃผ์š” ์†์„ฑ์„ ์ตœ์ƒ์œ„๋กœ ํŽผ์นจ (์ผ๋ฐ˜ ํ™”๋ฉด์˜ overrides ํ”Œ๋ž˜ํŠธ๋‹๊ณผ ๋™์ผ) const componentData = { id: comp.id, type: "component" as const, componentType: comp.componentType, label: comp.label, position: comp.position || { x: 0, y: 0 }, size: { width: displayWidth, height: displayHeight }, componentConfig: comp.componentConfig || {}, style: comp.style || {}, // ํŒŒ์ผ ์—…๋กœ๋“œ/๋ฏธ๋””์–ด ๋“ฑ์ด component.tableName, component.columnName์„ ์ง์ ‘ ์ฐธ์กฐํ•˜๋ฏ€๋กœ ํŽผ์นจ tableName: comp.componentConfig?.tableName, columnName: comp.componentConfig?.columnName, webType: comp.componentConfig?.webType, inputType: comp.inputType || comp.componentConfig?.inputType, }; if (isDesignMode) { // ๋””์ž์ธ ๋ชจ๋“œ: ํƒญ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผํ•˜๊ฒŒ ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง return (
{ e.stopPropagation(); // ํŒจ๋„ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ์‹œ ํƒญ ๋‚ด ์„ ํƒ ํ•ด์ œ if (comp.componentType !== "v2-tabs-widget") { setNestedTabSelectedCompId(undefined); } onSelectPanelComponent?.("left", comp.id, comp); }} > {/* ๋“œ๋ž˜๊ทธ ํ•ธ๋“ค - ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€ ์ƒ๋‹จ */}
handlePanelDragStart(e, "left", comp)} >
{comp.label || comp.componentType}
{/* ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง - ํ•ธ๋“ค ์•„๋ž˜์— ๋ณ„๋„ ์˜์—ญ */}
{/* ๐Ÿ†• ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ(ํƒญ, ๋ถ„ํ•  ํŒจ๋„)๋Š” ๋“œ๋กญ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ */}
{ handleNestedComponentUpdate("left", comp.id, updatedComp); }} // ๐Ÿ†• ์ค‘์ฒฉ๋œ ํƒญ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ - ๋ถ€๋ชจ ๋ถ„ํ•  ํŒจ๋„ ์ •๋ณด ํฌํ•จ onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => { console.log("๐Ÿ” [SplitPanel-Left] onSelectTabComponent ํ˜ธ์ถœ:", { tabId, compId, tabComp, parentSplitPanelId: component.id }); // ํƒญ ๋‚ด ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ์ƒํƒœ ์—…๋ฐ์ดํŠธ setNestedTabSelectedCompId(compId); // ๋ถ€๋ชจ ๋ถ„ํ•  ํŒจ๋„ ์ •๋ณด์™€ ํ•จ๊ป˜ ์ „์—ญ ์ด๋ฒคํŠธ ๋ฐœ์ƒ const event = new CustomEvent("nested-tab-component-select", { detail: { tabsComponentId: comp.id, tabId, componentId: compId, component: tabComp, parentSplitPanelId: component.id, parentPanelSide: "left", }, }); window.dispatchEvent(event); }} selectedTabComponentId={nestedTabSelectedCompId} />
{/* ๋ฆฌ์‚ฌ์ด์ฆˆ ๊ฐ€์žฅ์ž๋ฆฌ ์˜์—ญ - ์„ ํƒ๋œ ์ปดํฌ๋„ŒํŠธ์—๋งŒ ํ‘œ์‹œ */} {isSelectedComp && ( <> {/* ์˜ค๋ฅธ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ (๋„ˆ๋น„ ์กฐ์ ˆ) */}
handlePanelResizeStart(e, "left", comp, "e")} /> {/* ์•„๋ž˜ ๊ฐ€์žฅ์ž๋ฆฌ (๋†’์ด ์กฐ์ ˆ) */}
handlePanelResizeStart(e, "left", comp, "s")} /> {/* ์˜ค๋ฅธ์ชฝ ์•„๋ž˜ ๋ชจ์„œ๋ฆฌ (๋„ˆ๋น„+๋†’์ด ์กฐ์ ˆ) */}
handlePanelResizeStart(e, "left", comp, "se")} /> )}
); } else { // ์‹คํ–‰ ๋ชจ๋“œ: DynamicComponentRenderer๋กœ ๋ Œ๋”๋ง const componentData = { id: comp.id, type: "component" as const, componentType: comp.componentType, label: comp.label, position: comp.position || { x: 0, y: 0 }, size: comp.size || { width: 400, height: 300 }, componentConfig: comp.componentConfig || {}, style: comp.style || {}, }; return (
{ // ์ปค์Šคํ…€ ๋ชจ๋“œ: ์ขŒ์ธก ์นด๋“œ/ํ…Œ์ด๋ธ” ์„ ํƒ ์‹œ ๋ฐ์ดํ„ฐ ์บก์ฒ˜ if (data?.selectedRowsData && data.selectedRowsData.length > 0) { setCustomLeftSelectedData(data.selectedRowsData[0]); setSelectedLeftItem(data.selectedRowsData[0]); } else if (data?.selectedRowsData && data.selectedRowsData.length === 0) { setCustomLeftSelectedData({}); setSelectedLeftItem(null); } }} />
); } })}
) : ( // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์„ ๋•Œ ๋“œ๋กญ ์˜์—ญ ํ‘œ์‹œ

์ปค์Šคํ…€ ๋ชจ๋“œ

{isDesignMode ? "์ปดํฌ๋„ŒํŠธ๋ฅผ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ๋ฐฐ์น˜ํ•˜์„ธ์š”" : "๋ฐฐ์น˜๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"}

)}
) : componentConfig.leftPanel?.displayMode === "table" ? ( // ํ…Œ์ด๋ธ” ๋ชจ๋“œ
{isDesignMode ? ( // ๋””์ž์ธ ๋ชจ๋“œ: ์ƒ˜ํ”Œ ํ…Œ์ด๋ธ”
์ปฌ๋Ÿผ 1 ์ปฌ๋Ÿผ 2 ์ปฌ๋Ÿผ 3
๋ฐ์ดํ„ฐ 1-1 ๋ฐ์ดํ„ฐ 1-2 ๋ฐ์ดํ„ฐ 1-3
๋ฐ์ดํ„ฐ 2-1 ๋ฐ์ดํ„ฐ 2-2 ๋ฐ์ดํ„ฐ 2-3
) : isLoadingLeft ? (
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
) : ( (() => { // ๐Ÿ†• ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ๋œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ const dataSource = summedLeftData; console.log( "๐Ÿ” [ํ…Œ์ด๋ธ”๋ชจ๋“œ ๋ Œ๋”๋ง] dataSource ๊ฐœ์ˆ˜:", dataSource.length, "leftGroupSumConfig:", leftGroupSumConfig, ); // ๐Ÿ”ง ๋กœ์ปฌ ๊ฒ€์ƒ‰ ํ•„ํ„ฐ ์ ์šฉ const filteredData = leftSearchQuery ? dataSource.filter((item) => { const searchLower = leftSearchQuery.toLowerCase(); return Object.entries(item).some(([key, value]) => { if (value === null || value === undefined) return false; return String(value).toLowerCase().includes(searchLower); }); }) : dataSource; // ๐Ÿ”ง ๊ฐ€์‹œ์„ฑ ์ฒ˜๋ฆฌ๋œ ์ปฌ๋Ÿผ ์‚ฌ์šฉ const columnsToShow = visibleLeftColumns.length > 0 ? visibleLeftColumns.map((col: any) => { const colName = typeof col === "string" ? col : col.name || col.columnName; return { name: colName, label: leftColumnLabels[colName] || (typeof col === "object" ? col.label : null) || colName, width: typeof col === "object" ? col.width : 150, align: (typeof col === "object" ? col.align : "left") as "left" | "center" | "right", format: typeof col === "object" ? col.format : undefined, // ๐Ÿ†• ํฌ๋งท ์„ค์ • ํฌํ•จ }; }) : Object.keys(filteredData[0] || {}) .filter((key) => key !== "children" && key !== "level") .slice(0, 5) .map((key) => ({ name: key, label: leftColumnLabels[key] || key, width: 150, align: "left" as const, format: undefined, // ๐Ÿ†• ๊ธฐ๋ณธ๊ฐ’ })); // ๐Ÿ”ง ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง if (groupedLeftData.length > 0) { return (
{groupedLeftData.map((group, groupIdx) => (
{group.groupKey} ({group.count}๊ฐœ)
{columnsToShow.map((col, idx) => ( ))} {group.items.map((item, idx) => { const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; const itemId = item[sourceColumn] || item.id || item.ID || idx; const isSelected = selectedLeftItem && (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item); return ( handleLeftItemSelect(item)} className={`hover:bg-accent cursor-pointer transition-colors ${ isSelected ? "bg-primary/10" : "" }`} > {columnsToShow.map((col, colIdx) => ( ))} ); })}
{col.label}
{formatCellValue( col.name, getEntityJoinValue(item, col.name), leftCategoryMappings, col.format, )}
))}
); } // ๐Ÿ”ง ์ผ๋ฐ˜ ํ…Œ์ด๋ธ” ๋ Œ๋”๋ง (๊ทธ๋ฃนํ™” ์—†์Œ) return (
{columnsToShow.map((col, idx) => ( ))} {filteredData.map((item, idx) => { const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; const itemId = item[sourceColumn] || item.id || item.ID || idx; const isSelected = selectedLeftItem && (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item); return ( handleLeftItemSelect(item)} className={`hover:bg-accent cursor-pointer transition-colors ${ isSelected ? "bg-primary/10" : "" }`} > {columnsToShow.map((col, colIdx) => ( ))} ); })}
{col.label}
{formatCellValue( col.name, getEntityJoinValue(item, col.name), leftCategoryMappings, col.format, )}
); })() )}
) : ( // ๋ชฉ๋ก ๋ชจ๋“œ (๊ธฐ์กด)
{isDesignMode ? ( // ๋””์ž์ธ ๋ชจ๋“œ: ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ <>
handleLeftItemSelect({ id: 1, name: "ํ•ญ๋ชฉ 1" })} className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${ selectedLeftItem?.id === 1 ? "bg-primary/10 text-primary" : "" }`} >
ํ•ญ๋ชฉ 1
์„ค๋ช… ํ…์ŠคํŠธ
handleLeftItemSelect({ id: 2, name: "ํ•ญ๋ชฉ 2" })} className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${ selectedLeftItem?.id === 2 ? "bg-primary/10 text-primary" : "" }`} >
ํ•ญ๋ชฉ 2
์„ค๋ช… ํ…์ŠคํŠธ
handleLeftItemSelect({ id: 3, name: "ํ•ญ๋ชฉ 3" })} className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${ selectedLeftItem?.id === 3 ? "bg-primary/10 text-primary" : "" }`} >
ํ•ญ๋ชฉ 3
์„ค๋ช… ํ…์ŠคํŠธ
) : isLoadingLeft ? ( // ๋กœ๋”ฉ ์ค‘
๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
) : ( (() => { // ๐Ÿ†• ๊ทธ๋ฃน๋ณ„ ํ•ฉ์‚ฐ๋œ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ const dataToDisplay = summedLeftData; console.log( "๐Ÿ” [๋ Œ๋”๋ง] dataToDisplay ๊ฐœ์ˆ˜:", dataToDisplay.length, "leftGroupSumConfig:", leftGroupSumConfig, ); // ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง (ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ) const filteredLeftData = leftSearchQuery ? dataToDisplay.filter((item) => { const searchLower = leftSearchQuery.toLowerCase(); return Object.entries(item).some(([key, value]) => { if (value === null || value === undefined) return false; return String(value).toLowerCase().includes(searchLower); }); }) : dataToDisplay; // ์žฌ๊ท€ ๋ Œ๋”๋ง ํ•จ์ˆ˜ const renderTreeItem = (item: any, index: number): React.ReactNode => { const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; const itemId = item[sourceColumn] || item.id || item.ID || index; const isSelected = selectedLeftItem && (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item); const hasChildren = item.children && item.children.length > 0; const isExpanded = expandedItems.has(itemId); const level = item.level || 0; // ๐Ÿ”ง ์ˆ˜์ •: "ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ์„ ํƒ"์—์„œ ์„ค์ •ํ•œ ์ปฌ๋Ÿผ์„ ์šฐ์„  ์‚ฌ์šฉ const configuredColumns = componentConfig.leftPanel?.columns || []; let displayFields: { label: string; value: any }[] = []; // ๋””๋ฒ„๊ทธ ๋กœ๊ทธ if (index === 0) { console.log("๐Ÿ” ์ขŒ์ธก ํŒจ๋„ ํ‘œ์‹œ ๋กœ์ง:"); console.log(" - ์„ค์ •๋œ ํ‘œ์‹œ ์ปฌ๋Ÿผ:", configuredColumns); console.log(" - item keys:", Object.keys(item)); } if (configuredColumns.length > 0) { // ๐Ÿ”ง "ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ ์„ ํƒ"์—์„œ ์„ค์ •ํ•œ ์ปฌ๋Ÿผ ์‚ฌ์šฉ displayFields = configuredColumns.slice(0, 2).map((col: any) => { const colName = typeof col === "string" ? col : col.name || col.columnName; const colLabel = typeof col === "object" ? col.label : leftColumnLabels[colName] || colName; return { label: colLabel, value: item[colName], }; }); if (index === 0) { console.log(" โœ… ์„ค์ •๋œ ์ปฌ๋Ÿผ ๊ธฐ๋ฐ˜ ํ‘œ์‹œ:", displayFields); } } else { // ์„ค์ •๋œ ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ์ฒซ 2๊ฐœ ํ•„๋“œ ํ‘œ์‹œ const keys = Object.keys(item).filter( (k) => k !== "id" && k !== "ID" && k !== "children" && k !== "level" && shouldShowField(k), ); displayFields = keys.slice(0, 2).map((key) => ({ label: leftColumnLabels[key] || key, value: item[key], })); if (index === 0) { console.log(" โš ๏ธ ์„ค์ •๋œ ์ปฌ๋Ÿผ ์—†์Œ, ์ž๋™ ์„ ํƒ:", displayFields); } } const displayTitle = displayFields[0]?.value || item.name || item.title || `ํ•ญ๋ชฉ ${index + 1}`; const displaySubtitle = displayFields[1]?.value || null; return ( {/* ํ˜„์žฌ ํ•ญ๋ชฉ */}
{ handleLeftItemSelect(item); if (hasChildren) { toggleExpand(itemId); } }} > {/* ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ ์•„์ด์ฝ˜ */} {hasChildren ? (
{isExpanded ? ( ) : ( )}
) : (
)} {/* ํ•ญ๋ชฉ ๋‚ด์šฉ */}
{displayTitle}
{displaySubtitle && (
{displaySubtitle}
)}
{/* ํ•ญ๋ชฉ๋ณ„ ๋ฒ„ํŠผ๋“ค */} {!isDesignMode && (
{/* ์ˆ˜์ • ๋ฒ„ํŠผ (showEdit ํ™œ์„ฑํ™” ์‹œ์—๋งŒ ํ‘œ์‹œ) */} {(componentConfig.leftPanel?.showEdit !== false) && ( )} {/* ์‚ญ์ œ ๋ฒ„ํŠผ (showDelete ํ™œ์„ฑํ™” ์‹œ์—๋งŒ ํ‘œ์‹œ) */} {(componentConfig.leftPanel?.showDelete !== false) && ( )} {/* ํ•ญ๋ชฉ๋ณ„ ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} {componentConfig.leftPanel?.showItemAddButton && ( )}
)}
{/* ์ž์‹ ํ•ญ๋ชฉ๋“ค (์ ‘ํ˜€์žˆ์œผ๋ฉด ํ‘œ์‹œ ์•ˆํ•จ) */} {hasChildren && isExpanded && item.children.map((child: any, childIndex: number) => renderTreeItem(child, childIndex))} ); }; return filteredLeftData.length > 0 ? ( // ์‹ค์ œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ filteredLeftData.map((item, index) => renderTreeItem(item, index)) ) : ( // ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ
{leftSearchQuery ? ( <>

๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•ด๋ณด์„ธ์š”.

) : ( "๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." )}
); })() )}
)}
{/* ๋ฆฌ์‚ฌ์ด์ € */} {resizable && (
)} {/* ์šฐ์ธก ํŒจ๋„ */}
{/* ํƒญ์ด ์—†์œผ๋ฉด ์ œ๋ชฉ๋งŒ, ์žˆ์œผ๋ฉด ํƒญ์œผ๋กœ ์ „ํ™˜ */} {(componentConfig.rightPanel?.additionalTabs?.length || 0) > 0 ? (
{componentConfig.rightPanel?.additionalTabs?.map((tab: any, index: number) => ( ))}
) : ( {componentConfig.rightPanel?.title || "์šฐ์ธก ํŒจ๋„"} )}
{!isDesignMode && (
{/* ์ปค์Šคํ…€ ๋ชจ๋“œ ๊ธฐ๋ณธ์ •๋ณด ํƒญ: ์ €์žฅ ๋ฒ„ํŠผ */} {activeTabIndex === 0 && componentConfig.rightPanel?.displayMode === "custom" && selectedLeftItem && ( )} {activeTabIndex === 0 ? componentConfig.rightPanel?.showAdd && ( ) : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.showAdd && ( )}
)}
{componentConfig.rightPanel?.showSearch && (
setRightSearchQuery(e.target.value)} className="pl-9" />
)} {/* ์ถ”๊ฐ€ ํƒญ ์ปจํ…์ธ  */} {activeTabIndex > 0 ? ( (() => { const currentTabConfig = componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any; const currentTabData = tabsData[activeTabIndex] || []; const isTabLoading = tabsLoading[activeTabIndex]; if (isTabLoading) { return (
); } if (currentTabData.length === 0 && !isTabLoading) { return (

๊ด€๋ จ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

); } // ํƒญ ์ปฌ๋Ÿผ ์„ค์ • const tabColumns = currentTabConfig?.columns || []; // ํ…Œ์ด๋ธ” ๋ชจ๋“œ๋กœ ํ‘œ์‹œ (ํ–‰ ํด๋ฆญ ์‹œ ์ƒ์„ธ ์ •๋ณด ํŽผ์น˜๊ธฐ) if (currentTabConfig?.displayMode === "table") { const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete; // showInSummary๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ ๋ฉ”์ธ ํ…Œ์ด๋ธ”์— ํ‘œ์‹œ const tabSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false); return (
{tabSummaryColumns.map((col: any) => ( ))} {hasTabActions && ( )} {currentTabData.map((item: any, idx: number) => { const tabItemId = item.id || item.ID || idx; const isTabExpanded = expandedRightItems.has(`tab_${activeTabIndex}_${tabItemId}`); // ์ƒ์„ธ ์ •๋ณด์šฉ ์ „์ฒด ๊ฐ’ ๋ชฉ๋ก (showInDetail์ด false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ) const tabDetailColumns = tabColumns.filter((col: any) => col.showInDetail !== false); const tabAllValues: [string, any, string][] = tabDetailColumns.length > 0 ? tabDetailColumns.map((col: any) => [col.name, getEntityJoinValue(item, col.name), col.label || col.name] as [string, any, string]) : Object.entries(item) .filter(([, v]) => v !== null && v !== undefined && v !== "") .map(([k, v]) => [k, v, ""] as [string, any, string]); return ( toggleRightItemExpansion(`tab_${activeTabIndex}_${tabItemId}`)} > {tabSummaryColumns.map((col: any) => ( ))} {hasTabActions && ( )} {/* ์ƒ์„ธ ์ •๋ณด (ํ–‰ ํด๋ฆญ ์‹œ ํŽผ์ณ์ง) */} {isTabExpanded && ( )} ); })}
{col.label || col.name} ์ž‘์—…
{formatCellValue( col.name, getEntityJoinValue(item, col.name), rightCategoryMappings, col.format, )}
{currentTabConfig?.showEdit && ( )} {currentTabConfig?.showDelete && ( )}
์ƒ์„ธ ์ •๋ณด
{tabAllValues.map(([key, value, label]) => { const displayValue = (value === null || value === undefined || value === "") ? "-" : formatCellValue(key, value, rightCategoryMappings); return ( ); })}
{label || getColumnLabel(key)} {displayValue}
); } // ๋ฆฌ์ŠคํŠธ ๋ชจ๋“œ๋„ ํ…Œ์ด๋ธ”ํ˜•์œผ๋กœ ํ†ต์ผ (ํ–‰ ํด๋ฆญ ์‹œ ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ) { const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete; // showInSummary๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ ๋ฉ”์ธ ํ…Œ์ด๋ธ”์— ํ‘œ์‹œ const listSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false); return (
{listSummaryColumns.map((col: any) => ( ))} {hasTabActions && ( )} {currentTabData.map((item: any, idx: number) => { const tabItemId = item.id || item.ID || idx; const isTabExpanded = expandedRightItems.has(`tab_${activeTabIndex}_${tabItemId}`); // showInDetail์ด false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ ์ƒ์„ธ์— ํ‘œ์‹œ const listDetailColumns = tabColumns.filter((col: any) => col.showInDetail !== false); const tabAllValues: [string, any, string][] = listDetailColumns.length > 0 ? listDetailColumns.map((col: any) => [col.name, getEntityJoinValue(item, col.name), col.label || col.name] as [string, any, string]) : Object.entries(item) .filter(([, v]) => v !== null && v !== undefined && v !== "") .map(([k, v]) => [k, v, ""] as [string, any, string]); return ( toggleRightItemExpansion(`tab_${activeTabIndex}_${tabItemId}`)} > {listSummaryColumns.map((col: any) => ( ))} {hasTabActions && ( )} {isTabExpanded && ( )} ); })}
{col.label || col.name} ์ž‘์—…
{formatCellValue( col.name, getEntityJoinValue(item, col.name), rightCategoryMappings, col.format, )}
{currentTabConfig?.showEdit && ( )} {currentTabConfig?.showDelete && ( )}
์ƒ์„ธ ์ •๋ณด
{tabAllValues.map(([key, value, label]) => { const displayValue = (value === null || value === undefined || value === "") ? "-" : formatCellValue(key, value, rightCategoryMappings); return ( ); })}
{label || getColumnLabel(key)} {displayValue}
); } })() ) : componentConfig.rightPanel?.displayMode === "custom" ? ( // ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ํŒจ๋„ ์•ˆ์— ์ž์œ ๋กญ๊ฒŒ ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์น˜ // ์‹คํ–‰ ๋ชจ๋“œ์—์„œ ์ขŒ์ธก ๋ฏธ์„ ํƒ ์‹œ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ !isDesignMode && !selectedLeftItem ? (

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

) : (
{/* ๐Ÿ†• ์ปค์Šคํ…€ ๋ชจ๋“œ: ๋””์ž์ธ/์‹คํ–‰ ๋ชจ๋“œ ํ†ตํ•ฉ ๋ Œ๋”๋ง */} {componentConfig.rightPanel?.components && componentConfig.rightPanel.components.length > 0 ? (
{componentConfig.rightPanel.components.map((comp: PanelInlineComponent) => { const isSelectedComp = selectedPanelComponentId === comp.id; const isDraggingComp = draggingCompId === comp.id; const isResizingComp = resizingCompId === comp.id; // ๋“œ๋ž˜๊ทธ/๋ฆฌ์‚ฌ์ด์ฆˆ ์ค‘ ํ‘œ์‹œํ•  ํฌ๊ธฐ/์œ„์น˜ const displayX = isDraggingComp && dragPosition ? dragPosition.x : (comp.position?.x || 0); const displayY = isDraggingComp && dragPosition ? dragPosition.y : (comp.position?.y || 0); const displayWidth = isResizingComp && resizeSize ? resizeSize.width : (comp.size?.width || 200); const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100); // ์ปดํฌ๋„ŒํŠธ ๋ฐ์ดํ„ฐ๋ฅผ DynamicComponentRenderer ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ // componentConfig์˜ ์ฃผ์š” ์†์„ฑ์„ ์ตœ์ƒ์œ„๋กœ ํŽผ์นจ (์ผ๋ฐ˜ ํ™”๋ฉด์˜ overrides ํ”Œ๋ž˜ํŠธ๋‹๊ณผ ๋™์ผ) const componentData = { id: comp.id, type: "component" as const, componentType: comp.componentType, label: comp.label, position: comp.position || { x: 0, y: 0 }, size: { width: displayWidth, height: displayHeight }, componentConfig: comp.componentConfig || {}, style: comp.style || {}, // ํŒŒ์ผ ์—…๋กœ๋“œ/๋ฏธ๋””์–ด ๋“ฑ์ด component.tableName, component.columnName์„ ์ง์ ‘ ์ฐธ์กฐํ•˜๋ฏ€๋กœ ํŽผ์นจ tableName: comp.componentConfig?.tableName, columnName: comp.componentConfig?.columnName, webType: comp.componentConfig?.webType, inputType: comp.inputType || comp.componentConfig?.inputType, }; if (isDesignMode) { // ๋””์ž์ธ ๋ชจ๋“œ: ํƒญ ์ปดํฌ๋„ŒํŠธ์™€ ๋™์ผํ•˜๊ฒŒ ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง return (
{ e.stopPropagation(); // ํŒจ๋„ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ์‹œ ํƒญ ๋‚ด ์„ ํƒ ํ•ด์ œ if (comp.componentType !== "v2-tabs-widget") { setNestedTabSelectedCompId(undefined); } onSelectPanelComponent?.("right", comp.id, comp); }} > {/* ๋“œ๋ž˜๊ทธ ํ•ธ๋“ค - ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€ ์ƒ๋‹จ */}
handlePanelDragStart(e, "right", comp)} >
{comp.label || comp.componentType}
{/* ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง - ํ•ธ๋“ค ์•„๋ž˜์— ๋ณ„๋„ ์˜์—ญ */}
{/* ๐Ÿ†• ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ(ํƒญ, ๋ถ„ํ•  ํŒจ๋„)๋Š” ๋“œ๋กญ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ */}
{ handleNestedComponentUpdate("right", comp.id, updatedComp); }} // ๐Ÿ†• ์ค‘์ฒฉ๋œ ํƒญ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ํ•ธ๋“ค๋Ÿฌ - ๋ถ€๋ชจ ๋ถ„ํ•  ํŒจ๋„ ์ •๋ณด ํฌํ•จ onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => { console.log("๐Ÿ” [SplitPanel-Right] onSelectTabComponent ํ˜ธ์ถœ:", { tabId, compId, tabComp, parentSplitPanelId: component.id }); // ํƒญ ๋‚ด ์ปดํฌ๋„ŒํŠธ ์„ ํƒ ์ƒํƒœ ์—…๋ฐ์ดํŠธ setNestedTabSelectedCompId(compId); // ๋ถ€๋ชจ ๋ถ„ํ•  ํŒจ๋„ ์ •๋ณด์™€ ํ•จ๊ป˜ ์ „์—ญ ์ด๋ฒคํŠธ ๋ฐœ์ƒ const event = new CustomEvent("nested-tab-component-select", { detail: { tabsComponentId: comp.id, tabId, componentId: compId, component: tabComp, parentSplitPanelId: component.id, parentPanelSide: "right", }, }); window.dispatchEvent(event); }} selectedTabComponentId={nestedTabSelectedCompId} />
{/* ๋ฆฌ์‚ฌ์ด์ฆˆ ๊ฐ€์žฅ์ž๋ฆฌ ์˜์—ญ - ์„ ํƒ๋œ ์ปดํฌ๋„ŒํŠธ์—๋งŒ ํ‘œ์‹œ */} {isSelectedComp && ( <> {/* ์˜ค๋ฅธ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ (๋„ˆ๋น„ ์กฐ์ ˆ) */}
handlePanelResizeStart(e, "right", comp, "e")} /> {/* ์•„๋ž˜ ๊ฐ€์žฅ์ž๋ฆฌ (๋†’์ด ์กฐ์ ˆ) */}
handlePanelResizeStart(e, "right", comp, "s")} /> {/* ์˜ค๋ฅธ์ชฝ ์•„๋ž˜ ๋ชจ์„œ๋ฆฌ (๋„ˆ๋น„+๋†’์ด ์กฐ์ ˆ) */}
handlePanelResizeStart(e, "right", comp, "se")} /> )}
); } else { return (
{ setCustomLeftSelectedData((prev: Record) => ({ ...prev, [fieldName]: value })); }} tableName={componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName} menuObjid={(props as any).menuObjid} screenId={(props as any).screenId} userId={(props as any).userId} userName={(props as any).userName} companyCode={companyCode} allComponents={(props as any).allComponents} selectedRowsData={localSelectedRowsData} onSelectedRowsChange={handleLocalSelectedRowsChange} />
); } })}
) : ( // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์„ ๋•Œ ๋“œ๋กญ ์˜์—ญ ํ‘œ์‹œ

์ปค์Šคํ…€ ๋ชจ๋“œ

{isDesignMode ? "์ปดํฌ๋„ŒํŠธ๋ฅผ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ๋ฐฐ์น˜ํ•˜์„ธ์š”" : "๋ฐฐ์น˜๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"}

)}
) ) : isLoadingRight ? ( // ๋กœ๋”ฉ ์ค‘

๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

) : rightData ? ( // ์‹ค์ œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ Array.isArray(rightData) ? ( // ์กฐ์ธ ๋ชจ๋“œ: ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”/๋ฆฌ์ŠคํŠธ๋กœ ํ‘œ์‹œ (() => { // ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง const filteredData = rightSearchQuery ? rightData.filter((item) => { const searchLower = rightSearchQuery.toLowerCase(); return Object.entries(item).some(([key, value]) => { if (value === null || value === undefined) return false; return String(value).toLowerCase().includes(searchLower); }); }) : rightData; // ํ…Œ์ด๋ธ” ๋ชจ๋“œ ์ฒดํฌ const isTableMode = componentConfig.rightPanel?.displayMode === "table"; if (isTableMode) { // ํ…Œ์ด๋ธ” ๋ชจ๋“œ ๋ Œ๋”๋ง const displayColumns = componentConfig.rightPanel?.columns || []; // ๐Ÿ†• ๊ทธ๋ฃน ํ•ฉ์‚ฐ ๋ชจ๋“œ์ผ ๋•Œ: ๋ณตํ•ฉํ‚ค ์ปฌ๋Ÿผ์„ ์šฐ์„  ํ‘œ์‹œ const relationKeys = componentConfig.rightPanel?.relation?.keys || []; const keyColumns = relationKeys.map((k: any) => k.leftColumn).filter(Boolean); const isGroupedMode = selectedLeftItem?._originalItems?.length > 0; let columnsToShow: any[] = []; if (displayColumns.length > 0) { // ์„ค์ •๋œ ์ปฌ๋Ÿผ ์‚ฌ์šฉ (showInSummary๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ ํ…Œ์ด๋ธ”์— ํ‘œ์‹œ) columnsToShow = displayColumns .filter((col) => col.showInSummary !== false) .map((col) => ({ ...col, label: rightColumnLabels[col.name] || col.label || col.name, format: col.format, })); // ๐Ÿ†• ๊ทธ๋ฃน ํ•ฉ์‚ฐ ๋ชจ๋“œ์ด๊ณ , ํ‚ค ์ปฌ๋Ÿผ์ด ํ‘œ์‹œ ๋ชฉ๋ก์— ์—†์œผ๋ฉด ๋งจ ์•ž์— ์ถ”๊ฐ€ if (isGroupedMode && keyColumns.length > 0) { const existingColNames = columnsToShow.map((c) => c.name); const missingKeyColumns = keyColumns.filter((k: string) => !existingColNames.includes(k)); if (missingKeyColumns.length > 0) { const keyColsToAdd = missingKeyColumns.map((colName: string) => ({ name: colName, label: rightColumnLabels[colName] || colName, width: 120, align: "left" as const, format: undefined, _isKeyColumn: true, // ๊ตฌ๋ถ„์šฉ ํ”Œ๋ž˜๊ทธ })); columnsToShow = [...keyColsToAdd, ...columnsToShow]; console.log("๐Ÿ”— [์šฐ์ธกํŒจ๋„] ๊ทธ๋ฃน๋ชจ๋“œ - ํ‚ค ์ปฌ๋Ÿผ ์ถ”๊ฐ€:", missingKeyColumns); } } } else { // ๊ธฐ๋ณธ ์ปฌ๋Ÿผ ์ž๋™ ์ƒ์„ฑ columnsToShow = Object.keys(filteredData[0] || {}) .filter((key) => shouldShowField(key)) .slice(0, 5) .map((key) => ({ name: key, label: rightColumnLabels[key] || key, width: 150, align: "left" as const, format: undefined, })); } return (
{columnsToShow.map((col, idx) => ( ))} {/* ์ˆ˜์ • ๋˜๋Š” ์‚ญ์ œ ๋ฒ„ํŠผ์ด ํ•˜๋‚˜๋ผ๋„ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์„ ๋•Œ๋งŒ ์ž‘์—… ์ปฌ๋Ÿผ ํ‘œ์‹œ */} {!isDesignMode && ((componentConfig.rightPanel?.editButton?.enabled ?? true) || (componentConfig.rightPanel?.deleteButton?.enabled ?? true)) && ( )} {filteredData.map((item, idx) => { const itemId = item.id || item.ID || idx; return ( {columnsToShow.map((col, colIdx) => ( ))} {/* ์ˆ˜์ • ๋˜๋Š” ์‚ญ์ œ ๋ฒ„ํŠผ์ด ํ•˜๋‚˜๋ผ๋„ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์„ ๋•Œ๋งŒ ์ž‘์—… ์…€ ํ‘œ์‹œ */} {!isDesignMode && ((componentConfig.rightPanel?.editButton?.enabled ?? true) || (componentConfig.rightPanel?.deleteButton?.enabled ?? true)) && ( )} ); })}
{col.label} ์ž‘์—…
{formatCellValue( col.name, getEntityJoinValue(item, col.name), rightCategoryMappings, col.format, )}
{(componentConfig.rightPanel?.editButton?.enabled ?? true) && ( )} {(componentConfig.rightPanel?.deleteButton?.enabled ?? true) && ( )}
); } // ๋ชฉ๋ก ๋ชจ๋“œ - ํ…Œ์ด๋ธ”ํ˜• ๋””์ž์ธ (ํ–‰ ํด๋ฆญ ์‹œ ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ) { // ํ‘œ์‹œ ์ปฌ๋Ÿผ ๊ฒฐ์ • const rightColumns = componentConfig.rightPanel?.columns; let columnsToDisplay: { name: string; label: string; format?: string; bold?: boolean }[] = []; if (rightColumns && rightColumns.length > 0) { // showInSummary๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ ๋ฉ”์ธ ํ…Œ์ด๋ธ”์— ํ‘œ์‹œ columnsToDisplay = rightColumns .filter((col) => col.showInSummary !== false) .map((col) => ({ name: col.name, label: rightColumnLabels[col.name] || col.label || col.name, format: col.format, bold: col.bold, })); } else if (filteredData.length > 0) { columnsToDisplay = Object.keys(filteredData[0]) .filter((key) => shouldShowField(key)) .slice(0, 6) .map((key) => ({ name: key, label: rightColumnLabels[key] || key, })); } const hasEditButton = !isDesignMode && (componentConfig.rightPanel?.editButton?.enabled ?? true); const hasDeleteButton = !isDesignMode && (componentConfig.rightPanel?.deleteButton?.enabled ?? true); const hasActions = hasEditButton || hasDeleteButton; return filteredData.length > 0 ? (
{columnsToDisplay.map((col) => ( ))} {hasActions && ( )} {filteredData.map((item, idx) => { const itemId = item.id || item.ID || idx; const isExpanded = expandedRightItems.has(itemId); // ์ƒ์„ธ ์ •๋ณด์šฉ ์ „์ฒด ๊ฐ’ ๋ชฉ๋ก (showInDetail์ด false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ ํ‘œ์‹œ) let allValues: [string, any, string][] = []; if (rightColumns && rightColumns.length > 0) { allValues = rightColumns .filter((col) => col.showInDetail !== false) .map((col) => { const value = getEntityJoinValue(item, col.name); return [col.name, value, col.label] as [string, any, string]; }); } else { allValues = Object.entries(item) .filter(([, value]) => value !== null && value !== undefined && value !== "") .map(([key, value]) => [key, value, ""] as [string, any, string]); } return ( toggleRightItemExpansion(itemId)} > {columnsToDisplay.map((col) => ( ))} {hasActions && ( )} {/* ์ƒ์„ธ ์ •๋ณด (ํ–‰ ํด๋ฆญ ์‹œ ํŽผ์ณ์ง) */} {isExpanded && ( )} ); })}
{col.label} ์ž‘์—…
{formatCellValue( col.name, getEntityJoinValue(item, col.name), rightCategoryMappings, col.format, )}
{hasEditButton && ( )} {hasDeleteButton && ( )}
์ƒ์„ธ ์ •๋ณด
{allValues.map(([key, value, label]) => { const colConfig = rightColumns?.find((c) => c.name === key); const format = colConfig?.format; const displayValue = (value === null || value === undefined || value === "") ? "-" : formatCellValue(key, value, rightCategoryMappings, format); return ( ); })}
{label || getColumnLabel(key)} {displayValue}
) : (
{rightSearchQuery ? ( <>

๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•ด๋ณด์„ธ์š”.

) : ( "๊ด€๋ จ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." )}
); } })() ) : ( // ์ƒ์„ธ ๋ชจ๋“œ: ๋‹จ์ผ ๊ฐ์ฒด๋ฅผ ์ƒ์„ธ ์ •๋ณด๋กœ ํ‘œ์‹œ (() => { const rightColumns = componentConfig.rightPanel?.columns; let displayEntries: [string, any, string][] = []; if (rightColumns && rightColumns.length > 0) { console.log("๐Ÿ” [๋””๋ฒ„๊น…] ์ƒ์„ธ ๋ชจ๋“œ ํ‘œ์‹œ ๋กœ์ง:"); console.log(" ๐Ÿ“‹ rightData ์ „์ฒด:", rightData); console.log(" ๐Ÿ“‹ rightData keys:", Object.keys(rightData)); console.log( " โš™๏ธ ์„ค์ •๋œ ์ปฌ๋Ÿผ:", rightColumns.map((c) => `${c.name} (${c.label})`), ); // ์„ค์ •๋œ ์ปฌ๋Ÿผ๋งŒ ํ‘œ์‹œ (showInDetail์ด false๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋งŒ) displayEntries = rightColumns .filter((col) => col.showInDetail !== false) .map((col) => { // ๐Ÿ†• ์—”ํ‹ฐํ‹ฐ ์กฐ์ธ ์ปฌ๋Ÿผ ์ฒ˜๋ฆฌ (์˜ˆ: item_info.item_name โ†’ item_name) let value = rightData[col.name]; console.log(` ๐Ÿ”Ž ์ปฌ๋Ÿผ "${col.name}": ์ง์ ‘ ์ ‘๊ทผ = ${value}`); if (value === undefined && col.name.includes(".")) { const columnName = col.name.split(".").pop(); value = rightData[columnName || ""]; console.log(` โ†’ ๋ณ€ํ™˜ ํ›„ "${columnName}" ์ ‘๊ทผ = ${value}`); } return [col.name, value, col.label] as [string, any, string]; }) ; // ์„ค์ •๋œ ์ปฌ๋Ÿผ์€ null/empty์—ฌ๋„ ํ•ญ์ƒ ํ‘œ์‹œ console.log(" โœ… ์ตœ์ข… ํ‘œ์‹œํ•  ํ•ญ๋ชฉ:", displayEntries.length, "๊ฐœ"); } else { // ์„ค์ • ์—†์œผ๋ฉด ๋ชจ๋“  ์ปฌ๋Ÿผ ํ‘œ์‹œ displayEntries = Object.entries(rightData) .filter(([_, value]) => value !== null && value !== undefined && value !== "") .map(([key, value]) => [key, value, ""] as [string, any, string]); console.log(" โš ๏ธ ์ปฌ๋Ÿผ ์„ค์ • ์—†์Œ, ๋ชจ๋“  ์ปฌ๋Ÿผ ํ‘œ์‹œ"); } return (
{displayEntries.map(([key, value, label]) => (
{label || getColumnLabel(key)}
{(value === null || value === undefined || value === "") ? - : String(value)}
))}
); })() ) ) : selectedLeftItem && isDesignMode ? ( // ๋””์ž์ธ ๋ชจ๋“œ: ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ

{selectedLeftItem.name} ์ƒ์„ธ ์ •๋ณด

ํ•ญ๋ชฉ 1: ๊ฐ’ 1
ํ•ญ๋ชฉ 2: ๊ฐ’ 2
ํ•ญ๋ชฉ 3: ๊ฐ’ 3
) : ( // ๋ฐ์ดํ„ฐ ์—†์Œ ๋˜๋Š” ์ดˆ๊ธฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ
{componentConfig.rightPanel?.relation?.type === "join" ? ( <>

๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

) : ( <>

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

)}
)}
{/* ์ถ”๊ฐ€ ๋ชจ๋‹ฌ */} {addModalPanel === "left" ? `${componentConfig.leftPanel?.title} ์ถ”๊ฐ€` : addModalPanel === "right" ? `${componentConfig.rightPanel?.title} ์ถ”๊ฐ€` : `ํ•˜์œ„ ${componentConfig.leftPanel?.title} ์ถ”๊ฐ€`} {addModalPanel === "left-item" ? "์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ํ•˜์œ„ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." : "์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ํ•„์ˆ˜ ํ•ญ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."}
{(() => { // ์–ด๋–ค ์ปฌ๋Ÿผ๋“ค์„ ํ‘œ์‹œํ• ์ง€ ๊ฒฐ์ • let modalColumns: Array<{ name: string; label: string; required?: boolean }> | undefined; if (addModalPanel === "left") { modalColumns = componentConfig.leftPanel?.addModalColumns; } else if (addModalPanel === "right") { modalColumns = componentConfig.rightPanel?.addModalColumns; } else if (addModalPanel === "left-item") { modalColumns = componentConfig.leftPanel?.itemAddConfig?.addModalColumns; } return modalColumns?.map((col, index) => { // ํ•ญ๋ชฉ๋ณ„ ์ถ”๊ฐ€ ๋ฒ„ํŠผ์œผ๋กœ ์—ด๋ ธ์„ ๋•Œ, parentColumn์€ ๋ฏธ๋ฆฌ ์ฑ„์›Œ์ ธ ์žˆ๊ณ  ์ˆ˜์ • ๋ถˆ๊ฐ€ const isItemAddPreFilled = addModalPanel === "left-item" && componentConfig.leftPanel?.itemAddConfig?.parentColumn === col.name && addModalFormData[col.name]; // ์šฐ์ธก ํŒจ๋„ ์ถ”๊ฐ€ ์‹œ, ์กฐ์ธ ์ปฌ๋Ÿผ(rightColumn)์€ ๋ฏธ๋ฆฌ ์ฑ„์›Œ์ ธ ์žˆ๊ณ  ์ˆ˜์ • ๋ถˆ๊ฐ€ const isRightJoinPreFilled = addModalPanel === "right" && componentConfig.rightPanel?.rightColumn === col.name && addModalFormData[col.name]; const isPreFilled = isItemAddPreFilled || isRightJoinPreFilled; return (
{ setAddModalFormData((prev) => ({ ...prev, [col.name]: e.target.value, })); }} placeholder={`${col.label} ์ž…๋ ฅ`} className="h-8 text-xs sm:h-10 sm:text-sm" required={col.required} disabled={isPreFilled} />
); }); })()}
{/* ์ˆ˜์ • ๋ชจ๋‹ฌ */} {editModalPanel === "left" ? `${componentConfig.leftPanel?.title} ์ˆ˜์ •` : `${componentConfig.rightPanel?.title} ์ˆ˜์ •`} ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ํ•ญ๋ชฉ์„ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”.
{editModalItem && (() => { // ์ขŒ์ธก ํŒจ๋„ ์ˆ˜์ •: leftColumn๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ if (editModalPanel === "left") { const leftColumn = componentConfig.rightPanel?.relation?.leftColumn; // leftColumn๋งŒ ํ‘œ์‹œ if (!leftColumn || editModalFormData[leftColumn] === undefined) { return

์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ์ด ์—†์Šต๋‹ˆ๋‹ค.

; } return (
{ setEditModalFormData((prev) => ({ ...prev, [leftColumn]: e.target.value, })); }} placeholder={`${leftColumn} ์ž…๋ ฅ`} className="h-8 text-xs sm:h-10 sm:text-sm" />
); } // ์šฐ์ธก ํŒจ๋„ ์ˆ˜์ •: ์šฐ์ธก ํŒจ๋„์— ์„ค์ •๋œ ํ‘œ์‹œ ์ปฌ๋Ÿผ๋“ค๋งŒ if (editModalPanel === "right") { const rightColumns = componentConfig.rightPanel?.columns; if (rightColumns && rightColumns.length > 0) { // ์„ค์ •๋œ ์ปฌ๋Ÿผ๋งŒ ํ‘œ์‹œ return rightColumns.map((col) => (
{ setEditModalFormData((prev) => ({ ...prev, [col.name]: e.target.value, })); }} placeholder={`${col.label || col.name} ์ž…๋ ฅ`} className="h-8 text-xs sm:h-10 sm:text-sm" />
)); } else { // ์„ค์ •์ด ์—†์œผ๋ฉด ๋ชจ๋“  ์ปฌ๋Ÿผ ํ‘œ์‹œ (๋ฏผ๊ฐํ•œ ํ•„๋“œ ์ œ์™ธ) return Object.entries(editModalFormData) .filter(([key]) => shouldShowField(key)) .map(([key, value]) => (
{ setEditModalFormData((prev) => ({ ...prev, [key]: e.target.value, })); }} placeholder={`${key} ์ž…๋ ฅ`} className="h-8 text-xs sm:h-10 sm:text-sm" />
)); } } return null; })()}
{/* ์‚ญ์ œ ํ™•์ธ ๋ชจ๋‹ฌ */} ์‚ญ์ œ ํ™•์ธ ์ •๋ง๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
); }; /** * SplitPanelLayout ๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ */ export const SplitPanelLayoutWrapper: React.FC = (props) => { return ; };