feat: Improve entity join handling with enhanced column validation and support for complex keys

- Updated the entityJoinService to include type casting for source and reference columns, ensuring compatibility during joins.
- Implemented validation for reference columns in the TableManagementService, allowing automatic fallback to 'id' if the specified reference column does not exist.
- Enhanced logging for join configurations to provide better insights during the join setup process.
- Transitioned the SplitPanelLayoutComponent to utilize the entityJoinApi for handling single key to composite key transformations, improving data retrieval efficiency.
- Added support for displaying null or empty values as "-" in the SplitPanelLayout, enhancing user experience.
This commit is contained in:
DDD1542
2026-02-10 12:07:25 +09:00
parent 3c8c2ebcf4
commit 8894216ee8
3 changed files with 149 additions and 59 deletions

View File

@@ -1250,24 +1250,59 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
setRightData(filteredData);
} else {
// 단일키 (하위 호환성)
// 단일키 (하위 호환성) → entityJoinApi 사용으로 전환 (entity 조인 컬럼 지원)
const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;
const rightColumn = componentConfig.rightPanel?.relation?.foreignKey;
if (leftColumn && rightColumn && leftTable) {
const leftValue = leftItem[leftColumn];
const joinedData = await dataApi.getJoinedData(
leftTable,
const { entityJoinApi } = await import("@/lib/api/entityJoin");
// 단일키를 복합키 형식으로 변환
const searchConditions: Record<string, any> = {};
searchConditions[rightColumn] = leftValue;
// Entity 조인 컬럼 추출
const rightJoinColumnsLegacy = extractAdditionalJoinColumns(
componentConfig.rightPanel?.columns,
rightTableName,
leftColumn,
rightColumn,
leftValue,
componentConfig.rightPanel?.dataFilter, // 🆕 데이터 필터 전달
true, // 🆕 Entity 조인 활성화
componentConfig.rightPanel?.columns, // 🆕 표시 컬럼 전달 (item_info.item_name 등)
componentConfig.rightPanel?.deduplication, // 🆕 중복 제거 설정 전달
);
setRightData(joinedData || []); // 모든 관련 레코드 (배열)
if (rightJoinColumnsLegacy) {
console.log("🔗 [분할패널] 단일키 모드 additionalJoinColumns:", rightJoinColumnsLegacy);
}
const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
search: searchConditions,
enableEntityJoin: true,
size: 1000,
companyCodeOverride: companyCode,
additionalJoinColumns: rightJoinColumnsLegacy,
});
let filteredDataLegacy = result.data || [];
// 데이터 필터 적용
const dataFilterLegacy = componentConfig.rightPanel?.dataFilter;
if (dataFilterLegacy?.enabled && dataFilterLegacy.conditions?.length > 0) {
filteredDataLegacy = filteredDataLegacy.filter((item: any) => {
return dataFilterLegacy.conditions.every((cond: any) => {
const value = item[cond.column];
const condValue = cond.value;
switch (cond.operator) {
case "equals":
return value === condValue;
case "notEquals":
return value !== condValue;
case "contains":
return String(value).includes(String(condValue));
default:
return true;
}
});
});
}
setRightData(filteredDataLegacy || []);
}
}
}
@@ -3802,23 +3837,20 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (rightColumns && rightColumns.length > 0) {
// 설정된 컬럼만 표시 (엔티티 조인 컬럼 처리)
// 설정된 컬럼은 null/empty여도 항상 표시 (사용자가 명시적으로 설정한 컬럼이므로)
const summaryCount = componentConfig.rightPanel?.summaryColumnCount ?? 3;
firstValues = rightColumns
.slice(0, summaryCount)
.map((col) => {
// 🆕 엔티티 조인 컬럼 처리 (getEntityJoinValue 사용)
const value = getEntityJoinValue(item, col.name);
return [col.name, value, col.label] as [string, any, string];
})
.filter(([_, value]) => value !== null && value !== undefined && value !== "");
});
allValues = rightColumns
.map((col) => {
// 🆕 엔티티 조인 컬럼 처리 (getEntityJoinValue 사용)
const value = getEntityJoinValue(item, col.name);
return [col.name, value, col.label] as [string, any, string];
})
.filter(([_, value]) => value !== null && value !== undefined && value !== "");
});
} else {
// 설정 없으면 모든 컬럼 표시 (기존 로직)
const summaryCount = componentConfig.rightPanel?.summaryColumnCount ?? 3;
@@ -3851,8 +3883,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const format = colConfig?.format;
const boldValue = colConfig?.bold ?? false;
// 🆕 포맷 적용 (날짜/숫자/카테고리)
const displayValue = formatCellValue(key, value, rightCategoryMappings, format);
// 🆕 포맷 적용 (날짜/숫자/카테고리) - null/empty는 "-"로 표시
const displayValue = (value === null || value === undefined || value === "")
? "-"
: formatCellValue(key, value, rightCategoryMappings, format);
const showLabel = componentConfig.rightPanel?.summaryShowLabel ?? true;
@@ -3929,8 +3963,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const colConfig = rightColumns?.find((c) => c.name === key);
const format = colConfig?.format;
// 🆕 포맷 적용 (날짜/숫자/카테고리)
const displayValue = formatCellValue(key, value, rightCategoryMappings, format);
// 🆕 포맷 적용 (날짜/숫자/카테고리) - null/empty는 "-"로 표시
const displayValue = (value === null || value === undefined || value === "")
? "-"
: formatCellValue(key, value, rightCategoryMappings, format);
return (
<tr key={key} className="hover:bg-muted">
@@ -3993,13 +4029,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
return [col.name, value, col.label] as [string, any, string];
})
.filter(([key, value]) => {
const filtered = value === null || value === undefined || value === "";
if (filtered) {
console.log(` ❌ 필터링됨: "${key}" (값: ${value})`);
}
return !filtered;
});
; // 설정된 컬럼은 null/empty여도 항상 표시
console.log(" ✅ 최종 표시할 항목:", displayEntries.length, "개");
} else {
@@ -4017,7 +4047,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<div className="text-muted-foreground mb-1 text-xs font-semibold tracking-wide uppercase">
{label || getColumnLabel(key)}
</div>
<div className="text-sm">{String(value)}</div>
<div className="text-sm">
{(value === null || value === undefined || value === "") ? <span className="text-muted-foreground">-</span> : String(value)}
</div>
</div>
))}
</div>