import { useState, useMemo } from "react"; export type SortDirection = "asc" | "desc"; export interface SortConfig { key: keyof T | null; direction: SortDirection; } type Comparators = Partial number>>; /** * Generic hook for client-side sortable table data. * * @param data - The array of items to sort * @param defaultKey - The column key to sort by initially (null = no sort) * @param defaultDirection - Initial sort direction * @param comparators - Optional per-key custom comparators. Receives both items and the * current direction so the comparator can handle ascending/descending internally * (return positive to sort a after b). */ export function useSortableData( data: T[], defaultKey: keyof T | null = null, defaultDirection: SortDirection = "asc", comparators?: Comparators ): { sortedData: T[]; sortConfig: SortConfig; requestSort: (key: keyof T) => void; } { const [sortConfig, setSortConfig] = useState>({ key: defaultKey, direction: defaultDirection, }); const sortedData = useMemo(() => { if (!sortConfig.key) return data; const key = sortConfig.key; const dir = sortConfig.direction; return [...data].sort((a, b) => { // Use custom comparator if provided if (comparators?.[key]) { return comparators[key]!(a, b, dir); } const aVal = a[key]; const bVal = b[key]; if (aVal === null || aVal === undefined) return 1; if (bVal === null || bVal === undefined) return -1; let cmp = 0; if (typeof aVal === "string" && typeof bVal === "string") { cmp = aVal.localeCompare(bVal); } else { cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0; } return dir === "asc" ? cmp : -cmp; }); }, [data, sortConfig, comparators]); const requestSort = (key: keyof T) => { setSortConfig((prev) => ({ key, direction: prev.key === key && prev.direction === "asc" ? "desc" : "asc", })); }; return { sortedData, sortConfig, requestSort }; }