Files
BanGUI/frontend/src/hooks/useBans.ts
Lukas 53d664de4f Add origin field and filter for ban sources (Tasks 1 & 2)
- Task 1: Mark imported blocklist IP addresses
  - Add BanOrigin type and _derive_origin() to ban.py model
  - Populate origin field in ban_service list_bans() and bans_by_country()
  - BanTable and MapPage companion table show origin badge column
  - Tests: origin derivation in test_ban_service.py and test_dashboard.py

- Task 2: Add origin filter to dashboard and world map
  - ban_service: _origin_sql_filter() helper; origin param on list_bans()
    and bans_by_country()
  - dashboard router: optional origin query param forwarded to service
  - Frontend: BanOriginFilter type + BAN_ORIGIN_FILTER_LABELS in ban.ts
  - fetchBans / fetchBansByCountry forward origin to API
  - useBans / useMapData accept and pass origin; page resets on change
  - BanTable accepts origin prop; DashboardPage adds segmented filter
  - MapPage adds origin Select next to time-range picker
  - Tests: origin filter assertions in test_ban_service and test_dashboard
2026-03-07 20:03:43 +01:00

94 lines
2.7 KiB
TypeScript

/**
* `useBans` hook.
*
* Fetches and manages paginated ban-list data from the dashboard endpoint.
* Re-fetches automatically when `timeRange` or `page` changes.
*/
import { useCallback, useEffect, useRef, useState } from "react";
import { fetchBans } from "../api/dashboard";
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
/** Items per page for the ban table. */
const PAGE_SIZE = 100;
/** Return value shape for {@link useBans}. */
export interface UseBansResult {
/** Ban items for the current page. */
banItems: DashboardBanItem[];
/** Total records in the selected time window (for pagination). */
total: number;
/** Current 1-based page number. */
page: number;
/** Navigate to a specific page. */
setPage: (p: number) => void;
/** Whether a fetch is currently in flight. */
loading: boolean;
/** Error message if the last fetch failed, otherwise `null`. */
error: string | null;
/** Imperatively re-fetch the current page. */
refresh: () => void;
}
/**
* Fetch and manage dashboard ban-list data.
*
* Automatically re-fetches when `timeRange`, `origin`, or `page` changes.
*
* @param timeRange - Time-range preset that controls how far back to look.
* @param origin - Origin filter (default `"all"`).
* @returns Current data, pagination state, loading flag, and a `refresh`
* callback.
*/
export function useBans(
timeRange: TimeRange,
origin: BanOriginFilter = "all",
): UseBansResult {
const [banItems, setBanItems] = useState<DashboardBanItem[]>([]);
const [total, setTotal] = useState<number>(0);
const [page, setPage] = useState<number>(1);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
// Reset page when time range or origin filter changes.
useEffect(() => {
setPage(1);
}, [timeRange, origin]);
const doFetch = useCallback(async (): Promise<void> => {
setLoading(true);
setError(null);
try {
const data = await fetchBans(timeRange, page, PAGE_SIZE, origin);
setBanItems(data.items);
setTotal(data.total);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "Failed to fetch data");
} finally {
setLoading(false);
}
}, [timeRange, page, origin]);
// Stable ref to the latest doFetch so the refresh callback is always current.
const doFetchRef = useRef(doFetch);
doFetchRef.current = doFetch;
useEffect(() => {
void doFetch();
}, [doFetch]);
const refresh = useCallback((): void => {
void doFetchRef.current();
}, []);
return {
banItems,
total,
page,
setPage,
loading,
error,
refresh,
};
}