feat(dashboard): tighten explainability detail views
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createDefaultDashboardLayout } from "@capakraken/shared/schemas";
|
||||
import { shouldHydrateDashboardFromDb } from "./useDashboardLayout.js";
|
||||
|
||||
describe("shouldHydrateDashboardFromDb", () => {
|
||||
it("hydrates from the database on first load when no local changes happened yet", () => {
|
||||
expect(shouldHydrateDashboardFromDb({
|
||||
remoteLayout: createDefaultDashboardLayout(),
|
||||
hasHydratedFromDb: false,
|
||||
hasLocalChangesBeforeHydration: false,
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
it("skips initial database hydration after a local first-load change", () => {
|
||||
expect(shouldHydrateDashboardFromDb({
|
||||
remoteLayout: createDefaultDashboardLayout(),
|
||||
hasHydratedFromDb: false,
|
||||
hasLocalChangesBeforeHydration: true,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it("does not re-hydrate once the initial database handoff already happened", () => {
|
||||
expect(shouldHydrateDashboardFromDb({
|
||||
remoteLayout: createDefaultDashboardLayout(),
|
||||
hasHydratedFromDb: true,
|
||||
hasLocalChangesBeforeHydration: false,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it("does not hydrate when the backend has no stored layout", () => {
|
||||
expect(shouldHydrateDashboardFromDb({
|
||||
remoteLayout: null,
|
||||
hasHydratedFromDb: false,
|
||||
hasLocalChangesBeforeHydration: false,
|
||||
})).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -37,9 +37,23 @@ function saveToStorage(config: DashboardLayoutConfig) {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function shouldHydrateDashboardFromDb(params: {
|
||||
remoteLayout: DashboardLayoutConfig | null | undefined;
|
||||
hasHydratedFromDb: boolean;
|
||||
hasLocalChangesBeforeHydration: boolean;
|
||||
}): boolean {
|
||||
const { remoteLayout, hasHydratedFromDb, hasLocalChangesBeforeHydration } = params;
|
||||
return remoteLayout !== null
|
||||
&& remoteLayout !== undefined
|
||||
&& !hasHydratedFromDb
|
||||
&& !hasLocalChangesBeforeHydration;
|
||||
}
|
||||
|
||||
export function useDashboardLayout() {
|
||||
const [config, setConfig] = useState<DashboardLayoutConfig>(() => loadFromStorage() ?? createDefaultDashboardLayout());
|
||||
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const hasHydratedFromDbRef = useRef(false);
|
||||
const hasLocalChangesBeforeHydrationRef = useRef(false);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { data: dbData } = trpc.user.getDashboardLayout.useQuery(undefined, {
|
||||
@@ -50,14 +64,37 @@ export function useDashboardLayout() {
|
||||
|
||||
// Sync from DB on load (DB wins if it has data)
|
||||
useEffect(() => {
|
||||
if (dbData?.layout) {
|
||||
const dbConfig = normalizeDashboardLayout(dbData.layout);
|
||||
setConfig(dbConfig);
|
||||
saveToStorage(dbConfig);
|
||||
const remoteLayout = dbData?.layout ?? null;
|
||||
if (remoteLayout === null || hasHydratedFromDbRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldHydrateDashboardFromDb({
|
||||
remoteLayout,
|
||||
hasHydratedFromDb: hasHydratedFromDbRef.current,
|
||||
hasLocalChangesBeforeHydration: hasLocalChangesBeforeHydrationRef.current,
|
||||
})) {
|
||||
hasHydratedFromDbRef.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const dbConfig = normalizeDashboardLayout(remoteLayout);
|
||||
hasHydratedFromDbRef.current = true;
|
||||
setConfig(dbConfig);
|
||||
saveToStorage(dbConfig);
|
||||
}, [dbData]);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current);
|
||||
saveTimeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const persist = useCallback((nextConfig: DashboardLayoutConfig) => {
|
||||
if (!hasHydratedFromDbRef.current) {
|
||||
hasLocalChangesBeforeHydrationRef.current = true;
|
||||
}
|
||||
const newConfig = normalizeDashboardLayout(nextConfig);
|
||||
saveToStorage(newConfig);
|
||||
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
|
||||
|
||||
Reference in New Issue
Block a user