fix data flow issues
This commit is contained in:
@@ -4,8 +4,8 @@ import Breadcrumbs from '@/components/layout/Breadcrumbs';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import type { RacePenaltiesViewModel, RaceProtestsViewModel } from '@/lib/apiClient';
|
||||
import { apiClient } from '@/lib/apiClient';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { RaceStewardingViewModel } from '@/lib/view-models/RaceStewardingViewModel';
|
||||
import {
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
@@ -24,13 +24,11 @@ import { useEffect, useState } from 'react';
|
||||
export default function RaceStewardingPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const { raceStewardingService } = useServices();
|
||||
const raceId = params.id as string;
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
|
||||
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 [stewardingData, setStewardingData] = useState<RaceStewardingViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<'pending' | 'resolved' | 'penalties'>('pending');
|
||||
@@ -39,24 +37,13 @@ export default function RaceStewardingPage() {
|
||||
async function loadData() {
|
||||
setLoading(true);
|
||||
try {
|
||||
// Get race detail for basic info
|
||||
const raceDetail = await apiClient.races.getDetail(raceId, currentDriverId);
|
||||
setRace(raceDetail.race);
|
||||
setLeague(raceDetail.league);
|
||||
const data = await raceStewardingService.getRaceStewardingData(raceId, currentDriverId);
|
||||
setStewardingData(data);
|
||||
|
||||
if (raceDetail.league) {
|
||||
if (data.league) {
|
||||
// TODO: Implement admin check via API
|
||||
setIsAdmin(true);
|
||||
}
|
||||
|
||||
// Get protests and penalties
|
||||
const [protestsData, penaltiesData] = await Promise.all([
|
||||
apiClient.races.getProtests(raceId),
|
||||
apiClient.races.getPenalties(raceId),
|
||||
]);
|
||||
|
||||
setProtestsData(protestsData);
|
||||
setPenaltiesData(penaltiesData);
|
||||
} catch (err) {
|
||||
console.error('Failed to load data:', err);
|
||||
} finally {
|
||||
@@ -65,17 +52,10 @@ export default function RaceStewardingPage() {
|
||||
}
|
||||
|
||||
loadData();
|
||||
}, [raceId, currentDriverId]);
|
||||
}, [raceId, currentDriverId, raceStewardingService]);
|
||||
|
||||
const pendingProtests = protestsData?.protests.filter(
|
||||
(p) => p.status === 'pending' || p.status === 'under_review',
|
||||
) ?? [];
|
||||
const resolvedProtests = protestsData?.protests.filter(
|
||||
(p) =>
|
||||
p.status === 'upheld' ||
|
||||
p.status === 'dismissed' ||
|
||||
p.status === 'withdrawn',
|
||||
) ?? [];
|
||||
const pendingProtests = stewardingData?.pendingProtests ?? [];
|
||||
const resolvedProtests = stewardingData?.resolvedProtests ?? [];
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status) {
|
||||
@@ -131,7 +111,7 @@ export default function RaceStewardingPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!race) {
|
||||
if (!stewardingData?.race) {
|
||||
return (
|
||||
<div className="min-h-screen bg-deep-graphite py-8 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
@@ -158,7 +138,7 @@ export default function RaceStewardingPage() {
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Races', href: '/races' },
|
||||
{ label: race.track, href: `/races/${race.id}` },
|
||||
{ label: stewardingData?.race?.track || 'Race', href: `/races/${raceId}` },
|
||||
{ label: 'Stewarding' },
|
||||
];
|
||||
|
||||
@@ -186,9 +166,9 @@ export default function RaceStewardingPage() {
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Stewarding</h1>
|
||||
<p className="text-sm text-gray-400">
|
||||
{race.track} • {formatDate(race.scheduledAt)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{stewardingData?.race?.track} • {stewardingData?.race?.scheduledAt ? formatDate(stewardingData.race.scheduledAt) : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -199,21 +179,21 @@ export default function RaceStewardingPage() {
|
||||
<Clock className="w-4 h-4" />
|
||||
<span className="text-xs font-medium uppercase">Pending</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">{pendingProtests.length}</div>
|
||||
<div className="text-2xl font-bold text-white">{stewardingData?.pendingCount ?? 0}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-deep-graphite/50 border border-charcoal-outline p-4">
|
||||
<div className="flex items-center gap-2 text-performance-green mb-1">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
<span className="text-xs font-medium uppercase">Resolved</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">{resolvedProtests.length}</div>
|
||||
<div className="text-2xl font-bold text-white">{stewardingData?.resolvedCount ?? 0}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-deep-graphite/50 border border-charcoal-outline p-4">
|
||||
<div className="flex items-center gap-2 text-red-400 mb-1">
|
||||
<Gavel className="w-4 h-4" />
|
||||
<span className="text-xs font-medium uppercase">Penalties</span>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-white">{penaltiesData?.penalties.length ?? 0}</div>
|
||||
<div className="text-2xl font-bold text-white">{stewardingData?.penaltiesCount ?? 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -272,8 +252,8 @@ export default function RaceStewardingPage() {
|
||||
</Card>
|
||||
) : (
|
||||
pendingProtests.map((protest) => {
|
||||
const protester = protestsData?.driverMap[protest.protestingDriverId];
|
||||
const accused = protestsData?.driverMap[protest.accusedDriverId];
|
||||
const protester = stewardingData?.driverMap[protest.protestingDriverId];
|
||||
const accused = stewardingData?.driverMap[protest.accusedDriverId];
|
||||
const daysSinceFiled = Math.floor(
|
||||
(Date.now() - new Date(protest.filedAt).getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
@@ -330,9 +310,9 @@ export default function RaceStewardingPage() {
|
||||
</div>
|
||||
<p className="text-sm text-gray-300">{protest.incident.description}</p>
|
||||
</div>
|
||||
{isAdmin && league && (
|
||||
{isAdmin && stewardingData?.league && (
|
||||
<Link
|
||||
href={`/leagues/${league.id}/stewarding/protests/${protest.id}`}
|
||||
href={`/leagues/${stewardingData.league.id}/stewarding/protests/${protest.id}`}
|
||||
>
|
||||
<Button variant="primary">Review</Button>
|
||||
</Link>
|
||||
@@ -359,8 +339,8 @@ export default function RaceStewardingPage() {
|
||||
</Card>
|
||||
) : (
|
||||
resolvedProtests.map((protest) => {
|
||||
const protester = protestsData?.driverMap[protest.protestingDriverId];
|
||||
const accused = protestsData?.driverMap[protest.accusedDriverId];
|
||||
const protester = stewardingData?.driverMap[protest.protestingDriverId];
|
||||
const accused = stewardingData?.driverMap[protest.accusedDriverId];
|
||||
|
||||
return (
|
||||
<Card key={protest.id}>
|
||||
@@ -421,8 +401,8 @@ export default function RaceStewardingPage() {
|
||||
</p>
|
||||
</Card>
|
||||
) : (
|
||||
penaltiesData?.penalties.map((penalty) => {
|
||||
const driver = penaltiesData?.driverMap[penalty.driverId];
|
||||
stewardingData?.penalties.map((penalty) => {
|
||||
const driver = stewardingData?.driverMap[penalty.driverId];
|
||||
return (
|
||||
<Card key={penalty.id}>
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
@@ -7,8 +7,8 @@ import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import Breadcrumbs from '@/components/layout/Breadcrumbs';
|
||||
import { apiClient } from '@/lib/apiClient';
|
||||
import type { RacesPageDataViewModel, RacesPageDataRaceViewModel } from '@/lib/apiClient';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { RacesPageViewModel } from '@/lib/view-models/RacesPageViewModel';
|
||||
import {
|
||||
Calendar,
|
||||
Clock,
|
||||
@@ -34,8 +34,9 @@ type StatusFilter = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
|
||||
export default function AllRacesPage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { raceService } = useServices();
|
||||
|
||||
const [pageData, setPageData] = useState<RacesPageDataViewModel | null>(null);
|
||||
const [pageData, setPageData] = useState<RacesPageViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Pagination
|
||||
@@ -49,7 +50,7 @@ export default function AllRacesPage() {
|
||||
|
||||
const loadRaces = async () => {
|
||||
try {
|
||||
const viewModel = await apiClient.races.getAllPageData();
|
||||
const viewModel = await raceService.getAllRacesPageData();
|
||||
setPageData(viewModel);
|
||||
} catch (err) {
|
||||
console.error('Failed to load races:', err);
|
||||
@@ -62,7 +63,7 @@ export default function AllRacesPage() {
|
||||
void loadRaces();
|
||||
}, []);
|
||||
|
||||
const races: RacesPageDataRaceViewModel[] = pageData?.races ?? [];
|
||||
const races = pageData?.races ?? [];
|
||||
|
||||
const filteredRaces = useMemo(() => {
|
||||
return races.filter(race => {
|
||||
@@ -284,8 +285,8 @@ export default function AllRacesPage() {
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{paginatedRaces.map(race => {
|
||||
const config = statusConfig[race.status];
|
||||
const StatusIcon = config.icon;
|
||||
const config = statusConfig[race.status as keyof typeof statusConfig];
|
||||
const StatusIcon = config.icon;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -6,8 +6,8 @@ import Link from 'next/link';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Heading from '@/components/ui/Heading';
|
||||
import { apiClient } from '@/lib/apiClient';
|
||||
import type { RacesPageDataViewModel, RacesPageDataRaceViewModel } from '@/lib/apiClient';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { RacesPageViewModel } from '@/lib/view-models/RacesPageViewModel';
|
||||
import {
|
||||
Calendar,
|
||||
Clock,
|
||||
@@ -27,12 +27,13 @@ import {
|
||||
} from 'lucide-react';
|
||||
|
||||
type TimeFilter = 'all' | 'upcoming' | 'live' | 'past';
|
||||
type RaceStatusFilter = RacesPageDataRaceViewModel['status'];
|
||||
type RaceStatusFilter = 'scheduled' | 'running' | 'completed' | 'cancelled' | 'all';
|
||||
|
||||
export default function RacesPage() {
|
||||
const router = useRouter();
|
||||
const { raceService } = useServices();
|
||||
|
||||
const [pageData, setPageData] = useState<RacesPageDataViewModel | null>(null);
|
||||
const [pageData, setPageData] = useState<RacesPageViewModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Filters
|
||||
@@ -42,7 +43,7 @@ export default function RacesPage() {
|
||||
|
||||
const loadRaces = async () => {
|
||||
try {
|
||||
const data = await apiClient.races.getPageData();
|
||||
const data = await raceService.getRacesPageData();
|
||||
setPageData(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to load races:', err);
|
||||
@@ -87,11 +88,8 @@ export default function RacesPage() {
|
||||
|
||||
// Group races by date for calendar view
|
||||
const racesByDate = useMemo(() => {
|
||||
const grouped = new Map<string, RacesPageDataRaceViewModel[]>();
|
||||
const grouped = new Map<string, typeof filteredRaces[0][]>();
|
||||
filteredRaces.forEach((race) => {
|
||||
if (typeof race.scheduledAt !== 'string') {
|
||||
return;
|
||||
}
|
||||
const dateKey = race.scheduledAt.split('T')[0]!;
|
||||
if (!grouped.has(dateKey)) {
|
||||
grouped.set(dateKey, []);
|
||||
@@ -105,10 +103,10 @@ export default function RacesPage() {
|
||||
const liveRaces = filteredRaces.filter(r => r.isLive);
|
||||
const recentResults = filteredRaces.filter(r => r.isPast).slice(0, 5);
|
||||
const stats = {
|
||||
total: pageData?.races.length ?? 0,
|
||||
scheduled: pageData?.races.filter(r => r.status === 'scheduled').length ?? 0,
|
||||
running: pageData?.races.filter(r => r.status === 'running').length ?? 0,
|
||||
completed: pageData?.races.filter(r => r.status === 'completed').length ?? 0,
|
||||
total: pageData?.totalCount ?? 0,
|
||||
scheduled: pageData?.scheduledRaces.length ?? 0,
|
||||
running: pageData?.runningRaces.length ?? 0,
|
||||
completed: pageData?.completedRaces.length ?? 0,
|
||||
};
|
||||
|
||||
const formatDate = (date: Date | string) => {
|
||||
@@ -348,7 +346,7 @@ export default function RacesPage() {
|
||||
<div>
|
||||
<p className="text-white font-medium mb-1">No races found</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{pageData?.races.length === 0
|
||||
{pageData?.totalCount === 0
|
||||
? 'No races have been scheduled yet'
|
||||
: 'Try adjusting your filters'}
|
||||
</p>
|
||||
@@ -375,10 +373,7 @@ export default function RacesPage() {
|
||||
{/* Races for this date */}
|
||||
<div className="space-y-2">
|
||||
{dayRaces.map((race) => {
|
||||
if (!race.scheduledAt) {
|
||||
return null;
|
||||
}
|
||||
const config = statusConfig[race.status];
|
||||
const config = statusConfig[race.status as keyof typeof statusConfig];
|
||||
const StatusIcon = config.icon;
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user