import { LeagueWithCapacityDTO } from '../types/generated/LeagueWithCapacityDTO'; import { LeagueStatsDTO } from '../types/generated/LeagueStatsDTO'; import { LeagueMembershipsDTO } from '../types/generated/LeagueMembershipsDTO'; import { LeagueScheduleDTO } from '../types/generated/LeagueScheduleDTO'; import { LeagueStandingsDTO } from '../types/generated/LeagueStandingsDTO'; import { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO'; import { RaceDTO } from '../types/generated/RaceDTO'; import { LeagueScoringConfigDTO } from '../types/LeagueScoringConfigDTO'; import { RaceViewModel } from './RaceViewModel'; // Sponsor info type export interface SponsorInfo { id: string; name: string; logoUrl?: string; websiteUrl?: string; tier: 'main' | 'secondary'; tagline?: string; } // Driver summary for management section export interface DriverSummary { driver: GetDriverOutputDTO; rating: number | null; rank: number | null; } // League membership with role export interface LeagueMembershipWithRole { driverId: string; role: 'owner' | 'admin' | 'steward' | 'member'; status: 'active' | 'inactive'; joinedAt: string; } export class LeagueDetailPageViewModel { // League basic info id: string; name: string; description?: string; ownerId: string; createdAt: string; settings: { maxDrivers?: number; }; socialLinks?: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string; }; // Owner info owner: GetDriverOutputDTO | null; // Scoring configuration scoringConfig: LeagueScoringConfigDTO | null; // Drivers and memberships drivers: GetDriverOutputDTO[]; memberships: LeagueMembershipWithRole[]; // Races allRaces: RaceViewModel[]; runningRaces: RaceViewModel[]; // Stats averageSOF: number | null; completedRacesCount: number; // Sponsors sponsors: SponsorInfo[]; // Sponsor insights data sponsorInsights: { avgViewsPerRace: number; totalImpressions: number; engagementRate: string; estimatedReach: number; mainSponsorAvailable: boolean; secondarySlotsAvailable: number; mainSponsorPrice: number; secondaryPrice: number; tier: 'premium' | 'standard' | 'starter'; trustScore: number; discordMembers: number; monthlyActivity: number; }; // Driver summaries for management ownerSummary: DriverSummary | null; adminSummaries: DriverSummary[]; stewardSummaries: DriverSummary[]; constructor( league: LeagueWithCapacityDTO, owner: GetDriverOutputDTO | null, scoringConfig: LeagueScoringConfigDTO | null, drivers: GetDriverOutputDTO[], memberships: LeagueMembershipsDTO, allRaces: RaceViewModel[], leagueStats: LeagueStatsDTO, sponsors: SponsorInfo[] ) { this.id = league.id; this.name = league.name; this.description = league.description; this.ownerId = league.ownerId; this.createdAt = league.createdAt; this.settings = { maxDrivers: league.maxDrivers, }; this.socialLinks = league.socialLinks; this.owner = owner; this.scoringConfig = scoringConfig; this.drivers = drivers; this.memberships = memberships.memberships.map(m => ({ driverId: m.driverId, role: m.role, status: m.status, joinedAt: m.joinedAt, })); this.allRaces = allRaces; this.runningRaces = allRaces.filter(r => r.status === 'running'); this.averageSOF = leagueStats.averageSOF ?? null; this.completedRacesCount = leagueStats.completedRaces ?? 0; this.sponsors = sponsors; // Calculate sponsor insights const memberCount = this.memberships.length; const mainSponsorTaken = this.sponsors.some(s => s.tier === 'main'); const secondaryTaken = this.sponsors.filter(s => s.tier === 'secondary').length; this.sponsorInsights = { avgViewsPerRace: 5400 + memberCount * 50, totalImpressions: 45000 + memberCount * 500, engagementRate: (3.5 + (memberCount / 50)).toFixed(1), estimatedReach: memberCount * 150, mainSponsorAvailable: !mainSponsorTaken, secondarySlotsAvailable: Math.max(0, 2 - secondaryTaken), mainSponsorPrice: 800 + Math.floor(memberCount * 10), secondaryPrice: 250 + Math.floor(memberCount * 3), tier: (this.averageSOF && this.averageSOF > 3000 ? 'premium' : this.averageSOF && this.averageSOF > 2000 ? 'standard' : 'starter') as 'premium' | 'standard' | 'starter', trustScore: Math.min(100, 60 + memberCount + this.completedRacesCount), discordMembers: memberCount * 3, monthlyActivity: Math.min(100, 40 + this.completedRacesCount * 2), }; // Build driver summaries this.ownerSummary = this.buildDriverSummary(this.ownerId); this.adminSummaries = this.memberships .filter(m => m.role === 'admin') .slice(0, 3) .map(m => this.buildDriverSummary(m.driverId)) .filter((s): s is DriverSummary => s !== null); this.stewardSummaries = this.memberships .filter(m => m.role === 'steward') .slice(0, 3) .map(m => this.buildDriverSummary(m.driverId)) .filter((s): s is DriverSummary => s !== null); } private buildDriverSummary(driverId: string): DriverSummary | null { const driver = this.drivers.find(d => d.id === driverId); if (!driver) return null; // Detailed rating and rank data are not wired from the analytics services yet; // expose the driver identity only so the UI can still render role assignments. return { driver, rating: null, rank: null, }; } // UI helper methods get isSponsorMode(): boolean { // League detail pages are rendered in organizer mode in this build; sponsor-specific // mode switches will be introduced once sponsor dashboards share this view model. return false; } get currentUserMembership(): LeagueMembershipWithRole | null { // Current user identity is not available in this view model context yet; callers must // pass an explicit membership if they need per-user permissions. return null; } get canEndRaces(): boolean { return this.currentUserMembership?.role === 'admin' || this.currentUserMembership?.role === 'owner'; } }