173 lines
5.9 KiB
TypeScript
173 lines
5.9 KiB
TypeScript
|
|
import React, { useMemo } from "react";
|
||
|
|
import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization";
|
||
|
|
import codeCache from "@/lib/cache/codeCache";
|
||
|
|
|
||
|
|
interface TableListProps {
|
||
|
|
data: any[];
|
||
|
|
columns: Array<{
|
||
|
|
key: string;
|
||
|
|
label: string;
|
||
|
|
type?: "text" | "number" | "date" | "boolean";
|
||
|
|
sortable?: boolean;
|
||
|
|
filterable?: boolean;
|
||
|
|
}>;
|
||
|
|
relations?: Array<{
|
||
|
|
fromTable: string;
|
||
|
|
toTable: string;
|
||
|
|
joinType: "inner" | "left" | "right" | "full";
|
||
|
|
cardinality: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many";
|
||
|
|
}>;
|
||
|
|
onRowClick?: (row: any) => void;
|
||
|
|
onSort?: (column: string, direction: "asc" | "desc") => void;
|
||
|
|
onFilter?: (column: string, value: any) => void;
|
||
|
|
loading?: boolean;
|
||
|
|
pagination?: {
|
||
|
|
current: number;
|
||
|
|
total: number;
|
||
|
|
pageSize: number;
|
||
|
|
onChange: (page: number, size: number) => void;
|
||
|
|
};
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const TableListComponent: React.FC<TableListProps> = ({
|
||
|
|
data,
|
||
|
|
columns,
|
||
|
|
relations = [],
|
||
|
|
onRowClick,
|
||
|
|
onSort,
|
||
|
|
onFilter,
|
||
|
|
loading = false,
|
||
|
|
pagination,
|
||
|
|
className = "",
|
||
|
|
}) => {
|
||
|
|
// 조인 최적화 적용
|
||
|
|
const { optimization, getSortedRelations } = useEntityJoinOptimization(relations, {
|
||
|
|
expectedResultSize: data.length,
|
||
|
|
performanceProfile: "balanced",
|
||
|
|
});
|
||
|
|
|
||
|
|
// 최적화된 데이터 처리
|
||
|
|
const processedData = useMemo(() => {
|
||
|
|
if (!relations.length) return data;
|
||
|
|
|
||
|
|
const cacheKey = `table-list-processed:${JSON.stringify(data.slice(0, 5))}:${JSON.stringify(relations)}`;
|
||
|
|
const cached = codeCache.get(cacheKey);
|
||
|
|
if (cached) return cached;
|
||
|
|
|
||
|
|
// 관계 기반 데이터 처리 로직
|
||
|
|
const sortedRelations = getSortedRelations();
|
||
|
|
let processedResult = [...data];
|
||
|
|
|
||
|
|
// 여기에서 실제 조인 로직을 구현할 수 있습니다
|
||
|
|
// 현재는 기본 데이터를 반환
|
||
|
|
|
||
|
|
codeCache.set(cacheKey, processedResult, 30 * 1000); // 30초 캐시
|
||
|
|
return processedResult;
|
||
|
|
}, [data, relations, getSortedRelations]);
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div className={`animate-pulse ${className}`}>
|
||
|
|
<div className="mb-2 h-4 w-full rounded bg-gray-200"></div>
|
||
|
|
<div className="mb-2 h-4 w-3/4 rounded bg-gray-200"></div>
|
||
|
|
<div className="h-4 w-1/2 rounded bg-gray-200"></div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={`table-list-component ${className}`}>
|
||
|
|
<div className="overflow-x-auto">
|
||
|
|
<table className="min-w-full divide-y divide-gray-200">
|
||
|
|
<thead className="bg-gray-50">
|
||
|
|
<tr>
|
||
|
|
{columns.map((column) => (
|
||
|
|
<th
|
||
|
|
key={column.key}
|
||
|
|
className="cursor-pointer px-6 py-3 text-left text-xs font-medium tracking-wider text-gray-500 uppercase hover:bg-gray-100"
|
||
|
|
onClick={() => onSort?.(column.key, "asc")}
|
||
|
|
>
|
||
|
|
<div className="flex items-center space-x-1">
|
||
|
|
<span>{column.label}</span>
|
||
|
|
{column.sortable && (
|
||
|
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path
|
||
|
|
strokeLinecap="round"
|
||
|
|
strokeLinejoin="round"
|
||
|
|
strokeWidth={2}
|
||
|
|
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
|
||
|
|
/>
|
||
|
|
</svg>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</th>
|
||
|
|
))}
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody className="divide-y divide-gray-200 bg-white">
|
||
|
|
{processedData.map((row, index) => (
|
||
|
|
<tr key={index} className="cursor-pointer hover:bg-gray-50" onClick={() => onRowClick?.(row)}>
|
||
|
|
{columns.map((column) => (
|
||
|
|
<td key={column.key} className="px-6 py-4 text-sm whitespace-nowrap text-gray-900">
|
||
|
|
{formatCellValue(row[column.key], column.type)}
|
||
|
|
</td>
|
||
|
|
))}
|
||
|
|
</tr>
|
||
|
|
))}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{pagination && (
|
||
|
|
<div className="flex items-center justify-between border-t border-gray-200 bg-white px-6 py-3">
|
||
|
|
<div className="flex items-center">
|
||
|
|
<span className="text-sm text-gray-700">
|
||
|
|
총 {pagination.total}개 중{" "}
|
||
|
|
{Math.min((pagination.current - 1) * pagination.pageSize + 1, pagination.total)}-
|
||
|
|
{Math.min(pagination.current * pagination.pageSize, pagination.total)}개 표시
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center space-x-2">
|
||
|
|
<button
|
||
|
|
onClick={() => pagination.onChange(pagination.current - 1, pagination.pageSize)}
|
||
|
|
disabled={pagination.current <= 1}
|
||
|
|
className="rounded border px-3 py-1 text-sm disabled:cursor-not-allowed disabled:opacity-50"
|
||
|
|
>
|
||
|
|
이전
|
||
|
|
</button>
|
||
|
|
<span className="text-sm">
|
||
|
|
{pagination.current} / {Math.ceil(pagination.total / pagination.pageSize)}
|
||
|
|
</span>
|
||
|
|
<button
|
||
|
|
onClick={() => pagination.onChange(pagination.current + 1, pagination.pageSize)}
|
||
|
|
disabled={pagination.current >= Math.ceil(pagination.total / pagination.pageSize)}
|
||
|
|
className="rounded border px-3 py-1 text-sm disabled:cursor-not-allowed disabled:opacity-50"
|
||
|
|
>
|
||
|
|
다음
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
// 셀 값 포맷팅 유틸리티
|
||
|
|
function formatCellValue(value: any, type?: string): string {
|
||
|
|
if (value === null || value === undefined) return "-";
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case "date":
|
||
|
|
return new Date(value).toLocaleDateString();
|
||
|
|
case "number":
|
||
|
|
return typeof value === "number" ? value.toLocaleString() : value;
|
||
|
|
case "boolean":
|
||
|
|
return value ? "예" : "아니오";
|
||
|
|
default:
|
||
|
|
return String(value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default TableListComponent;
|