feat: enhance v2-split-panel-layout component
SplitPanelLayoutComponent, ConfigPanel, types 개선 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -667,8 +667,62 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [addModalPanel, setAddModalPanel] = useState<"left" | "right" | "left-item" | null>(null);
|
||||
const [addModalFormData, setAddModalFormData] = useState<Record<string, any>>({});
|
||||
|
||||
// 엔티티 관계 자동 감지 캐시 (좌측↔우측 테이블 간 FK 매핑)
|
||||
const [autoDetectedTabRelations, setAutoDetectedTabRelations] = useState<
|
||||
Record<string, Array<{ leftColumn: string; rightColumn: string }>>
|
||||
>({});
|
||||
const [bomExcelUploadOpen, setBomExcelUploadOpen] = useState(false);
|
||||
|
||||
// 좌측↔우측 테이블 간 엔티티 관계 자동 감지 (table_type_columns 기반)
|
||||
useEffect(() => {
|
||||
const leftTable = componentConfig.leftPanel?.tableName;
|
||||
if (!leftTable) return;
|
||||
|
||||
const detectAll = async () => {
|
||||
const { tableManagementApi } = await import("@/lib/api/tableManagement");
|
||||
const cache: Record<string, Array<{ leftColumn: string; rightColumn: string }>> = {};
|
||||
|
||||
// 기본 우측 패널
|
||||
const rightTable = componentConfig.rightPanel?.tableName;
|
||||
if (rightTable && rightTable !== leftTable) {
|
||||
try {
|
||||
const res = await tableManagementApi.getTableEntityRelations(leftTable, rightTable);
|
||||
if (res.success && res.data?.relations?.length) {
|
||||
cache[rightTable] = res.data.relations.map((r: any) => ({
|
||||
leftColumn: r.leftColumn,
|
||||
rightColumn: r.rightColumn,
|
||||
}));
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// 추가 탭들
|
||||
const tabs = componentConfig.rightPanel?.additionalTabs || [];
|
||||
for (const tab of tabs) {
|
||||
const tabTable = tab.tableName;
|
||||
if (!tabTable || cache[tabTable] !== undefined) continue;
|
||||
try {
|
||||
const res = await tableManagementApi.getTableEntityRelations(leftTable, tabTable);
|
||||
if (res.success && res.data?.relations?.length) {
|
||||
cache[tabTable] = res.data.relations.map((r: any) => ({
|
||||
leftColumn: r.leftColumn,
|
||||
rightColumn: r.rightColumn,
|
||||
}));
|
||||
} else {
|
||||
cache[tabTable] = [];
|
||||
}
|
||||
} catch {
|
||||
cache[tabTable] = [];
|
||||
}
|
||||
}
|
||||
|
||||
setAutoDetectedTabRelations(cache);
|
||||
};
|
||||
|
||||
detectAll();
|
||||
}, [componentConfig.leftPanel?.tableName, componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.additionalTabs]);
|
||||
|
||||
// 수정 모달 상태
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [editModalPanel, setEditModalPanel] = useState<"left" | "right" | null>(null);
|
||||
@@ -2518,6 +2572,15 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
}
|
||||
}
|
||||
|
||||
// table_type_columns 기반 엔티티 관계 자동 감지 (패널 설정 없어도 동작)
|
||||
if (currentTableName && autoDetectedTabRelations[currentTableName]) {
|
||||
for (const rel of autoDetectedTabRelations[currentTableName]) {
|
||||
if (parentData[rel.rightColumn] == null && selectedLeftItem[rel.leftColumn] != null) {
|
||||
parentData[rel.rightColumn] = selectedLeftItem[rel.leftColumn];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("openScreenModal", {
|
||||
detail: {
|
||||
@@ -2539,23 +2602,73 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
setAddModalPanel(panel);
|
||||
|
||||
// 우측 패널 추가 시, 좌측에서 선택된 항목의 조인 컬럼 값을 자동으로 채움
|
||||
if (
|
||||
panel === "right" &&
|
||||
selectedLeftItem &&
|
||||
componentConfig.leftPanel?.leftColumn &&
|
||||
componentConfig.rightPanel?.rightColumn
|
||||
) {
|
||||
const leftColumnValue = selectedLeftItem[componentConfig.leftPanel.leftColumn];
|
||||
setAddModalFormData({
|
||||
[componentConfig.rightPanel.rightColumn]: leftColumnValue,
|
||||
});
|
||||
if (panel === "right" && selectedLeftItem) {
|
||||
const prefill: Record<string, any> = {};
|
||||
|
||||
// 현재 활성 탭의 설정 가져오기 (기본 탭 or 추가 탭)
|
||||
const currentAddConfig =
|
||||
activeTabIndex === 0
|
||||
? componentConfig.rightPanel?.addConfig
|
||||
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.addConfig;
|
||||
const currentRelation =
|
||||
activeTabIndex === 0
|
||||
? componentConfig.rightPanel?.relation
|
||||
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.relation;
|
||||
|
||||
// 1) relation.keys 기반 FK 자동 채움
|
||||
if (currentRelation?.keys && Array.isArray(currentRelation.keys)) {
|
||||
for (const key of currentRelation.keys) {
|
||||
if (key.leftColumn && key.rightColumn && selectedLeftItem[key.leftColumn] != null) {
|
||||
prefill[key.rightColumn] = selectedLeftItem[key.leftColumn];
|
||||
}
|
||||
}
|
||||
} else if (currentRelation) {
|
||||
const leftCol = currentRelation.leftColumn || componentConfig.leftPanel?.leftColumn;
|
||||
const rightCol = currentRelation.foreignKey || currentRelation.rightColumn || componentConfig.rightPanel?.rightColumn;
|
||||
if (leftCol && rightCol && selectedLeftItem[leftCol] != null) {
|
||||
prefill[rightCol] = selectedLeftItem[leftCol];
|
||||
}
|
||||
} else if (componentConfig.leftPanel?.leftColumn && componentConfig.rightPanel?.rightColumn) {
|
||||
// 하위호환: leftPanel.leftColumn → rightPanel.rightColumn
|
||||
prefill[componentConfig.rightPanel.rightColumn] = selectedLeftItem[componentConfig.leftPanel.leftColumn];
|
||||
}
|
||||
|
||||
// 2) addConfig.leftPanelColumn → targetColumn (단일 키, 하위호환)
|
||||
if (currentAddConfig?.leftPanelColumn && currentAddConfig?.targetColumn) {
|
||||
const val = selectedLeftItem[currentAddConfig.leftPanelColumn];
|
||||
if (val != null) prefill[currentAddConfig.targetColumn] = val;
|
||||
}
|
||||
|
||||
// 3) addConfig.autoFillFromLeft — 복수 컬럼 자동 채움
|
||||
if (currentAddConfig?.autoFillFromLeft) {
|
||||
for (const mapping of currentAddConfig.autoFillFromLeft) {
|
||||
const val = selectedLeftItem[mapping.source];
|
||||
if (val != null) prefill[mapping.target] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// 4) table_type_columns 기반 엔티티 관계 자동 감지 (패널 설정 없어도 동작)
|
||||
const currentTableName =
|
||||
activeTabIndex === 0
|
||||
? componentConfig.rightPanel?.tableName
|
||||
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName;
|
||||
if (currentTableName && autoDetectedTabRelations[currentTableName]) {
|
||||
for (const rel of autoDetectedTabRelations[currentTableName]) {
|
||||
// 이미 다른 방식으로 채워진 값이 없을 때만 자동 채움
|
||||
if (prefill[rel.rightColumn] == null && selectedLeftItem[rel.leftColumn] != null) {
|
||||
prefill[rel.rightColumn] = selectedLeftItem[rel.leftColumn];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAddModalFormData(prefill);
|
||||
} else {
|
||||
setAddModalFormData({});
|
||||
}
|
||||
|
||||
setShowAddModal(true);
|
||||
},
|
||||
[selectedLeftItem, componentConfig, activeTabIndex],
|
||||
[selectedLeftItem, componentConfig, activeTabIndex, autoDetectedTabRelations],
|
||||
);
|
||||
|
||||
// 수정 버튼 핸들러
|
||||
@@ -3234,21 +3347,38 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
tableName = componentConfig.leftPanel?.tableName;
|
||||
modalColumns = componentConfig.leftPanel?.addModalColumns;
|
||||
} else if (addModalPanel === "right") {
|
||||
// 우측 패널: 중계 테이블 설정이 있는지 확인
|
||||
const addConfig = componentConfig.rightPanel?.addConfig;
|
||||
// 현재 활성 탭의 설정 가져오기 (기본 탭 or 추가 탭)
|
||||
const isAdditionalTab = activeTabIndex > 0;
|
||||
const tabConfig = isAdditionalTab
|
||||
? (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)
|
||||
: null;
|
||||
|
||||
const addConfig = isAdditionalTab
|
||||
? tabConfig?.addConfig
|
||||
: componentConfig.rightPanel?.addConfig;
|
||||
|
||||
if (addConfig?.targetTable) {
|
||||
// 중계 테이블 모드
|
||||
tableName = addConfig.targetTable;
|
||||
modalColumns = componentConfig.rightPanel?.addModalColumns;
|
||||
modalColumns = isAdditionalTab
|
||||
? tabConfig?.addModalColumns
|
||||
: componentConfig.rightPanel?.addModalColumns;
|
||||
|
||||
// 좌측 패널에서 선택된 값 자동 채우기
|
||||
// 좌측 패널에서 선택된 값 자동 채우기 (단일 키, 하위호환)
|
||||
if (addConfig.leftPanelColumn && addConfig.targetColumn && selectedLeftItem) {
|
||||
const leftValue = selectedLeftItem[addConfig.leftPanelColumn];
|
||||
finalData[addConfig.targetColumn] = leftValue;
|
||||
}
|
||||
|
||||
// 자동 채움 컬럼 추가
|
||||
// autoFillFromLeft — 복수 컬럼 자동 채움
|
||||
if (addConfig.autoFillFromLeft && selectedLeftItem) {
|
||||
for (const mapping of addConfig.autoFillFromLeft) {
|
||||
const val = selectedLeftItem[mapping.source];
|
||||
if (val != null) finalData[mapping.target] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// 자동 채움 컬럼 추가 (정적 값)
|
||||
if (addConfig.autoFillColumns) {
|
||||
Object.entries(addConfig.autoFillColumns).forEach(([key, value]) => {
|
||||
finalData[key] = value;
|
||||
@@ -3256,8 +3386,29 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
}
|
||||
} else {
|
||||
// 일반 테이블 모드
|
||||
tableName = componentConfig.rightPanel?.tableName;
|
||||
modalColumns = componentConfig.rightPanel?.addModalColumns;
|
||||
tableName = isAdditionalTab
|
||||
? tabConfig?.tableName
|
||||
: componentConfig.rightPanel?.tableName;
|
||||
modalColumns = isAdditionalTab
|
||||
? tabConfig?.addModalColumns
|
||||
: componentConfig.rightPanel?.addModalColumns;
|
||||
|
||||
// 일반 모드에서도 autoFillFromLeft 적용
|
||||
if (addConfig?.autoFillFromLeft && selectedLeftItem) {
|
||||
for (const mapping of addConfig.autoFillFromLeft) {
|
||||
const val = selectedLeftItem[mapping.source];
|
||||
if (val != null) finalData[mapping.target] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// table_type_columns 기반 엔티티 관계 자동 감지 (패널 설정 없어도 동작)
|
||||
if (tableName && autoDetectedTabRelations[tableName] && selectedLeftItem) {
|
||||
for (const rel of autoDetectedTabRelations[tableName]) {
|
||||
if (finalData[rel.rightColumn] == null && selectedLeftItem[rel.leftColumn] != null) {
|
||||
finalData[rel.rightColumn] = selectedLeftItem[rel.leftColumn];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (addModalPanel === "left-item") {
|
||||
// 하위 항목 추가 (좌측 테이블에 추가)
|
||||
@@ -3305,8 +3456,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
// 좌측 패널 데이터 새로고침 (일반 추가 또는 하위 항목 추가)
|
||||
loadLeftData();
|
||||
} else if (addModalPanel === "right") {
|
||||
// 우측 패널 데이터 새로고침
|
||||
loadRightData(selectedLeftItem);
|
||||
// 우측 패널 데이터 새로고침 (추가 탭이면 loadTabData)
|
||||
if (activeTabIndex > 0) {
|
||||
loadTabData(activeTabIndex, selectedLeftItem);
|
||||
} else {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
|
||||
Reference in New Issue
Block a user