This commit is contained in:
2026-01-11 13:04:33 +01:00
parent 6f2ab9fc56
commit 971aa7288b
44 changed files with 2168 additions and 1240 deletions

View File

@@ -1,217 +1,198 @@
import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO';
import type { DashboardDriverSummaryDTO } from '@/lib/types/generated/DashboardDriverSummaryDTO';
import type { DashboardRaceSummaryDTO } from '@/lib/types/generated/DashboardRaceSummaryDTO';
import type { DashboardLeagueStandingSummaryDTO } from '@/lib/types/generated/DashboardLeagueStandingSummaryDTO';
import type { DashboardFeedItemSummaryDTO } from '@/lib/types/generated/DashboardFeedItemSummaryDTO';
import type { DashboardFriendSummaryDTO } from '@/lib/types/generated/DashboardFriendSummaryDTO';
export class DashboardDriverSummaryViewModel {
constructor(private readonly dto: DashboardDriverSummaryDTO) {}
get id(): string {
return this.dto.id;
}
get name(): string {
return this.dto.name;
}
get avatarUrl(): string {
return this.dto.avatarUrl || '';
}
get country(): string {
return this.dto.country;
}
get totalRaces(): number {
return this.dto.totalRaces;
}
get wins(): number {
return this.dto.wins;
}
get podiums(): number {
return this.dto.podiums;
}
get rating(): number {
return this.dto.rating ?? 0;
}
get globalRank(): number {
return this.dto.globalRank ?? 0;
}
get consistency(): number {
return this.dto.consistency ?? 0;
}
}
export class DashboardRaceSummaryViewModel {
constructor(private readonly dto: DashboardRaceSummaryDTO) {}
get id(): string {
return this.dto.id;
}
get leagueId(): string {
return (this.dto as any).leagueId ?? '';
}
get leagueName(): string {
return (this.dto as any).leagueName ?? '';
}
get track(): string {
return this.dto.track;
}
get car(): string {
return this.dto.car;
}
get scheduledAt(): Date {
return new Date(this.dto.scheduledAt);
}
get status(): string {
return this.dto.status;
}
get isMyLeague(): boolean {
return this.dto.isMyLeague;
}
}
export class DashboardLeagueStandingSummaryViewModel {
constructor(private readonly dto: DashboardLeagueStandingSummaryDTO) {}
get leagueId(): string {
return this.dto.leagueId;
}
get leagueName(): string {
return this.dto.leagueName;
}
get position(): number {
return this.dto.position;
}
get points(): number {
return this.dto.points;
}
get totalDrivers(): number {
return this.dto.totalDrivers;
}
}
export class DashboardFeedItemSummaryViewModel {
constructor(private readonly dto: DashboardFeedItemSummaryDTO) {}
get id(): string {
return this.dto.id;
}
get type(): string {
return this.dto.type;
}
get headline(): string {
return this.dto.headline;
}
get body(): string | undefined {
return this.dto.body;
}
get timestamp(): Date {
return new Date(this.dto.timestamp);
}
get ctaHref(): string | undefined {
return this.dto.ctaHref;
}
get ctaLabel(): string | undefined {
return this.dto.ctaLabel;
}
}
export class DashboardFriendSummaryViewModel {
constructor(private readonly dto: DashboardFriendSummaryDTO) {}
get id(): string {
return this.dto.id;
}
get name(): string {
return this.dto.name;
}
get avatarUrl(): string {
return this.dto.avatarUrl || '';
}
get country(): string {
return this.dto.country;
}
}
import type { DashboardOverviewViewModelData } from './DashboardOverviewViewModelData';
import {
dashboardStatDisplay,
formatDashboardDate,
formatRating,
formatRank,
formatConsistency,
formatRaceCount,
formatFriendCount,
formatLeaguePosition,
formatPoints,
formatTotalDrivers,
} from '@/lib/display-objects/DashboardDisplay';
/**
* Dashboard Overview ViewModel
*
* Clean class that accepts DTO only and exposes derived values.
* This is client-only and instantiated after hydration.
*/
export class DashboardOverviewViewModel {
constructor(private readonly dto: DashboardOverviewDTO) {}
constructor(private readonly dto: DashboardOverviewViewModelData) {}
get currentDriver(): DashboardDriverSummaryViewModel {
// DTO uses optional property; enforce a consistent object for the UI
return new DashboardDriverSummaryViewModel(
(this.dto as any).currentDriver ?? {
id: '',
name: '',
country: '',
avatarUrl: '',
totalRaces: 0,
wins: 0,
podiums: 0,
},
);
// Current Driver - Derived Values
get currentDriverName(): string {
return this.dto.currentDriver?.name || '';
}
get nextRace(): DashboardRaceSummaryViewModel | null {
const nextRace = (this.dto as any).nextRace;
return nextRace ? new DashboardRaceSummaryViewModel(nextRace) : null;
get currentDriverAvatarUrl(): string {
return this.dto.currentDriver?.avatarUrl || '';
}
get upcomingRaces(): DashboardRaceSummaryViewModel[] {
const upcomingRaces = (this.dto as any).upcomingRaces ?? [];
return upcomingRaces.map((r: any) => new DashboardRaceSummaryViewModel(r));
get currentDriverCountry(): string {
return this.dto.currentDriver?.country || '';
}
get leagueStandings(): DashboardLeagueStandingSummaryViewModel[] {
const leagueStandings = (this.dto as any).leagueStandingsSummaries ?? (this.dto as any).leagueStandings ?? [];
return leagueStandings.map((s: any) => new DashboardLeagueStandingSummaryViewModel(s));
get currentDriverRating(): string {
return this.dto.currentDriver ? formatRating(this.dto.currentDriver.rating) : '0.0';
}
get feedItems(): DashboardFeedItemSummaryViewModel[] {
const feedItems = (this.dto as any).feedSummary?.items ?? (this.dto as any).feedItems ?? [];
return feedItems.map((i: any) => new DashboardFeedItemSummaryViewModel(i));
get currentDriverRank(): string {
return this.dto.currentDriver ? formatRank(this.dto.currentDriver.globalRank) : '0';
}
get friends(): DashboardFriendSummaryViewModel[] {
const friends = (this.dto as any).friends ?? [];
return friends.map((f: any) => new DashboardFriendSummaryViewModel(f));
get currentDriverTotalRaces(): string {
return this.dto.currentDriver ? formatRaceCount(this.dto.currentDriver.totalRaces) : '0';
}
get activeLeaguesCount(): number {
return (this.dto as any).activeLeaguesCount ?? 0;
get currentDriverWins(): string {
return this.dto.currentDriver ? formatRaceCount(this.dto.currentDriver.wins) : '0';
}
}
export {
DashboardDriverSummaryViewModel as DriverViewModel,
DashboardRaceSummaryViewModel as RaceViewModel,
DashboardLeagueStandingSummaryViewModel as LeagueStandingViewModel,
DashboardFriendSummaryViewModel as FriendViewModel,
};
get currentDriverPodiums(): string {
return this.dto.currentDriver ? formatRaceCount(this.dto.currentDriver.podiums) : '0';
}
get currentDriverConsistency(): string {
return this.dto.currentDriver ? formatConsistency(this.dto.currentDriver.consistency) : '0%';
}
// Next Race - Derived Values
get nextRace(): {
id: string;
track: string;
car: string;
scheduledAt: string;
status: string;
isMyLeague: boolean;
formattedDate: string;
formattedTime: string;
timeUntil: string;
} | null {
if (!this.dto.nextRace) return null;
const dateInfo = formatDashboardDate(new Date(this.dto.nextRace.scheduledAt));
return {
id: this.dto.nextRace.id,
track: this.dto.nextRace.track,
car: this.dto.nextRace.car,
scheduledAt: this.dto.nextRace.scheduledAt,
status: this.dto.nextRace.status,
isMyLeague: this.dto.nextRace.isMyLeague,
formattedDate: dateInfo.date,
formattedTime: dateInfo.time,
timeUntil: dateInfo.relative,
};
}
// Upcoming Races - Derived Values
get upcomingRaces(): Array<{
id: string;
track: string;
car: string;
scheduledAt: string;
status: string;
isMyLeague: boolean;
formattedDate: string;
formattedTime: string;
timeUntil: string;
}> {
return this.dto.upcomingRaces.map((race) => {
const dateInfo = formatDashboardDate(new Date(race.scheduledAt));
return {
id: race.id,
track: race.track,
car: race.car,
scheduledAt: race.scheduledAt,
status: race.status,
isMyLeague: race.isMyLeague,
formattedDate: dateInfo.date,
formattedTime: dateInfo.time,
timeUntil: dateInfo.relative,
};
});
}
// League Standings - Derived Values
get leagueStandings(): Array<{
leagueId: string;
leagueName: string;
position: string;
points: string;
totalDrivers: string;
}> {
return this.dto.leagueStandingsSummaries.map((standing) => ({
leagueId: standing.leagueId,
leagueName: standing.leagueName,
position: formatLeaguePosition(standing.position),
points: formatPoints(standing.points),
totalDrivers: formatTotalDrivers(standing.totalDrivers),
}));
}
// Feed Items - Derived Values
get feedItems(): Array<{
id: string;
type: string;
headline: string;
body?: string;
timestamp: string;
ctaHref?: string;
ctaLabel?: string;
formattedTime: string;
}> {
return this.dto.feedSummary.items.map((item) => ({
id: item.id,
type: item.type,
headline: item.headline,
body: item.body,
timestamp: item.timestamp,
ctaHref: item.ctaHref,
ctaLabel: item.ctaLabel,
formattedTime: formatDashboardDate(new Date(item.timestamp)).relative,
}));
}
// Friends - Derived Values
get friends(): Array<{
id: string;
name: string;
avatarUrl: string;
country: string;
}> {
// No additional formatting needed for friends
return this.dto.friends;
}
// Active Leagues Count
get activeLeaguesCount(): string {
return formatRaceCount(this.dto.activeLeaguesCount);
}
// Convenience getters for display
get hasNextRace(): boolean {
return this.dto.nextRace !== undefined;
}
get hasUpcomingRaces(): boolean {
return this.dto.upcomingRaces.length > 0;
}
get hasLeagueStandings(): boolean {
return this.dto.leagueStandingsSummaries.length > 0;
}
get hasFeedItems(): boolean {
return this.dto.feedSummary.items.length > 0;
}
get hasFriends(): boolean {
return this.dto.friends.length > 0;
}
get friendCount(): string {
return formatFriendCount(this.dto.friends.length);
}
}