Stage 8: world map view — backend endpoint, map component, map page

- BansByCountryResponse model added to ban.py
- bans_by_country() service: parallel geo lookup via asyncio.gather,
  aggregation by ISO alpha-2 country code (up to 2 000 bans)
- GET /api/dashboard/bans/by-country endpoint in dashboard router
- 290 tests pass (5 new), ruff + mypy clean (44 files)
- isoNumericToAlpha2.ts: 249-entry ISO numeric → alpha-2 static map
- types/map.ts, api/map.ts, hooks/useMapData.ts created
- WorldMap.tsx: react-simple-maps Mercator SVG map, per-country ban
  count overlay, colour intensity scaling, country click filtering,
  GeoLayer nested-component pattern for useGeographies context
- MapPage.tsx: time-range selector, WorldMap, country filter info bar,
  summary line, companion FluentUI Table with country filter
- Frontend tsc + ESLint clean (0 errors/warnings)
This commit is contained in:
2026-03-01 14:53:49 +01:00
parent 7f81f0614b
commit 54313fd3e0
13 changed files with 1343 additions and 20 deletions

19
frontend/src/api/map.ts Normal file
View File

@@ -0,0 +1,19 @@
/**
* API functions for the world map / bans-by-country endpoint.
*/
import { get } from "./client";
import { ENDPOINTS } from "./endpoints";
import type { BansByCountryResponse, TimeRange } from "../types/map";
/**
* Fetch ban counts aggregated by country for the given time window.
*
* @param range - Time-range preset.
*/
export async function fetchBansByCountry(
range: TimeRange = "24h",
): Promise<BansByCountryResponse> {
const url = `${ENDPOINTS.dashboardBansByCountry}?range=${encodeURIComponent(range)}`;
return get<BansByCountryResponse>(url);
}