Files
gridpilot.gg/apps/website/lib/view-models/LeagueDetailPageViewModel.ts
2026-01-24 01:25:46 +01:00

104 lines
3.9 KiB
TypeScript

import { ViewModel } from "../contracts/view-models/ViewModel";
import type { LeagueDetailPageViewData, LeagueMembershipWithRole, SponsorInfo } from '../view-data/LeagueDetailPageViewData';
import { DriverViewModel } from './DriverViewModel';
import { RaceViewModel } from './RaceViewModel';
export interface DriverSummary {
driver: DriverViewModel;
rating: number | null;
rank: number | null;
}
export class LeagueDetailPageViewModel extends ViewModel {
private readonly data: LeagueDetailPageViewData;
readonly allRaces: RaceViewModel[];
readonly runningRaces: RaceViewModel[];
readonly ownerSummary: DriverSummary | null;
readonly adminSummaries: DriverSummary[];
readonly stewardSummaries: DriverSummary[];
constructor(data: LeagueDetailPageViewData) {
super();
this.data = data;
this.allRaces = data.allRaces.map(r => r instanceof RaceViewModel ? r : new RaceViewModel(r));
this.runningRaces = this.allRaces.filter(r => r.status === 'running');
// Build driver summaries
this.ownerSummary = this.buildDriverSummary(data.ownerId);
this.adminSummaries = data.memberships
.filter(m => m.role === 'admin')
.slice(0, 3)
.map(m => this.buildDriverSummary(m.driverId))
.filter((s): s is DriverSummary => s !== null);
this.stewardSummaries = data.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 driverData = this.data.drivers.find(d => d.id === driverId) ||
(this.data.owner?.id === driverId ? this.data.owner : null);
if (!driverData) return null;
const driver = new DriverViewModel(driverData);
return {
driver,
rating: null,
rank: null,
};
}
get id(): string { return this.data.id; }
get name(): string { return this.data.name; }
get description(): string { return this.data.description ?? ''; }
get ownerId(): string { return this.data.ownerId; }
get createdAt(): string { return this.data.createdAt; }
get settings() { return this.data.settings; }
get socialLinks() { return this.data.socialLinks; }
get owner() { return this.data.owner; }
get scoringConfig() { return this.data.scoringConfig; }
get drivers() { return this.data.drivers; }
get memberships() { return this.data.memberships; }
get averageSOF(): number | null { return this.data.averageSOF; }
get completedRacesCount(): number { return this.data.completedRacesCount; }
get sponsors(): SponsorInfo[] { return this.data.sponsors; }
get sponsorInsights() {
const memberCount = this.memberships.length;
const mainSponsorTaken = this.sponsors.some(s => s.tier === 'main');
const secondaryTaken = this.sponsors.filter(s => s.tier === 'secondary').length;
return {
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),
};
}
get isSponsorMode(): boolean {
return false;
}
get currentUserMembership(): LeagueMembershipWithRole | null {
return null;
}
get canEndRaces(): boolean {
return this.currentUserMembership?.role === 'admin' || this.currentUserMembership?.role === 'owner';
}
}