fix: 엔티티 컬럼 표시 설정 문제 해결

- TableListComponent에서 엔티티 컬럼 라벨을 기준 테이블 라벨로 표시
- TableListConfigPanel에서 input_type 필드로 엔티티 컬럼 감지
- ScreenDesigner에서 컬럼 정보 로드 시 input_type 필드 포함
- UnifiedColumnInfo 타입에 input_type 필드 추가
- 엔티티 컬럼 감지 로직에 디버깅 로그 추가

이제 화면 편집기에서 엔티티 컬럼의 표시 컬럼 설정이 정상적으로 보여야 함
This commit is contained in:
kjs 2025-09-23 16:51:12 +09:00
parent de6c7a8008
commit 9d346a3d3a
5 changed files with 225 additions and 152 deletions

View File

@ -867,46 +867,6 @@ export default function TableManagementPage() {
</div>
)}
{/* 표시 컬럼 */}
{column.referenceTable && column.referenceTable !== "none" && (
<div>
<label className="mb-1 block text-xs text-gray-600"> </label>
<Select
value={column.displayColumn || "none"}
onValueChange={(value) =>
handleDetailSettingsChange(
column.columnName,
"entity_display_column",
value,
)
}
>
<SelectTrigger className="h-7 bg-white text-xs">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">-- --</SelectItem>
{referenceTableColumns[column.referenceTable]?.map((refCol, index) => (
<SelectItem
key={`display-col-${refCol.columnName}-${index}`}
value={refCol.columnName}
>
<span className="font-medium">{refCol.columnName}</span>
</SelectItem>
))}
{(!referenceTableColumns[column.referenceTable] ||
referenceTableColumns[column.referenceTable].length === 0) && (
<SelectItem value="loading" disabled>
<div className="flex items-center gap-2">
<div className="h-3 w-3 animate-spin rounded-full border border-blue-500 border-t-transparent"></div>
</div>
</SelectItem>
)}
</SelectContent>
</Select>
</div>
)}
</div>
{/* 설정 완료 표시 - 간소화 */}

View File

@ -664,6 +664,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
columnLabel: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
dataType: col.dataType || col.data_type || col.dbType,
webType: col.webType || col.web_type,
input_type: col.inputType || col.input_type, // 🎯 input_type 필드 추가
widgetType: col.widgetType || col.widget_type || col.webType || col.web_type,
isNullable: col.isNullable || col.is_nullable,
required: col.required !== undefined ? col.required : col.isNullable === "NO" || col.is_nullable === "NO",

View File

@ -194,10 +194,10 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// Entity 타입인 경우
if (column.webType === "entity") {
// 백엔드에서 받은 displayColumnLabel을 사용하거나, 없으면 기본값 사용
displayLabel = column.displayColumnLabel || column.displayColumn || `${column.columnName}_name`;
// 우선 기준 테이블의 컬럼 라벨을 사용
displayLabel = column.displayName || column.columnName;
console.log(
`🎯 Entity 조인 컬럼 라벨 설정: ${column.columnName} → "${displayLabel}" (${column.displayColumn || "기본값"})`,
`🎯 Entity 조인 컬럼 라벨 설정: ${column.columnName} → "${displayLabel}" (기준 테이블 라벨 사용)`,
);
}

View File

