95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* useViewPrefs — per-view persistence of sort state and row order.
|
|
*
|
|
* Uses a separate localStorage key ("viewprefs_<view>") from useColumnConfig
|
|
* ("colvis_<view>") to avoid format conflicts. Server-side the data is merged
|
|
* into the same User.columnPreferences JSONB via trpc.user.setColumnPreferences.
|
|
*
|
|
* Each user's sort + row order is independent (stored on their own User record).
|
|
*/
|
|
|
|
import { useState, useCallback, useRef } from "react";
|
|
import type { ViewKey } from "@planarchy/shared";
|
|
import { trpc } from "~/lib/trpc/client.js";
|
|
|
|
interface SavedSort {
|
|
field: string;
|
|
dir: "asc" | "desc";
|
|
}
|
|
|
|
interface LocalViewPrefs {
|
|
sort?: SavedSort;
|
|
rowOrder?: string[];
|
|
}
|
|
|
|
const LS_KEY = (view: ViewKey) => `viewprefs_${view}`;
|
|
|
|
function readLocal(view: ViewKey): LocalViewPrefs {
|
|
if (typeof window === "undefined") return {};
|
|
try {
|
|
const raw = localStorage.getItem(LS_KEY(view));
|
|
return raw ? (JSON.parse(raw) as LocalViewPrefs) : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function writeLocal(view: ViewKey, patch: { sort?: SavedSort | null; rowOrder?: string[] | null }) {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
const current = readLocal(view);
|
|
const next = { ...current, ...patch };
|
|
// Remove keys that are explicitly set to null/undefined
|
|
if (next.sort == null) delete next.sort;
|
|
if (!next.rowOrder?.length) delete next.rowOrder;
|
|
localStorage.setItem(LS_KEY(view), JSON.stringify(next));
|
|
} catch {
|
|
// ignore write errors (private browsing, quota, etc.)
|
|
}
|
|
}
|
|
|
|
export function useViewPrefs(view: ViewKey) {
|
|
// Initialise synchronously from localStorage so there's no flash
|
|
const init = readLocal(view);
|
|
|
|
const [savedSort, setSavedSortState] = useState<SavedSort | null>(init.sort ?? null);
|
|
const [rowOrder, setRowOrderState] = useState<string[]>(init.rowOrder ?? []);
|
|
|
|
const setMutation = trpc.user.setColumnPreferences.useMutation();
|
|
|
|
// Debounce server sync so rapid drags don't spam mutations
|
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
function scheduleSave(patch: Parameters<typeof setMutation.mutate>[0]) {
|
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
debounceRef.current = setTimeout(() => {
|
|
setMutation.mutate(patch);
|
|
}, 600);
|
|
}
|
|
|
|
const setSavedSort = useCallback(
|
|
(sort: SavedSort | null) => {
|
|
setSavedSortState(sort);
|
|
writeLocal(view, { sort: sort ?? null });
|
|
scheduleSave({ view, sort: sort ?? null });
|
|
},
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[view],
|
|
);
|
|
|
|
const setRowOrder = useCallback(
|
|
(ids: string[]) => {
|
|
setRowOrderState(ids);
|
|
writeLocal(view, { rowOrder: ids });
|
|
scheduleSave({ view, rowOrder: ids.length > 0 ? ids : null });
|
|
},
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[view],
|
|
);
|
|
|
|
return { savedSort, setSavedSort, rowOrder, setRowOrder };
|
|
}
|
|
|
|
export type ViewPrefsHandle = ReturnType<typeof useViewPrefs>;
|