view models

This commit is contained in:
2025-12-18 13:48:35 +01:00
parent cc2553876a
commit 91adbb9c83
71 changed files with 3119 additions and 359 deletions

View File

@@ -0,0 +1,21 @@
import { CreateLeagueOutputDTO } from '../types/generated/CreateLeagueOutputDTO';
/**
* View Model for Create League Result
*
* Represents the result of creating a league in a UI-ready format.
*/
export class CreateLeagueViewModel implements CreateLeagueOutputDTO {
leagueId: string;
success: boolean;
constructor(dto: CreateLeagueOutputDTO) {
this.leagueId = dto.leagueId;
this.success = dto.success;
}
/** UI-specific: Success message */
get successMessage(): string {
return this.success ? 'League created successfully!' : 'Failed to create league.';
}
}

View File

@@ -0,0 +1,19 @@
/**
* View Model for Create Team Result
*
* Represents the result of creating a team in a UI-ready format.
*/
export class CreateTeamViewModel {
id: string;
success: boolean;
constructor(dto: { id: string; success: boolean }) {
this.id = dto.id;
this.success = dto.success;
}
/** UI-specific: Success message */
get successMessage(): string {
return this.success ? 'Team created successfully!' : 'Failed to create team.';
}
}

View File

@@ -0,0 +1,26 @@
/**
* View Model for Driver's Team
*
* Represents a driver's team membership in a UI-ready format.
*/
export class DriverTeamViewModel {
teamId: string;
teamName: string;
role: string;
constructor(dto: { teamId: string; teamName: string; role: string }) {
this.teamId = dto.teamId;
this.teamName = dto.teamName;
this.role = dto.role;
}
/** UI-specific: Display role */
get displayRole(): string {
return this.role.charAt(0).toUpperCase() + this.role.slice(1);
}
/** UI-specific: Is owner */
get isOwner(): boolean {
return this.role === 'owner';
}
}

View File

@@ -0,0 +1,20 @@
// TODO: Create ImportRaceResultsSummaryDTO in apps/website/lib/types/generated when available
interface ImportRaceResultsSummaryDTO {
raceId: string;
importedCount: number;
errors: string[];
}
export class ImportRaceResultsSummaryViewModel implements ImportRaceResultsSummaryDTO {
raceId: string;
importedCount: number;
errors: string[];
constructor(dto: ImportRaceResultsSummaryDTO) {
this.raceId = dto.raceId;
this.importedCount = dto.importedCount;
this.errors = dto.errors;
}
// TODO: Add additional UI-specific fields when DTO is available
}

View File

