This commit is contained in:
2025-12-12 01:11:36 +01:00
parent ec3ddc3a5c
commit 6a88fe93ab
125 changed files with 1513 additions and 803 deletions

View File

@@ -33,6 +33,7 @@ import {
getSponsorRepository,
getSeasonSponsorshipRepository,
} from '@/lib/di-container';
import { LeagueScoringConfigPresenter } from '@/lib/presenters/LeagueScoringConfigPresenter';
import { Trophy, Star, ExternalLink } from 'lucide-react';
import { getMembership, getLeagueMembers } from '@/lib/leagueMembership';
import { useEffectiveDriverId } from '@/lib/currentDriver';
@@ -125,8 +126,9 @@ export default function LeagueDetailPage() {
// Load scoring configuration for the active season
const getLeagueScoringConfigUseCase = getGetLeagueScoringConfigUseCase();
await getLeagueScoringConfigUseCase.execute({ leagueId });
const scoringViewModel = getLeagueScoringConfigUseCase.presenter.getViewModel();
const scoringPresenter = new LeagueScoringConfigPresenter();
await getLeagueScoringConfigUseCase.execute({ leagueId }, scoringPresenter);
const scoringViewModel = scoringPresenter.getViewModel();
setScoringConfig(scoringViewModel as unknown as LeagueScoringConfigDTO);
// Load all drivers for standings and map to DTOs for UI components

View File

@@ -7,6 +7,7 @@ import {
getLeagueRepository,
getGetLeagueScoringConfigUseCase
} from '@/lib/di-container';
import { LeagueScoringConfigPresenter } from '@/lib/presenters/LeagueScoringConfigPresenter';
import type { LeagueScoringConfigDTO } from '@gridpilot/racing/application/dto/LeagueScoringConfigDTO';
import type { League } from '@gridpilot/racing/domain/entities/League';
@@ -35,8 +36,9 @@ export default function LeagueRulebookPage() {
setLeague(leagueData);
await scoringUseCase.execute({ leagueId });
const scoringViewModel = scoringUseCase.presenter.getViewModel();
const scoringPresenter = new LeagueScoringConfigPresenter();
await scoringUseCase.execute({ leagueId }, scoringPresenter);
const scoringViewModel = scoringPresenter.getViewModel();
setScoringConfig(scoringViewModel as unknown as LeagueScoringConfigDTO);
} catch (err) {
console.error('Failed to load scoring config:', err);

View File

@@ -14,6 +14,7 @@ import {
getDriverRepository,
getLeagueMembershipRepository
} from '@/lib/di-container';
import { LeagueDriverSeasonStatsPresenter } from '@/lib/presenters/LeagueDriverSeasonStatsPresenter';
import { useEffectiveDriverId } from '@/lib/currentDriver';
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
import type { MembershipRole, LeagueMembership } from '@/lib/leagueMembership';
@@ -36,15 +37,9 @@ export default function LeagueStandingsPage() {
const driverRepo = getDriverRepository();
const membershipRepo = getLeagueMembershipRepository();
await getLeagueDriverSeasonStatsUseCase.execute({ leagueId });
type GetLeagueDriverSeasonStatsUseCaseType = {
presenter: {
getViewModel(): { stats: LeagueDriverSeasonStatsDTO[] };
};
};
const typedUseCase =
getLeagueDriverSeasonStatsUseCase as GetLeagueDriverSeasonStatsUseCaseType;
const standingsViewModel = typedUseCase.presenter.getViewModel();
const presenter = new LeagueDriverSeasonStatsPresenter();
await getLeagueDriverSeasonStatsUseCase.execute({ leagueId }, presenter);
const standingsViewModel = presenter.getViewModel();
setStandings(standingsViewModel.stats);
const allDrivers = await driverRepo.findAll();

View File

@@ -30,7 +30,8 @@ import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Input from '@/components/ui/Input';
import Heading from '@/components/ui/Heading';
import type { LeagueSummaryDTO } from '@gridpilot/racing/application/dto/LeagueSummaryDTO';
import type { LeagueSummaryViewModel } from '@gridpilot/racing/application/presenters/IAllLeaguesWithCapacityAndScoringPresenter';
import { AllLeaguesWithCapacityAndScoringPresenter } from '@/lib/presenters/AllLeaguesWithCapacityAndScoringPresenter';
import { getGetAllLeaguesWithCapacityAndScoringUseCase } from '@/lib/di-container';
// ============================================================================
@@ -57,7 +58,7 @@ interface Category {
label: string;
icon: React.ElementType;
description: string;
filter: (league: LeagueSummaryDTO) => boolean;
filter: (league: LeagueSummaryViewModel) => boolean;
color?: string;
}
@@ -175,7 +176,7 @@ interface LeagueSliderProps {
title: string;
icon: React.ElementType;
description: string;
leagues: LeagueSummaryDTO[];
leagues: LeagueSummaryViewModel[];
onLeagueClick: (id: string) => void;
autoScroll?: boolean;
iconColor?: string;
@@ -377,25 +378,23 @@ function LeagueSlider({
export default function LeaguesPage() {
const router = useRouter();
const [realLeagues, setRealLeagues] = useState<LeagueSummaryDTO[]>([]);
const [realLeagues, setRealLeagues] = useState<LeagueSummaryViewModel[]>([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [activeCategory, setActiveCategory] = useState<CategoryId>('all');
const [showFilters, setShowFilters] = useState(false);
useEffect(() => {
loadLeagues();
void loadLeagues();
}, []);
const loadLeagues = async () => {
try {
const useCase = getGetAllLeaguesWithCapacityAndScoringUseCase();
await useCase.execute();
const presenter = useCase.presenter as unknown as {
getViewModel(): { leagues: LeagueSummaryDTO[] };
};
const presenter = new AllLeaguesWithCapacityAndScoringPresenter();
await useCase.execute(undefined as void, presenter);
const viewModel = presenter.getViewModel();
setRealLeagues(viewModel.leagues);
setRealLeagues(viewModel?.leagues ?? []);
} catch (error) {
console.error('Failed to load leagues:', error);
} finally {
@@ -434,7 +433,7 @@ export default function LeaguesPage() {
acc[category.id] = searchFilteredLeagues.filter(category.filter);
return acc;
},
{} as Record<CategoryId, LeagueSummaryDTO[]>,
{} as Record<CategoryId, LeagueSummaryViewModel[]>,
);
// Featured categories to show as sliders with different scroll speeds and alternating directions