mhkim-node #412
|
|
@ -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