Customization

Add a page

  1. Create src/app/your-page/page.tsx.
  2. Add it to navigation in src/config/navigation.ts (with an optional feature flag and permission).
  3. If it should be gateable, add a routeFeatureMap entry.

A typical page composes the same pieces every other page uses:

"use client";
export default function YourPage() {
  const { params } = useGlobalFilters();
  const query = useYourData(params);
  return (
    <>
      <PageHeader title="Your page" description="What it shows" />
      <FilterBar />
      <QueryState query={query} skeleton={<TableSkeleton />}>
        {(data) => <ChartCard title="Something">{/* … */}</ChartCard>}
      </QueryState>
    </>
  );
}

QueryState handles pending, error and empty for you; ChartCard wraps each widget in its own error boundary.

Add a data source method

  1. Add the method to DataAdapter in src/services/adapters/types.ts.
  2. Implement it in both mock-adapter.ts and http-adapter.ts.
  3. Add a facade entry in src/services/index.ts.
  4. Add a hook in src/hooks/use-financial-data.ts.

TypeScript will point at every place you still need to touch.

Add a widget to the custom dashboard

Append to widgetRegistry in src/components/dashboard/widget-registry.tsx:

{
  key: "chart:headcount",
  title: "Headcount",
  description: "People by department",
  defaultWidth: 1,
  render: (data) => <BreakdownBarChart items={data.headcount} />,
}

It appears in the widget library immediately; no other file changes.

Add a table column

Columns are plain TanStack ColumnDefs declared in the page. Use meta: { align: "right" } for numeric columns — it right-aligns the cell and applies tabular figures.

{ accessorKey: "margin", header: "Margin", meta: { align: "right" },
  cell: ({ row }) => formatPercent(row.original.margin, 1) }

Add a matching entry to exportColumns so the column appears in CSV output.

Make something drillable

const { drill } = useDrilldown();

<BreakdownBarChart
  items={items}
  onSelect={(item) => drill({ title: item.name, filters: { ...item.meta } })}
/>

Drill-downs stack; the drawer shows a breadcrumb and an "Open in Transactions" link that carries the filters into the full page.

Charts

Wrappers in src/components/charts/ cover line, area, bar, grouped bar, composed, horizontal ranked bars and donut. All read var(--chart-N), so they follow branding automatically. For something bespoke, build with Recharts directly and use axisProps, gridProps and ChartTooltip from chart-primitives.tsx to stay visually consistent.

Change the date presets

src/lib/dates.ts owns preset resolution and comparison ranges. Add a preset to the DatePreset union, handle it in resolvePreset, and add a label in src/components/filters/filter-bar.tsx.

Persist settings server-side

Every runtime setting flows through a provider backed by useLocalStorage. Replace that hook with a TanStack Query read plus mutation and the UI keeps working unchanged — it only consumes context.

Styling

Tokens live in src/app/globals.css (light and .dark). Change radius, surface colours or the font stack there rather than in components. The template deliberately uses a system font stack so it builds offline; to use a web font, add next/font in src/app/layout.tsx and point --font-sans at its variable.