"use client"; import React, { useState, useEffect } from "react"; import { Badge } from "@/components/ui/badge"; import { Database, Type, Hash, Calendar, CheckSquare, List, AlignLeft, Code, Building, File, Link2, ChevronDown, ChevronRight, } from "lucide-react"; import { TableInfo, WebType } from "@/types/screen"; import { entityJoinApi } from "@/lib/api/entityJoin"; interface EntityJoinColumn { columnName: string; columnLabel: string; dataType: string; inputType?: string; description?: string; } interface EntityJoinTable { tableName: string; currentDisplayColumn: string; availableColumns: EntityJoinColumn[]; } interface TablesPanelProps { tables: TableInfo[]; searchTerm: string; onSearchChange: (term: string) => void; onDragStart: (e: React.DragEvent, table: TableInfo, column?: any) => void; selectedTableName?: string; placedColumns?: Set; // 이미 배치된 컬럼명 집합 (tableName.columnName 형식) } // 위젯 타입별 아이콘 const getWidgetIcon = (widgetType: WebType) => { switch (widgetType) { case "text": case "email": case "tel": return ; case "number": case "decimal": return ; case "date": case "datetime": return ; case "select": case "dropdown": return ; case "textarea": case "text_area": return ; case "boolean": case "checkbox": return ; case "code": return ; case "entity": return ; case "file": return ; default: return ; } }; export const TablesPanel: React.FC = ({ tables, searchTerm, onDragStart, placedColumns = new Set(), }) => { // 엔티티 조인 컬럼 상태 const [entityJoinTables, setEntityJoinTables] = useState([]); const [loadingEntityJoins, setLoadingEntityJoins] = useState(false); const [expandedJoinTables, setExpandedJoinTables] = useState>(new Set()); // 시스템 컬럼 목록 (숨김 처리) const systemColumns = new Set([ "id", "created_date", "updated_date", "writer", "company_code", ]); // 메인 테이블명 추출 const mainTableName = tables[0]?.tableName; // 엔티티 조인 컬럼 로드 useEffect(() => { const fetchEntityJoinColumns = async () => { if (!mainTableName) { setEntityJoinTables([]); return; } setLoadingEntityJoins(true); try { const result = await entityJoinApi.getEntityJoinColumns(mainTableName); setEntityJoinTables(result.joinTables || []); // 기본적으로 모든 조인 테이블 펼치기 setExpandedJoinTables(new Set(result.joinTables?.map((t) => t.tableName) || [])); } catch (error) { console.error("엔티티 조인 컬럼 조회 오류:", error); setEntityJoinTables([]); } finally { setLoadingEntityJoins(false); } }; fetchEntityJoinColumns(); }, [mainTableName]); // 조인 테이블 펼치기/접기 토글 const toggleJoinTable = (tableName: string) => { setExpandedJoinTables((prev) => { const newSet = new Set(prev); if (newSet.has(tableName)) { newSet.delete(tableName); } else { newSet.add(tableName); } return newSet; }); }; // 엔티티 조인 컬럼 드래그 핸들러 const handleEntityJoinDragStart = ( e: React.DragEvent, joinTable: EntityJoinTable, column: EntityJoinColumn, ) => { // "테이블명.컬럼명" 형식으로 컬럼 정보 생성 const fullColumnName = `${joinTable.tableName}.${column.columnName}`; const columnData = { columnName: fullColumnName, columnLabel: column.columnLabel || column.columnName, dataType: column.dataType, widgetType: "text" as WebType, isEntityJoin: true, entityJoinTable: joinTable.tableName, entityJoinColumn: column.columnName, }; // 기존 테이블 정보를 기반으로 가상의 테이블 정보 생성 const virtualTable: TableInfo = { tableName: mainTableName || "", tableLabel: tables[0]?.tableLabel || mainTableName || "", columns: [columnData], }; onDragStart(e, virtualTable, columnData); }; // 이미 배치된 컬럼과 시스템 컬럼을 제외한 테이블 정보 생성 const tablesWithAvailableColumns = tables.map((table) => ({ ...table, columns: table.columns.filter((col) => { const columnKey = `${table.tableName}.${col.columnName}`; // 시스템 컬럼이거나 이미 배치된 컬럼은 제외 return !systemColumns.has(col.columnName.toLowerCase()) && !placedColumns.has(columnKey); }), })); // 검색어가 있으면 컬럼 필터링 const filteredTables = tablesWithAvailableColumns .map((table) => { if (!searchTerm) { return table; } const searchLower = searchTerm.toLowerCase(); // 테이블명이 검색어와 일치하면 모든 컬럼 표시 if ( table.tableName.toLowerCase().includes(searchLower) || (table.tableLabel && table.tableLabel.toLowerCase().includes(searchLower)) ) { return table; } // 그렇지 않으면 컬럼명/라벨이 검색어와 일치하는 컬럼만 필터링 const filteredColumns = table.columns.filter( (col) => col.columnName.toLowerCase().includes(searchLower) || (col.columnLabel && col.columnLabel.toLowerCase().includes(searchLower)), ); return { ...table, columns: filteredColumns, }; }) .filter((table) => table.columns.length > 0); // 컬럼이 있는 테이블만 표시 return (
{/* 테이블과 컬럼 평면 목록 */}
{filteredTables.map((table) => (
{/* 테이블 헤더 */}
{table.tableLabel || table.tableName} {table.columns.length}개
{/* 컬럼 목록 (항상 표시) */}
{table.columns.map((column) => (
onDragStart(e, table, column)} > {getWidgetIcon(column.widgetType)}
{column.columnLabel || column.columnName}
{column.widgetType} {column.required && ( 필수 )}
))}
))} {/* 엔티티 조인 컬럼 섹션 */} {entityJoinTables.length > 0 && (
엔티티 조인 컬럼 {entityJoinTables.length}
{entityJoinTables.map((joinTable) => { const isExpanded = expandedJoinTables.has(joinTable.tableName); // 검색어로 필터링 const filteredColumns = searchTerm ? joinTable.availableColumns.filter( (col) => col.columnName.toLowerCase().includes(searchTerm.toLowerCase()) || col.columnLabel.toLowerCase().includes(searchTerm.toLowerCase()), ) : joinTable.availableColumns; // 검색 결과가 없으면 표시하지 않음 if (searchTerm && filteredColumns.length === 0) { return null; } return (
{/* 조인 테이블 헤더 */}
toggleJoinTable(joinTable.tableName)} >
{isExpanded ? ( ) : ( )} {joinTable.tableName} {filteredColumns.length}개
{/* 조인 컬럼 목록 */} {isExpanded && (
{filteredColumns.map((column) => { const fullColumnName = `${joinTable.tableName}.${column.columnName}`; const isPlaced = placedColumns.has(fullColumnName); if (isPlaced) return null; return (
handleEntityJoinDragStart(e, joinTable, column)} title="읽기 전용 - 조인된 테이블에서 참조" >
{column.columnLabel || column.columnName}
읽기 {column.inputType || "text"}
); })}
)}
); })}
)} {/* 로딩 표시 */} {loadingEntityJoins && (
엔티티 조인 컬럼 로드 중...
)}
); }; export default TablesPanel;