From 86dc9619682f5d859e3ab20d0503d4ebe60b4059 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 24 Sep 2025 12:56:22 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A1=B0=EC=9D=B8=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/app/(main)/admin/tableMng/page.tsx | 1 - .../table-list/SingleTableWithSticky.tsx | 6 +- .../table-list/TableListComponent.tsx | 157 +++++++++++++++--- 3 files changed, 138 insertions(+), 26 deletions(-) diff --git a/frontend/app/(main)/admin/tableMng/page.tsx b/frontend/app/(main)/admin/tableMng/page.tsx index 9a83277a..3c95f4df 100644 --- a/frontend/app/(main)/admin/tableMng/page.tsx +++ b/frontend/app/(main)/admin/tableMng/page.tsx @@ -866,7 +866,6 @@ export default function TableManagementPage() { )} - {/* 설정 완료 표시 - 간소화 */} diff --git a/frontend/lib/registry/components/table-list/SingleTableWithSticky.tsx b/frontend/lib/registry/components/table-list/SingleTableWithSticky.tsx index 51cfdb6b..eeedfaaa 100644 --- a/frontend/lib/registry/components/table-list/SingleTableWithSticky.tsx +++ b/frontend/lib/registry/components/table-list/SingleTableWithSticky.tsx @@ -64,7 +64,7 @@ export const SingleTableWithSticky: React.FC = ({ return ( = ({ ) : ( data.map((row, index) => ( = ({ return ( = ({ const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(0); const [totalItems, setTotalItems] = useState(0); - const [searchTerm, setSearchTerm] = useState(""); + const [searchTerm] = useState(""); const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc"); const [columnLabels, setColumnLabels] = useState>({}); @@ -311,7 +311,7 @@ export const TableListComponent: React.FC = ({ console.log(`🔍 조인 탭 컬럼 처리: ${col.columnName} -> ${sourceTable}.${sourceColumn}`); return { - sourceTable: sourceTable, + sourceTable: sourceTable || tableConfig.selectedTable || "unknown", sourceColumn: sourceColumn, joinAlias: col.columnName, }; @@ -427,7 +427,7 @@ export const TableListComponent: React.FC = ({ // 🎯 코드 컬럼들의 캐시 미리 로드 (전역 캐시 사용) const codeColumns = Object.entries(columnMeta).filter( - ([_, meta]) => meta.webType === "code" && meta.codeCategory, + ([, meta]) => meta.webType === "code" && meta.codeCategory, ); if (codeColumns.length > 0) { @@ -462,16 +462,81 @@ export const TableListComponent: React.FC = ({ const actualApiColumns = Object.keys(result.data[0]); console.log("🔍 API 응답의 실제 컬럼들:", actualApiColumns); - // 🎯 조인 컬럼 매핑 테이블 (사용자 설정 → API 응답) - // 실제 API 응답에 존재하는 컬럼만 매핑 - const newJoinColumnMapping: Record = { - dept_code_dept_code: "dept_code", // user_info.dept_code - dept_code_status: "status", // user_info.status (dept_info.status가 조인되지 않음) - dept_code_company_name: "dept_name", // dept_info.dept_name (company_name이 조인되지 않음) - dept_code_name: "dept_code_name", // dept_info.dept_name - dept_name: "dept_name", // dept_info.dept_name - status: "status", // user_info.status - }; + // 🎯 조인 컬럼 매핑 테이블 동적 생성 (사용자 설정 → API 응답) + const newJoinColumnMapping: Record = {}; + + // 사용자가 설정한 컬럼들과 실제 API 응답 컬럼들을 동적으로 매핑 + processedColumns.forEach((userColumn) => { + // 체크박스는 제외 + if (userColumn.columnName === "__checkbox__") return; + + console.log(`🔍 컬럼 매핑 분석: "${userColumn.columnName}"`, { + displayName: userColumn.displayName, + isEntityJoin: userColumn.isEntityJoin, + entityJoinInfo: userColumn.entityJoinInfo, + available: actualApiColumns, + }); + + // 사용자 설정 컬럼명이 API 응답에 정확히 있는지 확인 + if (actualApiColumns.includes(userColumn.columnName)) { + // 직접 매칭되는 경우 + newJoinColumnMapping[userColumn.columnName] = userColumn.columnName; + console.log(`✅ 정확 매핑: ${userColumn.columnName} → ${userColumn.columnName}`); + } else { + // Entity 조인된 컬럼이거나 조인 탭에서 추가한 컬럼인 경우 + let foundMatch = false; + + // 1. Entity 조인 정보가 있는 경우 aliasColumn 우선 확인 + if (userColumn.entityJoinInfo?.joinAlias) { + const aliasColumn = userColumn.entityJoinInfo.joinAlias; + if (actualApiColumns.includes(aliasColumn)) { + newJoinColumnMapping[userColumn.columnName] = aliasColumn; + console.log(`🔗 Entity 별칭 매핑: ${userColumn.columnName} → ${aliasColumn}`); + foundMatch = true; + } + } + + // 2. 정확한 이름 매칭 (예: dept_code_company_name) + if (!foundMatch) { + const exactMatches = actualApiColumns.filter((apiCol) => apiCol === userColumn.columnName); + + if (exactMatches.length > 0) { + newJoinColumnMapping[userColumn.columnName] = exactMatches[0]; + console.log(`🎯 정확 이름 매핑: ${userColumn.columnName} → ${exactMatches[0]}`); + foundMatch = true; + } + } + + // 3. 부분 문자열 매칭 (컬럼명에 일부가 포함된 경우) + if (!foundMatch) { + const partialMatches = actualApiColumns.filter( + (apiCol) => apiCol.includes(userColumn.columnName) || userColumn.columnName.includes(apiCol), + ); + + if (partialMatches.length > 0) { + // 가장 길이가 비슷한 것 선택 + const bestMatch = partialMatches.reduce((best, current) => + Math.abs(current.length - userColumn.columnName.length) < + Math.abs(best.length - userColumn.columnName.length) + ? current + : best, + ); + + newJoinColumnMapping[userColumn.columnName] = bestMatch; + console.log(`🔍 부분 매핑: ${userColumn.columnName} → ${bestMatch}`); + foundMatch = true; + } + } + + // 4. 매칭 실패한 경우 원본 유지 (하지만 경고 표시) + if (!foundMatch) { + newJoinColumnMapping[userColumn.columnName] = userColumn.columnName; + console.warn( + `⚠️ 매핑 실패: "${userColumn.columnName}" - 사용 가능한 컬럼: [${actualApiColumns.join(", ")}]`, + ); + } + } + }); // 🎯 조인 컬럼 매핑 상태 업데이트 setJoinColumnMapping(newJoinColumnMapping); @@ -533,11 +598,23 @@ export const TableListComponent: React.FC = ({ if (result.entityJoinInfo?.joinConfigs) { result.entityJoinInfo.joinConfigs.forEach((joinConfig) => { // 원본 컬럼을 조인된 컬럼으로 교체 - const originalColumnIndex = processedColumns.findIndex((col) => col.columnName === joinConfig.sourceColumn); + let originalColumnIndex = processedColumns.findIndex((col) => col.columnName === joinConfig.sourceColumn); if (originalColumnIndex !== -1) { console.log(`🔄 컬럼 교체: ${joinConfig.sourceColumn} → ${joinConfig.aliasColumn}`); const originalColumn = processedColumns[originalColumnIndex]; + + // 🚨 중복 방지: 이미 같은 aliasColumn이 있는지 확인 + const existingAliasIndex = processedColumns.findIndex((col) => col.columnName === joinConfig.aliasColumn); + if (existingAliasIndex !== -1 && existingAliasIndex !== originalColumnIndex) { + console.warn(`🚨 중복 컬럼 발견: ${joinConfig.aliasColumn}이 이미 존재합니다. 중복 제거합니다.`); + processedColumns.splice(existingAliasIndex, 1); + // 인덱스 재조정 + if (existingAliasIndex < originalColumnIndex) { + originalColumnIndex--; + } + } + processedColumns[originalColumnIndex] = { ...originalColumn, columnName: joinConfig.aliasColumn, // dept_code → dept_code_name @@ -583,9 +660,26 @@ export const TableListComponent: React.FC = ({ processedColumns = autoColumns; } + // 🚨 processedColumns에서 중복 제거 + const uniqueProcessedColumns = processedColumns.filter( + (column, index, self) => self.findIndex((c) => c.columnName === column.columnName) === index, + ); + + if (uniqueProcessedColumns.length !== processedColumns.length) { + console.error("🚨 processedColumns에서 중복 발견:"); + console.error( + "원본:", + processedColumns.map((c) => c.columnName), + ); + console.error( + "중복 제거 후:", + uniqueProcessedColumns.map((c) => c.columnName), + ); + } + // 🎯 표시할 컬럼 상태 업데이트 - setDisplayColumns(processedColumns); - console.log("🎯 displayColumns 업데이트됨:", processedColumns); + setDisplayColumns(uniqueProcessedColumns); + console.log("🎯 displayColumns 업데이트됨:", uniqueProcessedColumns); console.log("🎯 데이터 개수:", result.data?.length || 0); console.log("🎯 전체 데이터:", result.data); } @@ -836,6 +930,25 @@ export const TableListComponent: React.FC = ({ "🎯 visibleColumns 컬럼명들:", columns.map((c) => c.columnName), ); + + // 🚨 중복 키 검사 + const columnNames = columns.map((c) => c.columnName); + const duplicates = columnNames.filter((name, index) => columnNames.indexOf(name) !== index); + if (duplicates.length > 0) { + console.error("🚨 중복된 컬럼명 발견:", duplicates); + console.error("🚨 전체 컬럼명 목록:", columnNames); + + // 중복 제거 + const uniqueColumns = columns.filter( + (column, index, self) => self.findIndex((c) => c.columnName === column.columnName) === index, + ); + console.log( + "🔧 중복 제거 후 컬럼들:", + uniqueColumns.map((c) => c.columnName), + ); + return uniqueColumns; + } + return columns; }, [displayColumns, tableConfig.columns, tableConfig.checkbox, isDesignMode]); @@ -1084,7 +1197,7 @@ export const TableListComponent: React.FC = ({ onClearFilters={handleClearAdvancedFilters} tableColumns={visibleColumns.map((col) => ({ columnName: col.columnName, - webType: columnMeta[col.columnName]?.webType || "text", + webType: (columnMeta[col.columnName]?.webType as any) || "text", displayName: columnLabels[col.columnName] || col.displayName || col.columnName, codeCategory: columnMeta[col.columnName]?.codeCategory, isVisible: col.visible, @@ -1138,9 +1251,9 @@ export const TableListComponent: React.FC = ({ - {visibleColumns.map((column) => ( + {visibleColumns.map((column, colIndex) => ( = ({ ) : ( data.map((row, index) => ( = ({ style={{ minHeight: "40px", height: "40px", lineHeight: "1" }} onClick={() => handleRowClick(row)} > - {visibleColumns.map((column) => ( + {visibleColumns.map((column, colIndex) => (