Migrates Tailwind v3 -> v4 (CSS-first, theme tokens for brand, workflow states and charts) and adds 23 shadcn/ui components on Radix. - lib/workflow.ts is now the single source of truth for state name, colour, icon and available actions; "Financial Review" is the canonical served name, with "Finance Review" kept as an alias so those rows stop falling through to the unstyled fallback and dropping out of every count - record view: TanStack Table with server-side sort/paginate, company column with initials avatar, page totals, density and a URL-synced query state - dashboard: one cached analytics fetch shared by tiles and charts, donut + aging + vendor spend, click-to-filter - detail view: rebuilt against the live detailview payload — restores the PO number and payment terms that the old key heuristics silently dropped, parses match_summary into a 3-way-match checklist, and separates the department's and reviewer's own words from AI-generated content - form modal on Radix Dialog (focus trap, scroll lock, escape handling)
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
// Shared URL state for the record view.
|
|
//
|
|
// Table sort/page/search/filters used to live in component state, so a refresh
|
|
// or a shared link lost the user's view entirely. These parsers put it in the
|
|
// query string instead, which also lets the dashboard tiles act as filters —
|
|
// a tile writes `status`, the table reads it.
|
|
|
|
import {
|
|
parseAsArrayOf,
|
|
parseAsInteger,
|
|
parseAsString,
|
|
parseAsStringLiteral,
|
|
} from "nuqs";
|
|
|
|
export const DENSITIES = ["compact", "regular", "relaxed"] as const;
|
|
export type Density = (typeof DENSITIES)[number];
|
|
|
|
/** Row heights per density — the 40/48/56px convention. */
|
|
export const DENSITY_ROW: Record<Density, string> = {
|
|
compact: "h-10",
|
|
regular: "h-12",
|
|
relaxed: "h-14",
|
|
};
|
|
|
|
export const DENSITY_CELL: Record<Density, string> = {
|
|
compact: "py-1.5",
|
|
regular: "py-3",
|
|
relaxed: "py-4",
|
|
};
|
|
|
|
export const tableParams = {
|
|
q: parseAsString.withDefault(""),
|
|
page: parseAsInteger.withDefault(1),
|
|
limit: parseAsInteger.withDefault(10),
|
|
sort: parseAsString.withDefault(""),
|
|
dir: parseAsStringLiteral(["asc", "desc"] as const).withDefault("desc"),
|
|
/** Workflow state names, matching `current_state_name`. */
|
|
status: parseAsArrayOf(parseAsString).withDefault([]),
|
|
density: parseAsStringLiteral(DENSITIES).withDefault("regular"),
|
|
};
|
|
|
|
/**
|
|
* Keep the URL short: nuqs omits any value equal to its default, and history
|
|
* is replaced rather than pushed so filtering doesn't fill the back button.
|
|
*/
|
|
export const tableParamsOptions = {
|
|
history: "replace" as const,
|
|
clearOnDefault: true,
|
|
shallow: true,
|
|
};
|