Add multi-columns rows sorting with customizable comparison function for each column.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
5ac1e4e7b8
commit
2513e711b1
3 changed files with 75 additions and 11 deletions
|
@ -1,6 +1,6 @@
|
|||
import React, {startTransition, useEffect, useRef, useState} from "react";
|
||||
import React, { useEffect, useRef, useState} from "react";
|
||||
import {ColumnKey} from "./Column";
|
||||
import {normalizeRowDefinition, RowCells, RowData, RowDefinition} from "./Row";
|
||||
import {normalizeRowDefinition, RowData, RowDefinition} from "./Row";
|
||||
import {CellDefinition} from "./Cell";
|
||||
import {SmartableData} from "./Smartable";
|
||||
import {Modify, Promisable} from "@kernelui/core";
|
||||
|
@ -84,7 +84,7 @@ class AsyncManager<CK extends ColumnKey>
|
|||
*/
|
||||
protected promisedRowsCells: Partial<Record<CK, Promised<CellDefinition>>>[] = [];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Rows data.
|
||||
* @protected
|
||||
|
|
|
@ -25,7 +25,7 @@ export function createColumns<K extends ColumnKey>(...columns: [K, Column][]): C
|
|||
/**
|
||||
* Smartable column definition.
|
||||
*/
|
||||
export interface Column
|
||||
export interface Column<T = any>
|
||||
{
|
||||
/**
|
||||
* Column title element.
|
||||
|
@ -36,6 +36,13 @@ export interface Column
|
|||
* Column cell default element.
|
||||
*/
|
||||
cellElement?: React.ReactElement;
|
||||
|
||||
/**
|
||||
* Cells data comparison in the column.
|
||||
* @param a First data to compare.
|
||||
* @param b Second data to compare.
|
||||
*/
|
||||
compare?: (a: T, b: T) => number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from "react";
|
||||
import {AutoColumnContextProvider, ColumnHeading, ColumnKey, Columns} from "./Column";
|
||||
import React, {useMemo} from "react";
|
||||
import {AutoColumnContextProvider, ColumnHeading, ColumnKey, Columns, SortState, SortType} from "./Column";
|
||||
import {SmartableProperties, useTable} from "./Smartable";
|
||||
import {RowInstance, RowLoader} from "./Row";
|
||||
import {useAsyncManager} from "./AsyncManager";
|
||||
import {CurrentRowData, useAsyncManager} from "./AsyncManager";
|
||||
|
||||
/**
|
||||
* Smartable instance component properties.
|
||||
|
@ -53,14 +53,19 @@ export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Colum
|
|||
export function TableBody<CK extends ColumnKey>()
|
||||
{
|
||||
// Get data from table.
|
||||
const {data} = useTable();
|
||||
const {data, columns, columnsSortState} = useTable<CK>();
|
||||
|
||||
// Get current data state from the async table value.
|
||||
const {currentDataState} = useAsyncManager(data);
|
||||
const {currentDataState} = useAsyncManager<CK>(data);
|
||||
|
||||
// Memorize sorted rows.
|
||||
const sortedRows = useMemo(() => (
|
||||
sortRows<CK>(currentDataState.rows, columns, columnsSortState)
|
||||
), [currentDataState.rows, columns, columnsSortState]);
|
||||
|
||||
return (
|
||||
Array.isArray(currentDataState?.rows) ? (
|
||||
currentDataState.rows.map((rowData, index) => (
|
||||
sortedRows ? (
|
||||
sortedRows.map((rowData, index) => (
|
||||
// Rendering each row from its definition.
|
||||
<RowInstance row={rowData} />
|
||||
))
|
||||
|
@ -69,3 +74,55 @@ export function TableBody<CK extends ColumnKey>()
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the given rows from the columns sort state.
|
||||
* @param rows Rows to sort.
|
||||
* @param columns Columns definition.
|
||||
* @param columnsSortState Columns current sort state.
|
||||
*/
|
||||
export function sortRows<CK extends ColumnKey>(rows: CurrentRowData<CK>[], columns: Columns<CK>, columnsSortState: Record<CK, SortState>): CurrentRowData<CK>[]
|
||||
{
|
||||
// Normalize value to undefined when rows are not loaded.
|
||||
if (!Array.isArray(rows)) return undefined;
|
||||
|
||||
// Get the sort column states following their priority order.
|
||||
const orderedSortColumns = (Object.entries(columnsSortState) as [CK, SortState][]).sort((a, b) => (
|
||||
a[1].order - b[1].order
|
||||
));
|
||||
|
||||
// Sort the given rows.
|
||||
return rows.toSorted((a, b) => {
|
||||
for (const [columnKey, columnSort] of orderedSortColumns)
|
||||
{
|
||||
// Get the current comparison result for the given cells' data.
|
||||
const comparison =
|
||||
(columnSort.type == SortType.DESC ? 1 : -1) *
|
||||
(columns[columnKey]?.compare ?? genericCompare)(a.cells[columnKey]?.data, b.cells[columnKey]?.data);
|
||||
|
||||
if (comparison != 0)
|
||||
// If an order can be determined from the current comparison, returning it.
|
||||
return comparison;
|
||||
}
|
||||
|
||||
// The comparisons always returned 0.
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic comparison function for table data.
|
||||
* @param a First data to compare.
|
||||
* @param b Second data to compare.
|
||||
*/
|
||||
export function genericCompare(a: any, b: any): number
|
||||
{
|
||||
if (typeof a == "number" && typeof a == typeof b)
|
||||
{ // Compare numbers.
|
||||
return b - a;
|
||||
}
|
||||
else
|
||||
{ // Compare data as strings.
|
||||
return String(b).localeCompare(String(a));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue