조인기능 최적화
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { ComponentRendererProps } from "@/types/component";
|
||||
import { TableListConfig, ColumnConfig, TableDataResponse } from "./types";
|
||||
import { TableListConfig, ColumnConfig } from "./types";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { commonCodeApi } from "@/lib/api/commonCode";
|
||||
import { codeCache } from "@/lib/cache/codeCache";
|
||||
import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
@@ -101,7 +101,12 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const [selectedSearchColumn, setSelectedSearchColumn] = useState<string>(""); // 선택된 검색 컬럼
|
||||
const [displayColumns, setDisplayColumns] = useState<ColumnConfig[]>([]); // 🎯 표시할 컬럼 (Entity 조인 적용됨)
|
||||
const [columnMeta, setColumnMeta] = useState<Record<string, { webType?: string; codeCategory?: string }>>({}); // 🎯 컬럼 메타정보 (웹타입, 코드카테고리)
|
||||
const [codeCache, setCodeCache] = useState<Record<string, Record<string, string>>>({}); // 🎯 코드명 캐시 (categoryCode: {codeValue: codeName})
|
||||
// 🎯 Entity 조인 최적화 훅 사용
|
||||
const { isOptimizing, metrics, optimizedConvertCode, getCacheStatus } = useEntityJoinOptimization(columnMeta, {
|
||||
enableBatchLoading: true,
|
||||
preloadCommonCodes: true,
|
||||
maxBatchSize: 5,
|
||||
});
|
||||
|
||||
// 높이 계산 함수
|
||||
const calculateOptimalHeight = () => {
|
||||
@@ -145,7 +150,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
try {
|
||||
const response = await tableTypeApi.getColumns(tableConfig.selectedTable);
|
||||
// API 응답 구조 확인 및 컬럼 배열 추출
|
||||
const columns = response.columns || response;
|
||||
const columns = Array.isArray(response) ? response : response.columns || [];
|
||||
const labels: Record<string, string> = {};
|
||||
const meta: Record<string, { webType?: string; codeCategory?: string }> = {};
|
||||
|
||||
@@ -167,45 +172,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 🎯 코드 캐시 로드 함수
|
||||
const loadCodeCache = async (categoryCode: string): Promise<void> => {
|
||||
if (codeCache[categoryCode]) {
|
||||
return; // 이미 캐시됨
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await commonCodeApi.options.getOptions(categoryCode);
|
||||
const codeMap: Record<string, string> = {};
|
||||
|
||||
if (response.success && response.data) {
|
||||
response.data.forEach((option: any) => {
|
||||
// 🎯 대소문자 구분 없이 저장 (모두 대문자로 키 저장)
|
||||
codeMap[option.value?.toUpperCase()] = option.label;
|
||||
});
|
||||
}
|
||||
|
||||
setCodeCache((prev) => ({
|
||||
...prev,
|
||||
[categoryCode]: codeMap,
|
||||
}));
|
||||
|
||||
console.log(`📋 코드 캐시 로드 완료 [${categoryCode}]:`, codeMap);
|
||||
} catch (error) {
|
||||
console.error(`❌ 코드 캐시 로드 실패 [${categoryCode}]:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// 🎯 코드값을 코드명으로 변환하는 함수 (대소문자 구분 없음)
|
||||
const convertCodeToName = (categoryCode: string, codeValue: string): string => {
|
||||
if (!categoryCode || !codeValue) return codeValue;
|
||||
|
||||
const codes = codeCache[categoryCode];
|
||||
if (!codes) return codeValue;
|
||||
|
||||
// 🎯 대소문자 구분 없이 검색
|
||||
const upperCodeValue = codeValue.toUpperCase();
|
||||
return codes[upperCodeValue] || codeValue;
|
||||
};
|
||||
// 🎯 전역 코드 캐시 사용으로 함수 제거 (codeCache.convertCodeToName 사용)
|
||||
|
||||
// 테이블 라벨명 가져오기
|
||||
const fetchTableLabel = async () => {
|
||||
@@ -313,7 +280,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
console.log("🔗 Entity 조인 없음");
|
||||
}
|
||||
|
||||
// 🎯 코드 컬럼들의 캐시 미리 로드
|
||||
// 🎯 코드 컬럼들의 캐시 미리 로드 (전역 캐시 사용)
|
||||
const codeColumns = Object.entries(columnMeta).filter(
|
||||
([_, meta]) => meta.webType === "code" && meta.codeCategory,
|
||||
);
|
||||
@@ -324,14 +291,12 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
codeColumns.map(([col, meta]) => `${col}(${meta.codeCategory})`),
|
||||
);
|
||||
|
||||
// 필요한 코드 캐시들을 병렬로 로드
|
||||
const loadPromises = codeColumns.map(([_, meta]) =>
|
||||
meta.codeCategory ? loadCodeCache(meta.codeCategory) : Promise.resolve(),
|
||||
);
|
||||
// 필요한 코드 카테고리들을 추출하여 배치 로드
|
||||
const categoryList = codeColumns.map(([, meta]) => meta.codeCategory).filter(Boolean) as string[];
|
||||
|
||||
try {
|
||||
await Promise.all(loadPromises);
|
||||
console.log("📋 모든 코드 캐시 로드 완료");
|
||||
await codeCache.preloadCodes(categoryList);
|
||||
console.log("📋 모든 코드 캐시 로드 완료 (전역 캐시)");
|
||||
} catch (error) {
|
||||
console.error("❌ 코드 캐시 로드 중 오류:", error);
|
||||
}
|
||||
@@ -475,35 +440,37 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return displayColumns.filter((col) => col.visible).sort((a, b) => a.order - b.order);
|
||||
}, [displayColumns, tableConfig.columns]);
|
||||
|
||||
// 🎯 값 포맷팅 (코드 변환 포함)
|
||||
const formatCellValue = (value: any, format?: string, columnName?: string) => {
|
||||
if (value === null || value === undefined) return "";
|
||||
// 🎯 값 포맷팅 (전역 코드 캐시 사용)
|
||||
const formatCellValue = useMemo(() => {
|
||||
return (value: any, format?: string, columnName?: string) => {
|
||||
if (value === null || value === undefined) return "";
|
||||
|
||||
// 🎯 코드 컬럼인 경우 코드명으로 변환
|
||||
if (columnName && columnMeta[columnName]?.webType === "code" && columnMeta[columnName]?.codeCategory) {
|
||||
const categoryCode = columnMeta[columnName].codeCategory!;
|
||||
const convertedValue = convertCodeToName(categoryCode, String(value));
|
||||
// 🎯 코드 컬럼인 경우 최적화된 코드명 변환 사용
|
||||
if (columnName && columnMeta[columnName]?.webType === "code" && columnMeta[columnName]?.codeCategory) {
|
||||
const categoryCode = columnMeta[columnName].codeCategory!;
|
||||
const convertedValue = optimizedConvertCode(categoryCode, String(value));
|
||||
|
||||
if (convertedValue !== String(value)) {
|
||||
console.log(`🔄 코드 변환: ${columnName}[${categoryCode}] ${value} → ${convertedValue}`);
|
||||
if (convertedValue !== String(value)) {
|
||||
console.log(`🔄 코드 변환: ${columnName}[${categoryCode}] ${value} → ${convertedValue}`);
|
||||
}
|
||||
|
||||
value = convertedValue;
|
||||
}
|
||||
|
||||
value = convertedValue;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "number":
|
||||
return typeof value === "number" ? value.toLocaleString() : value;
|
||||
case "currency":
|
||||
return typeof value === "number" ? `₩${value.toLocaleString()}` : value;
|
||||
case "date":
|
||||
return value instanceof Date ? value.toLocaleDateString() : value;
|
||||
case "boolean":
|
||||
return value ? "예" : "아니오";
|
||||
default:
|
||||
return String(value);
|
||||
}
|
||||
};
|
||||
switch (format) {
|
||||
case "number":
|
||||
return typeof value === "number" ? value.toLocaleString() : value;
|
||||
case "currency":
|
||||
return typeof value === "number" ? `₩${value.toLocaleString()}` : value;
|
||||
case "date":
|
||||
return value instanceof Date ? value.toLocaleDateString() : value;
|
||||
case "boolean":
|
||||
return value ? "예" : "아니오";
|
||||
default:
|
||||
return String(value);
|
||||
}
|
||||
};
|
||||
}, [columnMeta, optimizedConvertCode]); // 최적화된 변환 함수 의존성 추가
|
||||
|
||||
// 이벤트 핸들러
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
@@ -582,6 +549,39 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 성능 상태 표시 (개발 모드에서만) */}
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
<div className="flex items-center space-x-2 text-xs text-gray-500">
|
||||
{isOptimizing && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||
<span>최적화 중</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>캐시:</span>
|
||||
<span
|
||||
className={cn(
|
||||
"rounded px-1 py-0.5 font-mono text-xs",
|
||||
metrics.cacheHitRate > 0.8
|
||||
? "bg-green-100 text-green-700"
|
||||
: metrics.cacheHitRate > 0.5
|
||||
? "bg-yellow-100 text-yellow-700"
|
||||
: "bg-red-100 text-red-700",
|
||||
)}
|
||||
>
|
||||
{(metrics.cacheHitRate * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
{metrics.averageResponseTime > 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>응답:</span>
|
||||
<span className="font-mono text-xs">{metrics.averageResponseTime}ms</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 새로고침 */}
|
||||
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={loading}>
|
||||
<RefreshCw className={cn("h-4 w-4", loading && "animate-spin")} />
|
||||
|
||||
Reference in New Issue
Block a user