diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 955bae86..a7e7e874 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -6,11 +6,13 @@ 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 } 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"; @@ -64,6 +66,8 @@ export const SplitPanelLayoutComponent: React.FC const [expandedItems, setExpandedItems] = useState>(new Set()); // 펼쳐진 항목들 const [leftColumnLabels, setLeftColumnLabels] = useState>({}); // 좌측 컬럼 라벨 const [rightColumnLabels, setRightColumnLabels] = useState>({}); // 우측 컬럼 라벨 + const [leftCategoryMappings, setLeftCategoryMappings] = useState>>({}); // 좌측 카테고리 매핑 + const [rightCategoryMappings, setRightCategoryMappings] = useState>>({}); // 우측 카테고리 매핑 const { toast } = useToast(); // 추가 모달 상태 @@ -241,6 +245,39 @@ export const SplitPanelLayoutComponent: React.FC })); }, [leftData, leftGrouping]); + // 셀 값 포맷팅 함수 (카테고리 타입 처리) + const formatCellValue = useCallback(( + columnName: string, + value: any, + categoryMappings: Record> + ) => { + if (value === null || value === undefined) return "-"; + + // 카테고리 매핑이 있는지 확인 + const mapping = categoryMappings[columnName]; + if (mapping && mapping[String(value)]) { + const categoryData = mapping[String(value)]; + const displayLabel = categoryData.label || String(value); + const displayColor = categoryData.color || "#64748b"; + + // 배지로 표시 + return ( + + {displayLabel} + + ); + } + + // 일반 값 + return String(value); + }, []); + // 좌측 데이터 로드 const loadLeftData = useCallback(async () => { const leftTableName = componentConfig.leftPanel?.tableName; @@ -523,6 +560,112 @@ export const SplitPanelLayoutComponent: React.FC 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-type/category-values/${leftTableName}/${columnName}` + ); + + 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 { + // 1. 컬럼 메타 정보 조회 + const columnsResponse = await tableTypeApi.getColumns(rightTableName); + const categoryColumns = columnsResponse.filter( + (col: any) => col.inputType === "category" + ); + + if (categoryColumns.length === 0) { + setRightCategoryMappings({}); + return; + } + + // 2. 각 카테고리 컬럼에 대한 값 조회 + const mappings: Record> = {}; + + for (const col of categoryColumns) { + const columnName = col.columnName || col.column_name; + try { + const response = await apiClient.get( + `/table-type/category-values/${rightTableName}/${columnName}` + ); + + 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); + } + } + + setRightCategoryMappings(mappings); + } catch (error) { + console.error("우측 카테고리 매핑 로드 실패:", error); + } + }; + + loadRightCategoryMappings(); + }, [componentConfig.rightPanel?.tableName, isDesignMode]); + // 항목 펼치기/접기 토글 const toggleExpand = useCallback((itemId: any) => { setExpandedItems(prev => { @@ -1193,9 +1336,7 @@ export const SplitPanelLayoutComponent: React.FC className="whitespace-nowrap px-3 py-2 text-sm text-gray-900" style={{ textAlign: col.align || "left" }} > - {item[col.name] !== null && item[col.name] !== undefined - ? String(item[col.name]) - : "-"} + {formatCellValue(col.name, item[col.name], leftCategoryMappings)} ))} @@ -1246,9 +1387,7 @@ export const SplitPanelLayoutComponent: React.FC className="whitespace-nowrap px-3 py-2 text-sm text-gray-900" style={{ textAlign: col.align || "left" }} > - {item[col.name] !== null && item[col.name] !== undefined - ? String(item[col.name]) - : "-"} + {formatCellValue(col.name, item[col.name], leftCategoryMappings)} ))} @@ -1619,9 +1758,7 @@ export const SplitPanelLayoutComponent: React.FC className="whitespace-nowrap px-3 py-2 text-sm text-gray-900" style={{ textAlign: col.align || "left" }} > - {item[col.name] !== null && item[col.name] !== undefined - ? String(item[col.name]) - : "-"} + {formatCellValue(col.name, item[col.name], rightCategoryMappings)} ))} {!isDesignMode && (