@@ -1,4 +1,5 @@
import { LeagueMemberDTO } from '../types/generated/LeagueMemberDTO';
import { DriverViewModel } from './DriverViewModel';
export class LeagueMemberViewModel implements LeagueMemberDTO {
driverId: string;
@@ -12,7 +13,7 @@ export class LeagueMemberViewModel implements LeagueMemberDTO {
// Note: The generated DTO is incomplete
// These fields will need to be added when the OpenAPI spec is updated
driver?: any;
driver?: DriverViewModel;
role: string = 'member';
joinedAt: string = new Date().toISOString();

View File

@@ -0,0 +1,25 @@
import { LeagueMemberViewModel } from './LeagueMemberViewModel';
import type { LeagueMemberDTO } from '../types/generated/LeagueMemberDTO';
/**
* View Model for League Memberships
*
* Represents the league's memberships in a UI-ready format.
*/
export class LeagueMembershipsViewModel {
memberships: LeagueMemberViewModel[];
constructor(dto: { memberships: LeagueMemberDTO[] }, currentUserId: string) {
this.memberships = dto.memberships.map(membership => new LeagueMemberViewModel(membership, currentUserId));
}
/** UI-specific: Number of members */
get memberCount(): number {
return this.memberships.length;
}
/** UI-specific: Whether the league has members */
get hasMembers(): boolean {
return this.memberCount > 0;
}
}

View File

@@ -0,0 +1,22 @@
/**
* View Model for League Schedule
*
* Represents the league's race schedule in a UI-ready format.
*/
export class LeagueScheduleViewModel {
races: Array<unknown>;
constructor(dto: { races: Array<unknown> }) {
this.races = dto.races;
}
/** UI-specific: Number of races in the schedule */
get raceCount(): number {
return this.races.length;
}
/** UI-specific: Whether the schedule has races */
get hasRaces(): boolean {
return this.raceCount > 0;
}
}

View File

@@ -0,0 +1,17 @@
/**
* View Model for League Statistics
*
* Represents the total number of leagues in a UI-ready format.
*/
export class LeagueStatsViewModel {
totalLeagues: number;
constructor(dto: { totalLeagues: number }) {
this.totalLeagues = dto.totalLeagues;
}
/** UI-specific: Formatted total leagues display */
get formattedTotalLeagues(): string {
return this.totalLeagues.toLocaleString();
}
}

View File

@@ -1,37 +1,51 @@
import { MembershipFeeDto } from '../types/generated/MembershipFeeDto';
import type { MembershipFeeDto } from '../types/generated';
export class MembershipFeeViewModel implements MembershipFeeDto {
export class MembershipFeeViewModel {
id: string;
leagueId: string;
seasonId?: string;
type: string;
amount: number;
enabled: boolean;
createdAt: Date;
updatedAt: Date;
constructor(dto: MembershipFeeDto) {
this.id = dto.id;
this.leagueId = dto.leagueId;
Object.assign(this, dto);
}
// Note: The generated DTO is incomplete
// These fields will need to be added when the OpenAPI spec is updated
amount: number = 0;
currency: string = 'USD';
period: string = 'monthly';
/** UI-specific: Formatted amount */
get formattedAmount(): string {
return `${this.currency} ${this.amount.toFixed(2)}`;
return `${this.amount.toFixed(2)}`; // Assuming EUR
}
/** UI-specific: Period display */
get periodDisplay(): string {
switch (this.period) {
case 'monthly': return 'Monthly';
case 'yearly': return 'Yearly';
/** UI-specific: Type display */
get typeDisplay(): string {
switch (this.type) {
case 'season': return 'Per Season';
default: return this.period;
case 'monthly': return 'Monthly';
case 'per_race': return 'Per Race';
default: return this.type;
}
}
/** UI-specific: Amount per period */
get amountPerPeriod(): string {
return `${this.formattedAmount} ${this.periodDisplay.toLowerCase()}`;
/** UI-specific: Status display */
get statusDisplay(): string {
return this.enabled ? 'Enabled' : 'Disabled';
}
/** UI-specific: Status color */
get statusColor(): string {
return this.enabled ? 'green' : 'red';
}
/** UI-specific: Formatted created date */
get formattedCreatedAt(): string {
return this.createdAt.toLocaleString();
}
/** UI-specific: Formatted updated date */
get formattedUpdatedAt(): string {
return this.updatedAt.toLocaleString();
}
}

View File

@@ -1,19 +1,31 @@
import { PaymentDTO } from '../types/generated/PaymentDto';
import type { PaymentDto } from '../types/generated';
export class PaymentViewModel implements PaymentDTO {
export class PaymentViewModel {
id: string;
type: string;
amount: number;
currency: string;
platformFee: number;
netAmount: number;
payerId: string;
payerType: string;
leagueId: string;
seasonId?: string;
status: string;
createdAt: string;
createdAt: Date;
completedAt?: Date;
constructor(dto: PaymentDTO) {
constructor(dto: PaymentDto) {
Object.assign(this, dto);
}
/** UI-specific: Formatted amount */
get formattedAmount(): string {
return `${this.currency} ${this.amount.toFixed(2)}`;
return `${this.amount.toFixed(2)}`; // Assuming EUR currency
}
/** UI-specific: Formatted net amount */
get formattedNetAmount(): string {
return `${this.netAmount.toFixed(2)}`;
}
/** UI-specific: Status color */
@@ -22,17 +34,33 @@ export class PaymentViewModel implements PaymentDTO {
case 'completed': return 'green';
case 'pending': return 'yellow';
case 'failed': return 'red';
case 'refunded': return 'orange';
default: return 'gray';
}
}
/** UI-specific: Formatted created date */
get formattedCreatedAt(): string {
return new Date(this.createdAt).toLocaleString();
return this.createdAt.toLocaleString();
}
/** UI-specific: Formatted completed date */
get formattedCompletedAt(): string {
return this.completedAt ? this.completedAt.toLocaleString() : 'Not completed';
}
/** UI-specific: Status display */
get statusDisplay(): string {
return this.status.charAt(0).toUpperCase() + this.status.slice(1);
}
/** UI-specific: Type display */
get typeDisplay(): string {
return this.type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
}
/** UI-specific: Payer type display */
get payerTypeDisplay(): string {
return this.payerType.charAt(0).toUpperCase() + this.payerType.slice(1);
}
}

View File

@@ -1,29 +1,26 @@
import { PrizeDto } from '../types/generated/PrizeDto';
import type { PrizeDto } from '../types/generated';
export class PrizeViewModel implements PrizeDto {
export class PrizeViewModel {
id: string;
leagueId: string;
seasonId: string;
position: number;
name: string;
amount: number;
type: string;
description?: string;
awarded: boolean;
awardedTo?: string;
awardedAt?: Date;
createdAt: Date;
constructor(dto: PrizeDto) {
this.id = dto.id;
this.leagueId = dto.leagueId;
this.seasonId = dto.seasonId;
this.position = dto.position;
this.name = dto.name;
this.amount = dto.amount;
Object.assign(this, dto);
}
// Note: The generated DTO doesn't have currency
// This will need to be added when the OpenAPI spec is updated
currency: string = 'USD';
/** UI-specific: Formatted amount */
get formattedAmount(): string {
return `${this.currency} ${this.amount.toFixed(2)}`;
return `${this.amount.toFixed(2)}`; // Assuming EUR
}
/** UI-specific: Position display */
@@ -36,8 +33,38 @@ export class PrizeViewModel implements PrizeDto {
}
}
/** UI-specific: Type display */
get typeDisplay(): string {
switch (this.type) {
case 'cash': return 'Cash Prize';
case 'merchandise': return 'Merchandise';
case 'other': return 'Other';
default: return this.type;
}
}
/** UI-specific: Status display */
get statusDisplay(): string {
return this.awarded ? 'Awarded' : 'Available';
}
/** UI-specific: Status color */
get statusColor(): string {
return this.awarded ? 'green' : 'blue';
}
/** UI-specific: Prize description */
get prizeDescription(): string {
return `${this.name} - ${this.formattedAmount}`;
}
/** UI-specific: Formatted awarded date */
get formattedAwardedAt(): string {
return this.awardedAt ? this.awardedAt.toLocaleString() : 'Not awarded';
}
/** UI-specific: Formatted created date */
get formattedCreatedAt(): string {
return this.createdAt.toLocaleString();
}
}

View File

@@ -0,0 +1,18 @@
import { RaceStatsDTO } from '../types/generated';
/**
* Race stats view model
* Represents race statistics for display
*/
export class RaceStatsViewModel {
totalRaces: number;
constructor(dto: RaceStatsDTO) {
this.totalRaces = dto.totalRaces;
}
/** UI-specific: Formatted total races */
get formattedTotalRaces(): string {
return this.totalRaces.toLocaleString();
}
}

View File

@@ -0,0 +1,15 @@
import { RaceWithSOFDTO } from '../types/generated/RaceWithSOFDTO';
export class RaceWithSOFViewModel implements RaceWithSOFDTO {
id: string;
track: string;
constructor(dto: RaceWithSOFDTO) {
this.id = dto.id;
this.track = dto.track;
}
// TODO: Add additional fields when RaceWithSOFDTO is updated in OpenAPI spec
// sof?: number;
// results?: RaceResultViewModel[];
}

View File

@@ -0,0 +1,30 @@
import { RecordEngagementOutputDTO } from '../types/generated';
/**
* Record engagement output view model
* Represents the result of recording an engagement event for UI consumption
*/
export class RecordEngagementOutputViewModel {
eventId: string;
engagementWeight: number;
constructor(dto: RecordEngagementOutputDTO) {
this.eventId = dto.eventId;
this.engagementWeight = dto.engagementWeight;
}
/** UI-specific: Formatted event ID for display */
get displayEventId(): string {
return `Event: ${this.eventId}`;
}
/** UI-specific: Formatted engagement weight */
get displayEngagementWeight(): string {
return `${this.engagementWeight.toFixed(2)}`;
}
/** UI-specific: Is high engagement */
get isHighEngagement(): boolean {
return this.engagementWeight > 1.0;
}
}

View File

@@ -0,0 +1,18 @@
import { RecordPageViewOutputDTO } from '../types/generated';
/**
* Record page view output view model
* Represents the result of recording a page view for UI consumption
*/
export class RecordPageViewOutputViewModel {
pageViewId: string;
constructor(dto: RecordPageViewOutputDTO) {
this.pageViewId = dto.pageViewId;
}
/** UI-specific: Formatted page view ID for display */
get displayPageViewId(): string {
return `Page View: ${this.pageViewId}`;
}
}

View File

@@ -0,0 +1,19 @@
import { RemoveLeagueMemberOutputDTO } from '../types/generated/RemoveLeagueMemberOutputDTO';
/**
* View Model for Remove Member Result
*
* Represents the result of removing a member from a league in a UI-ready format.
*/
export class RemoveMemberViewModel implements RemoveLeagueMemberOutputDTO {
success: boolean;
constructor(dto: RemoveLeagueMemberOutputDTO) {
this.success = dto.success;
}
/** UI-specific: Success message */
get successMessage(): string {
return this.success ? 'Member removed successfully!' : 'Failed to remove member.';
}
}

View File

@@ -1,6 +1,19 @@
import { TeamMemberViewModel } from './TeamMemberViewModel';
// Note: No generated DTO available for TeamDetails yet
interface DriverDTO {
id: string;
name: string;
avatarUrl?: string;
iracingId?: string;
rating?: number;
}
interface TeamMemberDTO {
driverId: string;
driver?: DriverDTO;
role: string;
joinedAt: string;
}
interface TeamDetailsDTO {
id: string;
name: string;
@@ -8,7 +21,7 @@ interface TeamDetailsDTO {
logoUrl?: string;
memberCount: number;
ownerId: string;
members: any[];
members: TeamMemberDTO[];
}
export class TeamDetailsViewModel {

View File

@@ -1,5 +1,5 @@
// Note: No generated DTO available for TeamJoinRequest yet
interface TeamJoinRequestDTO {
export interface TeamJoinRequestDTO {
id: string;
teamId: string;
driverId: string;

View File

@@ -1,7 +1,15 @@
// Note: No generated DTO available for TeamMember yet
interface DriverDTO {
id: string;
name: string;
avatarUrl?: string;
iracingId?: string;
rating?: number;
}
interface TeamMemberDTO {
driverId: string;
driver?: any;
driver?: DriverDTO;
role: string;
joinedAt: string;
}

View File

@@ -0,0 +1,17 @@
/**
* View Model for Update Team Result
*
* Represents the result of updating a team in a UI-ready format.
*/
export class UpdateTeamViewModel {
success: boolean;
constructor(dto: { success: boolean }) {
this.success = dto.success;
}
/** UI-specific: Success message */
get successMessage(): string {
return this.success ? 'Team updated successfully!' : 'Failed to update team.';
}
}

View File

@@ -1,24 +1,30 @@
import { TransactionDto } from '../types/generated/TransactionDto';
export class WalletTransactionViewModel implements TransactionDto {
// TODO: Use generated TransactionDto when it includes all required fields
export type FullTransactionDto = TransactionDto & {
amount: number;
description: string;
createdAt: string;
type: 'deposit' | 'withdrawal';
};
export class WalletTransactionViewModel implements FullTransactionDto {
id: string;
walletId: string;
amount: number;
description: string;
createdAt: string;
type: 'deposit' | 'withdrawal';
constructor(dto: TransactionDto) {
constructor(dto: FullTransactionDto) {
this.id = dto.id;
this.walletId = dto.walletId;
this.amount = dto.amount;
this.description = dto.description;
this.createdAt = dto.createdAt;
this.type = dto.type;
}
// Note: The generated DTO doesn't have type field
// This will need to be added when the OpenAPI spec is updated
type: 'deposit' | 'withdrawal' = 'deposit';
/** UI-specific: Formatted amount with sign */
get formattedAmount(): string {
const sign = this.type === 'deposit' ? '+' : '-';

View File

@@ -1,5 +1,5 @@
import { WalletDto } from '../types/generated/WalletDto';
import { WalletTransactionViewModel } from './WalletTransactionViewModel';
import { WalletTransactionViewModel, FullTransactionDto } from './WalletTransactionViewModel';
export class WalletViewModel implements WalletDto {
id: string;
@@ -11,7 +11,7 @@ export class WalletViewModel implements WalletDto {
createdAt: string;
currency: string;
constructor(dto: WalletDto & { transactions?: any[] }) {
constructor(dto: WalletDto & { transactions?: FullTransactionDto[] }) {
this.id = dto.id;
this.leagueId = dto.leagueId;
this.balance = dto.balance;
@@ -20,16 +20,11 @@ export class WalletViewModel implements WalletDto {
this.totalWithdrawn = dto.totalWithdrawn;
this.createdAt = dto.createdAt;
this.currency = dto.currency;
// Map transactions if provided
if (dto.transactions) {
this.transactions = dto.transactions.map(t => new WalletTransactionViewModel(t));
}
this.transactions = dto.transactions?.map(t => new WalletTransactionViewModel(t)) || [];
}
// Note: The generated DTO doesn't have driverId or transactions
// These will need to be added when the OpenAPI spec is updated
driverId: string = '';
transactions: WalletTransactionViewModel[] = [];
/** UI-specific: Formatted balance */

View File

@@ -1,48 +0,0 @@
/**
* View Models Index
*
* Central export file for all view models.
* View models represent fully prepared UI state and should only be consumed by UI components.
*/
export { AnalyticsDashboardViewModel } from './AnalyticsDashboardViewModel';
export { AnalyticsMetricsViewModel } from './AnalyticsMetricsViewModel';
export { AvatarViewModel } from './AvatarViewModel';
export { CompleteOnboardingViewModel } from './CompleteOnboardingViewModel';
export { DeleteMediaViewModel } from './DeleteMediaViewModel';
export { DriverLeaderboardItemViewModel } from './DriverLeaderboardItemViewModel';
export { DriverLeaderboardViewModel } from './DriverLeaderboardViewModel';
export { DriverRegistrationStatusViewModel } from './DriverRegistrationStatusViewModel';
export { DriverViewModel } from './DriverViewModel';
export { LeagueAdminViewModel } from './LeagueAdminViewModel';
export { LeagueJoinRequestViewModel } from './LeagueJoinRequestViewModel';
export { LeagueMemberViewModel } from './LeagueMemberViewModel';
export { LeagueStandingsViewModel } from './LeagueStandingsViewModel';
export { LeagueSummaryViewModel } from './LeagueSummaryViewModel';
export { MediaViewModel } from './MediaViewModel';
export { MembershipFeeViewModel } from './MembershipFeeViewModel';
export { PaymentViewModel } from './PaymentViewModel';
export { PrizeViewModel } from './PrizeViewModel';
export { ProtestViewModel } from './ProtestViewModel';
export { RaceDetailViewModel } from './RaceDetailViewModel';
export { RaceListItemViewModel } from './RaceListItemViewModel';
export { RaceResultsDetailViewModel } from './RaceResultsDetailViewModel';
export { RaceResultViewModel } from './RaceResultViewModel';
export { RacesPageViewModel } from './RacesPageViewModel';
export { RequestAvatarGenerationViewModel } from './RequestAvatarGenerationViewModel';
export { SessionViewModel } from './SessionViewModel';
export { SponsorDashboardViewModel } from './SponsorDashboardViewModel';
export { SponsorshipDetailViewModel } from './SponsorshipDetailViewModel';
export { SponsorshipPricingViewModel } from './SponsorshipPricingViewModel';
export { SponsorSponsorshipsViewModel } from './SponsorSponsorshipsViewModel';
export { SponsorViewModel } from './SponsorViewModel';
export { StandingEntryViewModel } from './StandingEntryViewModel';
export { TeamDetailsViewModel } from './TeamDetailsViewModel';
export { TeamJoinRequestViewModel } from './TeamJoinRequestViewModel';
export { TeamMemberViewModel } from './TeamMemberViewModel';
export { TeamSummaryViewModel } from './TeamSummaryViewModel';
export { UpdateAvatarViewModel } from './UpdateAvatarViewModel';
export { UploadMediaViewModel } from './UploadMediaViewModel';
export { UserProfileViewModel } from './UserProfileViewModel';
export { WalletTransactionViewModel } from './WalletTransactionViewModel';
export { WalletViewModel } from './WalletViewModel';