Add dashboard country charts (Stages 1–3)

- Install Recharts v3 as the project charting library
- Add chartTheme utility with Fluent UI v9 token resolution helper
  and a 5-colour categorical palette (resolves CSS vars at runtime)
- Add TopCountriesPieChart: top-4 + Other slice, Tooltip, Legend
- Add TopCountriesBarChart: horizontal top-20 bar chart
- Add useDashboardCountryData hook (wraps /api/dashboard/bans/by-country)
- Integrate both charts into DashboardPage in a responsive chartsRow
  (side-by-side on wide screens, stacked on narrow)
- All tsc --noEmit and eslint checks pass with zero warnings
This commit is contained in:
2026-03-11 16:06:24 +01:00
parent d931e8c6a3
commit 2ddfddfbbb
8 changed files with 1356 additions and 166 deletions

View File

@@ -0,0 +1,72 @@
/**
* Chart theme utility — maps Fluent UI v9 design tokens to Recharts-compatible
* CSS colour strings.
*
* Recharts renders SVG elements and sets colour values as SVG attributes, not
* CSS properties. SVG attributes do not support CSS custom-property
* references (`var(…)`), so token values must be resolved to their actual
* colour strings at render time via `getComputedStyle`.
*
* Call `resolveFluentToken` inside a component (not at module level) so that
* the resolved value reflects the theme that is active when the component
* renders.
*/
import { tokens } from "@fluentui/react-components";
// ---------------------------------------------------------------------------
// Runtime resolver
// ---------------------------------------------------------------------------
/**
* Resolves a Fluent UI v9 token string (e.g. `tokens.colorNeutralForeground2`)
* to the literal CSS colour value defined in the active theme.
*
* @param tokenValue - A Fluent v9 token string such as
* `"var(--colorNeutralForeground2)"`.
* @returns The resolved colour string (e.g. `"#605e5c"`), or the original
* token value if resolution fails.
*/
export function resolveFluentToken(tokenValue: string): string {
const match = /var\((--[^,)]+)/.exec(tokenValue);
if (match == null || match[1] == null) return tokenValue;
const resolved = getComputedStyle(document.documentElement)
.getPropertyValue(match[1])
.trim();
return resolved !== "" ? resolved : tokenValue;
}
// ---------------------------------------------------------------------------
// Categorical palette
// ---------------------------------------------------------------------------
/**
* Five distinct categorical colours for pie/bar slices and line series,
* expressed as Fluent UI v9 CSS custom-property references.
*
* Resolve at render time with `resolveFluentToken` before passing to
* Recharts components.
*/
export const CHART_PALETTE: readonly string[] = [
tokens.colorPaletteBlueBorderActive,
tokens.colorPaletteRedBorderActive,
tokens.colorPaletteGreenBorderActive,
tokens.colorPaletteGoldBorderActive,
tokens.colorPalettePurpleBorderActive,
] as const;
// ---------------------------------------------------------------------------
// Structural colours
// ---------------------------------------------------------------------------
/** Fluent token for axis labels and tick text — resolves per active theme. */
export const CHART_AXIS_TEXT_TOKEN: string = tokens.colorNeutralForeground2;
/** Fluent token for CartesianGrid lines — resolves per active theme. */
export const CHART_GRID_LINE_TOKEN: string = tokens.colorNeutralStroke2;
/** Fluent token for tooltip background — resolves per active theme. */
export const CHART_TOOLTIP_BG_TOKEN: string = tokens.colorNeutralBackground1;
/** Fluent token for tooltip text — resolves per active theme. */
export const CHART_TOOLTIP_TEXT_TOKEN: string = tokens.colorNeutralForeground1;