view data fixes
This commit is contained in:
@@ -1,192 +1,79 @@
|
||||
import { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
|
||||
import { LeagueStatsDTO } from '@/lib/types/generated/LeagueStatsDTO';
|
||||
import { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
|
||||
import { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
|
||||
import { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO';
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
import { RaceViewModel } from './RaceViewModel';
|
||||
import { DriverViewModel } from './DriverViewModel';
|
||||
import type { LeagueDetailPageViewData, LeagueMembershipWithRole, SponsorInfo } from '../view-data/LeagueDetailPageViewData';
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Helper interfaces for type narrowing
|
||||
interface LeagueSettings {
|
||||
maxDrivers?: number;
|
||||
}
|
||||
|
||||
interface SocialLinks {
|
||||
discordUrl?: string;
|
||||
youtubeUrl?: string;
|
||||
websiteUrl?: string;
|
||||
}
|
||||
|
||||
interface LeagueStatsExtended {
|
||||
averageSOF?: number;
|
||||
averageRating?: number;
|
||||
completedRaces?: number;
|
||||
totalRaces?: number;
|
||||
}
|
||||
|
||||
interface MembershipsContainer {
|
||||
members?: Array<{ driverId: string; role: string; status?: 'active' | 'inactive'; joinedAt: string }>;
|
||||
memberships?: Array<{ driverId: string; role: string; status?: 'active' | 'inactive'; joinedAt: string }>;
|
||||
}
|
||||
|
||||
import { ViewModel } from "../contracts/view-models/ViewModel";
|
||||
|
||||
export class LeagueDetailPageViewModel extends ViewModel {
|
||||
// League basic info
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
ownerId: string;
|
||||
createdAt: string;
|
||||
settings: {
|
||||
maxDrivers?: number;
|
||||
};
|
||||
socialLinks: {
|
||||
discordUrl?: string;
|
||||
youtubeUrl?: string;
|
||||
websiteUrl?: string;
|
||||
} | undefined;
|
||||
private readonly data: LeagueDetailPageViewData;
|
||||
readonly allRaces: RaceViewModel[];
|
||||
readonly runningRaces: RaceViewModel[];
|
||||
readonly ownerSummary: DriverSummary | null;
|
||||
readonly adminSummaries: DriverSummary[];
|
||||
readonly stewardSummaries: DriverSummary[];
|
||||
|
||||
// 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: LeagueWithCapacityAndScoringDTO,
|
||||
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;
|
||||
constructor(data: LeagueDetailPageViewData) {
|
||||
super();
|
||||
this.data = data;
|
||||
|
||||
// Handle settings with proper type narrowing
|
||||
const settings = league.settings as LeagueSettings | undefined;
|
||||
const maxDrivers = settings?.maxDrivers;
|
||||
this.settings = {
|
||||
maxDrivers: maxDrivers,
|
||||
};
|
||||
|
||||
// Handle social links with proper type narrowing
|
||||
const socialLinks = league.socialLinks as SocialLinks | undefined;
|
||||
const discordUrl = socialLinks?.discordUrl;
|
||||
const youtubeUrl = socialLinks?.youtubeUrl;
|
||||
const websiteUrl = socialLinks?.websiteUrl;
|
||||
|
||||
this.socialLinks = {
|
||||
discordUrl,
|
||||
youtubeUrl,
|
||||
websiteUrl,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
this.owner = owner;
|
||||
this.scoringConfig = scoringConfig;
|
||||
this.drivers = drivers;
|
||||
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; }
|
||||
|
||||
// Handle memberships with proper type narrowing
|
||||
const membershipsContainer = memberships as MembershipsContainer;
|
||||
const membershipDtos = membershipsContainer.members ??
|
||||
membershipsContainer.memberships ??
|
||||
[];
|
||||
|
||||
this.memberships = membershipDtos.map((m) => ({
|
||||
driverId: m.driverId,
|
||||
role: m.role as 'owner' | 'admin' | 'steward' | 'member',
|
||||
status: m.status ?? 'active',
|
||||
joinedAt: m.joinedAt,
|
||||
}));
|
||||
|
||||
this.allRaces = allRaces;
|
||||
this.runningRaces = allRaces.filter(r => r.status === 'running');
|
||||
|
||||
// Calculate SOF from available data with proper type narrowing
|
||||
const statsExtended = leagueStats as LeagueStatsExtended;
|
||||
const averageSOF = statsExtended.averageSOF ??
|
||||
statsExtended.averageRating ?? undefined;
|
||||
const completedRaces = statsExtended.completedRaces ??
|
||||
statsExtended.totalRaces ?? undefined;
|
||||
|
||||
this.averageSOF = typeof averageSOF === 'number' ? averageSOF : null;
|
||||
this.completedRacesCount = typeof completedRaces === 'number' ? completedRaces : 0;
|
||||
|
||||
this.sponsors = sponsors;
|
||||
|
||||
// Calculate sponsor insights
|
||||
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;
|
||||
|
||||
this.sponsorInsights = {
|
||||
return {
|
||||
avgViewsPerRace: 5400 + memberCount * 50,
|
||||
totalImpressions: 45000 + memberCount * 500,
|
||||
engagementRate: (3.5 + (memberCount / 50)).toFixed(1),
|
||||
@@ -200,59 +87,17 @@ export class LeagueDetailPageViewModel extends ViewModel {
|
||||
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;
|
||||
|
||||
// Handle avatarUrl with proper type checking
|
||||
const driverAny = driverDto as { avatarUrl?: unknown };
|
||||
const avatarUrl = typeof driverAny.avatarUrl === 'string' ? driverAny.avatarUrl : null;
|
||||
|
||||
const driver = new DriverViewModel({
|
||||
id: driverDto.id,
|
||||
name: driverDto.name,
|
||||
avatarUrl: avatarUrl,
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user