wip
This commit is contained in:
100
apps/website/components/leagues/EndRaceModal.tsx
Normal file
100
apps/website/components/leagues/EndRaceModal.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { AlertTriangle, TestTube, CheckCircle2 } from 'lucide-react';
|
||||
import Button from '@/components/ui/Button';
|
||||
|
||||
interface EndRaceModalProps {
|
||||
raceId: string;
|
||||
raceName: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function EndRaceModal({ raceId, raceName, onConfirm, onCancel }: EndRaceModalProps) {
|
||||
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">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-warning-amber/10 border border-warning-amber/20">
|
||||
<TestTube className="w-6 h-6 text-warning-amber" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white">Development Test Function</h2>
|
||||
<p className="text-sm text-gray-400">End Race & Process Results</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="p-4 rounded-lg bg-deep-graphite border border-charcoal-outline">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-warning-amber mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white mb-1">Development Only Feature</h3>
|
||||
<p className="text-sm text-gray-300 leading-relaxed">
|
||||
This is a development/testing function to simulate ending a race and processing results.
|
||||
It will generate realistic race results, update driver ratings, and calculate final standings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-lg bg-performance-green/10 border border-performance-green/20">
|
||||
<div className="flex items-start gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-performance-green mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white mb-1">What This Does</h3>
|
||||
<ul className="text-sm text-gray-300 space-y-1">
|
||||
<li>• Marks the race as completed</li>
|
||||
<li>• Generates realistic finishing positions</li>
|
||||
<li>• Updates driver ratings based on performance</li>
|
||||
<li>• Calculates championship points</li>
|
||||
<li>• Updates league standings</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-400">
|
||||
Race: <span className="text-white font-medium">{raceName}</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
ID: {raceId}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onConfirm}
|
||||
className="flex-1 bg-performance-green hover:bg-performance-green/80"
|
||||
>
|
||||
<TestTube className="w-4 h-4 mr-2" />
|
||||
Run Test
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-4 pt-4 border-t border-charcoal-outline">
|
||||
<p className="text-xs text-gray-500 text-center">
|
||||
This action cannot be undone. Use only for testing purposes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
188
apps/website/components/leagues/QuickPenaltyModal.tsx
Normal file
188
apps/website/components/leagues/QuickPenaltyModal.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { getQuickPenaltyUseCase } from '@/lib/di-container';
|
||||
import type { Driver } from '@gridpilot/racing/application';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react';
|
||||
|
||||
interface QuickPenaltyModalProps {
|
||||
raceId: string;
|
||||
drivers: Driver[];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
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 }: QuickPenaltyModalProps) {
|
||||
const [selectedDriver, setSelectedDriver] = useState<string>('');
|
||||
const [infractionType, setInfractionType] = useState<string>('');
|
||||
const [severity, setSeverity] = useState<string>('');
|
||||
const [notes, setNotes] = useState<string>('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!selectedDriver || !infractionType || !severity) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const useCase = getQuickPenaltyUseCase();
|
||||
await useCase.execute({
|
||||
raceId,
|
||||
driverId: selectedDriver,
|
||||
adminId: 'driver-1', // TODO: Get from current user context
|
||||
infractionType: infractionType as any,
|
||||
severity: severity as any,
|
||||
notes: notes.trim() || undefined,
|
||||
});
|
||||
|
||||
// Refresh the page to show updated results
|
||||
router.refresh();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to apply penalty');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
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">
|
||||
{/* Driver Selection */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Driver
|
||||
</label>
|
||||
<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={loading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="flex-1"
|
||||
disabled={loading || !selectedDriver || !infractionType || !severity}
|
||||
>
|
||||
{loading ? 'Applying...' : 'Apply Penalty'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user