Merge pull request 'lhj' (#353) from lhj into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/353
This commit is contained in:
commit
054da65a26
|
|
@ -50,6 +50,9 @@ export class EntityJoinController {
|
||||||
// search가 문자열인 경우 JSON 파싱
|
// search가 문자열인 경우 JSON 파싱
|
||||||
searchConditions =
|
searchConditions =
|
||||||
typeof search === "string" ? JSON.parse(search) : search;
|
typeof search === "string" ? JSON.parse(search) : search;
|
||||||
|
|
||||||
|
// 🔍 디버그: 파싱된 검색 조건 로깅
|
||||||
|
logger.info(`🔍 파싱된 검색 조건:`, JSON.stringify(searchConditions, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn("검색 조건 파싱 오류:", error);
|
logger.warn("검색 조건 파싱 오류:", error);
|
||||||
searchConditions = {};
|
searchConditions = {};
|
||||||
|
|
|
||||||
|
|
@ -1147,8 +1147,28 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 🆕 추가 탭 데이터 로딩 함수
|
// 🆕 추가 탭 데이터 로딩 함수
|
||||||
const loadTabData = useCallback(
|
const loadTabData = useCallback(
|
||||||
async (tabIndex: number, leftItem: any) => {
|
async (tabIndex: number, leftItem: any) => {
|
||||||
|
console.log(`📥 loadTabData 호출됨: tabIndex=${tabIndex}`, {
|
||||||
|
leftItem: leftItem ? Object.keys(leftItem) : null,
|
||||||
|
additionalTabs: componentConfig.rightPanel?.additionalTabs?.length,
|
||||||
|
isDesignMode,
|
||||||
|
});
|
||||||
|
|
||||||
const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1];
|
const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1];
|
||||||
if (!tabConfig || !leftItem || isDesignMode) return;
|
|
||||||
|
console.log(`📥 tabConfig:`, {
|
||||||
|
tabIndex,
|
||||||
|
configIndex: tabIndex - 1,
|
||||||
|
tabConfig: tabConfig ? {
|
||||||
|
tableName: tabConfig.tableName,
|
||||||
|
relation: tabConfig.relation,
|
||||||
|
dataFilter: tabConfig.dataFilter
|
||||||
|
} : null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tabConfig || !leftItem || isDesignMode) {
|
||||||
|
console.log(`⚠️ loadTabData 중단:`, { hasTabConfig: !!tabConfig, hasLeftItem: !!leftItem, isDesignMode });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tabTableName = tabConfig.tableName;
|
const tabTableName = tabConfig.tableName;
|
||||||
if (!tabTableName) return;
|
if (!tabTableName) return;
|
||||||
|
|
@ -1160,6 +1180,14 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn;
|
const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn;
|
||||||
const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn;
|
const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn;
|
||||||
|
|
||||||
|
console.log(`🔑 [추가탭 ${tabIndex}] 조인 키 분석:`, {
|
||||||
|
hasRelation: !!tabConfig.relation,
|
||||||
|
keys,
|
||||||
|
leftColumn,
|
||||||
|
rightColumn,
|
||||||
|
willUseJoin: !!(leftColumn && rightColumn),
|
||||||
|
});
|
||||||
|
|
||||||
let resultData: any[] = [];
|
let resultData: any[] = [];
|
||||||
|
|
||||||
if (leftColumn && rightColumn) {
|
if (leftColumn && rightColumn) {
|
||||||
|
|
@ -1171,14 +1199,22 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 복합키
|
// 복합키
|
||||||
keys.forEach((key) => {
|
keys.forEach((key) => {
|
||||||
if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) {
|
if (key.leftColumn && key.rightColumn && leftItem[key.leftColumn] !== undefined) {
|
||||||
searchConditions[key.rightColumn] = leftItem[key.leftColumn];
|
// operator: "equals"를 추가하여 정확한 값 매칭 (entity 타입 컬럼에서 코드값으로 검색)
|
||||||
|
searchConditions[key.rightColumn] = {
|
||||||
|
value: leftItem[key.leftColumn],
|
||||||
|
operator: "equals",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 단일키
|
// 단일키
|
||||||
const leftValue = leftItem[leftColumn];
|
const leftValue = leftItem[leftColumn];
|
||||||
if (leftValue !== undefined) {
|
if (leftValue !== undefined) {
|
||||||
searchConditions[rightColumn] = leftValue;
|
// operator: "equals"를 추가하여 정확한 값 매칭 (entity 타입 컬럼에서 코드값으로 검색)
|
||||||
|
searchConditions[rightColumn] = {
|
||||||
|
value: leftValue,
|
||||||
|
operator: "equals",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1193,43 +1229,68 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
resultData = result.data || [];
|
resultData = result.data || [];
|
||||||
} else {
|
} else {
|
||||||
// 조인 조건이 없는 경우: 전체 데이터 조회 (독립 탭)
|
// 조인 조건이 없는 경우: 전체 데이터 조회 (독립 탭)
|
||||||
|
console.log(`📋 [추가탭 ${tabIndex}] 조인 없이 전체 데이터 조회: ${tabTableName}`);
|
||||||
const { entityJoinApi } = await import("@/lib/api/entityJoin");
|
const { entityJoinApi } = await import("@/lib/api/entityJoin");
|
||||||
const result = await entityJoinApi.getTableDataWithJoins(tabTableName, {
|
const result = await entityJoinApi.getTableDataWithJoins(tabTableName, {
|
||||||
enableEntityJoin: true,
|
enableEntityJoin: true,
|
||||||
size: 1000,
|
size: 1000,
|
||||||
});
|
});
|
||||||
resultData = result.data || [];
|
resultData = result.data || [];
|
||||||
|
console.log(`📋 [추가탭 ${tabIndex}] 전체 데이터 조회 결과:`, resultData.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 데이터 필터 적용
|
// 데이터 필터 적용
|
||||||
const dataFilter = tabConfig.dataFilter;
|
const dataFilter = tabConfig.dataFilter;
|
||||||
// filters 또는 conditions 배열 지원 (DataFilterConfigPanel은 filters 사용)
|
// filters 또는 conditions 배열 지원 (DataFilterConfigPanel은 filters 사용)
|
||||||
const filterConditions = dataFilter?.filters || dataFilter?.conditions || [];
|
const filterConditions = dataFilter?.filters || dataFilter?.conditions || [];
|
||||||
|
|
||||||
|
console.log(`🔍 [추가탭 ${tabIndex}] 필터 설정:`, {
|
||||||
|
enabled: dataFilter?.enabled,
|
||||||
|
filterConditions,
|
||||||
|
dataBeforeFilter: resultData.length,
|
||||||
|
});
|
||||||
|
|
||||||
if (dataFilter?.enabled && filterConditions.length > 0) {
|
if (dataFilter?.enabled && filterConditions.length > 0) {
|
||||||
|
const beforeCount = resultData.length;
|
||||||
resultData = resultData.filter((item: any) => {
|
resultData = resultData.filter((item: any) => {
|
||||||
return filterConditions.every((cond: any) => {
|
return filterConditions.every((cond: any) => {
|
||||||
// columnName 또는 column 지원
|
// columnName 또는 column 지원
|
||||||
const columnName = cond.columnName || cond.column;
|
const columnName = cond.columnName || cond.column;
|
||||||
const value = item[columnName];
|
const value = item[columnName];
|
||||||
const condValue = cond.value;
|
const condValue = cond.value;
|
||||||
|
|
||||||
|
let result = true;
|
||||||
switch (cond.operator) {
|
switch (cond.operator) {
|
||||||
case "equals":
|
case "equals":
|
||||||
return value === condValue;
|
result = value === condValue;
|
||||||
|
break;
|
||||||
case "notEquals":
|
case "notEquals":
|
||||||
return value !== condValue;
|
result = value !== condValue;
|
||||||
|
break;
|
||||||
case "contains":
|
case "contains":
|
||||||
return String(value).includes(String(condValue));
|
result = String(value).includes(String(condValue));
|
||||||
|
break;
|
||||||
case "is_null":
|
case "is_null":
|
||||||
case "NULL":
|
case "NULL":
|
||||||
return value === null || value === undefined || value === "";
|
result = value === null || value === undefined || value === "";
|
||||||
|
break;
|
||||||
case "is_not_null":
|
case "is_not_null":
|
||||||
case "NOT NULL":
|
case "NOT NULL":
|
||||||
return value !== null && value !== undefined && value !== "";
|
result = value !== null && value !== undefined && value !== "";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 첫 5개 항목만 로그 출력
|
||||||
|
if (resultData.indexOf(item) < 5) {
|
||||||
|
console.log(` 필터 체크: ${columnName}=${value}, operator=${cond.operator}, result=${result}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
console.log(`🔍 [추가탭 ${tabIndex}] 필터 적용 후: ${beforeCount} → ${resultData.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 중복 제거 적용
|
// 중복 제거 적용
|
||||||
|
|
@ -1301,6 +1362,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 🆕 탭 변경 핸들러
|
// 🆕 탭 변경 핸들러
|
||||||
const handleTabChange = useCallback(
|
const handleTabChange = useCallback(
|
||||||
(newTabIndex: number) => {
|
(newTabIndex: number) => {
|
||||||
|
console.log(`🔄 탭 변경: ${activeTabIndex} → ${newTabIndex}`, {
|
||||||
|
selectedLeftItem: !!selectedLeftItem,
|
||||||
|
tabsData: Object.keys(tabsData),
|
||||||
|
hasTabData: !!tabsData[newTabIndex],
|
||||||
|
});
|
||||||
|
|
||||||
setActiveTabIndex(newTabIndex);
|
setActiveTabIndex(newTabIndex);
|
||||||
|
|
||||||
// 선택된 좌측 항목이 있으면 해당 탭의 데이터 로드
|
// 선택된 좌측 항목이 있으면 해당 탭의 데이터 로드
|
||||||
|
|
@ -1311,14 +1378,15 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
loadRightData(selectedLeftItem);
|
loadRightData(selectedLeftItem);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 추가 탭: 해당 탭 데이터가 없으면 로드
|
// 추가 탭: 항상 새로 로드 (필터 설정 변경 반영을 위해)
|
||||||
if (!tabsData[newTabIndex]) {
|
console.log(`🔄 추가 탭 ${newTabIndex} 데이터 로드 (항상 새로고침)`);
|
||||||
loadTabData(newTabIndex, selectedLeftItem);
|
loadTabData(newTabIndex, selectedLeftItem);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`⚠️ 좌측 항목이 선택되지 않아 탭 데이터를 로드하지 않음`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedLeftItem, rightData, tabsData, loadRightData, loadTabData],
|
[selectedLeftItem, rightData, tabsData, loadRightData, loadTabData, activeTabIndex],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 우측 항목 확장/축소 토글
|
// 우측 항목 확장/축소 토글
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,12 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
|
||||||
// 탭 업데이트 헬퍼
|
// 탭 업데이트 헬퍼
|
||||||
const updateTab = (updates: Partial<AdditionalTabConfig>) => {
|
const updateTab = (updates: Partial<AdditionalTabConfig>) => {
|
||||||
const newTabs = [...(config.rightPanel?.additionalTabs || [])];
|
const newTabs = [...(config.rightPanel?.additionalTabs || [])];
|
||||||
newTabs[tabIndex] = { ...tab, ...updates };
|
// undefined 값도 명시적으로 덮어쓰기 위해 Object.assign 대신 직접 처리
|
||||||
|
const updatedTab = { ...tab };
|
||||||
|
Object.keys(updates).forEach((key) => {
|
||||||
|
(updatedTab as any)[key] = (updates as any)[key];
|
||||||
|
});
|
||||||
|
newTabs[tabIndex] = updatedTab;
|
||||||
updateRightPanel({ additionalTabs: newTabs });
|
updateRightPanel({ additionalTabs: newTabs });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -393,21 +398,31 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-[10px]">좌측 컬럼</Label>
|
<Label className="text-[10px]">좌측 컬럼</Label>
|
||||||
<Select
|
<Select
|
||||||
value={tab.relation?.keys?.[0]?.leftColumn || tab.relation?.leftColumn || ""}
|
value={tab.relation?.keys?.[0]?.leftColumn || tab.relation?.leftColumn || "__none__"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
updateTab({
|
if (value === "__none__") {
|
||||||
relation: {
|
// 선택 안 함 - 조인 키 제거
|
||||||
...tab.relation,
|
updateTab({
|
||||||
type: "join",
|
relation: undefined,
|
||||||
keys: [{ leftColumn: value, rightColumn: tab.relation?.keys?.[0]?.rightColumn || "" }],
|
});
|
||||||
},
|
} else {
|
||||||
});
|
updateTab({
|
||||||
|
relation: {
|
||||||
|
...tab.relation,
|
||||||
|
type: "join",
|
||||||
|
keys: [{ leftColumn: value, rightColumn: tab.relation?.keys?.[0]?.rightColumn || "" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-7 text-xs">
|
<SelectTrigger className="h-7 text-xs">
|
||||||
<SelectValue placeholder="선택" />
|
<SelectValue placeholder="선택" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
<SelectItem value="__none__">
|
||||||
|
<span className="text-muted-foreground">선택 안 함 (전체 데이터)</span>
|
||||||
|
</SelectItem>
|
||||||
{leftTableColumns.map((col) => (
|
{leftTableColumns.map((col) => (
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
<SelectItem key={col.columnName} value={col.columnName}>
|
||||||
{col.columnLabel || col.columnName}
|
{col.columnLabel || col.columnName}
|
||||||
|
|
@ -419,21 +434,31 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-[10px]">우측 컬럼</Label>
|
<Label className="text-[10px]">우측 컬럼</Label>
|
||||||
<Select
|
<Select
|
||||||
value={tab.relation?.keys?.[0]?.rightColumn || tab.relation?.foreignKey || ""}
|
value={tab.relation?.keys?.[0]?.rightColumn || tab.relation?.foreignKey || "__none__"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
updateTab({
|
if (value === "__none__") {
|
||||||
relation: {
|
// 선택 안 함 - 조인 키 제거
|
||||||
...tab.relation,
|
updateTab({
|
||||||
type: "join",
|
relation: undefined,
|
||||||
keys: [{ leftColumn: tab.relation?.keys?.[0]?.leftColumn || "", rightColumn: value }],
|
});
|
||||||
},
|
} else {
|
||||||
});
|
updateTab({
|
||||||
|
relation: {
|
||||||
|
...tab.relation,
|
||||||
|
type: "join",
|
||||||
|
keys: [{ leftColumn: tab.relation?.keys?.[0]?.leftColumn || "", rightColumn: value }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-7 text-xs">
|
<SelectTrigger className="h-7 text-xs">
|
||||||
<SelectValue placeholder="선택" />
|
<SelectValue placeholder="선택" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
<SelectItem value="__none__">
|
||||||
|
<span className="text-muted-foreground">선택 안 함 (전체 데이터)</span>
|
||||||
|
</SelectItem>
|
||||||
{tabColumns.map((col) => (
|
{tabColumns.map((col) => (
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
<SelectItem key={col.columnName} value={col.columnName}>
|
||||||
{col.columnLabel || col.columnName}
|
{col.columnLabel || col.columnName}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue