Files
gridpilot.gg/apps/website/components/leagues/QuickPenaltyModal.tsx
2026-01-14 23:46:04 +01:00

222 lines
8.4 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import Button from '@/ui/Button';
import { usePenaltyMutation } from "@/lib/hooks/league/usePenaltyMutation";
import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react';
interface DriverOption {
id: string;
name: string;
}
interface QuickPenaltyModalProps {
raceId?: string;
drivers: DriverOption[];
onClose: () => void;
preSelectedDriver?: DriverOption;
adminId: string;
races?: { id: string; track: string; scheduledAt: Date }[];
}
const INFRACTION_TYPES = [
{ value: 'track_limits', label: 'Track Limits', icon: Flag },
{ value: 'unsafe_rejoin', label: 'Unsafe Rejoin', icon: AlertTriangle },
{ value: 'aggressive_driving', label: 'Aggressive Driving', icon: Zap },
{ value: 'false_start', label: 'False Start', icon: Clock },
{ value: 'other', label: 'Other', icon: AlertTriangle },
] as const;
const SEVERITY_LEVELS = [
{ value: 'warning', label: 'Warning', description: 'Official warning only' },
{ value: 'minor', label: 'Minor', description: 'Light penalty' },
{ value: 'major', label: 'Major', description: 'Significant penalty' },
{ value: 'severe', label: 'Severe', description: 'Heavy penalty' },
] as const;
export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelectedDriver, adminId, races }: QuickPenaltyModalProps) {
const [selectedRaceId, setSelectedRaceId] = useState<string>(raceId || '');
const [selectedDriver, setSelectedDriver] = useState<string>(preSelectedDriver?.id || '');
const [infractionType, setInfractionType] = useState<string>('');
const [severity, setSeverity] = useState<string>('');
const [notes, setNotes] = useState<string>('');
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const penaltyMutation = usePenaltyMutation();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedRaceId || !selectedDriver || !infractionType || !severity) return;
setError(null);
try {
const command = {
raceId: selectedRaceId,
driverId: selectedDriver,
stewardId: adminId,
type: infractionType,
reason: severity,
notes: notes.trim() || undefined,
};
await penaltyMutation.mutateAsync(command);
// Refresh the page to show updated results
router.refresh();
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to apply penalty');
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
<div className="w-full max-w-md bg-iron-gray rounded-xl border border-charcoal-outline shadow-2xl">
<div className="p-6">
<h2 className="text-xl font-bold text-white mb-4">Quick Penalty</h2>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Race Selection */}
{races && !raceId && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Race
</label>
<select
value={selectedRaceId}
onChange={(e) => setSelectedRaceId(e.target.value)}
className="w-full px-3 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white focus:border-primary-blue focus:outline-none"
required
>
<option value="">Select race...</option>
{races.map((race) => (
<option key={race.id} value={race.id}>
{race.track} ({race.scheduledAt.toLocaleDateString()})
</option>
))}
</select>
</div>
)}
{/* Driver Selection */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Driver
</label>
{preSelectedDriver ? (
<div className="w-full px-3 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white">
{preSelectedDriver.name}
</div>
) : (
<select
value={selectedDriver}
onChange={(e) => setSelectedDriver(e.target.value)}
className="w-full px-3 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white focus:border-primary-blue focus:outline-none"
required
>
<option value="">Select driver...</option>
{drivers.map((driver) => (
<option key={driver.id} value={driver.id}>
{driver.name}
</option>
))}
</select>
)}
</div>
{/* Infraction Type */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Infraction Type
</label>
<div className="grid grid-cols-2 gap-2">
{INFRACTION_TYPES.map(({ value, label, icon: Icon }) => (
<button
key={value}
type="button"
onClick={() => setInfractionType(value)}
className={`flex items-center gap-2 p-3 rounded-lg border transition-colors ${
infractionType === value
? 'border-primary-blue bg-primary-blue/10 text-primary-blue'
: 'border-charcoal-outline bg-deep-graphite text-gray-300 hover:border-gray-500'
}`}
>
<Icon className="w-4 h-4" />
<span className="text-sm">{label}</span>
</button>
))}
</div>
</div>
{/* Severity */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Severity
</label>
<div className="space-y-2">
{SEVERITY_LEVELS.map(({ value, label, description }) => (
<button
key={value}
type="button"
onClick={() => setSeverity(value)}
className={`w-full text-left p-3 rounded-lg border transition-colors ${
severity === value
? 'border-primary-blue bg-primary-blue/10 text-primary-blue'
: 'border-charcoal-outline bg-deep-graphite text-gray-300 hover:border-gray-500'
}`}
>
<div className="font-medium">{label}</div>
<div className="text-xs opacity-75">{description}</div>
</button>
))}
</div>
</div>
{/* Notes */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Notes (Optional)
</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Additional details..."
className="w-full px-3 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none resize-none"
rows={3}
/>
</div>
{error && (
<div className="p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
<p className="text-sm text-red-400">{error}</p>
</div>
)}
{/* Actions */}
<div className="flex gap-3 pt-4">
<Button
type="button"
variant="secondary"
onClick={onClose}
className="flex-1"
disabled={penaltyMutation.isPending}
>
Cancel
</Button>
<Button
type="submit"
variant="primary"
className="flex-1"
disabled={penaltyMutation.isPending || !selectedRaceId || !selectedDriver || !infractionType || !severity}
>
{penaltyMutation.isPending ? 'Applying...' : 'Apply Penalty'}
</Button>
</div>
</form>
</div>
</div>
</div>
);
}