chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
"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>;
|
||||
Reference in New Issue
Block a user