66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import { useMemo, useCallback } from "react";
|
|
import type { ViewPrefsHandle } from "./useViewPrefs.js";
|
|
|
|
/**
|
|
* Manages manual drag-to-reorder row ordering for a table.
|
|
*
|
|
* - When a column sort is active (sortField !== null), row order is suppressed —
|
|
* the sorted rows are returned unchanged and dragging is disabled.
|
|
* - When no column sort is active and a saved rowOrder exists, rows are sorted
|
|
* by their position in rowOrder; rows without a saved position go to the end.
|
|
* - Calling reorder() saves the new order and clears any active column sort.
|
|
*
|
|
* Each user's rowOrder is persisted independently via useViewPrefs.
|
|
*/
|
|
export function useRowOrder<T extends { id: string }>(
|
|
rows: T[],
|
|
prefs: Pick<ViewPrefsHandle, "rowOrder" | "setRowOrder">,
|
|
activeSortField: string | null,
|
|
resetSort: () => void,
|
|
) {
|
|
const { rowOrder, setRowOrder } = prefs;
|
|
|
|
const orderedRows = useMemo((): T[] => {
|
|
// Column sort takes precedence — ignore manual order while sorting
|
|
if (activeSortField !== null) return rows;
|
|
if (rowOrder.length === 0) return rows;
|
|
|
|
const indexMap = new Map(rowOrder.map((id, i) => [id, i]));
|
|
return [...rows].sort((a, b) => {
|
|
const ai = indexMap.get(a.id) ?? Infinity;
|
|
const bi = indexMap.get(b.id) ?? Infinity;
|
|
return ai - bi;
|
|
});
|
|
}, [rows, rowOrder, activeSortField]);
|
|
|
|
const reorder = useCallback(
|
|
(draggedId: string, targetId: string) => {
|
|
if (draggedId === targetId) return;
|
|
|
|
// Build current ordered ID list from the current orderedRows snapshot
|
|
// (works correctly even when rowOrder is empty / partial)
|
|
const currentIds = orderedRows.map((r) => r.id);
|
|
const fromIdx = currentIds.indexOf(draggedId);
|
|
const toIdx = currentIds.indexOf(targetId);
|
|
if (fromIdx === -1 || toIdx === -1) return;
|
|
|
|
const next = [...currentIds];
|
|
next.splice(fromIdx, 1);
|
|
next.splice(toIdx, 0, draggedId);
|
|
|
|
// Clear any active column sort (mutual exclusion)
|
|
resetSort();
|
|
setRowOrder(next);
|
|
},
|
|
[orderedRows, resetSort, setRowOrder],
|
|
);
|
|
|
|
const resetOrder = useCallback(() => {
|
|
setRowOrder([]);
|
|
}, [setRowOrder]);
|
|
|
|
const isCustomOrder = rowOrder.length > 0 && activeSortField === null;
|
|
|
|
return { orderedRows, reorder, isCustomOrder, resetOrder };
|
|
}
|