website refactor

This commit is contained in:
2026-01-21 17:50:02 +01:00
parent 4b54c3603b
commit 02987f60c8
29 changed files with 1673 additions and 35 deletions

View File

@@ -3,7 +3,7 @@ import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershi
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
import type { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO';
import type { LeagueDetailViewData, LeagueInfoData, LiveRaceData, DriverSummaryData, SponsorInfo } from '@/lib/view-data/LeagueDetailViewData';
import type { LeagueDetailViewData, LeagueInfoData, LiveRaceData, DriverSummaryData, SponsorInfo, NextRaceInfo, SeasonProgress, RecentResult } from '@/lib/view-data/LeagueDetailViewData';
/**
* LeagueDetailViewDataBuilder
@@ -138,6 +138,45 @@ export class LeagueDetailViewDataBuilder {
profileUrl: `/drivers/${m.driverId}`,
}));
// Calculate next race (first upcoming race)
const now = new Date();
const nextRace: NextRaceInfo | undefined = races
.filter(r => new Date(r.date) > now)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
.map(r => ({
id: r.id,
name: r.name,
date: r.date,
track: (r as any).track,
car: (r as any).car,
}))[0];
// Calculate season progress (completed races vs total races)
const completedRaces = races.filter(r => {
const raceDate = new Date(r.date);
return raceDate < now;
}).length;
const totalRaces = races.length;
const percentage = totalRaces > 0 ? Math.round((completedRaces / totalRaces) * 100) : 0;
const seasonProgress: SeasonProgress = {
completedRaces,
totalRaces,
percentage,
};
// Get recent results (top 3 from last completed race)
const recentResults: RecentResult[] = races
.filter(r => new Date(r.date) < now)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.slice(0, 3)
.map(r => ({
raceId: r.id,
raceName: r.name,
position: (r as any).position || 0,
points: (r as any).points || 0,
finishedAt: r.date,
}));
return {
leagueId: league.id,
name: league.name,
@@ -151,6 +190,12 @@ export class LeagueDetailViewDataBuilder {
stewardSummaries,
memberSummaries,
sponsorInsights: null, // Only for sponsor mode
nextRace,
seasonProgress,
recentResults,
walletBalance: league.walletBalance,
pendingProtestsCount: league.pendingProtestsCount,
pendingJoinRequestsCount: league.pendingJoinRequestsCount,
};
}
}

View File

@@ -2,7 +2,7 @@ import { LeagueScheduleViewData } from '@/lib/view-data/leagues/LeagueScheduleVi
import { LeagueScheduleApiDto } from '@/lib/types/tbd/LeagueScheduleApiDto';
export class LeagueScheduleViewDataBuilder {
static build(apiDto: LeagueScheduleApiDto): LeagueScheduleViewData {
static build(apiDto: LeagueScheduleApiDto, currentDriverId?: string, isAdmin: boolean = false): LeagueScheduleViewData {
const now = new Date();
return {
@@ -22,8 +22,16 @@ export class LeagueScheduleViewDataBuilder {
isPast,
isUpcoming,
status: isPast ? 'completed' : 'scheduled',
// Registration info (would come from API in real implementation)
isUserRegistered: false,
canRegister: isUpcoming,
// Admin info
canEdit: isAdmin,
canReschedule: isAdmin,
};
}),
currentDriverId,
isAdmin,
};
}
}

View File

@@ -20,7 +20,8 @@ export class LeagueStandingsViewDataBuilder {
static build(
standingsDto: LeagueStandingsApiDto,
membershipsDto: LeagueMembershipsApiDto,
leagueId: string
leagueId: string,
isTeamChampionship: boolean = false
): LeagueStandingsViewData {
const standings = standingsDto.standings || [];
const members = membershipsDto.members || [];
@@ -35,6 +36,12 @@ export class LeagueStandingsViewDataBuilder {
avgFinish: null, // Not in DTO
penaltyPoints: 0, // Not in DTO
bonusPoints: 0, // Not in DTO
// New fields from Phase 3
positionChange: standing.positionChange || 0,
lastRacePoints: standing.lastRacePoints || 0,
droppedRaceIds: standing.droppedRaceIds || [],
wins: standing.wins || 0,
podiums: standing.podiums || 0,
}));
// Extract unique drivers from standings
@@ -70,6 +77,7 @@ export class LeagueStandingsViewDataBuilder {
leagueId,
currentDriverId: null, // Would need to get from auth
isAdmin: false, // Would need to check permissions
isTeamChampionship: isTeamChampionship,
};
}
}

View File

@@ -1,7 +1,6 @@
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
import { RacesApiClient } from "@/lib/api/races/RacesApiClient";
import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient";
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';
import { Result } from '@/lib/contracts/Result';
@@ -27,6 +26,7 @@ import type { UpdateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/Upd
import type { MembershipRole } from "@/lib/types/MembershipRole";
import { injectable, unmanaged } from 'inversify';
// TODO these data interfaces violate our architecture, see VIEW_DATA
export interface LeagueScheduleAdminData {
leagueId: string;
seasonId: string;
@@ -61,7 +61,6 @@ export class LeagueService implements Service {
private readonly baseUrl: string;
private apiClient: LeaguesApiClient;
private driversApiClient: DriversApiClient;
private sponsorsApiClient: SponsorsApiClient;
private racesApiClient: RacesApiClient;
constructor(@unmanaged() apiClient?: LeaguesApiClient) {
@@ -81,7 +80,6 @@ export class LeagueService implements Service {
}
this.driversApiClient = new DriversApiClient(baseUrl, errorReporter, logger);
this.sponsorsApiClient = new SponsorsApiClient(baseUrl, errorReporter, logger);
this.racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
}

View File

@@ -41,6 +41,9 @@ export class LeagueStandingsService implements Service {
wins: s.wins,
podiums: s.podiums,
races: s.races,
positionChange: s.positionChange,
lastRacePoints: s.lastRacePoints,
droppedRaceIds: s.droppedRaceIds,
})),
};

View File

@@ -15,4 +15,8 @@ export interface LeagueSeasonSummaryDTO {
endDate?: string;
isPrimary: boolean;
isParallelActive: boolean;
totalRaces: number;
completedRaces: number;
/** Format: date-time */
nextRaceAt?: string;
}

