import "react-data-grid/lib/styles.css"; import DataGrid, { SortDirection } from "react-data-grid"; import { useEffect, useMemo, useState } from "react"; import { Icon } from "../../tremor/Icon"; import { Title } from "../../tremor/Text"; import { FunnelIcon as FunnelIconOutline } from "@heroicons/react/24/outline"; import { FunnelIcon as FunnelIconSolid } from "@heroicons/react/24/solid"; import { BarsArrowDownIcon, BarsArrowUpIcon } from "@heroicons/react/20/solid"; import React from "react"; import { MultiSelect } from "../../tremor/MultiSelect"; import { useTheme } from "../../layouts/Dashboard/useTheme"; import { useBackend } from "../../layouts/Wrapper"; function urlify(text: string) { const markdownImageRegex = /!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g; const markdownLinkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g; const urlRegex = /(https?:\/\/[^\s]+)/g; let text1 = text + ""; text1 = text1.replace(markdownImageRegex, (_, altText, url) => { let properUrl = url.startsWith("http") ? url : "https://" + url; return `${altText}`; }); text1 = text1.replace(markdownLinkRegex, (_, linkText, url) => { let properUrl = url.startsWith("http") ? url : "https://" + url; return `${linkText}`; }); if (text1 !== text) { return text1; } return text1.replace(urlRegex, (url) => { let properUrl = url.startsWith("http") ? url : "https://" + url; return `${url}`; }); } const cellRenderer = (obj: { row: any; column: any }) => { let content = urlify(obj.row[obj.column.key]); return ; }; export interface TableChartProps { data: any; pagination?: { currentPage: number; pageSize: number; sortColumn?: string; sortDirection?: "ASC" | "DESC"; }; stats?: { totalPages: number; }; setPagination?: React.Dispatch< React.SetStateAction<{ currentPage: number; pageSize: number; sortColumn?: string; sortDirection?: "ASC" | "DESC"; }> >; padding?: number; } export const TableChart: React.FC = ({ data, pagination, setPagination, padding, }) => { const [filterEnabled, setFilterEnabled] = useState(false); const [options, setOptions] = useState<{ [key: string]: string[] }>({}); const [filters, setFilters] = useState<{ [key: string]: string[] }>({}); const theme = useTheme(); const { containerRef } = useBackend(); let [allObjects, fields] = useMemo(() => { let newRows: any[] = []; let label = data.options?.scales?.x?.title?.text || "Label"; data.data.datasets[0].data.map((item: any, i: number) => { let row: any = {}; if (data.data.labels && data.data.labels[i]) { row = { [label]: data.data.labels[i], }; } data.data.datasets.map((a: any, j: number) => { row[a.label] = a.data[i]; }); newRows.push(row); }); if (data.data.labels) { let newFields: string[] = [ label, ...data.data.datasets.map((a: any) => a.label), ]; return [newRows, newFields]; } else { let newFields: string[] = data.data.datasets.map((a: any) => a.label); return [newRows, newFields]; } }, [data]); let objects = useMemo(() => { let newRows = [...allObjects]; // Filter newRows based on filters if (filters) { newRows = newRows.filter((row) => { let pass = true; Object.keys(filters).forEach((key) => { if (filters[key].length > 0 && !filters[key].includes(row[key])) { pass = false; } }); return pass; }); } // Sort newRows here based on pagination data if (pagination) { const { sortColumn, sortDirection } = pagination; if (sortColumn && sortDirection) { const parseNumeric = (value: any): number | null => { if (value === "" || value == null) return null; // Strip formatting characters (commas, currency symbols, spaces, %) const cleaned = String(value).replace(/[,\s$€£¥%]/g, ""); const num = Number(cleaned); return isNaN(num) ? null : num; }; newRows.sort((a, b) => { const valueA = a[sortColumn]; const valueB = b[sortColumn]; const numA = parseNumeric(valueA); const numB = parseNumeric(valueB); const isNumeric = numA !== null && numB !== null; const comparison = isNumeric ? numA - numB : valueA < valueB ? -1 : valueA > valueB ? 1 : 0; return sortDirection === "ASC" ? comparison : -comparison; }); } } return newRows; }, [allObjects, filters, pagination]); useEffect(() => { let newOptions: { [key: string]: string[] } = {}; fields.forEach((field) => { newOptions[field] = []; allObjects.forEach((row) => { if (!newOptions[field].includes(row[field])) { newOptions[field].push(row[field]); } }); newOptions[field].sort(); }); setOptions(newOptions); }, [allObjects, fields]); const handleSortChange = (newSorting: { columnKey: string; direction: SortDirection; }[]) => { if (!setPagination || !pagination) { return; } if (newSorting.length > 0) { const { columnKey, direction } = newSorting[0]; setPagination({ ...pagination, sortColumn: columnKey, sortDirection: direction, currentPage: 1, }); } else { setPagination({ ...pagination, sortColumn: undefined, sortDirection: undefined, }); } }; const columns = useMemo(() => { return Object.keys(options).map((a) => ({ key: a, name: a, renderCell: cellRenderer, renderHeaderCell: (p: any) => { const isMatchingFilterColumn = pagination?.sortColumn === p.column.name; const filterTypeClass = isMatchingFilterColumn ? "onvo-table-widget-filter-active" : "onvo-table-widget-filter-inactive"; return (
{p.column.name}
{isMatchingFilterColumn && ( )} { setFilterEnabled((a) => !a); e.preventDefault(); e.stopPropagation(); }} icon={filterEnabled ? FunnelIconSolid : FunnelIconOutline} size="sm" />
{filterEnabled && ( { setFilters({ ...filters, [p.column.key]: val, }); }} items={options[a].map((c) => ({ label: c, value: c, }))} /> )}
); }, })); }, [options, filterEnabled, pagination]); const sortColumns = useMemo(() => { if (pagination && pagination.sortColumn) { return [{ columnKey: pagination.sortColumn, direction: pagination.sortDirection || "ASC" }]; } return []; }, [pagination]); return (
{data.options.plugins.title.text}
); };