wip
This commit is contained in:
@@ -928,7 +928,7 @@ export default function RaceDetailPage() {
|
||||
isOpen={showProtestModal}
|
||||
onClose={() => setShowProtestModal(false)}
|
||||
raceId={race.id}
|
||||
leagueId={league?.id}
|
||||
leagueId={league ? league.id : ''}
|
||||
protestingDriverId={currentDriverId}
|
||||
participants={entryList.map(d => ({ id: d.id, name: d.name }))}
|
||||
/>
|
||||
|
||||
@@ -115,13 +115,17 @@ export default function RaceResultsPage() {
|
||||
setPointsSystem(viewModel.pointsSystem);
|
||||
setFastestLapTime(viewModel.fastestLapTime);
|
||||
setCurrentDriverId(viewModel.currentDriverId);
|
||||
setPenalties(
|
||||
viewModel.penalties.map((p) => ({
|
||||
const mappedPenalties: PenaltyData[] = viewModel.penalties.map((p) => {
|
||||
const base: PenaltyData = {
|
||||
driverId: p.driverId,
|
||||
type: p.type as PenaltyTypeDTO,
|
||||
value: p.value,
|
||||
})),
|
||||
);
|
||||
};
|
||||
if (typeof p.value === 'number') {
|
||||
return { ...base, value: p.value };
|
||||
}
|
||||
return base;
|
||||
});
|
||||
setPenalties(mappedPenalties);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -287,9 +291,9 @@ export default function RaceResultsPage() {
|
||||
results={results}
|
||||
drivers={drivers}
|
||||
pointsSystem={pointsSystem}
|
||||
fastestLapTime={fastestLapTime}
|
||||
fastestLapTime={fastestLapTime ?? 0}
|
||||
penalties={penalties}
|
||||
currentDriverId={currentDriverId}
|
||||
currentDriverId={currentDriverId ?? ''}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
} from '@/lib/di-container';
|
||||
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 '@gridpilot/racing/application/presenters/IRaceProtestsPresenter';
|
||||
import type { RacePenaltyViewModel } from '@gridpilot/racing/application/presenters/IRacePenaltiesPresenter';
|
||||
import type { League } from '@gridpilot/racing/domain/entities/League';
|
||||
@@ -41,7 +43,9 @@ export default function RaceStewardingPage() {
|
||||
const router = useRouter();
|
||||
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[]>([]);
|
||||
@@ -78,13 +82,15 @@ export default function RaceStewardingPage() {
|
||||
setIsAdmin(membership ? isLeagueAdminOrHigherRole(membership.role) : false);
|
||||
}
|
||||
|
||||
await protestsUseCase.execute(raceId);
|
||||
const protestsViewModel = protestsUseCase.presenter.getViewModel();
|
||||
setProtests(protestsViewModel.protests);
|
||||
const protestsPresenter = new RaceProtestsPresenter();
|
||||
await protestsUseCase.execute({ raceId }, protestsPresenter);
|
||||
const protestsViewModel = protestsPresenter.getViewModel();
|
||||
setProtests(protestsViewModel?.protests ?? []);
|
||||
|
||||
await penaltiesUseCase.execute(raceId);
|
||||
const penaltiesViewModel = penaltiesUseCase.presenter.getViewModel();
|
||||
setPenalties(penaltiesViewModel.penalties);
|
||||
const penaltiesPresenter = new RacePenaltiesPresenter();
|
||||
await penaltiesUseCase.execute({ raceId }, penaltiesPresenter);
|
||||
const penaltiesViewModel = penaltiesPresenter.getViewModel();
|
||||
setPenalties(penaltiesViewModel?.penalties ?? []);
|
||||
} catch (err) {
|
||||
console.error('Failed to load data:', err);
|
||||
} finally {
|
||||
|
||||
@@ -105,8 +105,9 @@ export default function AllRacesPage() {
|
||||
setCurrentPage(1);
|
||||
}, [statusFilter, leagueFilter, searchQuery]);
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
const formatDate = (date: Date | string) => {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
@@ -114,8 +115,9 @@ export default function AllRacesPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
return new Date(date).toLocaleTimeString('en-US', {
|
||||
const formatTime = (date: Date | string) => {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
||||
@@ -93,8 +93,11 @@ export default function RacesPage() {
|
||||
// Group races by date for calendar view
|
||||
const racesByDate = useMemo(() => {
|
||||
const grouped = new Map<string, RaceListItemViewModel[]>();
|
||||
filteredRaces.forEach(race => {
|
||||
const dateKey = new Date(race.scheduledAt).toISOString().split('T')[0];
|
||||
filteredRaces.forEach((race) => {
|
||||
if (typeof race.scheduledAt !== 'string') {
|
||||
return;
|
||||
}
|
||||
const dateKey = race.scheduledAt.split('T')[0]!;
|
||||
if (!grouped.has(dateKey)) {
|
||||
grouped.set(dateKey, []);
|
||||
}
|
||||
@@ -108,23 +111,26 @@ export default function RacesPage() {
|
||||
const recentResults = pageData?.recentResults ?? [];
|
||||
const stats = pageData?.stats ?? { total: 0, scheduled: 0, running: 0, completed: 0 };
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
const formatDate = (date: Date | string) => {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
return new Date(date).toLocaleTimeString('en-US', {
|
||||
const formatTime = (date: Date | string) => {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
const formatFullDate = (date: Date) => {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
const formatFullDate = (date: Date | string) => {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
return d.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
@@ -132,9 +138,10 @@ export default function RacesPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const getRelativeTime = (date: Date) => {
|
||||
const getRelativeTime = (date?: Date | string) => {
|
||||
if (!date) return '';
|
||||
const now = new Date();
|
||||
const targetDate = new Date(date);
|
||||
const targetDate = typeof date === 'string' ? new Date(date) : date;
|
||||
const diffMs = targetDate.getTime() - now.getTime();
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
@@ -144,7 +151,7 @@ export default function RacesPage() {
|
||||
if (diffHours < 24) return `In ${diffHours}h`;
|
||||
if (diffDays === 1) return 'Tomorrow';
|
||||
if (diffDays < 7) return `In ${diffDays} days`;
|
||||
return formatDate(date);
|
||||
return formatDate(targetDate);
|
||||
};
|
||||
|
||||
const statusConfig = {
|
||||
@@ -368,6 +375,9 @@ 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 StatusIcon = config.icon;
|
||||
|
||||
@@ -385,9 +395,13 @@ export default function RacesPage() {
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Time Column */}
|
||||
<div className="flex-shrink-0 text-center min-w-[60px]">
|
||||
<p className="text-lg font-bold text-white">{formatTime(new Date(race.scheduledAt))}</p>
|
||||
<p className="text-lg font-bold text-white">
|
||||
{formatTime(race.scheduledAt)}
|
||||
</p>
|
||||
<p className={`text-xs ${config.color}`}>
|
||||
{race.status === 'running' ? 'LIVE' : getRelativeTime(new Date(race.scheduledAt))}
|
||||
{race.status === 'running'
|
||||
? 'LIVE'
|
||||
: getRelativeTime(race.scheduledAt)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -427,7 +441,7 @@ export default function RacesPage() {
|
||||
{/* League Link */}
|
||||
<div className="mt-3 pt-3 border-t border-charcoal-outline/50">
|
||||
<Link
|
||||
href={`/leagues/${race.leagueId}`}
|
||||
href={`/leagues/${race.leagueId ?? ''}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="inline-flex items-center gap-2 text-sm text-primary-blue hover:underline"
|
||||
>
|
||||
@@ -482,24 +496,30 @@ export default function RacesPage() {
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{upcomingRaces.map((race) => (
|
||||
<div
|
||||
key={race.id}
|
||||
onClick={() => router.push(`/races/${race.id}`)}
|
||||
className="flex items-center gap-3 p-2 rounded-lg hover:bg-deep-graphite cursor-pointer transition-colors"
|
||||
>
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-primary-blue/10 rounded-lg flex items-center justify-center">
|
||||
<span className="text-sm font-bold text-primary-blue">
|
||||
{new Date(race.scheduledAt).getDate()}
|
||||
</span>
|
||||
{upcomingRaces.map((race) => {
|
||||
if (!race.scheduledAt) {
|
||||
return null;
|
||||
}
|
||||
const scheduledAtDate = new Date(race.scheduledAt);
|
||||
return (
|
||||
<div
|
||||
key={race.id}
|
||||
onClick={() => router.push(`/races/${race.id}`)}
|
||||
className="flex items-center gap-3 p-2 rounded-lg hover:bg-deep-graphite cursor-pointer transition-colors"
|
||||
>
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-primary-blue/10 rounded-lg flex items-center justify-center">
|
||||
<span className="text-sm font-bold text-primary-blue">
|
||||
{scheduledAtDate.getDate()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-white truncate">{race.track}</p>
|
||||
<p className="text-xs text-gray-500">{formatTime(scheduledAtDate)}</p>
|
||||
</div>
|
||||
<ChevronRight className="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-white truncate">{race.track}</p>
|
||||
<p className="text-xs text-gray-500">{formatTime(new Date(race.scheduledAt))}</p>
|
||||
</div>
|
||||
<ChevronRight className="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user