탭 컴포넌트 외부 검색필터 동작 구현
This commit is contained in:
@@ -43,25 +43,24 @@ export const TableOptionsProvider: React.FC<{ children: ReactNode }> = ({
|
||||
|
||||
/**
|
||||
* 테이블 등록 해제
|
||||
* 주의:
|
||||
* 1. selectedTableId를 의존성으로 사용하면 무한 루프 발생 가능
|
||||
* 2. 재등록 시에도 unregister가 호출되므로 selectedTableId를 변경하면 안됨
|
||||
*/
|
||||
const unregisterTable = useCallback(
|
||||
(tableId: string) => {
|
||||
setRegisteredTables((prev) => {
|
||||
const newMap = new Map(prev);
|
||||
const removed = newMap.delete(tableId);
|
||||
|
||||
if (removed) {
|
||||
// 선택된 테이블이 제거되면 첫 번째 테이블 선택
|
||||
if (selectedTableId === tableId) {
|
||||
const firstTableId = newMap.keys().next().value;
|
||||
setSelectedTableId(firstTableId || null);
|
||||
}
|
||||
}
|
||||
|
||||
newMap.delete(tableId);
|
||||
return newMap;
|
||||
});
|
||||
|
||||
// 🚫 selectedTableId를 변경하지 않음
|
||||
// 이유: useEffect 재실행 시 cleanup → register 순서로 호출되는데,
|
||||
// cleanup에서 selectedTableId를 null로 만들면 필터 설정이 초기화됨
|
||||
// 다른 테이블이 선택되어야 하면 TableSearchWidget에서 자동 선택함
|
||||
},
|
||||
[selectedTableId]
|
||||
[] // 의존성 없음 - 무한 루프 방지
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,6 +59,9 @@ export function useEntityJoinOptimization(columnMeta: Record<string, ColumnMetaI
|
||||
|
||||
// 변환된 값 캐시 (중복 변환 방지)
|
||||
const convertedCache = useRef(new Map<string, string>());
|
||||
|
||||
// 초기화 완료 플래그 (무한 루프 방지)
|
||||
const initialLoadDone = useRef(false);
|
||||
|
||||
// 공통 코드 카테고리 추출 (메모이제이션)
|
||||
const codeCategories = useMemo(() => {
|
||||
@@ -293,24 +296,40 @@ export function useEntityJoinOptimization(columnMeta: Record<string, ColumnMetaI
|
||||
[codeCategories, batchLoadCodes, updateMetrics],
|
||||
);
|
||||
|
||||
// 초기화 시 공통 코드 프리로딩
|
||||
// 초기화 시 공통 코드 프리로딩 (한 번만 실행)
|
||||
useEffect(() => {
|
||||
// 이미 초기화되었으면 스킵 (무한 루프 방지)
|
||||
if (initialLoadDone.current) return;
|
||||
initialLoadDone.current = true;
|
||||
|
||||
preloadCommonCodesOnMount();
|
||||
}, [preloadCommonCodesOnMount]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// 컬럼 메타 변경 시 필요한 코드 추가 로딩
|
||||
// 이미 로딩 중이면 스킵하여 무한 루프 방지
|
||||
const loadedCategoriesRef = useRef<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
// 이미 최적화 중이거나 초기화 전이면 스킵
|
||||
if (isOptimizing) return;
|
||||
|
||||
if (codeCategories.length > 0) {
|
||||
const unloadedCategories = codeCategories.filter((category) => {
|
||||
// 이미 로드 요청을 보낸 카테고리는 스킵
|
||||
if (loadedCategoriesRef.current.has(category)) return false;
|
||||
return codeCache.getCodeSync(category) === null;
|
||||
});
|
||||
|
||||
if (unloadedCategories.length > 0) {
|
||||
// 로딩 요청 카테고리 기록
|
||||
unloadedCategories.forEach(cat => loadedCategoriesRef.current.add(cat));
|
||||
console.log(`🔄 새로운 코드 카테고리 감지, 추가 로딩: ${unloadedCategories.join(", ")}`);
|
||||
batchLoadCodes(unloadedCategories);
|
||||
}
|
||||
}
|
||||
}, [codeCategories, batchLoadCodes]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [codeCategories.join(",")]); // 배열 내용 기반 의존성
|
||||
|
||||
// 주기적으로 메트릭 업데이트
|
||||
useEffect(() => {
|
||||
|
||||
@@ -416,6 +416,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
// originalData를 사용 (최초 전달된 값, formData는 계속 변경되므로 사용하면 안됨)
|
||||
_initialData: originalData || formData,
|
||||
_originalData: originalData,
|
||||
// 🆕 탭 관련 정보 전달 (탭 내부의 테이블 컴포넌트에서 사용)
|
||||
parentTabId: props.parentTabId,
|
||||
parentTabsComponentId: props.parentTabsComponentId,
|
||||
};
|
||||
|
||||
// 렌더러가 클래스인지 함수인지 확인
|
||||
|
||||
@@ -1033,6 +1033,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return () => {
|
||||
unregisterTable(tableId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
tableId,
|
||||
tableConfig.selectedTable,
|
||||
@@ -1044,7 +1045,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
data, // 데이터 자체가 변경되면 재등록 (고유 값 조회용)
|
||||
totalItems, // 전체 항목 수가 변경되면 재등록
|
||||
registerTable,
|
||||
unregisterTable,
|
||||
// unregisterTable은 의존성에서 제외 - 무한 루프 방지
|
||||
// unregisterTable 함수는 의존성이 없어 안정적임
|
||||
]);
|
||||
|
||||
// 🎯 초기 로드 시 localStorage에서 정렬 상태 불러오기
|
||||
|
||||
@@ -138,33 +138,84 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
|
||||
// currentTable은 tableList(필터링된 목록)에서 가져와야 함
|
||||
const currentTable = useMemo(() => {
|
||||
console.log("🔍 [TableSearchWidget] currentTable 계산:", {
|
||||
selectedTableId,
|
||||
tableListLength: tableList.length,
|
||||
tableList: tableList.map(t => ({ id: t.tableId, name: t.tableName, parentTabId: t.parentTabId }))
|
||||
});
|
||||
|
||||
if (!selectedTableId) return undefined;
|
||||
|
||||
// 먼저 tableList(필터링된 목록)에서 찾기
|
||||
const tableFromList = tableList.find(t => t.tableId === selectedTableId);
|
||||
if (tableFromList) {
|
||||
console.log("✅ [TableSearchWidget] 테이블 찾음 (tableList):", tableFromList.tableName);
|
||||
return tableFromList;
|
||||
}
|
||||
|
||||
// tableList에 없으면 전체에서 찾기 (폴백)
|
||||
return getTable(selectedTableId);
|
||||
const tableFromAll = getTable(selectedTableId);
|
||||
console.log("🔄 [TableSearchWidget] 테이블 찾음 (전체):", tableFromAll?.tableName);
|
||||
return tableFromAll;
|
||||
}, [selectedTableId, tableList, getTable]);
|
||||
|
||||
// 🆕 활성 탭 ID 문자열 (변경 감지용)
|
||||
const activeTabIdsStr = useMemo(() => activeTabIds.join(","), [activeTabIds]);
|
||||
|
||||
// 🆕 이전 활성 탭 ID 추적 (탭 전환 감지용)
|
||||
const prevActiveTabIdsRef = useRef<string>(activeTabIdsStr);
|
||||
|
||||
// 대상 패널의 첫 번째 테이블 자동 선택
|
||||
useEffect(() => {
|
||||
if (!autoSelectFirstTable || tableList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 🆕 탭 전환 감지: 활성 탭이 변경되었는지 확인
|
||||
const tabChanged = prevActiveTabIdsRef.current !== activeTabIdsStr;
|
||||
if (tabChanged) {
|
||||
console.log("🔄 [TableSearchWidget] 탭 전환 감지:", {
|
||||
이전탭: prevActiveTabIdsRef.current,
|
||||
현재탭: activeTabIdsStr,
|
||||
가용테이블: tableList.map(t => ({ id: t.tableId, tableName: t.tableName, parentTabId: t.parentTabId })),
|
||||
현재선택테이블: selectedTableId
|
||||
});
|
||||
prevActiveTabIdsRef.current = activeTabIdsStr;
|
||||
|
||||
// 🆕 탭 전환 시: 해당 탭에 속한 테이블 중 첫 번째 강제 선택
|
||||
const activeTabTable = tableList.find(t => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const targetTable = activeTabTable || tableList[0];
|
||||
|
||||
if (targetTable) {
|
||||
console.log("✅ [TableSearchWidget] 탭 전환으로 테이블 강제 선택:", {
|
||||
테이블ID: targetTable.tableId,
|
||||
테이블명: targetTable.tableName,
|
||||
탭ID: targetTable.parentTabId,
|
||||
이전테이블: selectedTableId
|
||||
});
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
}
|
||||
return; // 탭 전환 시에는 여기서 종료
|
||||
}
|
||||
|
||||
// 현재 선택된 테이블이 대상 패널에 있는지 확인
|
||||
const isCurrentTableInTarget = selectedTableId && tableList.some(t => t.tableId === selectedTableId);
|
||||
|
||||
// 현재 선택된 테이블이 대상 패널에 없으면 대상 패널의 첫 번째 테이블 선택
|
||||
// 현재 선택된 테이블이 대상 패널에 없으면 첫 번째 테이블 선택
|
||||
if (!selectedTableId || !isCurrentTableInTarget) {
|
||||
const targetTable = tableList[0];
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
const activeTabTable = tableList.find(t => t.parentTabId && activeTabIds.includes(t.parentTabId));
|
||||
const targetTable = activeTabTable || tableList[0];
|
||||
|
||||
if (targetTable && targetTable.tableId !== selectedTableId) {
|
||||
console.log("✅ [TableSearchWidget] 테이블 자동 선택 (초기):", {
|
||||
테이블ID: targetTable.tableId,
|
||||
테이블명: targetTable.tableName,
|
||||
탭ID: targetTable.parentTabId
|
||||
});
|
||||
setSelectedTableId(targetTable.tableId);
|
||||
}
|
||||
}
|
||||
}, [tableList, selectedTableId, autoSelectFirstTable, setSelectedTableId, targetPanelPosition]);
|
||||
}, [tableList, selectedTableId, autoSelectFirstTable, setSelectedTableId, targetPanelPosition, activeTabIdsStr, activeTabIds]);
|
||||
|
||||
// 현재 선택된 테이블의 탭 ID (탭별 필터 저장용)
|
||||
const currentTableTabId = currentTable?.parentTabId;
|
||||
@@ -196,6 +247,13 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
|
||||
// 현재 테이블의 저장된 필터 불러오기 (동적 모드) 또는 고정 필터 적용 (고정 모드)
|
||||
useEffect(() => {
|
||||
console.log("📋 [TableSearchWidget] 필터 설정 useEffect 실행:", {
|
||||
currentTable: currentTable?.tableName,
|
||||
currentTableTabId,
|
||||
filterMode,
|
||||
selectedTableId,
|
||||
컬럼수: currentTable?.columns?.length
|
||||
});
|
||||
if (!currentTable?.tableName) return;
|
||||
|
||||
// 고정 모드: presetFilters를 activeFilters로 설정
|
||||
@@ -229,12 +287,20 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
return;
|
||||
}
|
||||
|
||||
// 동적 모드: 화면별 + 탭별로 독립적인 필터 설정 불러오기
|
||||
// 동적 모드: 화면별로 독립적인 필터 설정 불러오기
|
||||
// 참고: FilterPanel.tsx에서도 screenId만 사용하여 저장하므로 키가 일치해야 함
|
||||
const filterConfigKey = screenId
|
||||
? `table_filters_${currentTable.tableName}_screen_${screenId}${currentTableTabId ? `_tab_${currentTableTabId}` : ''}`
|
||||
? `table_filters_${currentTable.tableName}_screen_${screenId}`
|
||||
: `table_filters_${currentTable.tableName}`;
|
||||
const savedFilters = localStorage.getItem(filterConfigKey);
|
||||
|
||||
console.log("🔑 [TableSearchWidget] 필터 설정 키 확인:", {
|
||||
filterConfigKey,
|
||||
savedFilters: savedFilters ? `${savedFilters.substring(0, 100)}...` : null,
|
||||
screenId,
|
||||
tableName: currentTable.tableName
|
||||
});
|
||||
|
||||
if (savedFilters) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedFilters) as Array<{
|
||||
@@ -257,6 +323,13 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
width: f.width || 200,
|
||||
}));
|
||||
|
||||
console.log("📌 [TableSearchWidget] 필터 설정 로드:", {
|
||||
filterConfigKey,
|
||||
총필터수: parsed.length,
|
||||
활성화필터수: activeFiltersList.length,
|
||||
활성화필터: activeFiltersList.map(f => f.columnName)
|
||||
});
|
||||
|
||||
setActiveFilters(activeFiltersList);
|
||||
|
||||
// 탭별 저장된 필터 값 복원
|
||||
@@ -280,10 +353,19 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("저장된 필터 불러오기 실패:", error);
|
||||
// 파싱 에러 시 필터 초기화
|
||||
setActiveFilters([]);
|
||||
setFilterValues({});
|
||||
}
|
||||
} else {
|
||||
// 필터 설정이 없으면 초기화
|
||||
// 필터 설정이 없으면 activeFilters와 filterValues 모두 초기화
|
||||
console.log("⚠️ [TableSearchWidget] 저장된 필터 설정 없음 - 필터 초기화:", {
|
||||
tableName: currentTable.tableName,
|
||||
filterConfigKey
|
||||
});
|
||||
setActiveFilters([]);
|
||||
setFilterValues({});
|
||||
setSelectOptions({});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentTable?.tableName, filterMode, screenId, currentTableTabId, JSON.stringify(presetFilters)]);
|
||||
|
||||
Reference in New Issue
Block a user