This commit is contained in:
2025-12-17 14:04:11 +01:00
parent 1ea9c9649f
commit daa4bb6576
238 changed files with 4263 additions and 1752 deletions

View File

@@ -22,21 +22,10 @@ import {
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Breadcrumbs from '@/components/layout/Breadcrumbs';
import {
getGetRaceProtestsUseCase,
getGetRacePenaltiesUseCase,
getRaceRepository,
getLeagueRepository,
getLeagueMembershipRepository,
} from '@/lib/di-container';
import { apiClient } from '@/lib/apiClient';
import { useEffectiveDriverId } from '@/lib/currentDriver';
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
import { RaceProtestsPresenter } from '@/lib/presenters/RaceProtestsPresenter';
import { RacePenaltiesPresenter } from '@/lib/presenters/RacePenaltiesPresenter';
import type { RaceProtestViewModel } from '@core/racing/application/presenters/IRaceProtestsPresenter';
import type { RacePenaltyViewModel } from '@core/racing/application/presenters/IRacePenaltiesPresenter';
import type { League } from '@core/racing/domain/entities/League';
import type { Race } from '@core/racing/domain/entities/Race';
import type { RaceProtestsViewModel, RacePenaltiesViewModel } from '@/lib/apiClient';
export default function RaceStewardingPage() {
const params = useParams();
@@ -44,12 +33,10 @@ export default function RaceStewardingPage() {
const raceId = params.id as string;
const currentDriverId = useEffectiveDriverId();
const driversById: Record<string, { name?: string }> = {};
const [race, setRace] = useState<Race | null>(null);
const [league, setLeague] = useState<League | null>(null);
const [protests, setProtests] = useState<RaceProtestViewModel[]>([]);
const [penalties, setPenalties] = useState<RacePenaltyViewModel[]>([]);
const [race, setRace] = useState<any>(null); // TODO: Define proper race type
const [league, setLeague] = useState<any>(null); // TODO: Define proper league type
const [protestsData, setProtestsData] = useState<RaceProtestsViewModel | null>(null);
const [penaltiesData, setPenaltiesData] = useState<RacePenaltiesViewModel | null>(null);
const [loading, setLoading] = useState(true);
const [isAdmin, setIsAdmin] = useState(false);
const [activeTab, setActiveTab] = useState<'pending' | 'resolved' | 'penalties'>('pending');
@@ -58,39 +45,24 @@ export default function RaceStewardingPage() {
async function loadData() {
setLoading(true);
try {
const raceRepo = getRaceRepository();
const leagueRepo = getLeagueRepository();
const membershipRepo = getLeagueMembershipRepository();
const protestsUseCase = getGetRaceProtestsUseCase();
const penaltiesUseCase = getGetRacePenaltiesUseCase();
// Get race detail for basic info
const raceDetail = await apiClient.races.getDetail(raceId, currentDriverId);
setRace(raceDetail.race);
setLeague(raceDetail.league);
const raceData = await raceRepo.findById(raceId);
if (!raceData) {
setLoading(false);
return;
}
setRace(raceData);
const leagueData = await leagueRepo.findById(raceData.leagueId);
setLeague(leagueData);
if (leagueData) {
const membership = await membershipRepo.getMembership(
leagueData.id,
currentDriverId,
);
setIsAdmin(membership ? isLeagueAdminOrHigherRole(membership.role) : false);
if (raceDetail.league) {
// TODO: Implement admin check via API
setIsAdmin(true);
}
const protestsPresenter = new RaceProtestsPresenter();
await protestsUseCase.execute({ raceId }, protestsPresenter);
const protestsViewModel = protestsPresenter.getViewModel();
setProtests(protestsViewModel?.protests ?? []);
// Get protests and penalties
const [protestsData, penaltiesData] = await Promise.all([
apiClient.races.getProtests(raceId),
apiClient.races.getPenalties(raceId),
]);
const penaltiesPresenter = new RacePenaltiesPresenter();
await penaltiesUseCase.execute({ raceId }, penaltiesPresenter);
const penaltiesViewModel = penaltiesPresenter.getViewModel();
setPenalties(penaltiesViewModel?.penalties ?? []);
setProtestsData(protestsData);
setPenaltiesData(penaltiesData);
} catch (err) {
console.error('Failed to load data:', err);
} finally {
@@ -101,15 +73,15 @@ export default function RaceStewardingPage() {
loadData();
}, [raceId, currentDriverId]);
const pendingProtests = protests.filter(
const pendingProtests = protestsData?.protests.filter(
(p) => p.status === 'pending' || p.status === 'under_review',
);
const resolvedProtests = protests.filter(
) ?? [];
const resolvedProtests = protestsData?.protests.filter(
(p) =>
p.status === 'upheld' ||
p.status === 'dismissed' ||
p.status === 'withdrawn',
);
) ?? [];
const getStatusBadge = (status: string) => {
switch (status) {
@@ -247,7 +219,7 @@ export default function RaceStewardingPage() {
<Gavel className="w-4 h-4" />
<span className="text-xs font-medium uppercase">Penalties</span>
</div>
<div className="text-2xl font-bold text-white">{penalties.length}</div>
<div className="text-2xl font-bold text-white">{penaltiesData?.penalties.length ?? 0}</div>
</div>
</div>
</Card>
@@ -306,8 +278,8 @@ export default function RaceStewardingPage() {
</Card>
) : (
pendingProtests.map((protest) => {
const protester = driversById[protest.protestingDriverId];
const accused = driversById[protest.accusedDriverId];
const protester = protestsData?.driverMap[protest.protestingDriverId];
const accused = protestsData?.driverMap[protest.accusedDriverId];
const daysSinceFiled = Math.floor(
(Date.now() - new Date(protest.filedAt).getTime()) / (1000 * 60 * 60 * 24)
);
@@ -393,8 +365,8 @@ export default function RaceStewardingPage() {
</Card>
) : (
resolvedProtests.map((protest) => {
const protester = driversById[protest.protestingDriverId];
const accused = driversById[protest.accusedDriverId];
const protester = protestsData?.driverMap[protest.protestingDriverId];
const accused = protestsData?.driverMap[protest.accusedDriverId];
return (
<Card key={protest.id}>
@@ -455,8 +427,8 @@ export default function RaceStewardingPage() {
</p>
</Card>
) : (
penalties.map((penalty) => {
const driver = driversById[penalty.driverId];
penaltiesData?.penalties.map((penalty) => {
const driver = penaltiesData?.driverMap[penalty.driverId];
return (
<Card key={penalty.id}>
<div className="flex items-center gap-4">