chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
+94
View File
@@ -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>;