website refactor
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
'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 { useState } from 'react';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { usePenaltyMutation } from "@/hooks/league/usePenaltyMutation";
|
||||
import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { TextArea } from '@/ui/TextArea';
|
||||
|
||||
interface DriverOption {
|
||||
id: string;
|
||||
@@ -15,6 +21,7 @@ interface QuickPenaltyModalProps {
|
||||
raceId?: string;
|
||||
drivers: DriverOption[];
|
||||
onClose: () => void;
|
||||
onRefresh?: () => void;
|
||||
preSelectedDriver?: DriverOption;
|
||||
adminId: string;
|
||||
races?: { id: string; track: string; scheduledAt: Date }[];
|
||||
@@ -35,14 +42,13 @@ const SEVERITY_LEVELS = [
|
||||
{ value: 'severe', label: 'Severe', description: 'Heavy penalty' },
|
||||
] as const;
|
||||
|
||||
export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelectedDriver, adminId, races }: QuickPenaltyModalProps) {
|
||||
export function QuickPenaltyModal({ raceId, drivers, onClose, onRefresh, 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) => {
|
||||
@@ -64,7 +70,7 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
await penaltyMutation.mutateAsync(command);
|
||||
|
||||
// Refresh the page to show updated results
|
||||
router.refresh();
|
||||
onRefresh?.();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to apply penalty');
|
||||
@@ -72,135 +78,148 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
};
|
||||
|
||||
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>
|
||||
<Box position="fixed" inset="0" zIndex={50} display="flex" alignItems="center" justifyContent="center" p={4} bg="bg-black/70"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="backdrop-blur-sm"
|
||||
>
|
||||
<Box w="full" maxWidth="md" bg="bg-iron-gray" rounded="xl" border borderColor="border-charcoal-outline" shadow="2xl">
|
||||
<Box p={6}>
|
||||
<Heading level={2} fontSize="xl" weight="bold" color="text-white" mb={4}>Quick Penalty</Heading>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Box as="form" onSubmit={handleSubmit} display="flex" flexDirection="col" gap={4}>
|
||||
{/* Race Selection */}
|
||||
{races && !raceId && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" size="sm" weight="medium" color="text-gray-300" block mb={2}>
|
||||
Race
|
||||
</label>
|
||||
<select
|
||||
</Text>
|
||||
<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>
|
||||
options={[
|
||||
{ value: '', label: 'Select race...' },
|
||||
...races.map((race) => ({
|
||||
value: race.id,
|
||||
label: `${race.track} (${race.scheduledAt.toLocaleDateString()})`,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Driver Selection */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" size="sm" weight="medium" color="text-gray-300" block mb={2}>
|
||||
Driver
|
||||
</label>
|
||||
</Text>
|
||||
{preSelectedDriver ? (
|
||||
<div className="w-full px-3 py-2 bg-deep-graphite border border-charcoal-outline rounded-lg text-white">
|
||||
<Box w="full" px={3} py={2} bg="bg-deep-graphite" border borderColor="border-charcoal-outline" rounded="lg" color="text-white">
|
||||
{preSelectedDriver.name}
|
||||
</div>
|
||||
</Box>
|
||||
) : (
|
||||
<select
|
||||
<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>
|
||||
options={[
|
||||
{ value: '', label: 'Select driver...' },
|
||||
...drivers.map((driver) => ({
|
||||
value: driver.id,
|
||||
label: driver.name,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Infraction Type */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" size="sm" weight="medium" color="text-gray-300" block mb={2}>
|
||||
Infraction Type
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{INFRACTION_TYPES.map(({ value, label, icon: Icon }) => (
|
||||
<button
|
||||
</Text>
|
||||
<Box display="grid" gridCols={2} gap={2}>
|
||||
{INFRACTION_TYPES.map(({ value, label, icon: InfractionIcon }) => (
|
||||
<Box
|
||||
key={value}
|
||||
as="button"
|
||||
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'
|
||||
}`}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
p={3}
|
||||
rounded="lg"
|
||||
border
|
||||
transition
|
||||
borderColor={infractionType === value ? 'border-primary-blue' : 'border-charcoal-outline'}
|
||||
bg={infractionType === value ? 'bg-primary-blue/10' : 'bg-deep-graphite'}
|
||||
color={infractionType === value ? 'text-primary-blue' : 'text-gray-300'}
|
||||
hoverBorderColor={infractionType !== value ? 'border-gray-500' : undefined}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
<span className="text-sm">{label}</span>
|
||||
</button>
|
||||
<Icon icon={InfractionIcon} size={4} />
|
||||
<Text size="sm">{label}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Severity */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" size="sm" weight="medium" color="text-gray-300" block mb={2}>
|
||||
Severity
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
</Text>
|
||||
<Stack gap={2}>
|
||||
{SEVERITY_LEVELS.map(({ value, label, description }) => (
|
||||
<button
|
||||
<Box
|
||||
key={value}
|
||||
as="button"
|
||||
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'
|
||||
}`}
|
||||
w="full"
|
||||
textAlign="left"
|
||||
p={3}
|
||||
rounded="lg"
|
||||
border
|
||||
transition
|
||||
borderColor={severity === value ? 'border-primary-blue' : 'border-charcoal-outline'}
|
||||
bg={severity === value ? 'bg-primary-blue/10' : 'bg-deep-graphite'}
|
||||
color={severity === value ? 'text-primary-blue' : 'text-gray-300'}
|
||||
hoverBorderColor={severity !== value ? 'border-gray-500' : undefined}
|
||||
>
|
||||
<div className="font-medium">{label}</div>
|
||||
<div className="text-xs opacity-75">{description}</div>
|
||||
</button>
|
||||
<Text weight="medium" block>{label}</Text>
|
||||
<Text size="xs" opacity={0.75} block>{description}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Notes */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" size="sm" weight="medium" color="text-gray-300" block mb={2}>
|
||||
Notes (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
</Text>
|
||||
<TextArea
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => 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>
|
||||
</Box>
|
||||
|
||||
{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>
|
||||
<Box p={3} bg="bg-red-500/10" border borderColor="border-red-500/20" rounded="lg">
|
||||
<Text size="sm" color="text-red-400" block>{error}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Box display="flex" gap={3} pt={4}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
className="flex-1"
|
||||
fullWidth
|
||||
disabled={penaltyMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
@@ -208,15 +227,15 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelecte
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
className="flex-1"
|
||||
fullWidth
|
||||
disabled={penaltyMutation.isPending || !selectedRaceId || !selectedDriver || !infractionType || !severity}
|
||||
>
|
||||
{penaltyMutation.isPending ? 'Applying...' : 'Apply Penalty'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user