66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useMemo, useCallback } from "react";
|
|
|
|
export type SortDirection = "asc" | "desc" | null;
|
|
|
|
export interface SortState {
|
|
column: string | null;
|
|
direction: SortDirection;
|
|
}
|
|
|
|
function getValue(obj: Record<string, unknown>, key: string): unknown {
|
|
return obj[key];
|
|
}
|
|
|
|
function compareValues(a: unknown, b: unknown, direction: "asc" | "desc"): number {
|
|
// Handle nulls/undefined — push them to the end always
|
|
if (a == null && b == null) return 0;
|
|
if (a == null) return 1;
|
|
if (b == null) return -1;
|
|
|
|
const aVal = a;
|
|
const bVal = b;
|
|
|
|
// Numeric comparison
|
|
const aNum = Number(aVal);
|
|
const bNum = Number(bVal);
|
|
if (!isNaN(aNum) && !isNaN(bNum) && String(aVal).trim() !== "" && String(bVal).trim() !== "") {
|
|
return direction === "asc" ? aNum - bNum : bNum - aNum;
|
|
}
|
|
|
|
// Date comparison
|
|
const aStr = String(aVal);
|
|
const bStr = String(bVal);
|
|
const aDate = Date.parse(aStr);
|
|
const bDate = Date.parse(bStr);
|
|
if (!isNaN(aDate) && !isNaN(bDate) && aStr.length > 4 && bStr.length > 4) {
|
|
return direction === "asc" ? aDate - bDate : bDate - aDate;
|
|
}
|
|
|
|
// String comparison
|
|
const cmp = aStr.localeCompare(bStr, undefined, { sensitivity: "base", numeric: true });
|
|
return direction === "asc" ? cmp : -cmp;
|
|
}
|
|
|
|
export function useTableSort<T extends Record<string, unknown>>(data: T[]) {
|
|
const [sort, setSort] = useState<SortState>({ column: null, direction: null });
|
|
|
|
const toggleSort = useCallback((column: string) => {
|
|
setSort((prev) => {
|
|
if (prev.column !== column) return { column, direction: "asc" };
|
|
if (prev.direction === "asc") return { column, direction: "desc" };
|
|
return { column: null, direction: null };
|
|
});
|
|
}, []);
|
|
|
|
const sorted = useMemo(() => {
|
|
if (!sort.column || !sort.direction) return data;
|
|
return [...data].sort((a, b) =>
|
|
compareValues(getValue(a, sort.column!), getValue(b, sort.column!), sort.direction!)
|
|
);
|
|
}, [data, sort]);
|
|
|
|
return { sorted, sort, toggleSort };
|
|
}
|