fix/429error #75
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import React, { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||
import { TableListConfig, ColumnConfig } from "./types";
|
||||
import { WebType } from "@/types/common";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
|
|
@ -35,6 +35,51 @@ const cleanupTableCache = () => {
|
|||
if (typeof window !== "undefined") {
|
||||
setInterval(cleanupTableCache, 10 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 요청 디바운싱을 위한 전역 타이머
|
||||
const debounceTimers = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
// 진행 중인 요청 추적 (중복 요청 방지)
|
||||
const activeRequests = new Map<string, Promise<any>>();
|
||||
|
||||
// 디바운싱된 API 호출 함수 (중복 요청 방지 포함)
|
||||
const debouncedApiCall = <T extends any[], R>(key: string, fn: (...args: T) => Promise<R>, delay: number = 300) => {
|
||||
return (...args: T): Promise<R> => {
|
||||
// 이미 진행 중인 동일한 요청이 있으면 그 결과를 반환
|
||||
const activeRequest = activeRequests.get(key);
|
||||
if (activeRequest) {
|
||||
console.log(`🔄 진행 중인 요청 재사용: ${key}`);
|
||||
return activeRequest as Promise<R>;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 기존 타이머 제거
|
||||
const existingTimer = debounceTimers.get(key);
|
||||
if (existingTimer) {
|
||||
clearTimeout(existingTimer);
|
||||
}
|
||||
|
||||
// 새 타이머 설정
|
||||
const timer = setTimeout(async () => {
|
||||
try {
|
||||
// 요청 시작 시 활성 요청으로 등록
|
||||
const requestPromise = fn(...args);
|
||||
activeRequests.set(key, requestPromise);
|
||||
|
||||
const result = await requestPromise;
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
debounceTimers.delete(key);
|
||||
activeRequests.delete(key);
|
||||
}
|
||||
}, delay);
|
||||
|
||||
debounceTimers.set(key, timer);
|
||||
});
|
||||
};
|
||||
};
|
||||
import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
|
|
@ -148,6 +193,15 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
const [joinColumnMapping, setJoinColumnMapping] = useState<Record<string, string>>({});
|
||||
const [columnMeta, setColumnMeta] = useState<Record<string, { webType?: string; codeCategory?: string }>>({}); // 🎯 컬럼 메타정보 (웹타입, 코드카테고리)
|
||||
|
||||
// 컬럼 정보 메모이제이션
|
||||
const memoizedColumnInfo = useMemo(() => {
|
||||
return {
|
||||
labels: columnLabels,
|
||||
meta: columnMeta,
|
||||
visibleColumns: (tableConfig.columns || []).filter((col) => col.visible !== false),
|
||||
};
|
||||
}, [columnLabels, columnMeta, tableConfig.columns]);
|
||||
|
||||
// 고급 필터 관련 state
|
||||
const [searchValues, setSearchValues] = useState<Record<string, any>>({});
|
||||
|
||||
|
|
@ -369,8 +423,20 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
// 테이블 데이터 가져오기
|
||||
const fetchTableData = async () => {
|
||||
// 디바운싱된 테이블 데이터 가져오기
|
||||
const fetchTableDataDebounced = useCallback(
|
||||
debouncedApiCall(
|
||||
`fetchTableData_${tableConfig.selectedTable}_${currentPage}_${localPageSize}`,
|
||||
async () => {
|
||||
return fetchTableDataInternal();
|
||||
},
|
||||
200, // 200ms 디바운스
|
||||
),
|
||||
[tableConfig.selectedTable, currentPage, localPageSize, searchTerm, sortColumn, sortDirection, searchValues],
|
||||
);
|
||||
|
||||
// 실제 테이블 데이터 가져오기 함수
|
||||
const fetchTableDataInternal = async () => {
|
||||
if (!tableConfig.selectedTable) {
|
||||
setData([]);
|
||||
return;
|
||||
|
|
@ -558,14 +624,24 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
codeColumns.map(([col, meta]) => `${col}(${meta.codeCategory})`),
|
||||
);
|
||||
|
||||
// 필요한 코드 카테고리들을 추출하여 배치 로드
|
||||
const categoryList = codeColumns.map(([, meta]) => meta.codeCategory).filter(Boolean) as string[];
|
||||
// 필요한 코드 카테고리들을 추출하여 배치 로드 (중복 제거)
|
||||
const categoryList = [
|
||||
...new Set(codeColumns.map(([, meta]) => meta.codeCategory).filter(Boolean)),
|
||||
] as string[];
|
||||
|
||||
try {
|
||||
await codeCache.preloadCodes(categoryList);
|
||||
console.log("📋 모든 코드 캐시 로드 완료 (전역 캐시)");
|
||||
} catch (error) {
|
||||
console.error("❌ 코드 캐시 로드 중 오류:", error);
|
||||
// 이미 캐시된 카테고리는 제외
|
||||
const uncachedCategories = categoryList.filter((category) => !codeCache.getCodeSync(category));
|
||||
|
||||
if (uncachedCategories.length > 0) {
|
||||
try {
|
||||
console.log(`📋 새로운 코드 카테고리 로딩: ${uncachedCategories.join(", ")}`);
|
||||
await codeCache.preloadCodes(uncachedCategories);
|
||||
console.log("📋 모든 코드 캐시 로드 완료 (전역 캐시)");
|
||||
} catch (error) {
|
||||
console.error("❌ 코드 캐시 로드 중 오류:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("📋 모든 코드 카테고리가 이미 캐시됨");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -759,18 +835,18 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
const handleAdvancedSearch = () => {
|
||||
setCurrentPage(1);
|
||||
fetchTableData();
|
||||
fetchTableDataDebounced();
|
||||
};
|
||||
|
||||
const handleClearAdvancedFilters = () => {
|
||||
setSearchValues({});
|
||||
setCurrentPage(1);
|
||||
fetchTableData();
|
||||
fetchTableDataDebounced();
|
||||
};
|
||||
|
||||
// 새로고침
|
||||
const handleRefresh = () => {
|
||||
fetchTableData();
|
||||
fetchTableDataDebounced();
|
||||
};
|
||||
|
||||
// 체크박스 핸들러들
|
||||
|
|
@ -872,7 +948,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (tableConfig.autoLoad && !isDesignMode) {
|
||||
fetchTableData();
|
||||
fetchTableDataDebounced();
|
||||
}
|
||||
}, [
|
||||
tableConfig.selectedTable,
|
||||
|
|
@ -896,7 +972,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
console.log("🔄 선택 상태 초기화 - 빈 배열 전달");
|
||||
onSelectedRowsChange?.([], []);
|
||||
// 테이블 데이터 새로고침
|
||||
fetchTableData();
|
||||
fetchTableDataDebounced();
|
||||
}
|
||||
}, [refreshKey]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue