resolve manual DTOs

This commit is contained in:
2025-12-18 22:19:40 +01:00
parent 4a3087ae35
commit d617654928
179 changed files with 3716 additions and 1257 deletions

View File

@@ -1,16 +1,16 @@
import type { DriverDTO } from '../types/DriverDTO';
import type { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO';
/**
* View Model for driver summary with rating and rank
* Transform from DTO to ViewModel with UI fields
*/
export class DriverSummaryViewModel {
driver: DriverDTO;
driver: GetDriverOutputDTO;
rating: number | null;
rank: number | null;
constructor(dto: {
driver: DriverDTO;
driver: GetDriverOutputDTO;
rating?: number | null;
rank?: number | null;
}) {

View File

@@ -1,3 +1,5 @@
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
/**
* View Model for Driver's Team
*
@@ -6,21 +8,22 @@
export class DriverTeamViewModel {
teamId: string;
teamName: string;
tag: string;
role: string;
isOwner: boolean;
canManage: boolean;
constructor(dto: { teamId: string; teamName: string; role: string }) {
this.teamId = dto.teamId;
this.teamName = dto.teamName;
this.role = dto.role;
constructor(dto: GetDriverTeamOutputDTO) {
this.teamId = dto.team.id;
this.teamName = dto.team.name;
this.tag = dto.team.tag;
this.role = dto.membership.role;
this.isOwner = dto.isOwner;
this.canManage = dto.canManage;
}
/** 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

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

View File

@@ -3,7 +3,7 @@ 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 { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO';
import { RaceDTO } from '../types/generated/RaceDTO';
import { LeagueScoringConfigDTO } from '../types/LeagueScoringConfigDTO';
import { RaceViewModel } from './RaceViewModel';
@@ -20,7 +20,7 @@ export interface SponsorInfo {
// Driver summary for management section
export interface DriverSummary {
driver: DriverDTO;
driver: GetDriverOutputDTO;
rating: number | null;
rank: number | null;
}
@@ -50,13 +50,13 @@ export class LeagueDetailPageViewModel {
};
// Owner info
owner: DriverDTO | null;
owner: GetDriverOutputDTO | null;
// Scoring configuration
scoringConfig: LeagueScoringConfigDTO | null;
// Drivers and memberships
drivers: DriverDTO[];
drivers: GetDriverOutputDTO[];
memberships: LeagueMembershipWithRole[];
// Races
@@ -93,9 +93,9 @@ export class LeagueDetailPageViewModel {
constructor(
league: LeagueWithCapacityDTO,
owner: DriverDTO | null,
owner: GetDriverOutputDTO | null,
scoringConfig: LeagueScoringConfigDTO | null,
drivers: DriverDTO[],
drivers: GetDriverOutputDTO[],
memberships: LeagueMembershipsDTO,
allRaces: RaceViewModel[],
leagueStats: LeagueStatsDTO,

View File

@@ -1,4 +1,4 @@
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
import type { LeagueScoringPresetDTO } from '@core/racing/application/ports/LeagueScoringPresetProvider';
/**
* View Model for league scoring presets

View File

@@ -1,5 +1,5 @@
import type { LeagueConfigFormModel } from '../types/LeagueConfigFormModel';
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
import type { LeagueConfigFormModel } from '@core/racing/application';
import type { LeagueScoringPresetDTO } from '@core/racing/application/ports/LeagueScoringPresetProvider';
import { LeagueScoringPresetsViewModel } from './LeagueScoringPresetsViewModel';
import { DriverSummaryViewModel } from './DriverSummaryViewModel';

View File

@@ -1,14 +1,14 @@
import { LeagueStandingDTO } from '../types/generated/LeagueStandingDTO';
import { StandingEntryViewModel } from './StandingEntryViewModel';
import { DriverDTO } from '../types/DriverDTO';
import { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO';
import { LeagueMembership } from '../types/LeagueMembership';
export class LeagueStandingsViewModel {
standings: StandingEntryViewModel[];
drivers: DriverDTO[];
drivers: GetDriverOutputDTO[];
memberships: LeagueMembership[];
constructor(dto: { standings: LeagueStandingDTO[]; drivers: DriverDTO[]; memberships: LeagueMembership[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) {
constructor(dto: { standings: LeagueStandingDTO[]; drivers: GetDriverOutputDTO[]; memberships: LeagueMembership[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) {
const leaderPoints = dto.standings[0]?.points || 0;
this.standings = dto.standings.map((entry, index) => {
const nextPoints = dto.standings[index + 1]?.points || entry.points;

View File

@@ -6,9 +6,9 @@ describe('MediaViewModel', () => {
const dto = {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image' as const,
category: 'avatar' as const,
uploadedAt: new Date('2023-01-15'),
type: 'image',
category: 'avatar',
uploadedAt: '2023-01-15T00:00:00.000Z',
size: 2048000,
};
@@ -26,8 +26,8 @@ describe('MediaViewModel', () => {
const dto = {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image' as const,
uploadedAt: new Date('2023-01-15'),
type: 'image',
uploadedAt: '2023-01-15T00:00:00.000Z',
};
const viewModel = new MediaViewModel(dto);
@@ -41,7 +41,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
expect(viewModel.formattedSize).toBe('Unknown');
@@ -52,7 +52,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 512000, // 500 KB
});
@@ -64,7 +64,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 2048000, // 2 MB
});
@@ -76,7 +76,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 1024, // 1 KB
});
@@ -88,7 +88,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/video.mp4',
type: 'video',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 104857600, // 100 MB
});
@@ -100,19 +100,19 @@ describe('MediaViewModel', () => {
id: '1',
url: 'image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
const videoVm = new MediaViewModel({
id: '2',
url: 'video.mp4',
type: 'video',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
const docVm = new MediaViewModel({
id: '3',
url: 'doc.pdf',
type: 'document',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
expect(imageVm.type).toBe('image');
@@ -129,7 +129,7 @@ describe('MediaViewModel', () => {
url: 'https://example.com/image.jpg',
type: 'image',
category,
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
expect(viewModel.category).toBe(category);

View File

@@ -1,12 +1,4 @@
// Note: No generated DTO available for Media yet
interface MediaDTO {
id: string;
url: string;
type: 'image' | 'video' | 'document';
category?: 'avatar' | 'team-logo' | 'league-cover' | 'race-result';
uploadedAt: Date;
size?: number;
}
import type { GetMediaOutputDTO } from '../types/generated';
/**
* Media View Model
@@ -21,12 +13,12 @@ export class MediaViewModel {
uploadedAt: Date;
size?: number;
constructor(dto: MediaDTO) {
constructor(dto: GetMediaOutputDTO) {
this.id = dto.id;
this.url = dto.url;
this.type = dto.type;
this.uploadedAt = dto.uploadedAt;
if (dto.category !== undefined) this.category = dto.category;
this.type = dto.type as 'image' | 'video' | 'document';
this.uploadedAt = new Date(dto.uploadedAt);
if (dto.category !== undefined) this.category = dto.category as 'avatar' | 'team-logo' | 'league-cover' | 'race-result';
if (dto.size !== undefined) this.size = dto.size;
}

View File

@@ -1,70 +1,53 @@
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;
description?: string;
logoUrl?: string;
memberCount: number;
ownerId: string;
members: TeamMemberDTO[];
}
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
export class TeamDetailsViewModel {
id: string;
name: string;
tag: string;
description?: string;
logoUrl?: string;
memberCount: number;
ownerId: string;
members: TeamMemberViewModel[];
leagues: string[];
createdAt?: string;
specialization?: string;
region?: string;
languages?: string[];
membership: { role: string; joinedAt: string; isActive: boolean } | null;
canManage: boolean;
private currentUserId: string;
constructor(dto: TeamDetailsDTO, currentUserId: string) {
this.id = dto.id;
this.name = dto.name;
this.description = dto.description;
this.logoUrl = dto.logoUrl;
this.memberCount = dto.memberCount;
this.ownerId = dto.ownerId;
this.members = dto.members.map(m => new TeamMemberViewModel(m, currentUserId, dto.ownerId));
constructor(dto: GetTeamDetailsOutputDTO, currentUserId: string) {
this.id = dto.team.id;
this.name = dto.team.name;
this.tag = dto.team.tag;
this.description = dto.team.description;
this.ownerId = dto.team.ownerId;
this.leagues = dto.team.leagues;
this.createdAt = dto.team.createdAt;
this.specialization = dto.team.specialization;
this.region = dto.team.region;
this.languages = dto.team.languages;
this.membership = dto.membership;
this.canManage = dto.canManage;
this.currentUserId = currentUserId;
}
/** UI-specific: Whether current user is owner */
get isOwner(): boolean {
return this.currentUserId === this.ownerId;
return this.membership?.role === 'owner';
}
/** UI-specific: Whether can add members */
get canAddMembers(): boolean {
return this.isOwner && this.memberCount < 10; // Assuming max 10
/** UI-specific: Whether can manage team */
get canManage(): boolean {
return this.canManage;
}
/** UI-specific: Member management actions available */
get memberActionsAvailable(): boolean {
return this.isOwner;
/** UI-specific: Whether current user is member */
get isMember(): boolean {
return this.membership !== null;
}
/** UI-specific: Team status */
get teamStatus(): string {
if (this.memberCount < 5) return 'Recruiting';
if (this.memberCount < 10) return 'Active';
return 'Full';
/** UI-specific: Current user's role */
get userRole(): string {
return this.membership?.role || 'none';
}
}

View File

@@ -1,30 +1,23 @@
// 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?: DriverDTO;
role: string;
joinedAt: string;
}
import type { TeamMemberDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
export class TeamMemberViewModel {
driverId: string;
driver?: any;
role: string;
driverName: string;
role: 'owner' | 'manager' | 'member';
joinedAt: string;
isActive: boolean;
avatarUrl: string;
private currentUserId: string;
private teamOwnerId: string;
constructor(dto: TeamMemberDTO, currentUserId: string, teamOwnerId: string) {
Object.assign(this, dto);
this.driverId = dto.driverId;
this.driverName = dto.driverName;
this.role = dto.role;
this.joinedAt = dto.joinedAt;
this.isActive = dto.isActive;
this.avatarUrl = dto.avatarUrl;
this.currentUserId = currentUserId;
this.teamOwnerId = teamOwnerId;
}
@@ -33,7 +26,7 @@ export class TeamMemberViewModel {
get roleBadgeVariant(): string {
switch (this.role) {
case 'owner': return 'primary';
case 'captain': return 'secondary';
case 'manager': return 'secondary';
case 'member': return 'default';
default: return 'default';
}

View File

@@ -1,18 +1,10 @@
// Note: No generated DTO available for TeamSummary yet
interface TeamSummaryDTO {
id: string;
name: string;
logoUrl?: string;
memberCount: number;
rating: number;
}
import type { TeamListItemDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
export class TeamSummaryViewModel {
id: string;
name: string;
logoUrl?: string;
tag: string;
memberCount: number;
rating: number;
description?: string;
totalWins: number = 0;
totalRaces: number = 0;
@@ -21,23 +13,20 @@ export class TeamSummaryViewModel {
specialization?: string;
region?: string;
languages: string[] = [];
leagues: string[] = [];
private maxMembers = 10; // Assuming max members
constructor(dto: TeamSummaryDTO & { description?: string; totalWins?: number; totalRaces?: number; performanceLevel?: string; isRecruiting?: boolean; specialization?: string; region?: string; languages?: string[] }) {
constructor(dto: TeamListItemDTO) {
this.id = dto.id;
this.name = dto.name;
if (dto.logoUrl !== undefined) this.logoUrl = dto.logoUrl;
this.tag = dto.tag;
this.memberCount = dto.memberCount;
this.rating = dto.rating;
this.description = dto.description;
this.totalWins = dto.totalWins ?? 0;
this.totalRaces = dto.totalRaces ?? 0;
this.performanceLevel = dto.performanceLevel ?? '';
this.isRecruiting = dto.isRecruiting ?? false;
this.specialization = dto.specialization;
this.region = dto.region;
this.languages = dto.languages ?? [];
this.leagues = dto.leagues;
}
/** UI-specific: Whether team is full */
@@ -45,9 +34,9 @@ export class TeamSummaryViewModel {
return this.memberCount >= this.maxMembers;
}
/** UI-specific: Rating display */
get ratingDisplay(): string {
return this.rating.toFixed(0);
/** UI-specific: Tag display */
get tagDisplay(): string {
return `[${this.tag}]`;
}
/** UI-specific: Member count display */