196 lines
6.9 KiB
TypeScript
196 lines
6.9 KiB
TypeScript
'use client';
|
|
|
|
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
|
|
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
|
|
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, NextRaceInfo, SeasonProgress, RecentResult } from '@/lib/view-data/LeagueDetailViewData';
|
|
import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder';
|
|
|
|
type LeagueDetailInputDTO = {
|
|
league: LeagueWithCapacityAndScoringDTO;
|
|
owner: GetDriverOutputDTO | null;
|
|
scoringConfig: LeagueScoringConfigDTO | null;
|
|
memberships: LeagueMembershipsDTO;
|
|
races: RaceDTO[];
|
|
sponsors: Array<{
|
|
id: string;
|
|
name: string;
|
|
tier: string;
|
|
logoUrl?: string;
|
|
websiteUrl?: string;
|
|
tagline?: string;
|
|
}>;
|
|
}
|
|
|
|
export class LeagueDetailViewDataBuilder {
|
|
public static build(apiDto: LeagueDetailInputDTO): LeagueDetailViewData {
|
|
const { league, owner, scoringConfig, memberships, races, sponsors } = apiDto;
|
|
|
|
// Calculate running races - using available fields from RaceDTO
|
|
const runningRaces: LiveRaceData[] = races
|
|
.filter(r => r.name.includes('Running')) // Placeholder filter
|
|
.map(r => ({
|
|
id: r.id,
|
|
name: r.name,
|
|
date: r.date,
|
|
registeredCount: 0,
|
|
strengthOfField: 0,
|
|
}));
|
|
|
|
// Calculate info data
|
|
const membersCount = Array.isArray(memberships.members) ? memberships.members.length : 0;
|
|
|
|
// League overview wants total races, not just completed.
|
|
const racesCount = races.length;
|
|
|
|
// Compute real avgSOF from races
|
|
const racesWithSOF = races.filter(r => {
|
|
const sof = (r as RaceDTO & { strengthOfField?: number }).strengthOfField;
|
|
return typeof sof === 'number' && sof > 0;
|
|
});
|
|
const avgSOF = racesWithSOF.length > 0
|
|
? Math.round(racesWithSOF.reduce((sum, r) => sum + ((r as RaceDTO & { strengthOfField?: number }).strengthOfField || 0), 0) / racesWithSOF.length)
|
|
: null;
|
|
|
|
const info: LeagueInfoData = {
|
|
name: league.name,
|
|
description: league.description || '',
|
|
membersCount,
|
|
racesCount,
|
|
avgSOF,
|
|
structure: `Solo • ${league.settings?.maxDrivers ?? 32} max`,
|
|
scoring: scoringConfig?.scoringPresetId || 'Standard',
|
|
createdAt: league.createdAt,
|
|
discordUrl: league.socialLinks?.discordUrl,
|
|
youtubeUrl: league.socialLinks?.youtubeUrl,
|
|
websiteUrl: league.socialLinks?.websiteUrl,
|
|
};
|
|
|
|
// Convert owner to driver summary
|
|
const ownerSummary: DriverSummaryData | null = owner ? {
|
|
driverId: owner.id,
|
|
driverName: owner.name,
|
|
avatarUrl: owner.avatarUrl || null,
|
|
rating: null,
|
|
rank: null,
|
|
roleBadgeText: 'Owner',
|
|
roleBadgeClasses: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30',
|
|
profileUrl: `/drivers/${owner.id}`,
|
|
} : null;
|
|
|
|
// Convert sponsors
|
|
const sponsorInfo: SponsorInfo[] = sponsors.map(s => ({
|
|
id: s.id,
|
|
name: s.name,
|
|
tier: s.tier,
|
|
logoUrl: s.logoUrl,
|
|
websiteUrl: s.websiteUrl,
|
|
tagline: s.tagline,
|
|
}));
|
|
|
|
// Convert memberships to summaries
|
|
const adminSummaries: DriverSummaryData[] = (memberships.members || [])
|
|
.filter(m => m.role === 'admin')
|
|
.map(m => ({
|
|
driverId: m.driverId,
|
|
driverName: m.driver.name,
|
|
avatarUrl: (m.driver as GetDriverOutputDTO & { avatarUrl?: string }).avatarUrl || null,
|
|
rating: null,
|
|
rank: null,
|
|
roleBadgeText: 'Admin',
|
|
roleBadgeClasses: 'bg-blue-500/10 text-blue-500 border-blue-500/30',
|
|
profileUrl: `/drivers/${m.driverId}`,
|
|
}));
|
|
|
|
const stewardSummaries: DriverSummaryData[] = (memberships.members || [])
|
|
.filter(m => m.role === 'steward')
|
|
.map(m => ({
|
|
driverId: m.driverId,
|
|
driverName: m.driver.name,
|
|
avatarUrl: (m.driver as GetDriverOutputDTO & { avatarUrl?: string }).avatarUrl || null,
|
|
rating: null,
|
|
rank: null,
|
|
roleBadgeText: 'Steward',
|
|
roleBadgeClasses: 'bg-purple-500/10 text-purple-500 border-purple-500/30',
|
|
profileUrl: `/drivers/${m.driverId}`,
|
|
}));
|
|
|
|
const memberSummaries: DriverSummaryData[] = (memberships.members || [])
|
|
.filter(m => m.role === 'member')
|
|
.map(m => ({
|
|
driverId: m.driverId,
|
|
driverName: m.driver.name,
|
|
avatarUrl: (m.driver as GetDriverOutputDTO & { avatarUrl?: string }).avatarUrl || null,
|
|
rating: null,
|
|
rank: null,
|
|
roleBadgeText: 'Member',
|
|
roleBadgeClasses: 'bg-zinc-500/10 text-zinc-500 border-zinc-500/30',
|
|
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 RaceDTO & { track?: string }).track || '',
|
|
car: (r as RaceDTO & { car?: string }).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 RaceDTO & { position?: number }).position || 0,
|
|
points: (r as RaceDTO & { points?: number }).points || 0,
|
|
finishedAt: r.date,
|
|
}));
|
|
|
|
return {
|
|
leagueId: league.id,
|
|
name: league.name,
|
|
description: league.description || '',
|
|
logoUrl: league.logoUrl,
|
|
info,
|
|
runningRaces,
|
|
sponsors: sponsorInfo,
|
|
ownerSummary,
|
|
adminSummaries,
|
|
stewardSummaries,
|
|
memberSummaries,
|
|
sponsorInsights: null,
|
|
nextRace,
|
|
seasonProgress,
|
|
recentResults,
|
|
walletBalance: league.walletBalance ?? 0,
|
|
pendingProtestsCount: league.pendingProtestsCount ?? 0,
|
|
pendingJoinRequestsCount: league.pendingJoinRequestsCount ?? 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
LeagueDetailViewDataBuilder satisfies ViewDataBuilder<LeagueDetailInputDTO, LeagueDetailViewData>;
|