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

@@ -8,6 +8,9 @@
import { useState } from "react";
import {
MessageBar,
MessageBarBody,
Spinner,
Text,
ToggleButton,
Toolbar,
@@ -16,6 +19,9 @@ import {
} from "@fluentui/react-components";
import { BanTable } from "../components/BanTable";
import { ServerStatusBar } from "../components/ServerStatusBar";
import { TopCountriesPieChart } from "../components/TopCountriesPieChart";
import { TopCountriesBarChart } from "../components/TopCountriesBarChart";
import { useDashboardCountryData } from "../hooks/useDashboardCountryData";
import type { BanOriginFilter, TimeRange } from "../types/ban";
import { BAN_ORIGIN_FILTER_LABELS, TIME_RANGE_LABELS } from "../types/ban";
@@ -64,6 +70,16 @@ const useStyles = makeStyles({
tabContent: {
paddingTop: tokens.spacingVerticalS,
},
chartsRow: {
display: "flex",
flexDirection: "row",
gap: tokens.spacingHorizontalL,
flexWrap: "wrap",
},
chartCard: {
flex: "1 1 300px",
minWidth: "280px",
},
});
// ---------------------------------------------------------------------------
@@ -91,6 +107,9 @@ export function DashboardPage(): React.JSX.Element {
const [timeRange, setTimeRange] = useState<TimeRange>("24h");
const [originFilter, setOriginFilter] = useState<BanOriginFilter>("all");
const { countries, countryNames, isLoading: countryLoading, error: countryError } =
useDashboardCountryData(timeRange, originFilter);
return (
<div className={styles.root}>
{/* ------------------------------------------------------------------ */}
@@ -98,6 +117,40 @@ export function DashboardPage(): React.JSX.Element {
{/* ------------------------------------------------------------------ */}
<ServerStatusBar />
{/* ------------------------------------------------------------------ */}
{/* Charts section */}
{/* ------------------------------------------------------------------ */}
<div className={styles.section}>
<div className={styles.sectionHeader}>
<Text as="h2" size={500} weight="semibold">
Top Countries
</Text>
</div>
<div className={styles.tabContent}>
{countryError != null && (
<MessageBar intent="error">
<MessageBarBody>{countryError}</MessageBarBody>
</MessageBar>
)}
{countryLoading && countryError == null ? (
<Spinner label="Loading chart data…" />
) : (
<div className={styles.chartsRow}>
<div className={styles.chartCard}>
<TopCountriesPieChart
countries={countries}
countryNames={countryNames}
/>
</div> <div className={styles.chartCard}>
<TopCountriesBarChart
countries={countries}
countryNames={countryNames}
/>
</div> </div>
)}
</div>
</div>
{/* ------------------------------------------------------------------ */}
{/* Ban list section */}
{/* ------------------------------------------------------------------ */}