move static data

This commit is contained in:
2025-12-26 00:20:53 +01:00
parent c977defd6a
commit b6cbb81388
63 changed files with 1482 additions and 418 deletions

View File

@@ -1,6 +1,8 @@
"use client";
import { useState } from "react";
import { useMemo, useState } from "react";
import { usePenaltyTypesReference } from "@/hooks/usePenaltyTypesReference";
import type { PenaltyValueKindDTO } from "@/lib/types/PenaltyTypesReferenceDTO";
import { ProtestViewModel } from "../../lib/view-models/ProtestViewModel";
import Modal from "../ui/Modal";
import Button from "../ui/Button";
@@ -21,7 +23,7 @@ import {
FileWarning,
} from "lucide-react";
type PenaltyType = "time_penalty" | "grid_penalty" | "points_deduction" | "disqualification" | "warning" | "license_points" | "probation" | "fine" | "race_ban";
type PenaltyType = string;
interface ReviewProtestModalProps {
protest: ProtestViewModel | null;
@@ -94,25 +96,63 @@ export function ReviewProtestModal({
}
};
const getPenaltyLabel = (type: PenaltyType) => {
const getPenaltyName = (type: PenaltyType) => {
switch (type) {
case "time_penalty":
return "seconds";
return "Time Penalty";
case "grid_penalty":
return "grid positions";
return "Grid Penalty";
case "points_deduction":
return "points";
return "Points Deduction";
case "disqualification":
return "Disqualification";
case "warning":
return "Warning";
case "license_points":
return "points";
return "License Points";
case "probation":
return "Probation";
case "fine":
return "points";
return "Fine";
case "race_ban":
return "races";
return "Race Ban";
default:
return type.replaceAll("_", " ");
}
};
const getPenaltyValueLabel = (valueKind: PenaltyValueKindDTO): string => {
switch (valueKind) {
case "seconds":
return "seconds";
case "grid_positions":
return "grid positions";
case "points":
return "points";
case "races":
return "races";
case "none":
return "";
}
};
const getPenaltyDefaultValue = (type: PenaltyType, valueKind: PenaltyValueKindDTO): number => {
if (type === "license_points") return 2;
if (type === "race_ban") return 1;
switch (valueKind) {
case "seconds":
return 5;
case "grid_positions":
return 3;
case "points":
return 5;
case "races":
return 1;
case "none":
return 0;
}
};
const getPenaltyColor = (type: PenaltyType) => {
switch (type) {
case "time_penalty":
@@ -138,6 +178,25 @@ export function ReviewProtestModal({
}
};
const { data: penaltyTypesReference, isLoading: penaltyTypesLoading } = usePenaltyTypesReference();
const penaltyOptions = useMemo(() => {
const refs = penaltyTypesReference?.penaltyTypes ?? [];
return refs.map((ref) => ({
type: ref.type as PenaltyType,
name: getPenaltyName(ref.type),
requiresValue: ref.requiresValue,
valueLabel: getPenaltyValueLabel(ref.valueKind),
defaultValue: getPenaltyDefaultValue(ref.type, ref.valueKind),
Icon: getPenaltyIcon(ref.type),
colorClass: getPenaltyColor(ref.type),
}));
}, [penaltyTypesReference]);
const selectedPenalty = useMemo(() => {
return penaltyOptions.find((p) => p.type === penaltyType);
}, [penaltyOptions, penaltyType]);
if (showConfirmation) {
return (
<Modal title="Confirm Decision" isOpen={true} onOpenChange={() => setShowConfirmation(false)}>
@@ -160,7 +219,9 @@ export function ReviewProtestModal({
<h3 className="text-xl font-bold text-white">Confirm Decision</h3>
<p className="text-gray-400 mt-2">
{decision === "accept"
? `Issue ${penaltyValue} ${getPenaltyLabel(penaltyType)} penalty?`
? (selectedPenalty?.requiresValue
? `Issue ${penaltyValue} ${selectedPenalty.valueLabel} penalty?`
: `Issue ${selectedPenalty?.name ?? penaltyType} penalty?`)
: "Reject this protest?"}
</p>
</div>
@@ -300,43 +361,39 @@ export function ReviewProtestModal({
<label className="text-sm font-medium text-gray-400 mb-2 block">
Penalty Type
</label>
<div className="grid grid-cols-3 gap-2">
{[
{ type: "time_penalty" as PenaltyType, label: "Time Penalty" },
{ type: "grid_penalty" as PenaltyType, label: "Grid Penalty" },
{ type: "points_deduction" as PenaltyType, label: "Points Deduction" },
{ type: "disqualification" as PenaltyType, label: "Disqualification" },
{ type: "warning" as PenaltyType, label: "Warning" },
{ type: "license_points" as PenaltyType, label: "License Points" },
{ type: "probation" as PenaltyType, label: "Probation" },
{ type: "fine" as PenaltyType, label: "Fine" },
{ type: "race_ban" as PenaltyType, label: "Race Ban" },
].map(({ type, label }) => {
const Icon = getPenaltyIcon(type);
const colorClass = getPenaltyColor(type);
const isSelected = penaltyType === type;
return (
<button
key={type}
onClick={() => setPenaltyType(type)}
className={`p-3 rounded-lg border transition-all ${
isSelected
? `${colorClass} border-2`
: "border-charcoal-outline hover:border-gray-600 bg-iron-gray/50"
}`}
>
<Icon className={`h-5 w-5 mx-auto mb-1 ${isSelected ? '' : 'text-gray-400'}`} />
<p className={`text-xs font-medium ${isSelected ? '' : 'text-gray-400'}`}>{label}</p>
</button>
);
})}
</div>
{penaltyTypesLoading ? (
<div className="text-sm text-gray-500">Loading penalty types</div>
) : (
<div className="grid grid-cols-3 gap-2">
{penaltyOptions.map(({ type, name, Icon, colorClass, defaultValue }) => {
const isSelected = penaltyType === type;
return (
<button
key={type}
onClick={() => {
setPenaltyType(type);
setPenaltyValue(defaultValue);
}}
className={`p-3 rounded-lg border transition-all ${
isSelected
? `${colorClass} border-2`
: "border-charcoal-outline hover:border-gray-600 bg-iron-gray/50"
}`}
>
<Icon className={`h-5 w-5 mx-auto mb-1 ${isSelected ? "" : "text-gray-400"}`} />
<p className={`text-xs font-medium ${isSelected ? "" : "text-gray-400"}`}>{name}</p>
</button>
);
})}
</div>
)}
</div>
{['time_penalty', 'grid_penalty', 'points_deduction', 'license_points', 'fine', 'race_ban'].includes(penaltyType) && (
{selectedPenalty?.requiresValue && (
<div>
<label className="text-sm font-medium text-gray-400 mb-2 block">
Penalty Value ({getPenaltyLabel(penaltyType)})
Penalty Value ({selectedPenalty.valueLabel})
</label>
<input
type="number"