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

194 lines
5.4 KiB
TypeScript

import React from "react";
import { TableListComponent } from "./TableListComponent";
import codeCache from "@/lib/cache/codeCache";
interface TableListRendererProps {
config: {
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";
}>;
dataSource?: string;
pagination?: {
enabled: boolean;
pageSize: number;
};
sorting?: {
enabled: boolean;
defaultColumn?: string;
defaultDirection?: "asc" | "desc";
};
filtering?: {
enabled: boolean;
};
styling?: {
className?: string;
theme?: "default" | "compact" | "striped";
};
};
data?: any[];
onAction?: (action: string, payload: any) => void;
className?: string;
}
export const TableListRenderer: React.FC<TableListRendererProps> = ({
config,
data = [],
onAction,
className = "",
}) => {
const { columns = [], relations = [], pagination, sorting, filtering, styling } = config;
// 기본 컬럼 설정
const defaultColumns = React.useMemo(() => {
if (columns.length > 0) return columns;
// 데이터에서 자동으로 컬럼 추출
if (data.length > 0) {
const sampleRow = data[0];
return Object.keys(sampleRow).map((key) => ({
key,
label: key.charAt(0).toUpperCase() + key.slice(1),
type: inferColumnType(sampleRow[key]),
sortable: sorting?.enabled ?? true,
filterable: filtering?.enabled ?? true,
}));
}
return [];
}, [columns, data, sorting?.enabled, filtering?.enabled]);
// 페이지네이션 상태
const [currentPage, setCurrentPage] = React.useState(1);
const [pageSize, setPageSize] = React.useState(pagination?.pageSize || 10);
// 정렬 상태
const [sortColumn, setSortColumn] = React.useState(sorting?.defaultColumn || "");
const [sortDirection, setSortDirection] = React.useState<"asc" | "desc">(sorting?.defaultDirection || "asc");
// 필터 상태
const [filters, setFilters] = React.useState<Record<string, any>>({});
// 데이터 처리
const processedData = React.useMemo(() => {
let result = [...data];
// 필터링 적용
if (filtering?.enabled && Object.keys(filters).length > 0) {
result = result.filter((row) => {
return Object.entries(filters).every(([column, value]) => {
if (!value) return true;
const rowValue = String(row[column]).toLowerCase();
const filterValue = String(value).toLowerCase();
return rowValue.includes(filterValue);
});
});
}
// 정렬 적용
if (sorting?.enabled && sortColumn) {
result.sort((a, b) => {
const aVal = a[sortColumn];
const bVal = b[sortColumn];
let comparison = 0;
if (aVal < bVal) comparison = -1;
if (aVal > bVal) comparison = 1;
return sortDirection === "desc" ? -comparison : comparison;
});
}
return result;
}, [data, filters, sortColumn, sortDirection, filtering?.enabled, sorting?.enabled]);
// 페이지네이션 데이터
const paginatedData = React.useMemo(() => {
if (!pagination?.enabled) return processedData;
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return processedData.slice(startIndex, endIndex);
}, [processedData, currentPage, pageSize, pagination?.enabled]);
// 이벤트 핸들러
const handleRowClick = (row: any) => {
onAction?.("rowClick", { row });
};
const handleSort = (column: string, direction: "asc" | "desc") => {
setSortColumn(column);
setSortDirection(direction);
onAction?.("sort", { column, direction });
};
const handleFilter = (column: string, value: any) => {
setFilters((prev) => ({
...prev,
[column]: value,
}));
onAction?.("filter", { column, value });
};
const handlePageChange = (page: number, size: number) => {
setCurrentPage(page);
setPageSize(size);
onAction?.("pageChange", { page, size });
};
// 테마 클래스
const themeClass = React.useMemo(() => {
switch (styling?.theme) {
case "compact":
return "table-compact";
case "striped":
return "table-striped";
default:
return "";
}
}, [styling?.theme]);
return (
<TableListComponent
data={paginatedData}
columns={defaultColumns}
relations={relations}
onRowClick={handleRowClick}
onSort={handleSort}
onFilter={handleFilter}
pagination={
pagination?.enabled
? {
current: currentPage,
total: processedData.length,
pageSize,
onChange: handlePageChange,
}
: undefined
}
className={`${className} ${styling?.className || ""} ${themeClass}`}
/>
);
};
// 컬럼 타입 추론 유틸리티
function inferColumnType(value: any): "text" | "number" | "date" | "boolean" {
if (typeof value === "boolean") return "boolean";
if (typeof value === "number") return "number";
if (value instanceof Date || (typeof value === "string" && !isNaN(Date.parse(value)))) {
return "date";
}
return "text";
}
export default TableListRenderer;