wip
This commit is contained in:
@@ -2,14 +2,13 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Race } from '@gridpilot/racing/domain/entities/Race';
|
||||
import {
|
||||
getRaceRepository,
|
||||
getIsDriverRegisteredForRaceQuery,
|
||||
getRegisterForRaceUseCase,
|
||||
getWithdrawFromRaceUseCase,
|
||||
} from '@/lib/di-container';
|
||||
import { useEffectiveDriverId } from '@/lib/currentDriver';
|
||||
import {
|
||||
loadLeagueSchedule,
|
||||
registerForRace,
|
||||
withdrawFromRace,
|
||||
type LeagueScheduleRaceItemViewModel,
|
||||
} from '@/lib/presenters/LeagueSchedulePresenter';
|
||||
|
||||
interface LeagueScheduleProps {
|
||||
leagueId: string;
|
||||
@@ -17,7 +16,7 @@ interface LeagueScheduleProps {
|
||||
|
||||
export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
const router = useRouter();
|
||||
const [races, setRaces] = useState<Race[]>([]);
|
||||
const [races, setRaces] = useState<LeagueScheduleRaceItemViewModel[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState<'all' | 'upcoming' | 'past'>('upcoming');
|
||||
const [registrationStates, setRegistrationStates] = useState<Record<string, boolean>>({});
|
||||
@@ -25,30 +24,16 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
|
||||
const loadRaces = useCallback(async () => {
|
||||
const loadRacesCallback = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const raceRepo = getRaceRepository();
|
||||
const allRaces = await raceRepo.findAll();
|
||||
const leagueRaces = allRaces
|
||||
.filter((race) => race.leagueId === leagueId)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime(),
|
||||
);
|
||||
setRaces(leagueRaces);
|
||||
const viewModel = await loadLeagueSchedule(leagueId, currentDriverId);
|
||||
setRaces(viewModel.races);
|
||||
|
||||
const isRegisteredQuery = getIsDriverRegisteredForRaceQuery();
|
||||
const states: Record<string, boolean> = {};
|
||||
await Promise.all(
|
||||
leagueRaces.map(async (race) => {
|
||||
const registered = await isRegisteredQuery.execute({
|
||||
raceId: race.id,
|
||||
driverId: currentDriverId,
|
||||
});
|
||||
states[race.id] = registered;
|
||||
}),
|
||||
);
|
||||
for (const race of viewModel.races) {
|
||||
states[race.id] = race.isRegistered;
|
||||
}
|
||||
setRegistrationStates(states);
|
||||
} catch (error) {
|
||||
console.error('Failed to load races:', error);
|
||||
@@ -58,27 +43,19 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
}, [leagueId, currentDriverId]);
|
||||
|
||||
useEffect(() => {
|
||||
loadRaces();
|
||||
}, [loadRaces]);
|
||||
void loadRacesCallback();
|
||||
}, [loadRacesCallback]);
|
||||
|
||||
|
||||
const handleRegister = async (race: Race, e: React.MouseEvent) => {
|
||||
const handleRegister = async (race: LeagueScheduleRaceItemViewModel, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const confirmed = window.confirm(
|
||||
`Register for ${race.track}?`
|
||||
);
|
||||
|
||||
const confirmed = window.confirm(`Register for ${race.track}?`);
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
setProcessingRace(race.id);
|
||||
try {
|
||||
const useCase = getRegisterForRaceUseCase();
|
||||
await useCase.execute({
|
||||
raceId: race.id,
|
||||
leagueId,
|
||||
driverId: currentDriverId,
|
||||
});
|
||||
await registerForRace(race.id, leagueId, currentDriverId);
|
||||
setRegistrationStates((prev) => ({ ...prev, [race.id]: true }));
|
||||
} catch (err) {
|
||||
alert(err instanceof Error ? err.message : 'Failed to register');
|
||||
@@ -87,22 +64,16 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleWithdraw = async (race: Race, e: React.MouseEvent) => {
|
||||
const handleWithdraw = async (race: LeagueScheduleRaceItemViewModel, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
const confirmed = window.confirm(
|
||||
'Withdraw from this race?'
|
||||
);
|
||||
const confirmed = window.confirm('Withdraw from this race?');
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
setProcessingRace(race.id);
|
||||
try {
|
||||
const useCase = getWithdrawFromRaceUseCase();
|
||||
await useCase.execute({
|
||||
raceId: race.id,
|
||||
driverId: currentDriverId,
|
||||
});
|
||||
await withdrawFromRace(race.id, currentDriverId);
|
||||
setRegistrationStates((prev) => ({ ...prev, [race.id]: false }));
|
||||
} catch (err) {
|
||||
alert(err instanceof Error ? err.message : 'Failed to withdraw');
|
||||
@@ -111,18 +82,17 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const now = new Date();
|
||||
const upcomingRaces = races.filter(race => race.status === 'scheduled' && new Date(race.scheduledAt) > now);
|
||||
const pastRaces = races.filter(race => race.status === 'completed' || new Date(race.scheduledAt) <= now);
|
||||
const upcomingRaces = races.filter((race) => race.isUpcoming);
|
||||
const pastRaces = races.filter((race) => race.isPast);
|
||||
|
||||
const getDisplayRaces = () => {
|
||||
switch (filter) {
|
||||
case 'upcoming':
|
||||
return upcomingRaces;
|
||||
case 'past':
|
||||
return pastRaces.reverse();
|
||||
return [...pastRaces].reverse();
|
||||
case 'all':
|
||||
return [...upcomingRaces, ...pastRaces.reverse()];
|
||||
return [...upcomingRaces, ...[...pastRaces].reverse()];
|
||||
default:
|
||||
return races;
|
||||
}
|
||||
@@ -190,8 +160,8 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{displayRaces.map((race) => {
|
||||
const isPast = race.status === 'completed' || new Date(race.scheduledAt) <= now;
|
||||
const isUpcoming = race.status === 'scheduled' && new Date(race.scheduledAt) > now;
|
||||
const isPast = race.isPast;
|
||||
const isUpcoming = race.isUpcoming;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -231,19 +201,19 @@ export default function LeagueSchedule({ leagueId }: LeagueScheduleProps) {
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-right">
|
||||
<p className="text-white font-medium">
|
||||
{new Date(race.scheduledAt).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{new Date(race.scheduledAt).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</p>
|
||||
<p className="text-white font-medium">
|
||||
{race.scheduledAt.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{race.scheduledAt.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</p>
|
||||
{isPast && race.status === 'completed' && (
|
||||
<p className="text-xs text-primary-blue mt-1">View Results →</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user