112 lines
4.4 KiB
TypeScript
112 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Protest } from "@gridpilot/racing/domain/entities/Protest";
|
|
import { Race } from "@gridpilot/racing/domain/entities/Race";
|
|
import { DriverDTO } from "@gridpilot/racing/application/dto/DriverDTO";
|
|
import Card from "../ui/Card";
|
|
import Button from "../ui/Button";
|
|
import { Clock, Grid3x3, TrendingDown, AlertCircle, Filter, Flag } from "lucide-react";
|
|
|
|
type PenaltyType = "time_penalty" | "grid_penalty" | "points_deduction" | "disqualification" | "warning" | "license_points";
|
|
|
|
interface PenaltyHistoryListProps {
|
|
protests: Protest[];
|
|
races: Record<string, Race>;
|
|
drivers: Record<string, DriverDTO>;
|
|
}
|
|
|
|
export function PenaltyHistoryList({
|
|
protests,
|
|
races,
|
|
drivers,
|
|
}: PenaltyHistoryListProps) {
|
|
const [filteredProtests, setFilteredProtests] = useState<Protest[]>([]);
|
|
const [filterType, setFilterType] = useState<"all">("all");
|
|
|
|
useEffect(() => {
|
|
setFilteredProtests(protests);
|
|
}, [protests]);
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case "upheld":
|
|
return "text-red-400 bg-red-500/20";
|
|
case "dismissed":
|
|
return "text-gray-400 bg-gray-500/20";
|
|
case "withdrawn":
|
|
return "text-blue-400 bg-blue-500/20";
|
|
default:
|
|
return "text-orange-400 bg-orange-500/20";
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{filteredProtests.length === 0 ? (
|
|
<Card className="p-12 text-center">
|
|
<div className="flex flex-col items-center gap-4 text-gray-400">
|
|
<AlertCircle className="h-12 w-12 opacity-50" />
|
|
<div>
|
|
<p className="font-medium text-lg">No Resolved Protests</p>
|
|
<p className="text-sm mt-1">
|
|
No protests have been resolved in this league
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{filteredProtests.map((protest) => {
|
|
const race = races[protest.raceId];
|
|
const protester = drivers[protest.protestingDriverId];
|
|
const accused = drivers[protest.accusedDriverId];
|
|
|
|
return (
|
|
<Card key={protest.id} className="p-4">
|
|
<div className="flex items-start gap-4">
|
|
<div className={`h-10 w-10 rounded-full flex items-center justify-center flex-shrink-0 ${getStatusColor(protest.status)}`}>
|
|
<Flag className="h-5 w-5" />
|
|
</div>
|
|
<div className="flex-1 space-y-2">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h3 className="font-semibold text-white">
|
|
Protest #{protest.id.substring(0, 8)}
|
|
</h3>
|
|
<p className="text-sm text-gray-400">
|
|
Resolved {new Date(protest.reviewedAt || protest.filedAt).toLocaleDateString()}
|
|
</p>
|
|
</div>
|
|
<span className={`px-3 py-1 rounded-full text-xs font-medium flex-shrink-0 ${getStatusColor(protest.status)}`}>
|
|
{protest.status.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
<div className="space-y-1 text-sm">
|
|
<p className="text-gray-400">
|
|
<span className="font-medium">{protester?.name || 'Unknown'}</span> vs <span className="font-medium">{accused?.name || 'Unknown'}</span>
|
|
</p>
|
|
{race && (
|
|
<p className="text-gray-500">
|
|
{race.track} ({race.car}) - Lap {protest.incident.lap}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<p className="text-gray-300 text-sm">{protest.incident.description}</p>
|
|
{protest.decisionNotes && (
|
|
<div className="mt-2 p-2 rounded bg-iron-gray/30 border border-charcoal-outline/50">
|
|
<p className="text-xs text-gray-400">
|
|
<span className="font-medium">Steward Notes:</span> {protest.decisionNotes}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |