chore(repo): checkpoint current capakraken implementation state
This commit is contained in:
@@ -108,8 +108,10 @@ export function useDashboardLayout() {
|
||||
const onLayoutChange = useCallback(
|
||||
(layout: { i: string; x: number; y: number; w: number; h: number }[]) => {
|
||||
setConfig((prev) => {
|
||||
const layoutMap = new Map(layout.map((item) => [item.i, item]));
|
||||
const previousWidgetMap = new Map(prev.widgets.map((widget) => [widget.id, widget]));
|
||||
const updatedWidgets = prev.widgets.map((w) => {
|
||||
const item = layout.find((l) => l.i === w.id);
|
||||
const item = layoutMap.get(w.id);
|
||||
if (!item) return w;
|
||||
return { ...w, x: item.x, y: item.y, w: item.w, h: item.h };
|
||||
});
|
||||
@@ -118,7 +120,7 @@ export function useDashboardLayout() {
|
||||
// react-grid-layout fires onLayoutChange on mount too — we skip that
|
||||
// to avoid overwriting saved positions with compacted coordinates.
|
||||
const changed = updatedWidgets.some((w) => {
|
||||
const orig = prev.widgets.find((o) => o.id === w.id);
|
||||
const orig = previousWidgetMap.get(w.id);
|
||||
return orig && (w.x !== orig.x || w.y !== orig.y || w.w !== orig.w || w.h !== orig.h);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
export interface ClientReference {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string | null;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface CountryReference {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface RoleReference {
|
||||
id: string;
|
||||
name: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface ReferenceDataSelection {
|
||||
clients?: boolean;
|
||||
countries?: boolean;
|
||||
roles?: boolean;
|
||||
chapters?: boolean;
|
||||
}
|
||||
|
||||
const LOOKUP_STALE_TIME_MS = 300_000;
|
||||
|
||||
export function useReferenceData(selection: ReferenceDataSelection = {}) {
|
||||
const shouldLoadClients = selection.clients === true;
|
||||
const shouldLoadCountries = selection.countries === true;
|
||||
const shouldLoadRoles = selection.roles === true;
|
||||
const shouldLoadChapters = selection.chapters === true;
|
||||
|
||||
const { data: clientsRaw } = trpc.clientEntity.list.useQuery(
|
||||
{ isActive: true },
|
||||
{ staleTime: LOOKUP_STALE_TIME_MS, enabled: shouldLoadClients },
|
||||
);
|
||||
|
||||
const { data: countriesRaw } = trpc.country.list.useQuery(
|
||||
{ isActive: true },
|
||||
{ staleTime: LOOKUP_STALE_TIME_MS, enabled: shouldLoadCountries },
|
||||
);
|
||||
|
||||
const { data: rolesRaw } = trpc.role.list.useQuery(
|
||||
{ isActive: true },
|
||||
{ staleTime: LOOKUP_STALE_TIME_MS, enabled: shouldLoadRoles },
|
||||
);
|
||||
|
||||
const { data: chaptersRaw } = trpc.resource.chapters.useQuery(undefined, {
|
||||
staleTime: LOOKUP_STALE_TIME_MS,
|
||||
enabled: shouldLoadChapters,
|
||||
});
|
||||
|
||||
const clients = useMemo<ClientReference[]>(() => {
|
||||
if (!shouldLoadClients) return [];
|
||||
const list = (
|
||||
Array.isArray(clientsRaw) ? clientsRaw : ((clientsRaw as { clients?: ClientReference[] } | undefined)?.clients ?? [])
|
||||
) as ClientReference[];
|
||||
return [...list]
|
||||
.filter((client) => client.isActive !== false)
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
}, [clientsRaw, shouldLoadClients]);
|
||||
|
||||
const countries = useMemo<CountryReference[]>(() => {
|
||||
if (!shouldLoadCountries) return [];
|
||||
const list = (Array.isArray(countriesRaw) ? countriesRaw : []) as CountryReference[];
|
||||
return [...list]
|
||||
.filter((country) => country.isActive !== false)
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
}, [countriesRaw, shouldLoadCountries]);
|
||||
|
||||
const roles = useMemo<RoleReference[]>(() => {
|
||||
if (!shouldLoadRoles) return [];
|
||||
const list = (Array.isArray(rolesRaw) ? rolesRaw : []) as RoleReference[];
|
||||
return [...list]
|
||||
.filter((role) => role.isActive !== false)
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
}, [rolesRaw, shouldLoadRoles]);
|
||||
|
||||
const chapters = useMemo<string[]>(() => {
|
||||
if (!shouldLoadChapters) return [];
|
||||
const list = (Array.isArray(chaptersRaw) ? chaptersRaw : []) as string[];
|
||||
return [...list].sort((left, right) => left.localeCompare(right));
|
||||
}, [chaptersRaw, shouldLoadChapters]);
|
||||
|
||||
return {
|
||||
clients,
|
||||
countries,
|
||||
roles,
|
||||
chapters,
|
||||
};
|
||||
}
|
||||
@@ -1,59 +1,36 @@
|
||||
/**
|
||||
* Shared hook for loading filter options used across dashboard widgets.
|
||||
* Loads clients, countries, roles, and chapters once with long cache TTL.
|
||||
* Loads only the requested lookup sets and exposes them as filter options.
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
import { useReferenceData, type ReferenceDataSelection } from "~/hooks/useReferenceData.js";
|
||||
|
||||
export interface FilterOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function useWidgetFilterOptions() {
|
||||
const { data: clientsRaw } = trpc.clientEntity.list.useQuery(
|
||||
{ isActive: true },
|
||||
{ staleTime: 300_000 },
|
||||
);
|
||||
|
||||
const { data: countriesRaw } = trpc.country.list.useQuery(
|
||||
{ isActive: true },
|
||||
{ staleTime: 300_000 },
|
||||
);
|
||||
|
||||
const { data: rolesRaw } = trpc.role.list.useQuery(
|
||||
{ isActive: true },
|
||||
{ staleTime: 300_000 },
|
||||
);
|
||||
export function useWidgetFilterOptions(selection: ReferenceDataSelection = {}) {
|
||||
const { clients: clientRows, countries: countryRows, roles: roleRows, chapters: chapterRows } =
|
||||
useReferenceData(selection);
|
||||
|
||||
const clients = useMemo<FilterOption[]>(() => {
|
||||
const list = (Array.isArray(clientsRaw) ? clientsRaw : (clientsRaw as any)?.clients ?? []) as Array<{ id: string; name: string }>;
|
||||
return list.map((c) => ({ value: c.id, label: c.name }));
|
||||
}, [clientsRaw]);
|
||||
return clientRows.map((client) => ({ value: client.id, label: client.name }));
|
||||
}, [clientRows]);
|
||||
|
||||
const countries = useMemo<FilterOption[]>(() => {
|
||||
const list = (Array.isArray(countriesRaw) ? countriesRaw : []) as Array<{ id: string; name: string }>;
|
||||
return list.map((c) => ({ value: c.id, label: c.name }));
|
||||
}, [countriesRaw]);
|
||||
return countryRows.map((country) => ({ value: country.id, label: country.name }));
|
||||
}, [countryRows]);
|
||||
|
||||
const roles = useMemo<FilterOption[]>(() => {
|
||||
const list = (Array.isArray(rolesRaw) ? rolesRaw : []) as Array<{ id: string; name: string }>;
|
||||
return list.map((r) => ({ value: r.id, label: r.name }));
|
||||
}, [rolesRaw]);
|
||||
return roleRows.map((role) => ({ value: role.id, label: role.name }));
|
||||
}, [roleRows]);
|
||||
|
||||
// Chapters are derived from roles or can be hardcoded common ones
|
||||
const chapters = useMemo<FilterOption[]>(() => {
|
||||
const common = [
|
||||
"Digital Content Production",
|
||||
"Project Management",
|
||||
"Art Direction",
|
||||
"CGI-Dev",
|
||||
"Product Data Management",
|
||||
];
|
||||
return common.map((c) => ({ value: c, label: c }));
|
||||
}, []);
|
||||
return chapterRows.map((chapter) => ({ value: chapter, label: chapter }));
|
||||
}, [chapterRows]);
|
||||
|
||||
return { clients, countries, roles, chapters };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user