'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;