Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
@@ -473,6 +473,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [deleteModalPanel, setDeleteModalPanel] = useState<"left" | "right" | null>(null);
|
||||
const [deleteModalItem, setDeleteModalItem] = useState<any>(null);
|
||||
const [deleteModalTableName, setDeleteModalTableName] = useState<string | null>(null); // 추가 탭 삭제 시 테이블명
|
||||
|
||||
// 리사이저 드래그 상태
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
@@ -1102,7 +1103,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
searchValues,
|
||||
]);
|
||||
|
||||
// 우측 데이터 로드
|
||||
// 우측 데이터 로드 (leftItem이 null이면 전체 데이터 로드)
|
||||
const loadRightData = useCallback(
|
||||
async (leftItem: any) => {
|
||||
const relationshipType = componentConfig.rightPanel?.relation?.type || "detail";
|
||||
@@ -1110,10 +1111,84 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
|
||||
if (!rightTableName || isDesignMode) return;
|
||||
|
||||
// 좌측 미선택 시: 전체 데이터 로드 (dataFilter 적용)
|
||||
if (!leftItem && relationshipType === "join") {
|
||||
setIsLoadingRight(true);
|
||||
try {
|
||||
const rightJoinColumns = extractAdditionalJoinColumns(
|
||||
componentConfig.rightPanel?.columns,
|
||||
rightTableName,
|
||||
);
|
||||
|
||||
const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
|
||||
enableEntityJoin: true,
|
||||
size: 1000,
|
||||
companyCodeOverride: companyCode,
|
||||
additionalJoinColumns: rightJoinColumns,
|
||||
dataFilter: componentConfig.rightPanel?.dataFilter,
|
||||
});
|
||||
|
||||
// dataFilter 적용
|
||||
let filteredData = result.data || [];
|
||||
const dataFilter = componentConfig.rightPanel?.dataFilter;
|
||||
if (dataFilter?.enabled && dataFilter.filters?.length > 0) {
|
||||
filteredData = filteredData.filter((item: any) => {
|
||||
return dataFilter.filters.every((cond: any) => {
|
||||
const value = item[cond.columnName];
|
||||
switch (cond.operator) {
|
||||
case "equals":
|
||||
return value === cond.value;
|
||||
case "notEquals":
|
||||
return value !== cond.value;
|
||||
case "contains":
|
||||
return String(value || "").includes(String(cond.value));
|
||||
case "is_null":
|
||||
return value === null || value === undefined || value === "";
|
||||
case "is_not_null":
|
||||
return value !== null && value !== undefined && value !== "";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// conditions 형식 dataFilter도 지원 (하위 호환성)
|
||||
const dataFilterConditions = componentConfig.rightPanel?.dataFilter;
|
||||
if (dataFilterConditions?.enabled && dataFilterConditions.conditions?.length > 0) {
|
||||
filteredData = filteredData.filter((item: any) => {
|
||||
return dataFilterConditions.conditions.every((cond: any) => {
|
||||
const value = item[cond.column];
|
||||
switch (cond.operator) {
|
||||
case "equals":
|
||||
return value === cond.value;
|
||||
case "notEquals":
|
||||
return value !== cond.value;
|
||||
case "contains":
|
||||
return String(value || "").includes(String(cond.value));
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setRightData(filteredData);
|
||||
} catch (error) {
|
||||
console.error("우측 전체 데이터 로드 실패:", error);
|
||||
} finally {
|
||||
setIsLoadingRight(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// leftItem이 null이면 join 모드 이외에는 데이터 로드 불가
|
||||
if (!leftItem) return;
|
||||
|
||||
setIsLoadingRight(true);
|
||||
try {
|
||||
if (relationshipType === "detail") {
|
||||
// 상세 모드: 동일 테이블의 상세 정보 (🆕 엔티티 조인 활성화)
|
||||
// 상세 모드: 동일 테이블의 상세 정보 (엔티티 조인 활성화)
|
||||
const primaryKey = leftItem.id || leftItem.ID || Object.values(leftItem)[0];
|
||||
|
||||
// 🆕 엔티티 조인 API 사용
|
||||
@@ -1342,11 +1417,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
],
|
||||
);
|
||||
|
||||
// 추가 탭 데이터 로딩 함수
|
||||
// 추가 탭 데이터 로딩 함수 (leftItem이 null이면 전체 데이터 로드)
|
||||
const loadTabData = useCallback(
|
||||
async (tabIndex: number, leftItem: any) => {
|
||||
const tabConfig = componentConfig.rightPanel?.additionalTabs?.[tabIndex - 1];
|
||||
if (!tabConfig || !leftItem || isDesignMode) return;
|
||||
if (!tabConfig || isDesignMode) return;
|
||||
|
||||
const tabTableName = tabConfig.tableName;
|
||||
if (!tabTableName) return;
|
||||
@@ -1357,7 +1432,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const leftColumn = tabConfig.relation?.leftColumn || keys?.[0]?.leftColumn;
|
||||
const rightColumn = tabConfig.relation?.foreignKey || keys?.[0]?.rightColumn;
|
||||
|
||||
// 🆕 탭 config의 Entity 조인 컬럼 추출
|
||||
// 탭 config의 Entity 조인 컬럼 추출
|
||||
const tabJoinColumns = extractAdditionalJoinColumns(tabConfig.columns, tabTableName);
|
||||
if (tabJoinColumns) {
|
||||
console.log(`🔗 [분할패널] 탭 ${tabIndex} additionalJoinColumns:`, tabJoinColumns);
|
||||
@@ -1365,7 +1440,20 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
|
||||
let resultData: any[] = [];
|
||||
|
||||
if (leftColumn && rightColumn) {
|
||||
// 탭의 dataFilter (API 전달용)
|
||||
const tabDataFilterForApi = (tabConfig as any).dataFilter;
|
||||
|
||||
if (!leftItem) {
|
||||
// 좌측 미선택: 전체 데이터 로드 (dataFilter는 API에 전달)
|
||||
const result = await entityJoinApi.getTableDataWithJoins(tabTableName, {
|
||||
enableEntityJoin: true,
|
||||
size: 1000,
|
||||
companyCodeOverride: companyCode,
|
||||
additionalJoinColumns: tabJoinColumns,
|
||||
dataFilter: tabDataFilterForApi,
|
||||
});
|
||||
resultData = result.data || [];
|
||||
} else if (leftColumn && rightColumn) {
|
||||
const searchConditions: Record<string, any> = {};
|
||||
|
||||
if (keys && keys.length > 0) {
|
||||
@@ -1391,18 +1479,46 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
search: searchConditions,
|
||||
enableEntityJoin: true,
|
||||
size: 1000,
|
||||
additionalJoinColumns: tabJoinColumns, // 🆕 Entity 조인 컬럼 전달
|
||||
companyCodeOverride: companyCode,
|
||||
additionalJoinColumns: tabJoinColumns,
|
||||
dataFilter: tabDataFilterForApi,
|
||||
});
|
||||
resultData = result.data || [];
|
||||
} else {
|
||||
const result = await entityJoinApi.getTableDataWithJoins(tabTableName, {
|
||||
enableEntityJoin: true,
|
||||
size: 1000,
|
||||
additionalJoinColumns: tabJoinColumns, // 🆕 Entity 조인 컬럼 전달
|
||||
companyCodeOverride: companyCode,
|
||||
additionalJoinColumns: tabJoinColumns,
|
||||
dataFilter: tabDataFilterForApi,
|
||||
});
|
||||
resultData = result.data || [];
|
||||
}
|
||||
|
||||
// 탭별 dataFilter 적용
|
||||
const tabDataFilter = (tabConfig as any).dataFilter;
|
||||
if (tabDataFilter?.enabled && tabDataFilter.filters?.length > 0) {
|
||||
resultData = resultData.filter((item: any) => {
|
||||
return tabDataFilter.filters.every((cond: any) => {
|
||||
const value = item[cond.columnName];
|
||||
switch (cond.operator) {
|
||||
case "equals":
|
||||
return value === cond.value;
|
||||
case "notEquals":
|
||||
return value !== cond.value;
|
||||
case "contains":
|
||||
return String(value || "").includes(String(cond.value));
|
||||
case "is_null":
|
||||
return value === null || value === undefined || value === "";
|
||||
case "is_not_null":
|
||||
return value !== null && value !== undefined && value !== "";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setTabsData((prev) => ({ ...prev, [tabIndex]: resultData }));
|
||||
} catch (error) {
|
||||
console.error(`추가탭 ${tabIndex} 데이터 로드 실패:`, error);
|
||||
@@ -1418,29 +1534,55 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
[componentConfig.rightPanel?.additionalTabs, isDesignMode, toast],
|
||||
);
|
||||
|
||||
// 탭 변경 핸들러
|
||||
// 탭 변경 핸들러 (좌측 미선택 시에도 전체 데이터 로드)
|
||||
const handleTabChange = useCallback(
|
||||
(newTabIndex: number) => {
|
||||
setActiveTabIndex(newTabIndex);
|
||||
|
||||
if (selectedLeftItem) {
|
||||
if (newTabIndex === 0) {
|
||||
if (!rightData || (Array.isArray(rightData) && rightData.length === 0)) {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
} else {
|
||||
if (!tabsData[newTabIndex]) {
|
||||
loadTabData(newTabIndex, selectedLeftItem);
|
||||
}
|
||||
if (newTabIndex === 0) {
|
||||
if (!rightData || (Array.isArray(rightData) && rightData.length === 0)) {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
} else {
|
||||
if (!tabsData[newTabIndex]) {
|
||||
loadTabData(newTabIndex, selectedLeftItem);
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedLeftItem, rightData, tabsData, loadRightData, loadTabData],
|
||||
);
|
||||
|
||||
// 좌측 항목 선택 핸들러
|
||||
// 좌측 항목 선택 핸들러 (동일 항목 재클릭 시 선택 해제 → 전체 데이터 표시)
|
||||
const handleLeftItemSelect = useCallback(
|
||||
(item: any) => {
|
||||
// 동일 항목 클릭 시 선택 해제 (전체 보기로 복귀)
|
||||
const leftPk = componentConfig.rightPanel?.relation?.leftColumn ||
|
||||
componentConfig.rightPanel?.relation?.keys?.[0]?.leftColumn;
|
||||
const isSameItem = selectedLeftItem && leftPk &&
|
||||
selectedLeftItem[leftPk] === item[leftPk];
|
||||
|
||||
if (isSameItem) {
|
||||
// 선택 해제 → 전체 데이터 로드
|
||||
setSelectedLeftItem(null);
|
||||
setExpandedRightItems(new Set());
|
||||
setTabsData({});
|
||||
if (activeTabIndex === 0) {
|
||||
loadRightData(null);
|
||||
} else {
|
||||
loadTabData(activeTabIndex, null);
|
||||
}
|
||||
// 추가 탭들도 전체 데이터 로드
|
||||
const tabs = componentConfig.rightPanel?.additionalTabs;
|
||||
if (tabs && tabs.length > 0) {
|
||||
tabs.forEach((_: any, idx: number) => {
|
||||
if (idx + 1 !== activeTabIndex) {
|
||||
loadTabData(idx + 1, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedLeftItem(item);
|
||||
setExpandedRightItems(new Set()); // 좌측 항목 변경 시 우측 확장 초기화
|
||||
setTabsData({}); // 모든 탭 데이터 초기화
|
||||
@@ -1461,7 +1603,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
});
|
||||
}
|
||||
},
|
||||
[loadRightData, loadTabData, activeTabIndex, componentConfig.leftPanel?.tableName, isDesignMode],
|
||||
[loadRightData, loadTabData, activeTabIndex, componentConfig.leftPanel?.tableName, componentConfig.rightPanel?.relation, componentConfig.rightPanel?.additionalTabs, isDesignMode, selectedLeftItem],
|
||||
);
|
||||
|
||||
// 우측 항목 확장/축소 토글
|
||||
@@ -2037,10 +2179,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
if (editModalPanel === "left") {
|
||||
loadLeftData();
|
||||
// 우측 패널도 새로고침 (FK가 변경되었을 수 있음)
|
||||
if (selectedLeftItem) {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
} else if (editModalPanel === "right" && selectedLeftItem) {
|
||||
loadRightData(selectedLeftItem);
|
||||
} else if (editModalPanel === "right") {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
} else {
|
||||
@@ -2069,32 +2209,39 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
loadRightData,
|
||||
]);
|
||||
|
||||
// 삭제 버튼 핸들러
|
||||
const handleDeleteClick = useCallback((panel: "left" | "right", item: any) => {
|
||||
// 삭제 버튼 핸들러 (tableName: 추가 탭 등 특정 테이블 지정 시 사용)
|
||||
const handleDeleteClick = useCallback((panel: "left" | "right", item: any, tableName?: string) => {
|
||||
setDeleteModalPanel(panel);
|
||||
setDeleteModalItem(item);
|
||||
setDeleteModalTableName(tableName || null);
|
||||
setShowDeleteModal(true);
|
||||
}, []);
|
||||
|
||||
// 삭제 확인
|
||||
const handleDeleteConfirm = useCallback(async () => {
|
||||
// 우측 패널 삭제 시 중계 테이블 확인
|
||||
let tableName =
|
||||
deleteModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName;
|
||||
// 1. 테이블명 결정: deleteModalTableName이 있으면 우선 사용 (추가 탭 등)
|
||||
let tableName = deleteModalTableName;
|
||||
|
||||
// 우측 패널 + 중계 테이블 모드인 경우
|
||||
if (deleteModalPanel === "right" && componentConfig.rightPanel?.addConfig?.targetTable) {
|
||||
tableName = componentConfig.rightPanel.addConfig.targetTable;
|
||||
console.log("🔗 중계 테이블 모드: 삭제 대상 테이블 =", tableName);
|
||||
if (!tableName) {
|
||||
tableName =
|
||||
deleteModalPanel === "left" ? componentConfig.leftPanel?.tableName : componentConfig.rightPanel?.tableName;
|
||||
|
||||
// 우측 패널 + 중계 테이블 모드인 경우
|
||||
if (deleteModalPanel === "right" && componentConfig.rightPanel?.addConfig?.targetTable) {
|
||||
tableName = componentConfig.rightPanel.addConfig.targetTable;
|
||||
console.log("🔗 중계 테이블 모드: 삭제 대상 테이블 =", tableName);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
|
||||
let primaryKey: any = deleteModalItem[sourceColumn] || deleteModalItem.id || deleteModalItem.ID;
|
||||
// 2. Primary Key 추출: id 필드를 우선 사용, 없으면 전체 객체 전달 (복합키)
|
||||
let primaryKey: any = deleteModalItem?.id || deleteModalItem?.ID;
|
||||
|
||||
// 복합키 처리: deleteModalItem 전체를 전달 (백엔드에서 복합키 자동 처리)
|
||||
if (deleteModalItem && typeof deleteModalItem === "object") {
|
||||
if (!primaryKey && deleteModalItem && typeof deleteModalItem === "object") {
|
||||
// id가 없는 경우에만 전체 객체 전달 (복합키 테이블)
|
||||
primaryKey = deleteModalItem;
|
||||
console.log("🔑 복합키 가능성: 전체 객체 전달", primaryKey);
|
||||
console.log("🔑 복합키: 전체 객체 전달", Object.keys(primaryKey));
|
||||
} else {
|
||||
console.log("🔑 단일키 삭제: id =", primaryKey, "테이블 =", tableName);
|
||||
}
|
||||
|
||||
if (!tableName || !primaryKey) {
|
||||
@@ -2162,6 +2309,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
// 모달 닫기
|
||||
setShowDeleteModal(false);
|
||||
setDeleteModalItem(null);
|
||||
setDeleteModalTableName(null);
|
||||
|
||||
// 데이터 새로고침
|
||||
if (deleteModalPanel === "left") {
|
||||
@@ -2171,8 +2319,13 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
setSelectedLeftItem(null);
|
||||
setRightData(null);
|
||||
}
|
||||
} else if (deleteModalPanel === "right" && selectedLeftItem) {
|
||||
loadRightData(selectedLeftItem);
|
||||
} else if (deleteModalPanel === "right") {
|
||||
// 추가 탭에서 삭제한 경우 해당 탭 데이터 리로드
|
||||
if (deleteModalTableName && activeTabIndex > 0) {
|
||||
loadTabData(activeTabIndex, selectedLeftItem);
|
||||
} else {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
@@ -2196,7 +2349,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}, [deleteModalPanel, componentConfig, deleteModalItem, toast, selectedLeftItem, loadLeftData, loadRightData]);
|
||||
}, [deleteModalPanel, deleteModalTableName, componentConfig, deleteModalItem, toast, selectedLeftItem, loadLeftData, loadRightData, loadTabData, activeTabIndex]);
|
||||
|
||||
// 항목별 추가 버튼 핸들러 (좌측 항목의 + 버튼 - 하위 항목 추가)
|
||||
const handleItemAddClick = useCallback(
|
||||
@@ -2328,7 +2481,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
if (addModalPanel === "left" || addModalPanel === "left-item") {
|
||||
// 좌측 패널 데이터 새로고침 (일반 추가 또는 하위 항목 추가)
|
||||
loadLeftData();
|
||||
} else if (addModalPanel === "right" && selectedLeftItem) {
|
||||
} else if (addModalPanel === "right") {
|
||||
// 우측 패널 데이터 새로고침
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
@@ -2416,10 +2569,22 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
}
|
||||
}, [leftColumnVisibility, componentConfig.leftPanel?.tableName, currentUserId]);
|
||||
|
||||
// 초기 데이터 로드
|
||||
// 초기 데이터 로드 (좌측 + 우측 전체 데이터)
|
||||
useEffect(() => {
|
||||
if (!isDesignMode && componentConfig.autoLoad !== false) {
|
||||
loadLeftData();
|
||||
// 좌측 미선택 상태에서 우측 전체 데이터 기본 로드
|
||||
const relationshipType = componentConfig.rightPanel?.relation?.type || "detail";
|
||||
if (relationshipType === "join") {
|
||||
loadRightData(null);
|
||||
// 추가 탭도 전체 데이터 로드
|
||||
const tabs = componentConfig.rightPanel?.additionalTabs;
|
||||
if (tabs && tabs.length > 0) {
|
||||
tabs.forEach((_: any, idx: number) => {
|
||||
loadTabData(idx + 1, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isDesignMode, componentConfig.autoLoad]);
|
||||
@@ -2432,19 +2597,17 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [leftFilters]);
|
||||
|
||||
// 🆕 전역 테이블 새로고침 이벤트 리스너
|
||||
// 전역 테이블 새로고침 이벤트 리스너
|
||||
useEffect(() => {
|
||||
const handleRefreshTable = () => {
|
||||
if (!isDesignMode) {
|
||||
console.log("🔄 [SplitPanel] refreshTable 이벤트 수신 - 데이터 새로고침");
|
||||
loadLeftData();
|
||||
// 선택된 항목이 있으면 현재 활성 탭 데이터 새로고침
|
||||
if (selectedLeftItem) {
|
||||
if (activeTabIndex === 0) {
|
||||
loadRightData(selectedLeftItem);
|
||||
} else {
|
||||
loadTabData(activeTabIndex, selectedLeftItem);
|
||||
}
|
||||
// 현재 활성 탭 데이터 새로고침 (좌측 미선택 시에도 전체 데이터 로드)
|
||||
if (activeTabIndex === 0) {
|
||||
loadRightData(selectedLeftItem);
|
||||
} else {
|
||||
loadTabData(activeTabIndex, selectedLeftItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -3359,15 +3522,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
);
|
||||
}
|
||||
|
||||
if (!selectedLeftItem) {
|
||||
return (
|
||||
<div className="text-muted-foreground flex h-full flex-col items-center justify-center gap-2 py-12 text-sm">
|
||||
<p>좌측에서 항목을 선택하세요</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentTabData.length === 0) {
|
||||
if (currentTabData.length === 0 && !isTabLoading) {
|
||||
return (
|
||||
<div className="text-muted-foreground flex h-full flex-col items-center justify-center gap-2 py-12 text-sm">
|
||||
<p>관련 데이터가 없습니다.</p>
|
||||
@@ -3420,7 +3575,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
)}
|
||||
{currentTabConfig?.showDelete && (
|
||||
<Button size="sm" variant="ghost" className="text-destructive h-7 px-2 text-xs"
|
||||
onClick={() => handleDeleteClick("right", item)}
|
||||
onClick={() => handleDeleteClick("right", item, currentTabConfig?.tableName)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
@@ -3464,7 +3619,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
)}
|
||||
{currentTabConfig?.showDelete && (
|
||||
<Button size="sm" variant="ghost" className="text-destructive h-7 px-2 text-xs"
|
||||
onClick={() => handleDeleteClick("right", item)}
|
||||
onClick={() => handleDeleteClick("right", item, currentTabConfig?.tableName)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
@@ -4136,11 +4291,20 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// 선택 없음
|
||||
// 데이터 없음 또는 초기 로딩 대기
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-muted-foreground text-center text-sm">
|
||||
<p className="mb-2">좌측에서 항목을 선택하세요</p>
|
||||
<p className="text-xs">선택한 항목의 상세 정보가 여기에 표시됩니다</p>
|
||||
{componentConfig.rightPanel?.relation?.type === "join" ? (
|
||||
<>
|
||||
<Loader2 className="text-muted-foreground mx-auto h-6 w-6 animate-spin" />
|
||||
<p className="mt-2">데이터를 불러오는 중...</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="mb-2">좌측에서 항목을 선택하세요</p>
|
||||
<p className="text-xs">선택한 항목의 상세 정보가 여기에 표시됩니다</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user