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:
@@ -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 */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
|
||||
Reference in New Issue
Block a user