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