website refactor
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
@@ -15,4 +15,8 @@ export interface LeagueSeasonSummaryDTO {
|
||||
endDate?: string;
|
||||
isPrimary: boolean;
|
||||
isParallelActive: boolean;
|
||||
totalRaces: number;
|
||||
completedRaces: number;
|
||||
/** Format: date-time */
|
||||
nextRaceAt?: string;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,7 @@ export interface LeagueStandingDTO {
|
||||
wins: number;
|
||||
podiums: number;
|
||||
races: number;
|
||||
positionChange: number;
|
||||
lastRacePoints: number;
|
||||
droppedRaceIds: string[];
|
||||
}
|
||||
|
||||
@@ -22,4 +22,7 @@ export interface LeagueWithCapacityAndScoringDTO {
|
||||
scoring?: LeagueCapacityAndScoringSummaryScoringDTO;
|
||||
timingSummary?: string;
|
||||
logoUrl?: string;
|
||||
pendingJoinRequestsCount?: number;
|
||||
pendingProtestsCount?: number;
|
||||
walletBalance?: number;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user