View File

@@ -15,4 +15,7 @@ export interface LeagueStandingDTO {
wins: number;
podiums: number;
races: number;
positionChange: number;
lastRacePoints: number;
droppedRaceIds: string[];
}

View File

@@ -22,4 +22,7 @@ export interface LeagueWithCapacityAndScoringDTO {
scoring?: LeagueCapacityAndScoringSummaryScoringDTO;
timingSummary?: string;
logoUrl?: string;
pendingJoinRequestsCount?: number;
pendingProtestsCount?: number;
walletBalance?: number;
}

View File

@@ -65,6 +65,28 @@ export interface SponsorshipSlot {
benefits: string[];
}
export interface NextRaceInfo {
id: string;
name: string;
date: string;
track?: string;
car?: string;
}
export interface SeasonProgress {
completedRaces: number;
totalRaces: number;
percentage: number;
}
export interface RecentResult {
raceId: string;
raceName: string;
position: number;
points: number;
finishedAt: string;
}
export interface LeagueDetailViewData extends ViewData {
// Basic info
leagueId: string;
@@ -104,4 +126,14 @@ export interface LeagueDetailViewData extends ViewData {
metrics: SponsorMetric[];
slots: SponsorshipSlot[];
} | null;
// New fields for enhanced league pages
nextRace?: NextRaceInfo;
seasonProgress?: SeasonProgress;
recentResults?: RecentResult[];
// Admin fields
walletBalance?: number;
pendingProtestsCount?: number;
pendingJoinRequestsCount?: number;
}

View File

@@ -13,6 +13,12 @@ export interface StandingEntryData {
penaltyPoints: number;
bonusPoints: number;
teamName?: string;
// New fields from Phase 3
positionChange: number;
lastRacePoints: number;
droppedRaceIds: string[];
wins: number;
podiums: number;
}
export interface DriverData {
@@ -39,4 +45,6 @@ export interface LeagueStandingsViewData {
leagueId: string;
currentDriverId: string | null;
isAdmin: boolean;
// New fields for team standings toggle
isTeamChampionship?: boolean;
}

View File

@@ -11,5 +11,14 @@ export interface LeagueScheduleViewData {
isUpcoming: boolean;
status: 'scheduled' | 'completed';
strengthOfField?: number;
// Registration info
isUserRegistered?: boolean;
canRegister?: boolean;
// Admin info
canEdit?: boolean;
canReschedule?: boolean;
}>;
// User permissions
currentDriverId?: string;
isAdmin: boolean;
}