import * as DialogPrimitive from "@radix-ui/react-dialog"; import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, SortingState, useReactTable, Column, Row, } from "@tanstack/react-table"; import clsx from "clsx"; import { useEffect, useMemo, useRef, useState } from "react"; import xss from "xss"; import useDarkMode from "../../utils/useDarkMode"; import useLocalStorage from "../../utils/useLocalStorage"; import { formatNumber, formatNumberMagnitude, formatNumberNoMagnitude, fuzzyFilter, includesDateNames, includesPriceNames, isEqual, } from "../../utils/utils"; import CloseIcon from "../Icons/Close"; import Select from "../Select"; import Toast from "../Toast"; import DraggableColumnHeader, { isoYearRegex, magnitudeRegex, } from "./ColumnHeader"; import DownloadFinishedDialog from "./DownloadFinishedDialog"; import Export from "./Export"; import FilterColumns from "./FilterColumns"; import Pagination, { validatePageSize } from "./Pagination"; const date = new Date(); const MAX_COLUMNS = 50; export const DEFAULT_ROWS_PER_PAGE = 30; //@ts-ignore function getCellWidth(row, column) { try { const indexLabel = row.hasOwnProperty("index") ? "index" : row.hasOwnProperty("Index") ? "Index" : null; const indexValue = indexLabel ? row[indexLabel] : null; const value = row[column]; const valueType = typeof value; const only_numbers = value?.toString().replace(/[^0-9]/g, ""); const probablyDate = only_numbers?.length >= 4 && (includesDateNames(column) || column.toLowerCase() === "index" || (indexValue && typeof indexValue === "string" && (indexValue.toLowerCase().includes("date") || indexValue.toLowerCase().includes("day") || indexValue.toLowerCase().includes("time") || indexValue.toLowerCase().includes("timestamp") || indexValue.toLowerCase().includes("year") || indexValue.toLowerCase().includes("month") || indexValue.toLowerCase().includes("week") || indexValue.toLowerCase().includes("hour") || indexValue.toLowerCase().includes("minute")))); const probablyLink = valueType === "string" && value.startsWith("http"); if (probablyLink || !probablyDate) { return value?.toString().length ?? 0; } if ( probablyDate && !isNaN(new Date(value).getTime()) && !isoYearRegex.test(value?.toString()) ) { if (typeof value === "string") { return value?.toString().length ?? 0; } try { const date = new Date(value); let dateFormatted = ""; if ( date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0 && date.getMilliseconds() === 0 ) { dateFormatted = date.toISOString().split("T")[0]; } else { dateFormatted = date.toISOString(); dateFormatted = `${dateFormatted.split("T")[0]} ${ dateFormatted.split("T")[1].split(".")[0] }`; } return dateFormatted?.toString().length ?? 0; } catch (e) { return value?.toString().length ?? 0; } } return value?.toString().length ?? 0; } catch (e) { return 0; } } export const EXPORT_TYPES = ["csv", "png"]; export default function Table({ data, columns, title, initialTheme, cmd = "", }: { data: any[]; columns: any[]; title: string; initialTheme: "light" | "dark"; cmd?: string; }) { const [type, setType] = useLocalStorage("exportType", EXPORT_TYPES[0]); const [downloadFinished, setDownloadFinished] = useState(false); const [colorTheme, setTheme] = useDarkMode(initialTheme); const [darkMode, setDarkMode] = useState( colorTheme === "dark" ? true : false, ); const toggleDarkMode = (checked: boolean) => { //@ts-ignore setTheme(colorTheme); setDarkMode(checked); }; const [currentPage, setCurrentPage] = useLocalStorage( "rowsPerPage", DEFAULT_ROWS_PER_PAGE, validatePageSize, ); const [advanced, setAdvanced] = useLocalStorage("advanced", false); const [colors, setColors] = useLocalStorage("colors", false); const [sorting, setSorting] = useState([]); const [globalFilter, setGlobalFilter] = useState(""); const [fontSize, setFontSize] = useLocalStorage("fontSize", "1"); const [open, setOpen] = useState(false); const defaultVisibleColumns = columns.reduce((acc, cur, idx) => { acc[cur] = idx < MAX_COLUMNS ? true : false; return acc; }, {}); const [columnVisibility, setColumnVisibility] = useState( defaultVisibleColumns, ); //@ts-ignore const getColumnWidth = (rows, accessor, headerText) => { const maxWidth = 200; const magicSpacing = 12; const cellLength = Math.max( //@ts-ignore ...rows.map((row) => getCellWidth(row, accessor)), headerText?.length ? headerText?.length + 8 : 0, ); return Math.min(maxWidth, cellLength * magicSpacing); }; const rtColumns = useMemo( () => [ ...columns.map((column: any, index: number) => ({ accessorKey: column, accessorFn: (row: any) => { const indexLabel = row.hasOwnProperty("index") ? "index" : row.hasOwnProperty("Index") ? "Index" : columns[0]; const indexValue = indexLabel ? row[indexLabel] : null; const value = row[column]; const only_numbers = value?.toString()?.split(".")?.[0]?.replace(/[^0-9]/g, "") ?? ""; const probablyDate = only_numbers?.length >= 4 && (includesDateNames(column) || column.toLowerCase() === "index" || (indexValue && typeof indexValue === "string" && (indexValue.toLowerCase().includes("date") || indexValue.toLowerCase().includes("time") || indexValue.toLowerCase().includes("timestamp") || indexValue.toLowerCase().includes("year") || indexValue.toLowerCase().includes("month") || indexValue.toLowerCase().includes("week") || indexValue.toLowerCase().includes("hour") || indexValue.toLowerCase().includes("minute")))); if ( probablyDate && value?.length === 4 && isoYearRegex.test(value?.toString()) ) return value; if (probablyDate) { if (typeof value === "number") return value; return new Date(value).getTime(); } return value; }, id: column, header: column, size: getColumnWidth(data, column, column), footer: column, cell: ({ row }: any) => { const indexLabel = row.original.hasOwnProperty("index") ? "index" : row.original.hasOwnProperty("Index") ? "Index" : columns[0]; const indexValue = indexLabel ? row.original[indexLabel] : null; const value = row.original[column]; const valueType = typeof value; const only_numbers = value?.toString()?.split(".")?.[0]?.replace(/[^0-9]/g, "") ?? ""; const probablyDate = only_numbers?.length >= 4 && (includesDateNames(column) || column.toLowerCase() === "index" || (indexValue && typeof indexValue === "string" && (indexValue.toLowerCase().includes("date") || indexValue.toLowerCase().includes("time") || indexValue.toLowerCase().includes("timestamp") || indexValue.toLowerCase().includes("year")))); const probablyLink = valueType === "string" && value.startsWith("http"); if (probablyLink) { return ( {value?.length > 25 ? `${value.substring(0, 25)}...` : value} ); } if ( probablyDate && value?.length === 4 && isoYearRegex.test(value?.toString()) ) { return

