"use client"; import React, { useState, useEffect, useMemo } from "react"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { tableManagementApi } from "@/lib/api/tableManagement"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Trash2, Database, ChevronsUpDown, Check } from "lucide-react"; import { cn } from "@/lib/utils"; interface CardDisplayConfigPanelProps { config: any; onChange: (config: any) => void; screenTableName?: string; tableColumns?: any[]; } interface EntityJoinColumn { tableName: string; columnName: string; columnLabel: string; dataType: string; joinAlias: string; suggestedLabel: string; } interface JoinTable { tableName: string; currentDisplayColumn: string; joinConfig?: { sourceColumn: string; }; availableColumns: Array<{ columnName: string; columnLabel: string; dataType: string; description?: string; }>; } /** * CardDisplay 설정 패널 * 카드 레이아웃과 동일한 설정 UI 제공 + 엔티티 조인 컬럼 지원 */ export const CardDisplayConfigPanel: React.FC = ({ config, onChange, screenTableName, tableColumns = [], }) => { // 테이블 선택 상태 const [tableComboboxOpen, setTableComboboxOpen] = useState(false); const [allTables, setAllTables] = useState>([]); const [loadingTables, setLoadingTables] = useState(false); const [availableColumns, setAvailableColumns] = useState([]); const [loadingColumns, setLoadingColumns] = useState(false); // 엔티티 조인 컬럼 상태 const [entityJoinColumns, setEntityJoinColumns] = useState<{ availableColumns: EntityJoinColumn[]; joinTables: JoinTable[]; }>({ availableColumns: [], joinTables: [] }); const [loadingEntityJoins, setLoadingEntityJoins] = useState(false); // 현재 사용할 테이블명 const targetTableName = useMemo(() => { if (config.useCustomTable && config.customTableName) { return config.customTableName; } return config.tableName || screenTableName; }, [config.useCustomTable, config.customTableName, config.tableName, screenTableName]); // 전체 테이블 목록 로드 useEffect(() => { const loadAllTables = async () => { setLoadingTables(true); try { const response = await tableManagementApi.getTableList(); if (response.success && response.data) { setAllTables(response.data.map((t: any) => ({ tableName: t.tableName || t.table_name, displayName: t.tableLabel || t.displayName || t.tableName || t.table_name, }))); } } catch (error) { console.error("테이블 목록 로드 실패:", error); } finally { setLoadingTables(false); } }; loadAllTables(); }, []); // 선택된 테이블의 컬럼 로드 useEffect(() => { const loadColumns = async () => { if (!targetTableName) { setAvailableColumns([]); return; } // 커스텀 테이블이 아니면 props로 받은 tableColumns 사용 if (!config.useCustomTable && tableColumns && tableColumns.length > 0) { setAvailableColumns(tableColumns); return; } setLoadingColumns(true); try { const result = await tableManagementApi.getColumnList(targetTableName); if (result.success && result.data?.columns) { setAvailableColumns(result.data.columns.map((col: any) => ({ columnName: col.columnName, columnLabel: col.displayName || col.columnLabel || col.columnName, dataType: col.dataType, }))); } } catch (error) { console.error("컬럼 목록 로드 실패:", error); setAvailableColumns([]); } finally { setLoadingColumns(false); } }; loadColumns(); }, [targetTableName, config.useCustomTable, tableColumns]); // 엔티티 조인 컬럼 정보 가져오기 useEffect(() => { const fetchEntityJoinColumns = async () => { if (!targetTableName) { setEntityJoinColumns({ availableColumns: [], joinTables: [] }); return; } setLoadingEntityJoins(true); try { const result = await entityJoinApi.getEntityJoinColumns(targetTableName); setEntityJoinColumns({ availableColumns: result.availableColumns || [], joinTables: result.joinTables || [], }); } catch (error) { console.error("Entity 조인 컬럼 조회 오류:", error); setEntityJoinColumns({ availableColumns: [], joinTables: [] }); } finally { setLoadingEntityJoins(false); } }; fetchEntityJoinColumns(); }, [targetTableName]); // 테이블 선택 핸들러 const handleTableSelect = (tableName: string, isScreenTable: boolean) => { if (isScreenTable) { // 화면 기본 테이블 선택 onChange({ ...config, useCustomTable: false, customTableName: undefined, tableName: tableName, columnMapping: { displayColumns: [] }, // 컬럼 매핑 초기화 }); } else { // 다른 테이블 선택 onChange({ ...config, useCustomTable: true, customTableName: tableName, tableName: tableName, columnMapping: { displayColumns: [] }, // 컬럼 매핑 초기화 }); } setTableComboboxOpen(false); }; // 현재 선택된 테이블 표시명 가져오기 const getSelectedTableDisplay = () => { if (!targetTableName) return "테이블을 선택하세요"; const found = allTables.find(t => t.tableName === targetTableName); return found?.displayName || targetTableName; }; const handleChange = (key: string, value: any) => { onChange({ ...config, [key]: value }); }; const handleNestedChange = (path: string, value: any) => { const keys = path.split("."); let newConfig = { ...config }; let current = newConfig; for (let i = 0; i < keys.length - 1; i++) { if (!current[keys[i]]) { current[keys[i]] = {}; } current = current[keys[i]]; } current[keys[keys.length - 1]] = value; onChange(newConfig); }; // 컬럼 선택 시 조인 컬럼이면 joinColumns 설정도 함께 업데이트 const handleColumnSelect = (path: string, columnName: string) => { const joinColumn = entityJoinColumns.availableColumns.find( (col) => col.joinAlias === columnName ); if (joinColumn) { const joinColumnsConfig = config.joinColumns || []; const existingJoinColumn = joinColumnsConfig.find( (jc: any) => jc.columnName === columnName ); if (!existingJoinColumn) { const joinTableInfo = entityJoinColumns.joinTables?.find( (jt) => jt.tableName === joinColumn.tableName ); const newJoinColumnConfig = { columnName: joinColumn.joinAlias, label: joinColumn.suggestedLabel || joinColumn.columnLabel, sourceColumn: joinTableInfo?.joinConfig?.sourceColumn || "", referenceTable: joinColumn.tableName, referenceColumn: joinColumn.columnName, isJoinColumn: true, }; onChange({ ...config, columnMapping: { ...config.columnMapping, [path.split(".")[1]]: columnName, }, joinColumns: [...joinColumnsConfig, newJoinColumnConfig], }); return; } } handleNestedChange(path, columnName); }; // 표시 컬럼 추가 const addDisplayColumn = () => { const currentColumns = config.columnMapping?.displayColumns || []; const newColumns = [...currentColumns, ""]; handleNestedChange("columnMapping.displayColumns", newColumns); }; // 표시 컬럼 삭제 const removeDisplayColumn = (index: number) => { const currentColumns = [...(config.columnMapping?.displayColumns || [])]; currentColumns.splice(index, 1); handleNestedChange("columnMapping.displayColumns", currentColumns); }; // 표시 컬럼 값 변경 const updateDisplayColumn = (index: number, value: string) => { const currentColumns = [...(config.columnMapping?.displayColumns || [])]; currentColumns[index] = value; const joinColumn = entityJoinColumns.availableColumns.find( (col) => col.joinAlias === value ); if (joinColumn) { const joinColumnsConfig = config.joinColumns || []; const existingJoinColumn = joinColumnsConfig.find( (jc: any) => jc.columnName === value ); if (!existingJoinColumn) { const joinTableInfo = entityJoinColumns.joinTables?.find( (jt) => jt.tableName === joinColumn.tableName ); const newJoinColumnConfig = { columnName: joinColumn.joinAlias, label: joinColumn.suggestedLabel || joinColumn.columnLabel, sourceColumn: joinTableInfo?.joinConfig?.sourceColumn || "", referenceTable: joinColumn.tableName, referenceColumn: joinColumn.columnName, isJoinColumn: true, }; onChange({ ...config, columnMapping: { ...config.columnMapping, displayColumns: currentColumns, }, joinColumns: [...joinColumnsConfig, newJoinColumnConfig], }); return; } } handleNestedChange("columnMapping.displayColumns", currentColumns); }; // 테이블별로 조인 컬럼 그룹화 const joinColumnsByTable: Record = {}; entityJoinColumns.availableColumns.forEach((col) => { if (!joinColumnsByTable[col.tableName]) { joinColumnsByTable[col.tableName] = []; } joinColumnsByTable[col.tableName].push(col); }); // 현재 사용할 컬럼 목록 (커스텀 테이블이면 로드한 컬럼, 아니면 props) const currentTableColumns = config.useCustomTable ? availableColumns : (tableColumns.length > 0 ? tableColumns : availableColumns); // 컬럼 선택 셀렉트 박스 렌더링 (Shadcn UI) const renderColumnSelect = ( value: string, onChangeHandler: (value: string) => void, placeholder: string = "컬럼을 선택하세요" ) => { return ( ); }; return (
카드 디스플레이 설정
{/* 테이블 선택 */}
테이블을 찾을 수 없습니다. {/* 화면 기본 테이블 */} {screenTableName && ( handleTableSelect(screenTableName, true)} className="text-xs" > {allTables.find(t => t.tableName === screenTableName)?.displayName || screenTableName} )} {/* 전체 테이블 */} {allTables .filter(t => t.tableName !== screenTableName) .map((table) => ( handleTableSelect(table.tableName, false)} className="text-xs" > {table.displayName} ))} {config.useCustomTable && (

화면 기본 테이블이 아닌 다른 테이블의 데이터를 표시합니다.

)}
{/* 테이블이 선택된 경우 컬럼 매핑 설정 */} {(currentTableColumns.length > 0 || loadingColumns) && (
컬럼 매핑
{(loadingEntityJoins || loadingColumns) && (
{loadingColumns ? "컬럼 로딩 중..." : "조인 컬럼 로딩 중..."}
)}
{renderColumnSelect( config.columnMapping?.titleColumn || "", (value) => handleColumnSelect("columnMapping.titleColumn", value) )}
{renderColumnSelect( config.columnMapping?.subtitleColumn || "", (value) => handleColumnSelect("columnMapping.subtitleColumn", value) )}
{renderColumnSelect( config.columnMapping?.descriptionColumn || "", (value) => handleColumnSelect("columnMapping.descriptionColumn", value) )}
{renderColumnSelect( config.columnMapping?.imageColumn || "", (value) => handleColumnSelect("columnMapping.imageColumn", value) )}
{/* 동적 표시 컬럼 추가 */}
{(config.columnMapping?.displayColumns || []).map((column: string, index: number) => (
{renderColumnSelect( column, (value) => updateDisplayColumn(index, value) )}
))} {(!config.columnMapping?.displayColumns || config.columnMapping.displayColumns.length === 0) && (
"컬럼 추가" 버튼을 클릭하여 표시할 컬럼을 추가하세요
)}
)} {/* 카드 스타일 설정 */}
카드 스타일
handleChange("cardsPerRow", parseInt(e.target.value))} className="h-8 text-xs" />
handleChange("cardSpacing", parseInt(e.target.value))} className="h-8 text-xs" />
handleNestedChange("cardStyle.showTitle", checked)} />
handleNestedChange("cardStyle.showSubtitle", checked)} />
handleNestedChange("cardStyle.showDescription", checked)} />
handleNestedChange("cardStyle.showImage", checked)} />
handleNestedChange("cardStyle.showActions", checked)} />
{/* 개별 버튼 설정 */} {(config.cardStyle?.showActions ?? true) && (
handleNestedChange("cardStyle.showViewButton", checked)} />
handleNestedChange("cardStyle.showEditButton", checked)} />
handleNestedChange("cardStyle.showDeleteButton", checked)} />
)}
handleNestedChange("cardStyle.maxDescriptionLength", parseInt(e.target.value))} className="h-8 text-xs" />
{/* 공통 설정 */}
공통 설정
handleChange("disabled", checked)} />
handleChange("readonly", checked)} />
); };