207 lines
6.5 KiB
TypeScript
207 lines
6.5 KiB
TypeScript
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 '@/lib/types/generated/LeagueScoringConfigDTO';
|
|
import { RaceViewModel } from './RaceViewModel';
|
|
import { DriverViewModel } from './DriverViewModel';
|
|
|
|
// 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: DriverViewModel;
|
|
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;
|
|
} | undefined;
|
|
|
|
// 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.settings?.maxDrivers,
|
|
};
|
|
this.socialLinks = {
|
|
discordUrl: league.discordUrl,
|
|
youtubeUrl: league.youtubeUrl,
|
|
websiteUrl: league.websiteUrl,
|
|
};
|
|
|
|
this.owner = owner;
|
|
this.scoringConfig = scoringConfig;
|
|
this.drivers = drivers;
|
|
this.memberships = memberships.members.map(m => ({
|
|
driverId: m.driverId,
|
|
role: m.role as 'owner' | 'admin' | 'steward' | 'member',
|
|
status: 'active',
|
|
joinedAt: m.joinedAt,
|
|
}));
|
|
|
|
this.allRaces = allRaces;
|
|
this.runningRaces = allRaces.filter(r => r.status === 'running');
|
|
|
|
// Calculate SOF from available data
|
|
this.averageSOF = leagueStats.averageRating ?? null;
|
|
this.completedRacesCount = leagueStats.totalRaces ?? 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 driverDto = this.drivers.find(d => d.id === driverId);
|
|
if (!driverDto) return null;
|
|
|
|
const driver = new DriverViewModel({
|
|
id: driverDto.id,
|
|
name: driverDto.name,
|
|
iracingId: driverDto.iracingId,
|
|
});
|
|
|
|
// 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';
|
|
}
|
|
} |