@ -62,12 +62,17 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
const [loadingEntityJoins, setLoadingEntityJoins] = useState(false);
// 🎯 엔티티 컬럼 표시 설정을 위한 상태
const [entityDisplayConfigs, setEntityDisplayConfigs] = useState<Record<string, {
sourceColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
joinColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
selectedColumns: string[];
separator: string;
}>>({});
const [entityDisplayConfigs, setEntityDisplayConfigs] = useState<
Record<
string,
{
sourceColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
joinColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
selectedColumns: string[];
separator: string;
}
>
>({});
// 화면 테이블명이 있으면 자동으로 설정
useEffect(() => {
@ -284,6 +289,72 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
handleChange("columns", updatedColumns);
};
// 🎯 기존 컬럼들을 체크하여 엔티티 타입인 경우 isEntityJoin 플래그 설정
useEffect(() => {
console.log("🔍 엔티티 컬럼 감지 useEffect 실행:", {
hasColumns: !!config.columns,
columnsCount: config.columns?.length || 0,
hasTableColumns: !!tableColumns,
tableColumnsCount: tableColumns?.length || 0,
selectedTable: config.selectedTable
});
if (!config.columns || !tableColumns) {
console.log("⚠️ 컬럼 또는 테이블 컬럼 정보가 없어서 엔티티 감지 스킵");
return;
}
const updatedColumns = config.columns.map((column) => {
// 이미 isEntityJoin이 설정된 경우 스킵
if (column.isEntityJoin) {
console.log("✅ 이미 엔티티 플래그 설정됨:", column.columnName);
return column;
}
// 테이블 컬럼 정보에서 해당 컬럼 찾기
const tableColumn = tableColumns.find((tc) => tc.column_name === column.columnName);
console.log("🔍 컬럼 검색:", {
columnName: column.columnName,
found: !!tableColumn,
inputType: tableColumn?.input_type,
webType: tableColumn?.web_type
});
// 엔티티 타입인 경우 isEntityJoin 플래그 설정 (input_type 또는 web_type 확인)
if (tableColumn && (tableColumn.input_type === "entity" || tableColumn.web_type === "entity")) {
console.log("🎯 엔티티 컬럼 감지 및 플래그 설정:", column.columnName);
return {
...column,
isEntityJoin: true,
entityJoinInfo: {
sourceTable: config.selectedTable || "",
sourceColumn: column.columnName,
joinAlias: column.columnName,
},
entityDisplayConfig: {
displayColumns: [], // 빈 배열로 초기화
separator: " - ",
sourceTable: config.selectedTable || "",
joinTable: tableColumn.reference_table || "",
},
};
}
return column;
});
// 변경사항이 있는 경우에만 업데이트
const hasChanges = updatedColumns.some((col, index) => col.isEntityJoin !== config.columns![index].isEntityJoin);
if (hasChanges) {
console.log("🎯 엔티티 컬럼 플래그 업데이트:", updatedColumns);
handleChange("columns", updatedColumns);
} else {
console.log(" 엔티티 컬럼 변경사항 없음");
}
}, [config.columns, tableColumns, config.selectedTable]);
// 🎯 엔티티 컬럼의 표시 컬럼 정보 로드
const loadEntityDisplayConfig = async (column: ColumnConfig) => {
if (!column.isEntityJoin || !column.entityJoinInfo || !column.entityDisplayConfig) return;
@ -304,7 +375,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
const sourceColumns = sourceResult.columns || [];
const joinColumns = joinResult.columns || [];
setEntityDisplayConfigs(prev => ({
setEntityDisplayConfigs((prev) => ({
...prev,
[configKey]: {
sourceColumns,
@ -325,10 +396,10 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
if (!config) return;
const newSelectedColumns = config.selectedColumns.includes(selectedColumn)
? config.selectedColumns.filter(col => col !== selectedColumn)
? config.selectedColumns.filter((col) => col !== selectedColumn)
: [...config.selectedColumns, selectedColumn];
setEntityDisplayConfigs(prev => ({
setEntityDisplayConfigs((prev) => ({
...prev,
[configKey]: {
...prev[configKey],
@ -351,7 +422,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
const config = entityDisplayConfigs[configKey];
if (!config) return;
setEntityDisplayConfigs(prev => ({
setEntityDisplayConfigs((prev) => ({
...prev,
[configKey]: {
...prev[configKey],
@ -791,6 +862,135 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
{/* 컬럼 설정 탭 */}
<TabsContent value="columns" className="space-y-4">
<ScrollArea className="h-[600px] pr-4">
{/* 🎯 엔티티 컬럼 표시 설정 섹션 - 컬럼 설정 패널 바깥으로 분리 */}
{config.columns?.some((col) => col.isEntityJoin) && (
<Card className="border-l-4 border-l-orange-500">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-base">🎯 </CardTitle>
<CardDescription> </CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{config.columns
?.filter((col) => col.isEntityJoin && col.entityDisplayConfig)
.map((column) => (
<div key={column.columnName} className="space-y-3 rounded-lg border bg-orange-50 p-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge variant="outline" className="border-orange-300 text-orange-600">
{column.columnName}
</Badge>
<span className="text-sm font-medium">{column.displayName}</span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => loadEntityDisplayConfig(column)}
className="h-6 text-xs"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
{entityDisplayConfigs[column.columnName] && (
<div className="space-y-3">
{/* 구분자 설정 */}
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
value={entityDisplayConfigs[column.columnName].separator}
onChange={(e) => updateEntityDisplaySeparator(column.columnName, e.target.value)}
className="h-7 text-xs"
placeholder=" - "
/>
</div>
{/* 기본 테이블 컬럼 */}
<div className="space-y-1">
<Label className="text-xs text-blue-600">
: {column.entityDisplayConfig?.sourceTable}
</Label>
<div className="grid max-h-20 grid-cols-2 gap-1 overflow-y-auto">
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<div key={col.columnName} className="flex items-center space-x-1">
<Checkbox
id={`source-${column.columnName}-${col.columnName}`}
checked={entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
)}
onCheckedChange={() =>
toggleEntityDisplayColumn(column.columnName, col.columnName)
}
className="h-3 w-3"
/>
<Label
htmlFor={`source-${column.columnName}-${col.columnName}`}
className="flex-1 cursor-pointer text-xs"
>
{col.displayName}
</Label>
</div>
))}
</div>
</div>
{/* 조인 테이블 컬럼 */}
<div className="space-y-1">
<Label className="text-xs text-green-600">
: {column.entityDisplayConfig?.joinTable}
</Label>
<div className="grid max-h-20 grid-cols-2 gap-1 overflow-y-auto">
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<div key={col.columnName} className="flex items-center space-x-1">
<Checkbox
id={`join-${column.columnName}-${col.columnName}`}
checked={entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
)}
onCheckedChange={() =>
toggleEntityDisplayColumn(column.columnName, col.columnName)
}
className="h-3 w-3"
/>
<Label
htmlFor={`join-${column.columnName}-${col.columnName}`}
className="flex-1 cursor-pointer text-xs"
>
{col.displayName}
</Label>
</div>
))}
</div>
</div>
{/* 선택된 컬럼 미리보기 */}
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
<div className="space-y-1">
<Label className="text-xs"></Label>
<div className="flex flex-wrap gap-1 rounded bg-gray-50 p-2 text-xs">
{entityDisplayConfigs[column.columnName].selectedColumns.map((colName, idx) => (
<React.Fragment key={colName}>
<Badge variant="secondary" className="text-xs">
{colName}
</Badge>
{idx < entityDisplayConfigs[column.columnName].selectedColumns.length - 1 && (
<span className="text-gray-400">
{entityDisplayConfigs[column.columnName].separator}
</span>
)}
</React.Fragment>
))}
</div>
</div>
)}
</div>
)}
</div>
))}
</CardContent>
</Card>
)}
{!screenTableName ? (
<Card>
<CardContent className="pt-6">
@ -921,105 +1121,17 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
/>
</div>
{/* 🎯 엔티티 타입 컬럼일 때 표시 컬럼 선택 UI */}
{column.isEntityJoin && column.entityDisplayConfig && (
<div className="col-span-2 space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium"> </Label>
<Button
variant="outline"
size="sm"
onClick={() => loadEntityDisplayConfig(column)}
className="h-6 text-xs"
>
<Plus className="h-3 w-3 mr-1" />
</Button>
{/* 엔티티 타입 컬럼 표시 */}
{column.isEntityJoin && (
<div className="col-span-2">
<div className="flex items-center gap-2 rounded bg-orange-50 p-2">
<Badge variant="outline" className="border-orange-300 text-orange-600">
</Badge>
<span className="text-xs text-orange-600">
"🎯 엔티티 컬럼 표시 설정"
</span>
</div>
{entityDisplayConfigs[column.columnName] && (
<div className="space-y-3">
{/* 구분자 설정 */}
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
value={entityDisplayConfigs[column.columnName].separator}
onChange={(e) => updateEntityDisplaySeparator(column.columnName, e.target.value)}
className="h-7 text-xs"
placeholder=" - "
/>
</div>
{/* 기본 테이블 컬럼 */}
<div className="space-y-1">
<Label className="text-xs text-blue-600">
: {column.entityDisplayConfig.sourceTable}
</Label>
<div className="grid grid-cols-2 gap-1 max-h-20 overflow-y-auto">
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<div key={col.columnName} className="flex items-center space-x-1">
<Checkbox
id={`source-${column.columnName}-${col.columnName}`}
checked={entityDisplayConfigs[column.columnName].selectedColumns.includes(col.columnName)}
onCheckedChange={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
className="h-3 w-3"
/>
<Label
htmlFor={`source-${column.columnName}-${col.columnName}`}
className="text-xs cursor-pointer flex-1"
>
{col.displayName}
</Label>
</div>
))}
</div>
</div>
{/* 조인 테이블 컬럼 */}
<div className="space-y-1">
<Label className="text-xs text-green-600">
: {column.entityDisplayConfig.joinTable}
</Label>
<div className="grid grid-cols-2 gap-1 max-h-20 overflow-y-auto">
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<div key={col.columnName} className="flex items-center space-x-1">
<Checkbox
id={`join-${column.columnName}-${col.columnName}`}
checked={entityDisplayConfigs[column.columnName].selectedColumns.includes(col.columnName)}
onCheckedChange={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
className="h-3 w-3"
/>
<Label
htmlFor={`join-${column.columnName}-${col.columnName}`}
className="text-xs cursor-pointer flex-1"
>
{col.displayName}
</Label>
</div>
))}
</div>
</div>
{/* 선택된 컬럼 미리보기 */}
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
<div className="space-y-1">
<Label className="text-xs"></Label>
<div className="flex flex-wrap gap-1 rounded bg-gray-50 p-2 text-xs">
{entityDisplayConfigs[column.columnName].selectedColumns.map((colName, idx) => (
<React.Fragment key={colName}>
<Badge variant="secondary" className="text-xs">
{colName}
</Badge>
{idx < entityDisplayConfigs[column.columnName].selectedColumns.length - 1 && (
<span className="text-gray-400">{entityDisplayConfigs[column.columnName].separator}</span>
)}
</React.Fragment>
))}
</div>
</div>
)}
</div>
)}
</div>
)}
@ -1528,7 +1640,6 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
</ScrollArea>
</TabsContent>
</Tabs>
</div>
);
};

View File

@ -46,6 +46,7 @@ export interface UnifiedColumnInfo {
// 입력 설정
inputType: "direct" | "auto";
input_type?: string; // 🎯 데이터베이스의 input_type 필드 (entity, text, number 등)
detailSettings?: Record<string, unknown>; // JSON 파싱된 객체
description?: string;