import { auth } from "~/lib/auth.server"; import { format } from "date-fns"; import { Link, Form, useSearchParams } from "react-router"; import type { Route } from "./+types/$leagueId.audit-log"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select"; import { findLeagueById, isCommissioner, isUserLeagueMember } from "~/models"; import { findCurrentSeasonWithSports } from "~/models/season"; import { findTeamsBySeasonId } from "~/models/team"; import { getAuditLogForSeason, type AuditAction, type AuditLogEntry } from "~/models/audit-log"; import { AUDIT_ACTION_LABELS, formatAuditDetail } from "~/lib/audit-log-display"; import * as schema from "~/database/schema"; const VALID_AUDIT_ACTIONS = new Set(schema.auditActionEnum.enumValues); export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { return [{ title: `Audit Log — ${data?.league?.name ?? "League"} - Brackt` }]; } const PAGE_SIZE = 50; export async function loader(args: Route.LoaderArgs) { const session = await auth.api.getSession({ headers: args.request.headers }); const userId = session?.user.id ?? null; const { params, request } = args; const { leagueId } = params; if (!userId) { throw new Response("You must be logged in to view this page", { status: 401, }); } const league = await findLeagueById(leagueId); if (!league) { throw new Response("League not found", { status: 404 }); } const [userIsCommissioner, userIsMember] = await Promise.all([ isCommissioner(leagueId, userId), isUserLeagueMember(leagueId, userId), ]); if (!userIsCommissioner && !userIsMember) { throw new Response("You do not have access to this league", { status: 403, }); } const season = await findCurrentSeasonWithSports(leagueId); if (!season) { return { league, season: null, auditLog: { entries: [] as AuditLogEntry[], total: 0, hasMore: false }, teams: [] as Array<{ id: string; name: string }>, page: 0, isUserCommissioner: userIsCommissioner, }; } const searchParams = new URL(request.url).searchParams; const page = Math.max(0, parseInt(searchParams.get("page") ?? "0", 10) || 0); const rawAction = searchParams.get("action"); const actionFilter: AuditAction | null = rawAction && VALID_AUDIT_ACTIONS.has(rawAction) ? (rawAction as AuditAction) : null; const [auditLog, teams] = await Promise.all([ getAuditLogForSeason(season.id, { limit: PAGE_SIZE, offset: page * PAGE_SIZE, actions: actionFilter ? [actionFilter] : undefined, }), findTeamsBySeasonId(season.id), ]); return { league, season, auditLog, teams: teams.map((t) => ({ id: t.id, name: t.name })), page, isUserCommissioner: userIsCommissioner, }; } function getActionBadgeVariant( action: string ): "default" | "secondary" | "destructive" | "outline" { if (action === "draft_reset" || action === "draft_rollback") { return "destructive"; } if ( action === "draft_started" || action === "draft_paused" || action === "draft_resumed" ) { return "secondary"; } if ( action === "league_settings_changed" || action === "draft_settings_changed" || action === "scoring_rules_changed" || action === "sports_changed" ) { return "outline"; } return "default"; } interface AuditLogRowProps { entry: AuditLogEntry; teamMap: Map; } function AuditLogRow({ entry, teamMap }: AuditLogRowProps) { const affectedTeamNames = entry.affectedTeamIds && entry.affectedTeamIds.length > 0 ? entry.affectedTeamIds .map((id) => teamMap.get(id) ?? id) .join(", ") : "—"; return ( {format(new Date(entry.createdAt), "MMM d, yyyy HH:mm")} {entry.actorDisplayName ?? entry.actorClerkId} {AUDIT_ACTION_LABELS[entry.action] ?? entry.action} {affectedTeamNames} {formatAuditDetail(entry)} ); } export default function AuditLogPage({ loaderData }: Route.ComponentProps) { const { league, season, auditLog, teams, page, isUserCommissioner } = loaderData; const [searchParams] = useSearchParams(); const actionFilter = searchParams.get("action") ?? ""; const teamMap = new Map(teams.map((t) => [t.id, t.name])); const totalPages = Math.ceil(auditLog.total / PAGE_SIZE); function buildPageUrl(newPage: number) { const p = new URLSearchParams(searchParams); p.set("page", String(newPage)); return `?${p.toString()}`; } return (
{/* Header */}

Audit Log

{league.name}

{isUserCommissioner && ( )}
{!season ? ( No season found for this league. ) : ( <> {/* Filter bar */}
{actionFilter && ( )}
{/* Results table */} Commissioner Actions {auditLog.total === 0 ? "No entries found" : `${auditLog.total} ${auditLog.total === 1 ? "entry" : "entries"}`} {totalPages > 1 && ` — page ${page + 1} of ${totalPages}`} {auditLog.entries.length === 0 ? (

No activity recorded yet.

) : ( Time Commissioner Action Teams Affected Details {auditLog.entries.map((entry) => ( ))}
)} {/* Pagination */} {totalPages > 1 && (
{page > 0 && ( )} Page {page + 1} of {totalPages} {auditLog.hasMore && ( )}
)}
)}
); }