엔티티 표시기능 개선

This commit is contained in:
kjs 2025-12-04 14:30:52 +09:00
parent a90ddac512
commit 2cddb42255
2 changed files with 198 additions and 191 deletions

View File

@ -1183,13 +1183,20 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
referenceTable: col.additionalJoinInfo!.referenceTable,
}));
// console.log("🔍 [TableList] API 호출 시작", {
// tableName: tableConfig.selectedTable,
// page,
// pageSize,
// sortBy,
// sortOrder,
// });
// 🎯 화면별 엔티티 표시 설정 수집
const screenEntityConfigs: Record<string, any> = {};
(tableConfig.columns || [])
.filter((col) => col.entityDisplayConfig && col.entityDisplayConfig.displayColumns?.length > 0)
.forEach((col) => {
screenEntityConfigs[col.columnName] = {
displayColumns: col.entityDisplayConfig!.displayColumns,
separator: col.entityDisplayConfig!.separator || " - ",
sourceTable: col.entityDisplayConfig!.sourceTable || tableConfig.selectedTable,
joinTable: col.entityDisplayConfig!.joinTable,
};
});
console.log("🎯 [TableList] 화면별 엔티티 설정:", screenEntityConfigs);
// 🎯 항상 entityJoinApi 사용 (writer 컬럼 자동 조인 지원)
response = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, {
@ -1200,6 +1207,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
search: hasFilters ? filters : undefined,
enableEntityJoin: true,
additionalJoinColumns: entityJoinColumns.length > 0 ? entityJoinColumns : undefined,
screenEntityConfigs: Object.keys(screenEntityConfigs).length > 0 ? screenEntityConfigs : undefined, // 🎯 화면별 엔티티 설정 전달
dataFilter: tableConfig.dataFilter, // 🆕 데이터 필터 전달
});
@ -1756,33 +1764,46 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const formatCellValue = useCallback(
(value: any, column: ColumnConfig, rowData?: Record<string, any>) => {
if (value === null || value === undefined) return "-";
// 🎯 writer 컬럼 자동 변환: user_id -> user_name
if (column.columnName === "writer" && rowData && rowData.writer_name) {
return rowData.writer_name;
}
// 🎯 엔티티 컬럼 표시 설정이 있는 경우
// 🎯 엔티티 컬럼 표시 설정이 있는 경우 - value가 null이어도 rowData에서 조합 가능
// 이 체크를 가장 먼저 수행 (null 체크보다 앞에)
if (column.entityDisplayConfig && rowData) {
// displayColumns 또는 selectedColumns 둘 다 체크
const displayColumns = column.entityDisplayConfig.displayColumns || column.entityDisplayConfig.selectedColumns;
const displayColumns = column.entityDisplayConfig.displayColumns || (column.entityDisplayConfig as any).selectedColumns;
const separator = column.entityDisplayConfig.separator;
if (displayColumns && displayColumns.length > 0) {
// 선택된 컬럼들의 값을 구분자로 조합
const values = displayColumns
.map((colName) => {
const cellValue = rowData[colName];
.map((colName: string) => {
// 1. 먼저 직접 컬럼명으로 시도 (기본 테이블 컬럼인 경우)
let cellValue = rowData[colName];
// 2. 없으면 ${sourceColumn}_${colName} 형식으로 시도 (조인 테이블 컬럼인 경우)
if (cellValue === null || cellValue === undefined) {
const joinedKey = `${column.columnName}_${colName}`;
cellValue = rowData[joinedKey];
}
if (cellValue === null || cellValue === undefined) return "";
return String(cellValue);
})
.filter((v) => v !== ""); // 빈 값 제외
.filter((v: string) => v !== ""); // 빈 값 제외
return values.join(separator || " - ");
const result = values.join(separator || " - ");
if (result) {
return result; // 결과가 있으면 반환
}
// 결과가 비어있으면 아래로 계속 진행 (원래 값 사용)
}
}
// value가 null/undefined면 "-" 반환
if (value === null || value === undefined) return "-";
// 🎯 writer 컬럼 자동 변환: user_id -> user_name
if (column.columnName === "writer" && rowData && rowData.writer_name) {
return rowData.writer_name;
}
const meta = columnMeta[column.columnName];
// inputType 기반 포맷팅 (columnMeta에서 가져온 inputType 우선)

View File

@ -467,42 +467,22 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
// 🎯 엔티티 컬럼의 표시 컬럼 정보 로드
const loadEntityDisplayConfig = async (column: ColumnConfig) => {
if (!column.isEntityJoin || !column.entityJoinInfo) {
return;
}
const configKey = `${column.columnName}`;
// 이미 로드된 경우 스킵
if (entityDisplayConfigs[configKey]) return;
// entityDisplayConfig가 없으면 초기화
if (!column.entityDisplayConfig) {
// sourceTable을 결정: entityJoinInfo -> config.selectedTable -> screenTableName 순서
const initialSourceTable = column.entityJoinInfo?.sourceTable || config.selectedTable || screenTableName;
if (!initialSourceTable) {
return;
}
const updatedColumns = config.columns?.map((col) => {
if (col.columnName === column.columnName) {
return {
...col,
entityDisplayConfig: {
displayColumns: [],
separator: " - ",
sourceTable: initialSourceTable,
joinTable: "",
},
};
}
return col;
});
if (updatedColumns) {
handleChange("columns", updatedColumns);
// 업데이트된 컬럼으로 다시 시도
const updatedColumn = updatedColumns.find((col) => col.columnName === column.columnName);
if (updatedColumn) {
return loadEntityDisplayConfig(updatedColumn);
}
}
if (!column.isEntityJoin) {
// 엔티티 컬럼이 아니면 빈 상태로 설정하여 로딩 상태 해제
setEntityDisplayConfigs((prev) => ({
...prev,
[configKey]: {
sourceColumns: [],
joinColumns: [],
selectedColumns: [],
separator: " - ",
},
}));
return;
}
@ -512,32 +492,56 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
// 3. config.selectedTable
// 4. screenTableName
const sourceTable =
column.entityDisplayConfig.sourceTable ||
column.entityDisplayConfig?.sourceTable ||
column.entityJoinInfo?.sourceTable ||
config.selectedTable ||
screenTableName;
let joinTable = column.entityDisplayConfig.joinTable;
// sourceTable이 여전히 비어있으면 에러
// sourceTable이 비어있으면 빈 상태로 설정
if (!sourceTable) {
console.warn("⚠️ sourceTable을 찾을 수 없음:", column.columnName);
setEntityDisplayConfigs((prev) => ({
...prev,
[configKey]: {
sourceColumns: [],
joinColumns: [],
selectedColumns: column.entityDisplayConfig?.displayColumns || [],
separator: column.entityDisplayConfig?.separator || " - ",
},
}));
return;
}
if (!joinTable && sourceTable) {
// joinTable이 없으면 tableTypeApi로 조회해서 설정
let joinTable = column.entityDisplayConfig?.joinTable;
// joinTable이 없으면 tableTypeApi로 조회해서 설정
if (!joinTable) {
try {
console.log("🔍 tableTypeApi로 컬럼 정보 조회:", {
tableName: sourceTable,
columnName: column.columnName,
});
const columnList = await tableTypeApi.getColumns(sourceTable);
const columnInfo = columnList.find((col: any) => (col.column_name || col.columnName) === column.columnName);
console.log("🔍 컬럼 정보 조회 결과:", {
columnInfo: columnInfo,
referenceTable: columnInfo?.reference_table || columnInfo?.referenceTable,
referenceColumn: columnInfo?.reference_column || columnInfo?.referenceColumn,
});
if (columnInfo?.reference_table || columnInfo?.referenceTable) {
joinTable = columnInfo.reference_table || columnInfo.referenceTable;
console.log("✅ tableTypeApi에서 조인 테이블 정보 찾음:", joinTable);
// entityDisplayConfig 업데이트
const updatedConfig = {
...column.entityDisplayConfig,
sourceTable: sourceTable,
joinTable: joinTable,
displayColumns: column.entityDisplayConfig?.displayColumns || [],
separator: column.entityDisplayConfig?.separator || " - ",
};
// 컬럼 설정 업데이트
@ -553,74 +557,27 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
}
} catch (error) {
console.error("tableTypeApi 컬럼 정보 조회 실패:", error);
console.log("❌ 조회 실패 상세:", { sourceTable, columnName: column.columnName });
}
} else if (!joinTable) {
console.warn("⚠️ sourceTable이 없어서 joinTable 조회 불가:", column.columnName);
}
console.log("🔍 최종 추출한 값:", { sourceTable, joinTable });
const configKey = `${column.columnName}`;
// 이미 로드된 경우 스킵
if (entityDisplayConfigs[configKey]) return;
// joinTable이 비어있으면 tableTypeApi로 컬럼 정보를 다시 가져와서 referenceTable 정보를 찾기
let actualJoinTable = joinTable;
if (!actualJoinTable && sourceTable) {
try {
console.log("🔍 tableTypeApi로 컬럼 정보 다시 조회:", {
tableName: sourceTable,
columnName: column.columnName,
});
const columnList = await tableTypeApi.getColumns(sourceTable);
const columnInfo = columnList.find((col: any) => (col.column_name || col.columnName) === column.columnName);
console.log("🔍 컬럼 정보 조회 결과:", {
columnInfo: columnInfo,
referenceTable: columnInfo?.reference_table || columnInfo?.referenceTable,
referenceColumn: columnInfo?.reference_column || columnInfo?.referenceColumn,
});
if (columnInfo?.reference_table || columnInfo?.referenceTable) {
actualJoinTable = columnInfo.reference_table || columnInfo.referenceTable;
console.log("✅ tableTypeApi에서 조인 테이블 정보 찾음:", actualJoinTable);
// entityDisplayConfig 업데이트
const updatedConfig = {
...column.entityDisplayConfig,
joinTable: actualJoinTable,
};
// 컬럼 설정 업데이트
const updatedColumns = config.columns?.map((col) =>
col.columnName === column.columnName ? { ...col, entityDisplayConfig: updatedConfig } : col,
);
if (updatedColumns) {
handleChange("columns", updatedColumns);
}
}
} catch (error) {
console.error("tableTypeApi 컬럼 정보 조회 실패:", error);
}
}
// sourceTable과 joinTable이 모두 있어야 로드
if (!sourceTable || !actualJoinTable) {
return;
}
try {
// 기본 테이블과 조인 테이블의 컬럼 정보를 병렬로 로드
const [sourceResult, joinResult] = await Promise.all([
entityJoinApi.getReferenceTableColumns(sourceTable),
entityJoinApi.getReferenceTableColumns(actualJoinTable),
]);
// 기본 테이블 컬럼 정보는 항상 로드
const sourceResult = await entityJoinApi.getReferenceTableColumns(sourceTable);
const sourceColumns = sourceResult.columns || [];
const joinColumns = joinResult.columns || [];
// joinTable이 있으면 조인 테이블 컬럼도 로드
let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = [];
if (joinTable) {
try {
const joinResult = await entityJoinApi.getReferenceTableColumns(joinTable);
joinColumns = joinResult.columns || [];
} catch (joinError) {
console.warn("⚠️ 조인 테이블 컬럼 로드 실패:", joinTable, joinError);
// 조인 테이블 로드 실패해도 소스 테이블 컬럼은 표시
}
}
setEntityDisplayConfigs((prev) => ({
...prev,
@ -633,6 +590,16 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
}));
} catch (error) {
console.error("엔티티 표시 컬럼 정보 로드 실패:", error);
// 에러 발생 시에도 빈 상태로 설정하여 로딩 상태 해제
setEntityDisplayConfigs((prev) => ({
...prev,
[configKey]: {
sourceColumns: [],
joinColumns: [],
selectedColumns: column.entityDisplayConfig?.displayColumns || [],
separator: column.entityDisplayConfig?.separator || " - ",
},
}));
}
};
@ -873,76 +840,95 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
{/* 표시 컬럼 선택 (다중 선택) */}
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-6 w-full justify-between text-xs"
style={{ fontSize: "12px" }}
>
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0
? `${entityDisplayConfigs[column.columnName].selectedColumns.length}개 선택됨`
: "컬럼 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0" align="start">
<Command>
<CommandInput placeholder="컬럼 검색..." className="text-xs" />
<CommandList>
<CommandEmpty className="text-xs"> .</CommandEmpty>
{entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
<CommandGroup heading={`기본: ${column.entityDisplayConfig?.sourceTable}`}>
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<CommandItem
key={`source-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
</CommandItem>
))}
</CommandGroup>
)}
{entityDisplayConfigs[column.columnName].joinColumns.length > 0 && (
<CommandGroup heading={`조인: ${column.entityDisplayConfig?.joinTable}`}>
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<CommandItem
key={`join-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
{entityDisplayConfigs[column.columnName].sourceColumns.length === 0 &&
entityDisplayConfigs[column.columnName].joinColumns.length === 0 ? (
<div className="py-2 text-center text-xs text-gray-400">
.
{!column.entityDisplayConfig?.joinTable && (
<p className="mt-1 text-[10px]">
.
</p>
)}
</div>
) : (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-6 w-full justify-between text-xs"
style={{ fontSize: "12px" }}
>
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0
? `${entityDisplayConfigs[column.columnName].selectedColumns.length}개 선택됨`
: "컬럼 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0" align="start">
<Command>
<CommandInput placeholder="컬럼 검색..." className="text-xs" />
<CommandList>
<CommandEmpty className="text-xs"> .</CommandEmpty>
{entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
<CommandGroup heading={`기본 테이블: ${column.entityDisplayConfig?.sourceTable || config.selectedTable || screenTableName}`}>
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<CommandItem
key={`source-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
</CommandItem>
))}
</CommandGroup>
)}
{entityDisplayConfigs[column.columnName].joinColumns.length > 0 && (
<CommandGroup heading={`참조 테이블: ${column.entityDisplayConfig?.joinTable}`}>
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<CommandItem
key={`join-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
)}
</div>
{/* 참조 테이블 미설정 안내 */}
{!column.entityDisplayConfig?.joinTable && entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
<div className="rounded bg-blue-50 p-2 text-[10px] text-blue-600">
. .
</div>
)}
{/* 선택된 컬럼 미리보기 */}
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
<div className="space-y-1">