refactor page to use services
This commit is contained in:
181
apps/website/lib/view-models/DashboardOverviewViewModel.ts
Normal file
181
apps/website/lib/view-models/DashboardOverviewViewModel.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { DashboardOverviewDto, DriverDto, RaceDto, LeagueStandingDto, FeedItemDto, FriendDto } from '../api/dashboard/DashboardApiClient';
|
||||
|
||||
export class DriverViewModel {
|
||||
constructor(private readonly dto: DriverDto) {}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
get globalRank(): number {
|
||||
return this.dto.globalRank;
|
||||
}
|
||||
|
||||
get consistency(): number {
|
||||
return this.dto.consistency;
|
||||
}
|
||||
}
|
||||
|
||||
export class RaceViewModel {
|
||||
constructor(private readonly dto: RaceDto) {}
|
||||
|
||||
get id(): string {
|
||||
return this.dto.id;
|
||||
}
|
||||
|
||||
get track(): string {
|
||||
return this.dto.track;
|
||||
}
|
||||
|
||||
get car(): string {
|
||||
return this.dto.car;
|
||||
}
|
||||
|
||||
get scheduledAt(): Date {
|
||||
return new Date(this.dto.scheduledAt);
|
||||
}
|
||||
|
||||
get isMyLeague(): boolean {
|
||||
return this.dto.isMyLeague;
|
||||
}
|
||||
|
||||
get leagueName(): string | undefined {
|
||||
return this.dto.leagueName;
|
||||
}
|
||||
}
|
||||
|
||||
export class LeagueStandingViewModel {
|
||||
constructor(private readonly dto: LeagueStandingDto) {}
|
||||
|
||||
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: FeedItemDto) {}
|
||||
|
||||
get id(): string {
|
||||
return this.dto.id;
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
return this.dto.type;
|
||||
}
|
||||
|
||||
get headline(): string {
|
||||
return this.dto.headline;
|
||||
}
|
||||
|
||||
get body(): string | null {
|
||||
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 FriendViewModel {
|
||||
constructor(private readonly dto: FriendDto) {}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export class DashboardOverviewViewModel {
|
||||
constructor(private readonly dto: DashboardOverviewDto) {}
|
||||
|
||||
get currentDriver(): DriverViewModel {
|
||||
return new DriverViewModel(this.dto.currentDriver);
|
||||
}
|
||||
|
||||
get nextRace(): RaceViewModel | null {
|
||||
return this.dto.nextRace ? new RaceViewModel(this.dto.nextRace) : null;
|
||||
}
|
||||
|
||||
get upcomingRaces(): RaceViewModel[] {
|
||||
return this.dto.upcomingRaces.map(dto => new RaceViewModel(dto));
|
||||
}
|
||||
|
||||
get leagueStandings(): LeagueStandingViewModel[] {
|
||||
return this.dto.leagueStandings.map(dto => new LeagueStandingViewModel(dto));
|
||||
}
|
||||
|
||||
get feedItems(): DashboardFeedItemSummaryViewModel[] {
|
||||
return this.dto.feedItems.map(dto => new DashboardFeedItemSummaryViewModel(dto));
|
||||
}
|
||||
|
||||
get friends(): FriendViewModel[] {
|
||||
return this.dto.friends.map(dto => new FriendViewModel(dto));
|
||||
}
|
||||
|
||||
get activeLeaguesCount(): number {
|
||||
return this.dto.activeLeaguesCount;
|
||||
}
|
||||
}
|
||||
141
apps/website/lib/view-models/DriverProfileViewModel.ts
Normal file
141
apps/website/lib/view-models/DriverProfileViewModel.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
export interface DriverProfileDriverSummaryViewModel {
|
||||
id: string;
|
||||
name: string;
|
||||
country: string;
|
||||
avatarUrl: string;
|
||||
iracingId: string | null;
|
||||
joinedAt: string;
|
||||
rating: number | null;
|
||||
globalRank: number | null;
|
||||
consistency: number | null;
|
||||
bio: string | null;
|
||||
totalDrivers: number | null;
|
||||
}
|
||||
|
||||
export interface DriverProfileStatsViewModel {
|
||||
totalRaces: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
dnfs: number;
|
||||
avgFinish: number | null;
|
||||
bestFinish: number | null;
|
||||
worstFinish: number | null;
|
||||
finishRate: number | null;
|
||||
winRate: number | null;
|
||||
podiumRate: number | null;
|
||||
percentile: number | null;
|
||||
rating: number | null;
|
||||
consistency: number | null;
|
||||
overallRank: number | null;
|
||||
}
|
||||
|
||||
export interface DriverProfileFinishDistributionViewModel {
|
||||
totalRaces: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
topTen: number;
|
||||
dnfs: number;
|
||||
other: number;
|
||||
}
|
||||
|
||||
export interface DriverProfileTeamMembershipViewModel {
|
||||
teamId: string;
|
||||
teamName: string;
|
||||
teamTag: string | null;
|
||||
role: string;
|
||||
joinedAt: string;
|
||||
isCurrent: boolean;
|
||||
}
|
||||
|
||||
export interface DriverProfileSocialFriendSummaryViewModel {
|
||||
id: string;
|
||||
name: string;
|
||||
country: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface DriverProfileSocialSummaryViewModel {
|
||||
friendsCount: number;
|
||||
friends: DriverProfileSocialFriendSummaryViewModel[];
|
||||
}
|
||||
|
||||
export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord';
|
||||
|
||||
export type DriverProfileAchievementRarity = 'common' | 'rare' | 'epic' | 'legendary';
|
||||
|
||||
export interface DriverProfileAchievementViewModel {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
||||
rarity: DriverProfileAchievementRarity;
|
||||
earnedAt: string;
|
||||
}
|
||||
|
||||
export interface DriverProfileSocialHandleViewModel {
|
||||
platform: DriverProfileSocialPlatform;
|
||||
handle: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface DriverProfileExtendedProfileViewModel {
|
||||
socialHandles: DriverProfileSocialHandleViewModel[];
|
||||
achievements: DriverProfileAchievementViewModel[];
|
||||
racingStyle: string;
|
||||
favoriteTrack: string;
|
||||
favoriteCar: string;
|
||||
timezone: string;
|
||||
availableHours: string;
|
||||
lookingForTeam: boolean;
|
||||
openToRequests: boolean;
|
||||
}
|
||||
|
||||
export interface DriverProfileViewModel {
|
||||
currentDriver: DriverProfileDriverSummaryViewModel | null;
|
||||
stats: DriverProfileStatsViewModel | null;
|
||||
finishDistribution: DriverProfileFinishDistributionViewModel | null;
|
||||
teamMemberships: DriverProfileTeamMembershipViewModel[];
|
||||
socialSummary: DriverProfileSocialSummaryViewModel;
|
||||
extendedProfile: DriverProfileExtendedProfileViewModel | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver Profile View Model
|
||||
*
|
||||
* Represents a fully prepared UI state for driver profile display.
|
||||
* Transforms API DTOs into UI-ready data structures.
|
||||
*/
|
||||
export class DriverProfileViewModel {
|
||||
constructor(private readonly dto: DriverProfileViewModel) {}
|
||||
|
||||
get currentDriver(): DriverProfileDriverSummaryViewModel | null {
|
||||
return this.dto.currentDriver;
|
||||
}
|
||||
|
||||
get stats(): DriverProfileStatsViewModel | null {
|
||||
return this.dto.stats;
|
||||
}
|
||||
|
||||
get finishDistribution(): DriverProfileFinishDistributionViewModel | null {
|
||||
return this.dto.finishDistribution;
|
||||
}
|
||||
|
||||
get teamMemberships(): DriverProfileTeamMembershipViewModel[] {
|
||||
return this.dto.teamMemberships;
|
||||
}
|
||||
|
||||
get socialSummary(): DriverProfileSocialSummaryViewModel {
|
||||
return this.dto.socialSummary;
|
||||
}
|
||||
|
||||
get extendedProfile(): DriverProfileExtendedProfileViewModel | null {
|
||||
return this.dto.extendedProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw DTO for serialization or further processing
|
||||
*/
|
||||
toDTO(): DriverProfileViewModel {
|
||||
return this.dto;
|
||||
}
|
||||
}
|
||||
21
apps/website/lib/view-models/DriverSummaryViewModel.ts
Normal file
21
apps/website/lib/view-models/DriverSummaryViewModel.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { DriverDTO } from '../types/DriverDTO';
|
||||
|
||||
/**
|
||||
* View Model for driver summary with rating and rank
|
||||
* Transform from DTO to ViewModel with UI fields
|
||||
*/
|
||||
export class DriverSummaryViewModel {
|
||||
driver: DriverDTO;
|
||||
rating: number | null;
|
||||
rank: number | null;
|
||||
|
||||
constructor(dto: {
|
||||
driver: DriverDTO;
|
||||
rating?: number | null;
|
||||
rank?: number | null;
|
||||
}) {
|
||||
this.driver = dto.driver;
|
||||
this.rating = dto.rating ?? null;
|
||||
this.rank = dto.rank ?? null;
|
||||
}
|
||||
}
|
||||
192
apps/website/lib/view-models/LeagueDetailPageViewModel.ts
Normal file
192
apps/website/lib/view-models/LeagueDetailPageViewModel.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
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 { DriverDTO } from '../types/DriverDTO';
|
||||
import { RaceDTO } from '../types/generated/RaceDTO';
|
||||
import { LeagueScoringConfigDTO } from '../types/LeagueScoringConfigDTO';
|
||||
|
||||
// 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: DriverDTO;
|
||||
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: DriverDTO | null;
|
||||
|
||||
// Scoring configuration
|
||||
scoringConfig: LeagueScoringConfigDTO | null;
|
||||
|
||||
// Drivers and memberships
|
||||
drivers: DriverDTO[];
|
||||
memberships: LeagueMembershipWithRole[];
|
||||
|
||||
// Races
|
||||
allRaces: RaceDTO[];
|
||||
runningRaces: RaceDTO[];
|
||||
|
||||
// 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: DriverDTO | null,
|
||||
scoringConfig: LeagueScoringConfigDTO | null,
|
||||
drivers: DriverDTO[],
|
||||
memberships: LeagueMembershipsDTO,
|
||||
allRaces: RaceDTO[],
|
||||
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;
|
||||
|
||||
// TODO: Get driver stats and rankings from service
|
||||
// For now, return basic info
|
||||
return {
|
||||
driver,
|
||||
rating: null, // TODO: fetch from service
|
||||
rank: null, // TODO: fetch from service
|
||||
};
|
||||
}
|
||||
|
||||
// UI helper methods
|
||||
get isSponsorMode(): boolean {
|
||||
// TODO: implement sponsor mode check
|
||||
return false;
|
||||
}
|
||||
|
||||
get currentUserMembership(): LeagueMembershipWithRole | null {
|
||||
// TODO: get current user ID and find membership
|
||||
return null;
|
||||
}
|
||||
|
||||
get canEndRaces(): boolean {
|
||||
return this.currentUserMembership?.role === 'admin' || this.currentUserMembership?.role === 'owner';
|
||||
}
|
||||
}
|
||||
35
apps/website/lib/view-models/LeagueDetailViewModel.ts
Normal file
35
apps/website/lib/view-models/LeagueDetailViewModel.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface MainSponsorInfo {
|
||||
name: string;
|
||||
logoUrl: string;
|
||||
websiteUrl: string;
|
||||
}
|
||||
|
||||
export class LeagueDetailViewModel {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
ownerName: string;
|
||||
mainSponsor: MainSponsorInfo | null;
|
||||
isAdmin: boolean;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
description: string,
|
||||
ownerId: string,
|
||||
ownerName: string,
|
||||
mainSponsor: MainSponsorInfo | null,
|
||||
isAdmin: boolean
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.ownerId = ownerId;
|
||||
this.ownerName = ownerName;
|
||||
this.mainSponsor = mainSponsor;
|
||||
this.isAdmin = isAdmin;
|
||||
}
|
||||
|
||||
// UI-specific getters can be added here if needed
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
|
||||
|
||||
/**
|
||||
* View Model for league scoring presets
|
||||
* Transform from DTO to ViewModel with UI fields
|
||||
*/
|
||||
export class LeagueScoringPresetsViewModel {
|
||||
presets: LeagueScoringPresetDTO[];
|
||||
totalCount: number;
|
||||
|
||||
constructor(dto: {
|
||||
presets: LeagueScoringPresetDTO[];
|
||||
totalCount?: number;
|
||||
}) {
|
||||
this.presets = dto.presets;
|
||||
this.totalCount = dto.totalCount ?? dto.presets.length;
|
||||
}
|
||||
}
|
||||
39
apps/website/lib/view-models/LeagueSettingsViewModel.ts
Normal file
39
apps/website/lib/view-models/LeagueSettingsViewModel.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { LeagueConfigFormModel } from '../types/LeagueConfigFormModel';
|
||||
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
|
||||
import type { DriverDTO } from '../types/DriverDTO';
|
||||
import { LeagueScoringPresetsViewModel } from './LeagueScoringPresetsViewModel';
|
||||
import { DriverSummaryViewModel } from './DriverSummaryViewModel';
|
||||
|
||||
/**
|
||||
* View Model for league settings page
|
||||
* Combines league config, presets, owner, and members
|
||||
*/
|
||||
export class LeagueSettingsViewModel {
|
||||
league: {
|
||||
id: string;
|
||||
name: string;
|
||||
ownerId: string;
|
||||
};
|
||||
config: LeagueConfigFormModel;
|
||||
presets: LeagueScoringPresetDTO[];
|
||||
owner: DriverSummaryViewModel | null;
|
||||
members: DriverDTO[];
|
||||
|
||||
constructor(dto: {
|
||||
league: {
|
||||
id: string;
|
||||
name: string;
|
||||
ownerId: string;
|
||||
};
|
||||
config: LeagueConfigFormModel;
|
||||
presets: LeagueScoringPresetDTO[];
|
||||
owner: DriverSummaryViewModel | null;
|
||||
members: DriverDTO[];
|
||||
}) {
|
||||
this.league = dto.league;
|
||||
this.config = dto.config;
|
||||
this.presets = dto.presets;
|
||||
this.owner = dto.owner;
|
||||
this.members = dto.members;
|
||||
}
|
||||
}
|
||||
@@ -7,29 +7,27 @@ import { ProtestDTO } from '../types/generated/ProtestDTO';
|
||||
export class ProtestViewModel {
|
||||
id: string;
|
||||
raceId: string;
|
||||
complainantId: string;
|
||||
defendantId: string;
|
||||
protestingDriverId: string;
|
||||
accusedDriverId: string;
|
||||
description: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
submittedAt: string;
|
||||
|
||||
constructor(dto: ProtestDTO) {
|
||||
this.id = dto.id;
|
||||
this.raceId = dto.raceId;
|
||||
this.complainantId = dto.complainantId;
|
||||
this.defendantId = dto.defendantId;
|
||||
this.protestingDriverId = dto.protestingDriverId;
|
||||
this.accusedDriverId = dto.accusedDriverId;
|
||||
this.description = dto.description;
|
||||
this.status = dto.status;
|
||||
this.createdAt = dto.createdAt;
|
||||
this.submittedAt = dto.submittedAt;
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted created date */
|
||||
get formattedCreatedAt(): string {
|
||||
return new Date(this.createdAt).toLocaleString();
|
||||
/** UI-specific: Formatted submitted date */
|
||||
get formattedSubmittedAt(): string {
|
||||
return new Date(this.submittedAt).toLocaleString();
|
||||
}
|
||||
|
||||
/** UI-specific: Status display */
|
||||
/** UI-specific: Status display - placeholder since status not in current DTO */
|
||||
get statusDisplay(): string {
|
||||
return this.status.charAt(0).toUpperCase() + this.status.slice(1);
|
||||
return 'Pending'; // TODO: Update when status is added to DTO
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { RaceDetailViewModel } from './RaceDetailViewModel';
|
||||
import type { RaceDetailRaceDTO } from '../types/generated/RaceDetailRaceDTO';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { RaceDetailLeagueDTO } from '../types/generated/RaceDetailLeagueDTO';
|
||||
import type { RaceDetailEntryDTO } from '../types/generated/RaceDetailEntryDTO';
|
||||
import type { RaceDetailRaceDTO } from '../types/generated/RaceDetailRaceDTO';
|
||||
import type { RaceDetailRegistrationDTO } from '../types/generated/RaceDetailRegistrationDTO';
|
||||
import type { RaceDetailUserResultDTO } from '../types/generated/RaceDetailUserResultDTO';
|
||||
import type { RaceDetailEntryDTO } from '../types/RaceDetailEntryDTO';
|
||||
import { RaceDetailViewModel } from './RaceDetailViewModel';
|
||||
|
||||
describe('RaceDetailViewModel', () => {
|
||||
const createMockRace = (overrides?: Partial<RaceDetailRaceDTO>): RaceDetailRaceDTO => ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RaceDetailRaceDTO } from '../types/generated/RaceDetailRaceDTO';
|
||||
import { RaceDetailLeagueDTO } from '../types/generated/RaceDetailLeagueDTO';
|
||||
import { RaceDetailEntryDTO } from '../types/generated/RaceDetailEntryDTO';
|
||||
import { RaceDetailRaceDTO } from '../types/generated/RaceDetailRaceDTO';
|
||||
import { RaceDetailRegistrationDTO } from '../types/generated/RaceDetailRegistrationDTO';
|
||||
import { RaceDetailUserResultDTO } from '../types/generated/RaceDetailUserResultDTO';
|
||||
import { RaceDetailEntryDTO } from '../types/RaceDetailEntryDTO';
|
||||
|
||||
export class RaceDetailViewModel {
|
||||
race: RaceDetailRaceDTO | null;
|
||||
|
||||
Reference in New Issue
Block a user