/** * `BanTable` component. * * Renders a Fluent UI v9 `DataGrid` for the dashboard ban-list view. * Uses the {@link useBans} hook to fetch and manage paginated data from * the backend. * * Columns: Time, IP, Service, Country, Jail, Ban Count. */ import { Badge, Button, DataGrid, DataGridBody, DataGridCell, DataGridHeader, DataGridHeaderCell, DataGridRow, Text, Tooltip, makeStyles, tokens, type TableColumnDefinition, createTableColumn, } from "@fluentui/react-components"; import { PageEmpty, PageError, PageLoading } from "./PageFeedback"; import { ChevronLeftRegular, ChevronRightRegular } from "@fluentui/react-icons"; import { useBans } from "../hooks/useBans"; import { formatTimestamp } from "../utils/formatDate"; import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- /** Props for the {@link BanTable} component. */ interface BanTableProps { /** * Active time-range preset — controlled by the parent `DashboardPage`. * Changing this value triggers a re-fetch. */ timeRange: TimeRange; /** * Active origin filter — controlled by the parent `DashboardPage`. * Changing this value triggers a re-fetch and resets to page 1. */ origin?: BanOriginFilter; } // --------------------------------------------------------------------------- // Styles // --------------------------------------------------------------------------- const useStyles = makeStyles({ root: { display: "flex", flexDirection: "column", gap: tokens.spacingVerticalS, minHeight: "300px", }, centred: { display: "flex", justifyContent: "center", alignItems: "center", padding: tokens.spacingVerticalXXL, }, tableWrapper: { overflowX: "auto", }, pagination: { display: "flex", alignItems: "center", justifyContent: "flex-end", gap: tokens.spacingHorizontalS, paddingTop: tokens.spacingVerticalS, }, mono: { fontFamily: "Consolas, 'Courier New', monospace", fontSize: tokens.fontSizeBase200, }, truncate: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: "280px", display: "inline-block", }, countBadge: { fontVariantNumeric: "tabular-nums", }, }); // --------------------------------------------------------------------------- // Column definitions // --------------------------------------------------------------------------- /** Columns for the ban-list view (`mode === "bans"`). */ function buildBanColumns(styles: ReturnType): TableColumnDefinition[] { return [ createTableColumn({ columnId: "banned_at", renderHeaderCell: () => "Time of Ban", renderCell: (item) => ( {formatTimestamp(item.banned_at)} ), }), createTableColumn({ columnId: "ip", renderHeaderCell: () => "IP Address", renderCell: (item) => ( {item.ip} ), }), createTableColumn({ columnId: "service", renderHeaderCell: () => "Service / URL", renderCell: (item) => item.service ? ( {item.service} ) : ( ), }), createTableColumn({ columnId: "country", renderHeaderCell: () => "Country", renderCell: (item) => item.country_name ?? item.country_code ? ( {item.country_name ?? item.country_code} ) : ( ), }), createTableColumn({ columnId: "jail", renderHeaderCell: () => "Jail", renderCell: (item) => {item.jail}, }), createTableColumn({ columnId: "origin", renderHeaderCell: () => "Origin", renderCell: (item) => ( {item.origin === "blocklist" ? "Blocklist" : "Selfblock"} ), }), createTableColumn({ columnId: "ban_count", renderHeaderCell: () => "Bans", renderCell: (item) => ( 1 ? "filled" : "outline"} color={item.ban_count > 5 ? "danger" : item.ban_count > 1 ? "warning" : "informative"} className={styles.countBadge} > {item.ban_count} ), }), ]; } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- /** * Data table for the dashboard ban-list view. * * @param props.timeRange - Active time-range preset from the parent page. * @param props.origin - Active origin filter from the parent page. */ export function BanTable({ timeRange, origin = "all" }: BanTableProps): React.JSX.Element { const styles = useStyles(); const { banItems, total, page, setPage, loading, error, refresh } = useBans(timeRange, origin); const banColumns = buildBanColumns(styles); // -------------------------------------------------------------------------- // Loading state // -------------------------------------------------------------------------- if (loading) { return ; } // -------------------------------------------------------------------------- // Error state // -------------------------------------------------------------------------- if (error) { return ; } // -------------------------------------------------------------------------- // Empty state // -------------------------------------------------------------------------- if (banItems.length === 0) { return ; } // -------------------------------------------------------------------------- // Pagination helpers // -------------------------------------------------------------------------- const pageSize = 100; const totalPages = Math.max(1, Math.ceil(total / pageSize)); const hasPrev = page > 1; const hasNext = page < totalPages; // -------------------------------------------------------------------------- // Render // -------------------------------------------------------------------------- return (
`${item.ip}:${item.jail}:${item.banned_at}`} > {({ renderHeaderCell }) => ( {renderHeaderCell()} )} > {({ item, rowId }) => ( key={rowId}> {({ renderCell }) => ( {renderCell(item)} )} )}
{total} total · Page {page} of {totalPages}
); }