카드 디스플레이 설정 패널에 테이블 선택 기능을 추가하고, 커스텀 테이블 사용 여부에 따라 컬럼 목록을 동적으로 로드하도록 개선했습니다. 또한, 다국어 지원 및 테이블 설정 현황 문서의 내용을 업데이트하여 적용 상태를 명확히 하였습니다.

This commit is contained in:
kjs
2026-01-15 17:07:18 +09:00
parent 57d86c8ef1
commit 7181822832
3 changed files with 273 additions and 72 deletions

View File

@@ -1,7 +1,8 @@
"use client";
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useMemo } from "react";
import { entityJoinApi } from "@/lib/api/entityJoin";
import { tableManagementApi } from "@/lib/api/tableManagement";
import {
Select,
SelectContent,
@@ -11,11 +12,14 @@ import {
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 } from "lucide-react";
import { Trash2, Database, ChevronsUpDown, Check } from "lucide-react";
import { cn } from "@/lib/utils";
interface CardDisplayConfigPanelProps {
config: any;
@@ -57,6 +61,13 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
screenTableName,
tableColumns = [],
}) => {
// 테이블 선택 상태
const [tableComboboxOpen, setTableComboboxOpen] = useState(false);
const [allTables, setAllTables] = useState<Array<{ tableName: string; displayName: string }>>([]);
const [loadingTables, setLoadingTables] = useState(false);
const [availableColumns, setAvailableColumns] = useState<any[]>([]);
const [loadingColumns, setLoadingColumns] = useState(false);
// 엔티티 조인 컬럼 상태
const [entityJoinColumns, setEntityJoinColumns] = useState<{
availableColumns: EntityJoinColumn[];
@@ -64,18 +75,80 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
}>({ 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 () => {
const tableName = config.tableName || screenTableName;
if (!tableName) {
if (!targetTableName) {
setEntityJoinColumns({ availableColumns: [], joinTables: [] });
return;
}
setLoadingEntityJoins(true);
try {
const result = await entityJoinApi.getEntityJoinColumns(tableName);
const result = await entityJoinApi.getEntityJoinColumns(targetTableName);
setEntityJoinColumns({
availableColumns: result.availableColumns || [],
joinTables: result.joinTables || [],
@@ -89,7 +162,38 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
};
fetchEntityJoinColumns();
}, [config.tableName, screenTableName]);
}, [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 });
@@ -219,6 +323,9 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
joinColumnsByTable[col.tableName].push(col);
});
// 현재 사용할 컬럼 목록 (커스텀 테이블이면 로드한 컬럼, 아니면 props)
const currentTableColumns = config.useCustomTable ? availableColumns : (tableColumns.length > 0 ? tableColumns : availableColumns);
// 컬럼 선택 셀렉트 박스 렌더링 (Shadcn UI)
const renderColumnSelect = (
value: string,
@@ -240,12 +347,12 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
</SelectItem>
{/* 기본 테이블 컬럼 */}
{tableColumns.length > 0 && (
{currentTableColumns.length > 0 && (
<SelectGroup>
<SelectLabel className="text-xs font-semibold text-muted-foreground">
</SelectLabel>
{tableColumns.map((column) => (
{currentTableColumns.map((column: any) => (
<SelectItem
key={column.columnName}
value={column.columnName}
@@ -283,13 +390,99 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
<div className="space-y-4">
<div className="text-sm font-medium"> </div>
{/* 테이블 선택 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={tableComboboxOpen}
className="h-9 w-full justify-between text-xs"
disabled={loadingTables}
>
<div className="flex items-center gap-2 truncate">
<Database className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">{getSelectedTableDisplay()}</span>
</div>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder="테이블 검색..." className="text-xs" />
<CommandList>
<CommandEmpty className="py-3 text-center text-xs text-muted-foreground">
.
</CommandEmpty>
{/* 화면 기본 테이블 */}
{screenTableName && (
<CommandGroup heading="기본 (화면 테이블)">
<CommandItem
value={screenTableName}
onSelect={() => handleTableSelect(screenTableName, true)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
targetTableName === screenTableName && !config.useCustomTable
? "opacity-100"
: "opacity-0"
)}
/>
<Database className="mr-2 h-3.5 w-3.5 text-blue-500" />
{allTables.find(t => t.tableName === screenTableName)?.displayName || screenTableName}
</CommandItem>
</CommandGroup>
)}
{/* 전체 테이블 */}
<CommandGroup heading="전체 테이블">
{allTables
.filter(t => t.tableName !== screenTableName)
.map((table) => (
<CommandItem
key={table.tableName}
value={table.tableName}
onSelect={() => handleTableSelect(table.tableName, false)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.useCustomTable && targetTableName === table.tableName
? "opacity-100"
: "opacity-0"
)}
/>
<Database className="mr-2 h-3.5 w-3.5 text-muted-foreground" />
<span className="truncate">{table.displayName}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{config.useCustomTable && (
<p className="text-[10px] text-muted-foreground">
.
</p>
)}
</div>
{/* 테이블이 선택된 경우 컬럼 매핑 설정 */}
{tableColumns && tableColumns.length > 0 && (
{(currentTableColumns.length > 0 || loadingColumns) && (
<div className="space-y-3">
<h5 className="text-xs font-medium text-muted-foreground"> </h5>
{loadingEntityJoins && (
<div className="text-xs text-muted-foreground"> ...</div>
{(loadingEntityJoins || loadingColumns) && (
<div className="text-xs text-muted-foreground">
{loadingColumns ? "컬럼 로딩 중..." : "조인 컬럼 로딩 중..."}
</div>
)}
<div className="space-y-1">