import React, { useState } from "react"; import { EntryTemplate } from "../../types"; /** * Table field configuration object. Each field maps to one column of the table. * * * label - used as column label if no icon is specfied * * icon - (Optional) used as column label and cell value for boolean values * * valueKey - Accessor key to the field on an data entry object that maps to the * field described by this config object. This value will be used for display * inside the table cell. * For nested access, use a `.` to define nested routes (see function `getValByKey` * for more information). * * sortKey - Accessor key to the field on an data entry object that maps to the * field described by this config object. This value will be used for sorting * entries by the attrbiute that this field represents. * For nested access, use a `.` to define nested routes (see function `getValByKey` * for more information). */ type TableField = { label: string; icon?: React.FC | JSX.Element; valueKey: string; sortKey: string; }; /** * Helper function to support access to fields of nested objects * with dynamic key routes as strings. * * e.g * obj: {a: "hello"} keyRoute: "a" => "hello" * obj: {a: "hello", b: {c: 42}} keyRoute: "b" => {c: 42} * obj: {a: "hello", b: {c: 42}} keyRoute: "b.c" => 42 */ const getValByKey = (obj: any, keyRoute: string): any => { const keys = keyRoute.split("."); if (keys.length == 1) { return obj[keyRoute]; } else { return getValByKey(obj[keys[0]], keyRoute.replace(`${keys[0]}.`, "")); } }; /** * General template to display an array of arbitrary objects as a table that * is filterable and sortable. Optionally, the table can also be configured * to support to select an entry from it (props: selectedEntry and setSelectedEntry) * * The main table configuration is done via the array of `TableField` objects, * that are specified via the property `tableFields`. Each element of `tableFields` * is mapped to one column of the table. * * # Props: * * tableFields - List of `TableField` objects to configure the columns of the table. * * collection - List of objects that should be displayed via the table. * * selectedEntry - (Optional) reference to the `Entry` object that is currently selected by the user. * * setCollection - Function to set/update the collection list that should be displayed. * * setSelectedEntry - (Optional) function to specifiy, which entry from the table the user has currenty selected. */ const TableTemplate: React.FC<{ tableFields: TableField[]; collection: EntryTemplate[]; selectedEntry?: EntryTemplate; setCollection: Function; setSelectedEntry?: Function; }> = (props) => { // string that is applied to each entry to filter the entries of the displayed collection const [filter, setFilter] = useState(""); // object that will receive fields overtime to indicate the sort order of columns so that // the component knows to sort in the opposite direction every other sort request of a column const [sortOrderByField, setSortOrderByField] = useState({}); // Callback function to sort a set of `Entry` objects by the specified field of the // objects. const byField = (field: string, asc: boolean) => { return (a: EntryTemplate, b: EntryTemplate) => { // number, bool let x = getValByKey(a, field); let y = getValByKey(b, field); // string if (typeof x === "string") { x = x.toLowerCase(); y = y.toLowerCase(); } // ascending if (asc) { if (x < y) { return -1; } if (x > y) { return 1; } } else { if (x < y) { return 1; } if (x > y) { return -1; } } // descending return 0; }; }; const sortByField = (field: string) => { let order = Object.hasOwn(sortOrderByField, field) ? sortOrderByField[field] : true; props.setCollection([...props.collection].sort(byField(field, order))); sortOrderByField[field] = !order; setSortOrderByField(sortOrderByField); }; const applyFilter = (entry: EntryTemplate) => { let result = false; for (let i = 0; i < props.tableFields.length; i++) { const val = getValByKey(entry, props.tableFields[i].valueKey); if (typeof val === "number" || typeof val === "string") { if (val.toString().toLowerCase().includes(filter)) { result = true; break; } } } return result; }; return (
setFilter(e.currentTarget.value)} placeholder="Filter" />
{props.tableFields.map((field) => ( ))} {props.collection .filter((entry) => applyFilter(entry)) .map((entry) => ( props.setSelectedEntry ? props.setSelectedEntry(entry) : ""} > {props.tableFields.map((field) => ( ))} ))}
{field.icon ? ( getValByKey(entry, field.valueKey) ? ( <>{field.icon} ) : ( "" ) ) : ( getValByKey(entry, field.valueKey) )}
); }; export default TableTemplate;