80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
import {ReactNode, useState} from "react";
|
|
import './DataTable.css';
|
|
|
|
export type DataTableColumnDefinition<T> = {
|
|
field: string,
|
|
label?: string,
|
|
visualize?: (value: T) => ReactNode,
|
|
sorter?: (a: T, b: T) => number
|
|
};
|
|
|
|
|
|
function DataTableRow({rowData, columns}: {
|
|
rowData: any,
|
|
columns: DataTableColumnDefinition<any>[]
|
|
}) {
|
|
return <tr>
|
|
{
|
|
columns.map(column => {
|
|
return <td key={column.field}>
|
|
{
|
|
column.visualize
|
|
? column.visualize(rowData)
|
|
: rowData[column.field]
|
|
}
|
|
</td>;
|
|
})
|
|
}
|
|
</tr>;
|
|
}
|
|
|
|
export default function DataTable<T>({data, columns, className}: {
|
|
data: T[],
|
|
columns: DataTableColumnDefinition<any>[],
|
|
className?: string
|
|
}) {
|
|
const [sortBy, setSortBy] = useState<DataTableColumnDefinition<any> | null>(null);
|
|
const [sortReversed, setSortReversed] = useState(false);
|
|
|
|
const headerClick = (column: DataTableColumnDefinition<any>) => {
|
|
console.log('column clicked', column);
|
|
|
|
if (column.field === sortBy?.field)
|
|
setSortReversed(prevState => !prevState);
|
|
else if (column.sorter)
|
|
setSortBy(column);
|
|
else
|
|
console.log('cannot sort by', column)
|
|
};
|
|
|
|
const dataToDisplay = [...data];
|
|
const actualSorter = sortReversed && sortBy?.sorter
|
|
? (a: T, b: T) => -sortBy.sorter!(a, b)
|
|
: sortBy?.sorter;
|
|
|
|
dataToDisplay.sort(actualSorter)
|
|
|
|
console.log('sorted', {dataToDisplay});
|
|
|
|
return <div className={'DataTable ' + (className ?? '')}>
|
|
<table>
|
|
<thead className='DataTableHead'>
|
|
<tr>
|
|
{
|
|
columns.map(column =>
|
|
<th key={column.field} onClick={() => headerClick(column)}>
|
|
{column.label ?? column.field}
|
|
</th>)
|
|
}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{
|
|
dataToDisplay.map((element, index) =>
|
|
<DataTableRow key={`${sortBy?.field}-${index}`} rowData={element} columns={columns}/>)
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>;
|
|
}
|