{value}

; } if (probablyDate && !isNaN(new Date(value).getTime())) { if (typeof value === "string") { const date = value.split("T")[0]; const time = value.split("T")[1]?.split(".")[0]; if (time === "00:00:00") { return

{date}

; } return (

{date} {time}

); } try { const date = new Date(value); let dateFormatted = ""; if ( date.getUTCHours() === 0 && date.getUTCMinutes() === 0 && date.getUTCSeconds() === 0 && date.getMilliseconds() === 0 ) { dateFormatted = date.toISOString().split("T")[0]; } else { dateFormatted = date.toISOString(); dateFormatted = `${dateFormatted.split("T")[0]} ${ dateFormatted.split("T")[1].split(".")[0] }`; } return

{dateFormatted}

; } catch (e) { return

{value}

; } } if ( valueType === "number" || magnitudeRegex.test(value?.toString()) ) { let valueFormatted = formatNumberMagnitude(value, column); const valueFormattedNoMagnitude = Number( formatNumberNoMagnitude(value), ); if ( typeof indexValue === "string" && includesPriceNames(indexValue) ) { valueFormatted = Number(formatNumberNoMagnitude(value)); const maxFixed = valueFormatted < 2 ? 4 : 2; valueFormatted = valueFormatted.toLocaleString("en-US", { maximumFractionDigits: maxFixed, minimumFractionDigits: 2, }); } return (

0 && colors, "text-[#F87171]": valueFormattedNoMagnitude < 0 && colors, "text-[#404040]": valueFormattedNoMagnitude === 0 && colors, })} title={formatNumber(value).toString() ?? ""} > {valueFormattedNoMagnitude !== 0 ? valueFormattedNoMagnitude > 0 ? `${valueFormatted}` : `${valueFormatted}` : valueFormatted}

); } else if (valueType === "string") { return
; } return

