194 lines
5.4 KiB
TypeScript
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;
|