feat: enhance category mapping and label resolution in split panel layouts
- Added functionality to resolve unresolved category labels after data loading in SplitPanelLayout2Component. - Implemented batch API calls to fetch missing category labels based on unresolved codes. - Improved category mapping logic in SplitPanelLayoutComponent to handle join tables and provide fallback mappings. - Enhanced the user experience by ensuring that category labels are correctly displayed even when they are initially unresolved. These changes aim to improve the robustness of category handling across the split panel components.
This commit is contained in:
parent
20c85569b0
commit
cc61ef3ff4
|
|
@ -36,7 +36,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
|||
import { toast } from "sonner";
|
||||
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { getCategoryValues } from "@/lib/api/tableCategoryValue";
|
||||
import { getCategoryValues, getCategoryLabelsByCodes } from "@/lib/api/tableCategoryValue";
|
||||
|
||||
export interface SplitPanelLayout2ComponentProps extends ComponentRendererProps {
|
||||
// 추가 props
|
||||
|
|
@ -1354,6 +1354,40 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
|
|||
loadCategoryLabels();
|
||||
}, [isDesignMode, config.leftPanel?.tableName, config.rightPanel?.tableName, config.leftPanel?.displayColumns, config.rightPanel?.displayColumns, config.rightPanel?.tabConfig?.tabSourceColumn, config.leftPanel?.tabConfig?.tabSourceColumn]);
|
||||
|
||||
// 데이터 로드 후 미해결 카테고리 코드를 batch API로 변환
|
||||
useEffect(() => {
|
||||
if (isDesignMode) return;
|
||||
const allData = [...leftData, ...rightData];
|
||||
if (allData.length === 0) return;
|
||||
|
||||
const unresolvedCodes = new Set<string>();
|
||||
const checkValue = (v: unknown) => {
|
||||
if (typeof v === "string" && (v.startsWith("CAT_") || v.startsWith("CATEGORY_"))) {
|
||||
if (!categoryLabelMap[v]) unresolvedCodes.add(v);
|
||||
}
|
||||
};
|
||||
for (const item of allData) {
|
||||
for (const val of Object.values(item)) {
|
||||
if (Array.isArray(val)) {
|
||||
val.forEach(checkValue);
|
||||
} else {
|
||||
checkValue(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unresolvedCodes.size === 0) return;
|
||||
|
||||
const resolveMissingLabels = async () => {
|
||||
const result = await getCategoryLabelsByCodes(Array.from(unresolvedCodes));
|
||||
if (result.success && result.data && Object.keys(result.data).length > 0) {
|
||||
setCategoryLabelMap((prev) => ({ ...prev, ...result.data }));
|
||||
}
|
||||
};
|
||||
|
||||
resolveMissingLabels();
|
||||
}, [isDesignMode, leftData, rightData, categoryLabelMap]);
|
||||
|
||||
// 컴포넌트 언마운트 시 DataProvider 해제
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -1001,23 +1001,24 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
return formatNumberValue(value, format);
|
||||
}
|
||||
|
||||
// 🆕 카테고리 매핑 찾기 (여러 키 형태 시도)
|
||||
// 카테고리 매핑 찾기 (여러 키 형태 시도)
|
||||
// 1. 전체 컬럼명 (예: "item_info.material")
|
||||
// 2. 컬럼명만 (예: "material")
|
||||
// 3. 전역 폴백: 모든 매핑에서 value 검색
|
||||
let mapping = categoryMappings[columnName];
|
||||
|
||||
if (!mapping && columnName.includes(".")) {
|
||||
// 조인된 컬럼의 경우 컬럼명만으로 다시 시도
|
||||
const simpleColumnName = columnName.split(".").pop() || columnName;
|
||||
mapping = categoryMappings[simpleColumnName];
|
||||
}
|
||||
|
||||
if (mapping && mapping[String(value)]) {
|
||||
const categoryData = mapping[String(value)];
|
||||
const displayLabel = categoryData.label || String(value);
|
||||
const strValue = String(value);
|
||||
|
||||
if (mapping && mapping[strValue]) {
|
||||
const categoryData = mapping[strValue];
|
||||
const displayLabel = categoryData.label || strValue;
|
||||
const displayColor = categoryData.color || "#64748b";
|
||||
|
||||
// 배지로 표시
|
||||
return (
|
||||
<Badge
|
||||
style={{
|
||||
|
|
@ -1031,6 +1032,29 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
);
|
||||
}
|
||||
|
||||
// 전역 폴백: 컬럼명으로 매핑을 못 찾았을 때, 전체 매핑에서 값 검색
|
||||
if (!mapping && (strValue.startsWith("CAT_") || strValue.startsWith("CATEGORY_"))) {
|
||||
for (const key of Object.keys(categoryMappings)) {
|
||||
const m = categoryMappings[key];
|
||||
if (m && m[strValue]) {
|
||||
const categoryData = m[strValue];
|
||||
const displayLabel = categoryData.label || strValue;
|
||||
const displayColor = categoryData.color || "#64748b";
|
||||
return (
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: displayColor,
|
||||
borderColor: displayColor,
|
||||
}}
|
||||
className="text-white"
|
||||
>
|
||||
{displayLabel}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 자동 날짜 감지 (ISO 8601 형식 또는 Date 객체)
|
||||
if (typeof value === "string" && value.match(/^\d{4}-\d{2}-\d{2}(T|\s)/)) {
|
||||
return formatDateValue(value, "YYYY-MM-DD");
|
||||
|
|
@ -1981,43 +2005,59 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
loadRightTableColumns();
|
||||
}, [componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.additionalTabs, isDesignMode]);
|
||||
|
||||
// 좌측 테이블 카테고리 매핑 로드
|
||||
// 좌측 테이블 카테고리 매핑 로드 (조인된 테이블 포함)
|
||||
useEffect(() => {
|
||||
const loadLeftCategoryMappings = async () => {
|
||||
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||
if (!leftTableName || isDesignMode) return;
|
||||
|
||||
try {
|
||||
// 1. 컬럼 메타 정보 조회
|
||||
const columnsResponse = await tableTypeApi.getColumns(leftTableName);
|
||||
const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category");
|
||||
|
||||
if (categoryColumns.length === 0) {
|
||||
setLeftCategoryMappings({});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 각 카테고리 컬럼에 대한 값 조회
|
||||
const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};
|
||||
const tablesToLoad = new Set<string>([leftTableName]);
|
||||
|
||||
for (const col of categoryColumns) {
|
||||
const columnName = col.columnName || col.column_name;
|
||||
// 좌측 패널 컬럼 설정에서 조인된 테이블 추출
|
||||
const leftColumns = componentConfig.leftPanel?.columns || [];
|
||||
leftColumns.forEach((col: any) => {
|
||||
const colName = col.name || col.columnName;
|
||||
if (colName && colName.includes(".")) {
|
||||
const joinTableName = colName.split(".")[0];
|
||||
tablesToLoad.add(joinTableName);
|
||||
}
|
||||
});
|
||||
|
||||
// 각 테이블에 대해 카테고리 매핑 로드
|
||||
for (const tableName of tablesToLoad) {
|
||||
try {
|
||||
const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values?includeInactive=true`);
|
||||
const columnsResponse = await tableTypeApi.getColumns(tableName);
|
||||
const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category");
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
const valueMap: Record<string, { label: string; color?: string }> = {};
|
||||
response.data.data.forEach((item: any) => {
|
||||
valueMap[item.value_code || item.valueCode] = {
|
||||
label: item.value_label || item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
});
|
||||
mappings[columnName] = valueMap;
|
||||
console.log(`✅ 좌측 카테고리 매핑 로드 [${columnName}]:`, valueMap);
|
||||
for (const col of categoryColumns) {
|
||||
const columnName = col.columnName || col.column_name;
|
||||
try {
|
||||
const response = await apiClient.get(`/table-categories/${tableName}/${columnName}/values?includeInactive=true`);
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
const valueMap: Record<string, { label: string; color?: string }> = {};
|
||||
response.data.data.forEach((item: any) => {
|
||||
valueMap[item.value_code || item.valueCode] = {
|
||||
label: item.value_label || item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
});
|
||||
|
||||
// 조인된 테이블은 "테이블명.컬럼명" 형태로도 저장
|
||||
const mappingKey = tableName === leftTableName ? columnName : `${tableName}.${columnName}`;
|
||||
mappings[mappingKey] = valueMap;
|
||||
|
||||
// 컬럼명만으로도 접근 가능하도록 추가 저장
|
||||
mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`좌측 카테고리 값 조회 실패 [${tableName}.${columnName}]:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`좌측 카테고리 값 조회 실패 [${columnName}]:`, error);
|
||||
console.error(`좌측 카테고리 테이블 컬럼 조회 실패 [${tableName}]:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2028,7 +2068,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
};
|
||||
|
||||
loadLeftCategoryMappings();
|
||||
}, [componentConfig.leftPanel?.tableName, isDesignMode]);
|
||||
}, [componentConfig.leftPanel?.tableName, componentConfig.leftPanel?.columns, isDesignMode]);
|
||||
|
||||
// 우측 테이블 카테고리 매핑 로드 (조인된 테이블 포함)
|
||||
useEffect(() => {
|
||||
|
|
@ -3720,9 +3760,22 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
displayFields = configuredColumns.slice(0, 2).map((col: any) => {
|
||||
const colName = typeof col === "string" ? col : col.name || col.columnName;
|
||||
const colLabel = typeof col === "object" ? col.label : leftColumnLabels[colName] || colName;
|
||||
const rawValue = getEntityJoinValue(item, colName);
|
||||
// 카테고리 매핑이 있으면 라벨로 변환
|
||||
let displayValue = rawValue;
|
||||
if (rawValue != null && rawValue !== "") {
|
||||
const strVal = String(rawValue);
|
||||
let mapping = leftCategoryMappings[colName];
|
||||
if (!mapping && colName.includes(".")) {
|
||||
mapping = leftCategoryMappings[colName.split(".").pop() || colName];
|
||||
}
|
||||
if (mapping && mapping[strVal]) {
|
||||
displayValue = mapping[strVal].label;
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: colLabel,
|
||||
value: item[colName],
|
||||
value: displayValue,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -3734,10 +3787,21 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const keys = Object.keys(item).filter(
|
||||
(k) => k !== "id" && k !== "ID" && k !== "children" && k !== "level" && shouldShowField(k),
|
||||
);
|
||||
displayFields = keys.slice(0, 2).map((key) => ({
|
||||
label: leftColumnLabels[key] || key,
|
||||
value: item[key],
|
||||
}));
|
||||
displayFields = keys.slice(0, 2).map((key) => {
|
||||
const rawValue = item[key];
|
||||
let displayValue = rawValue;
|
||||
if (rawValue != null && rawValue !== "") {
|
||||
const strVal = String(rawValue);
|
||||
const mapping = leftCategoryMappings[key];
|
||||
if (mapping && mapping[strVal]) {
|
||||
displayValue = mapping[strVal].label;
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: leftColumnLabels[key] || key,
|
||||
value: displayValue,
|
||||
};
|
||||
});
|
||||
|
||||
if (index === 0) {
|
||||
console.log(" ⚠️ 설정된 컬럼 없음, 자동 선택:", displayFields);
|
||||
|
|
|
|||
Loading…
Reference in New Issue