조인컬럼 오류

This commit is contained in:
kjs 2025-09-24 12:56:22 +09:00
parent d52d6c129b
commit 86dc961968
3 changed files with 138 additions and 26 deletions

View File

@ -866,7 +866,6 @@ export default function TableManagementPage() {
</Select> </Select>
</div> </div>
)} )}
</div> </div>
{/* 설정 완료 표시 - 간소화 */} {/* 설정 완료 표시 - 간소화 */}

View File

@ -64,7 +64,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
return ( return (
<TableHead <TableHead
key={column.columnName} key={`sticky-header-${colIndex}-${column.columnName}`}
className={cn( className={cn(
column.columnName === "__checkbox__" column.columnName === "__checkbox__"
? "h-10 border-b px-4 py-2 text-center align-middle" ? "h-10 border-b px-4 py-2 text-center align-middle"
@ -129,7 +129,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
) : ( ) : (
data.map((row, index) => ( data.map((row, index) => (
<TableRow <TableRow
key={`row-${index}`} key={`sticky-row-${index}`}
className={cn( className={cn(
"h-10 cursor-pointer border-b leading-none", "h-10 cursor-pointer border-b leading-none",
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50", tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
@ -155,7 +155,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
return ( return (
<TableCell <TableCell
key={`cell-${column.columnName}`} key={`sticky-cell-${index}-${colIndex}-${column.columnName}`}
className={cn( className={cn(
"h-10 px-4 py-2 align-middle text-sm whitespace-nowrap", "h-10 px-4 py-2 align-middle text-sm whitespace-nowrap",
`text-${column.align}`, `text-${column.align}`,

View File

@ -97,7 +97,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0); const [totalPages, setTotalPages] = useState(0);
const [totalItems, setTotalItems] = useState(0); const [totalItems, setTotalItems] = useState(0);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm] = useState("");
const [sortColumn, setSortColumn] = useState<string | null>(null); const [sortColumn, setSortColumn] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc"); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
const [columnLabels, setColumnLabels] = useState<Record<string, string>>({}); const [columnLabels, setColumnLabels] = useState<Record<string, string>>({});
@ -311,7 +311,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
console.log(`🔍 조인 탭 컬럼 처리: ${col.columnName} -> ${sourceTable}.${sourceColumn}`); console.log(`🔍 조인 탭 컬럼 처리: ${col.columnName} -> ${sourceTable}.${sourceColumn}`);
return { return {
sourceTable: sourceTable, sourceTable: sourceTable || tableConfig.selectedTable || "unknown",
sourceColumn: sourceColumn, sourceColumn: sourceColumn,
joinAlias: col.columnName, joinAlias: col.columnName,
}; };
@ -427,7 +427,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 🎯 코드 컬럼들의 캐시 미리 로드 (전역 캐시 사용) // 🎯 코드 컬럼들의 캐시 미리 로드 (전역 캐시 사용)
const codeColumns = Object.entries(columnMeta).filter( const codeColumns = Object.entries(columnMeta).filter(
([_, meta]) => meta.webType === "code" && meta.codeCategory, ([, meta]) => meta.webType === "code" && meta.codeCategory,
); );
if (codeColumns.length > 0) { if (codeColumns.length > 0) {
@ -462,16 +462,81 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
const actualApiColumns = Object.keys(result.data[0]); const actualApiColumns = Object.keys(result.data[0]);
console.log("🔍 API 응답의 실제 컬럼들:", actualApiColumns); console.log("🔍 API 응답의 실제 컬럼들:", actualApiColumns);
// 🎯 조인 컬럼 매핑 테이블 (사용자 설정 → API 응답) // 🎯 조인 컬럼 매핑 테이블 동적 생성 (사용자 설정 → API 응답)
// 실제 API 응답에 존재하는 컬럼만 매핑 const newJoinColumnMapping: Record<string, string> = {};
const newJoinColumnMapping: Record<string, string> = {
dept_code_dept_code: "dept_code", // user_info.dept_code // 사용자가 설정한 컬럼들과 실제 API 응답 컬럼들을 동적으로 매핑
dept_code_status: "status", // user_info.status (dept_info.status가 조인되지 않음) processedColumns.forEach((userColumn) => {
dept_code_company_name: "dept_name", // dept_info.dept_name (company_name이 조인되지 않음) // 체크박스는 제외
dept_code_name: "dept_code_name", // dept_info.dept_name if (userColumn.columnName === "__checkbox__") return;
dept_name: "dept_name", // dept_info.dept_name
status: "status", // user_info.status 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); setJoinColumnMapping(newJoinColumnMapping);
@ -533,11 +598,23 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
if (result.entityJoinInfo?.joinConfigs) { if (result.entityJoinInfo?.joinConfigs) {
result.entityJoinInfo.joinConfigs.forEach((joinConfig) => { 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) { if (originalColumnIndex !== -1) {
console.log(`🔄 컬럼 교체: ${joinConfig.sourceColumn}${joinConfig.aliasColumn}`); console.log(`🔄 컬럼 교체: ${joinConfig.sourceColumn}${joinConfig.aliasColumn}`);
const originalColumn = processedColumns[originalColumnIndex]; 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] = { processedColumns[originalColumnIndex] = {
...originalColumn, ...originalColumn,
columnName: joinConfig.aliasColumn, // dept_code → dept_code_name columnName: joinConfig.aliasColumn, // dept_code → dept_code_name
@ -583,9 +660,26 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
processedColumns = autoColumns; 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); setDisplayColumns(uniqueProcessedColumns);
console.log("🎯 displayColumns 업데이트됨:", processedColumns); console.log("🎯 displayColumns 업데이트됨:", uniqueProcessedColumns);
console.log("🎯 데이터 개수:", result.data?.length || 0); console.log("🎯 데이터 개수:", result.data?.length || 0);
console.log("🎯 전체 데이터:", result.data); console.log("🎯 전체 데이터:", result.data);
} }
@ -836,6 +930,25 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
"🎯 visibleColumns 컬럼명들:", "🎯 visibleColumns 컬럼명들:",
columns.map((c) => c.columnName), 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; return columns;
}, [displayColumns, tableConfig.columns, tableConfig.checkbox, isDesignMode]); }, [displayColumns, tableConfig.columns, tableConfig.checkbox, isDesignMode]);
@ -1084,7 +1197,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
onClearFilters={handleClearAdvancedFilters} onClearFilters={handleClearAdvancedFilters}
tableColumns={visibleColumns.map((col) => ({ tableColumns={visibleColumns.map((col) => ({
columnName: col.columnName, 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, displayName: columnLabels[col.columnName] || col.displayName || col.columnName,
codeCategory: columnMeta[col.columnName]?.codeCategory, codeCategory: columnMeta[col.columnName]?.codeCategory,
isVisible: col.visible, isVisible: col.visible,
@ -1138,9 +1251,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
<Table> <Table>
<TableHeader className={tableConfig.stickyHeader ? "sticky top-0 z-10 bg-white" : ""}> <TableHeader className={tableConfig.stickyHeader ? "sticky top-0 z-10 bg-white" : ""}>
<TableRow style={{ minHeight: "40px !important", height: "40px !important", lineHeight: "1" }}> <TableRow style={{ minHeight: "40px !important", height: "40px !important", lineHeight: "1" }}>
{visibleColumns.map((column) => ( {visibleColumns.map((column, colIndex) => (
<TableHead <TableHead
key={column.columnName} key={`header-${colIndex}-${column.columnName}`}
style={{ style={{
width: column.width ? `${column.width}px` : undefined, width: column.width ? `${column.width}px` : undefined,
minHeight: "40px !important", minHeight: "40px !important",
@ -1192,7 +1305,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
) : ( ) : (
data.map((row, index) => ( data.map((row, index) => (
<TableRow <TableRow
key={index} key={`row-${index}-${getRowKey(row, index)}`}
className={cn( className={cn(
"h-10 cursor-pointer leading-none", "h-10 cursor-pointer leading-none",
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50", tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
@ -1201,9 +1314,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
style={{ minHeight: "40px", height: "40px", lineHeight: "1" }} style={{ minHeight: "40px", height: "40px", lineHeight: "1" }}
onClick={() => handleRowClick(row)} onClick={() => handleRowClick(row)}
> >
{visibleColumns.map((column) => ( {visibleColumns.map((column, colIndex) => (
<TableCell <TableCell
key={column.columnName} key={`cell-${index}-${colIndex}-${column.columnName}`}
className={cn("h-10 align-middle whitespace-nowrap", `text-${column.align}`)} className={cn("h-10 align-middle whitespace-nowrap", `text-${column.align}`)}
style={{ minHeight: "40px", height: "40px", verticalAlign: "middle" }} style={{ minHeight: "40px", height: "40px", verticalAlign: "middle" }}
> >