ERP-node/frontend/lib/registry/components/table-list/TableListComponent.tsx

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;