{value}

; }, })), ], [advanced, colors], ); const [lockFirstColumn, setLockFirstColumn] = useState(false); const [columnOrder, setColumnOrder] = useState( rtColumns.map((column) => column.id as string), ); const resetOrder = () => setColumnOrder(columns.map((column) => column.id as string)); const needsReorder = useMemo(() => { const currentOrder = columnOrder.map((columnId) => columnId); const defaultOrder = rtColumns.map((column) => column.id as string); return !isEqual(currentOrder, defaultOrder); }, [columnOrder, rtColumns]); const table = useReactTable({ data, columns: rtColumns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), columnResizeMode: "onChange", onColumnVisibilityChange: setColumnVisibility, onColumnOrderChange: setColumnOrder, onSortingChange: setSorting, onGlobalFilterChange: setGlobalFilter, globalFilterFn: fuzzyFilter, state: { sorting, globalFilter, columnOrder, columnVisibility, }, initialState: { pagination: { pageIndex: 0, pageSize: typeof currentPage === "string" ? currentPage.includes("All") ? data?.length : parseInt(currentPage) : currentPage, }, }, }); const tableContainerRef = useRef(null); const { rows } = table.getRowModel(); const visibleColumns = table.getVisibleFlatColumns(); const [downloadFinishedDialogOpen, setDownloadFinishedDialogOpen] = useState(false); useEffect(() => { if (downloadFinished) { setDownloadFinished(false); setDownloadFinishedDialogOpen(true); } }, [downloadFinished]); return ( <> setDownloadFinishedDialogOpen(false)} />

{title} {/* {source && ( {`[${source}]`} )} */}

{new Intl.DateTimeFormat("en-GB", { dateStyle: "full", timeStyle: "long", }) .format(date) .replace(/:\d\d /, " ")}
{cmd}

{/* {source && typeof source === "string" && source.includes("*") && (

*not affiliated

)} */}
{table.getHeaderGroups().map((headerGroup, idx) => ( {headerGroup.headers.map((header, idx2) => { return ( ); })} ))} {table.getRowModel().rows.map((row, idx) => { return ( {row.getVisibleCells().map((cell, idx2) => { return ( ); })} ); })} {rows?.length > 30 && visibleColumns?.length > 4 && ( {table.getFooterGroups().map((footerGroup) => ( {footerGroup.headers.map((header) => ( ))} ))} )}
{flexRender( cell.column.columnDef.cell, cell.getContext(), )}
{header.isPlaceholder ? null : flexRender( header.column.columnDef.footer, header.getContext(), )}
Settings Settings
{needsReorder && ( )} { setType(value); }} label="Export type" placeholder="Select export type" groups={[ { label: "Export type", items: EXPORT_TYPES.map((type) => ({ label: type, value: type, })), }, ]} /> { setAdvanced(value === "advanced"); }} label="Type" placeholder="Select type" groups={[ { label: "Type", items: [ { label: "Simple", value: "simple", }, { label: "Advanced", value: "advanced", }, ], }, ]} />
setColors(!colors)} />
); }