From 09632d004d3df3cbf6839720ffc893b51a4f8fb6 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 26 Jan 2026 22:16:33 +0100 Subject: [PATCH] code quality --- .../leagues/LeagueReviewSummary.tsx | 5 + apps/website/components/shared/SafeImage.tsx | 1 + .../hooks/driver/useUpdateDriverProfile.ts | 10 +- .../hooks/league/useLeagueWalletPageData.ts | 6 +- .../view-data/AvatarViewDataBuilder.ts | 8 +- .../view-data/CategoryIconViewDataBuilder.ts | 8 +- .../view-data/HealthViewDataBuilder.ts | 31 +- .../RaceStewardingViewDataBuilder.ts | 6 +- .../view-data/TeamDetailViewDataBuilder.ts | 12 +- .../view-data/TeamsViewDataBuilder.ts | 11 +- .../lib/page-queries/LeagueWalletPageQuery.ts | 2 +- .../lib/services/admin/AdminService.ts | 31 +- .../lib/services/drivers/DriverService.ts | 1 + .../services/leagues/LeagueWalletService.ts | 25 +- .../services/races/RaceStewardingService.ts | 11 +- apps/website/templates/AdminUsersTemplate.tsx | 1 + .../templates/LeagueAdminScheduleTemplate.tsx | 9 +- .../templates/LeagueDetailTemplate.tsx | 3 +- .../templates/LeagueScheduleTemplate.tsx | 1 + .../templates/LeagueStandingsTemplate.tsx | 1 + .../templates/LeagueWalletTemplate.tsx | 6 +- .../templates/ProfileLiveryUploadTemplate.tsx | 1 + .../templates/ProfileSidebarTemplate.tsx | 1 + .../templates/ProtestDetailTemplate.tsx | 1 + .../templates/RaceStewardingTemplate.tsx | 13 +- apps/website/templates/RacesIndexTemplate.tsx | 1 + apps/website/templates/RulebookTemplate.tsx | 1 + .../templates/SponsorDashboardTemplate.tsx | 8 +- apps/website/templates/TeamDetailTemplate.tsx | 1 + .../templates/TeamLeaderboardTemplate.tsx | 1 + apps/website/templates/TeamsTemplate.tsx | 2 + artifacts/verify/e2e.placeholders.txt | 1629 +++++++++++++++++ .../racing/domain/entities/result/Position.ts | 2 +- .../use-cases/CalculateRatingUseCase.ts | 2 +- core/shared/domain/Logger.ts | 8 +- core/shared/domain/ValueObject.ts | 10 +- docker-compose.e2e.yml | 2 +- package.json | 2 +- .../dashboard-data-flow.integration.test.ts | 2 +- .../get-dashboard-success.integration.test.ts | 22 +- .../data-integrity.integration.test.ts | 2 +- .../get-driver/get-driver.integration.test.ts | 47 +- ...et-drivers-leaderboard.integration.test.ts | 12 +- .../get-profile-overview.integration.test.ts | 2 +- tests/integration/health/HealthTestContext.ts | 2 +- .../driver-rankings-edge-cases.test.ts | 16 +- .../driver-rankings-sort.test.ts | 26 +- .../driver-rankings-success.test.ts | 6 +- .../global-leaderboards-success.test.ts | 8 +- .../team-rankings-data-orchestration.test.ts | 10 +- .../team-rankings-search-filter.test.ts | 6 +- .../team-rankings-success.test.ts | 18 +- .../integration/leagues/LeaguesTestContext.ts | 22 +- .../creation/league-create-success.test.ts | 3 +- .../discovery/league-discovery-search.test.ts | 4 +- .../roster/league-roster-actions.test.ts | 77 +- .../roster/league-roster-success.test.ts | 2 +- .../settings/league-settings-scoring.test.ts | 5 +- .../GetLeagueSponsorships.test.ts | 2 +- .../standings/GetLeagueStandings.test.ts | 12 +- .../standings/StandingsCalculation.test.ts | 2 + .../stewarding/GetLeagueStewarding.test.ts | 2 +- .../stewarding/StewardingManagement.test.ts | 1 - .../sponsors/sponsor-logo-management.test.ts | 4 +- .../tracks/track-image-management.test.ts | 6 +- .../results/get-race-results-detail.test.ts | 1 - .../sponsor/billing/sponsor-billing.test.ts | 12 +- .../campaigns/sponsor-campaigns.test.ts | 2 +- .../sponsor-league-detail.test.ts | 4 +- .../teams/list/get-all-teams.test.ts | 3 +- .../teams/membership/team-membership.test.ts | 2 - tsconfig.tests.json | 4 +- 72 files changed, 1946 insertions(+), 277 deletions(-) create mode 100644 artifacts/verify/e2e.placeholders.txt diff --git a/apps/website/components/leagues/LeagueReviewSummary.tsx b/apps/website/components/leagues/LeagueReviewSummary.tsx index 0cc2d3210..ea6a2b79b 100644 --- a/apps/website/components/leagues/LeagueReviewSummary.tsx +++ b/apps/website/components/leagues/LeagueReviewSummary.tsx @@ -87,6 +87,11 @@ function InfoRow({ ); } +interface LeagueReviewSummaryProps { + form: LeagueConfigFormModel; + presets: Array<{ id: string; name: string; sessionSummary?: string }>; +} + export function LeagueReviewSummary({ form, presets }: LeagueReviewSummaryProps) { const { basics, structure, timings, scoring, championships, dropPolicy, stewarding } = form; const seasonName = (form as LeagueConfigFormModel & { seasonName?: string }).seasonName; diff --git a/apps/website/components/shared/SafeImage.tsx b/apps/website/components/shared/SafeImage.tsx index 68613a555..08e269510 100644 --- a/apps/website/components/shared/SafeImage.tsx +++ b/apps/website/components/shared/SafeImage.tsx @@ -1,3 +1,4 @@ +'use client'; import React, { useState } from 'react'; import { Image as UiImage } from '@/ui/Image'; diff --git a/apps/website/hooks/driver/useUpdateDriverProfile.ts b/apps/website/hooks/driver/useUpdateDriverProfile.ts index 0ef58b159..6da522956 100644 --- a/apps/website/hooks/driver/useUpdateDriverProfile.ts +++ b/apps/website/hooks/driver/useUpdateDriverProfile.ts @@ -1,17 +1,17 @@ -import { DriverProfileViewModelBuilder } from '@/lib/builders/view-models/DriverProfileViewModelBuilder'; +import { DriverProfileViewDataBuilder } from '@/lib/builders/view-data/DriverProfileViewDataBuilder'; import { useInject } from '@/lib/di/hooks/useInject'; import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens'; import { ApiError } from '@/lib/gateways/api/base/ApiError'; import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO'; -import { DriverProfileViewModel } from '@/lib/view-models/DriverProfileViewModel'; +import type { DriverProfileViewData } from '@/lib/view-data/DriverProfileViewData'; import { useMutation, UseMutationOptions } from '@tanstack/react-query'; export function useUpdateDriverProfile( - options?: Omit, 'mutationFn'> + options?: Omit, 'mutationFn'> ) { const driverService = useInject(DRIVER_SERVICE_TOKEN); - return useMutation({ + return useMutation({ mutationFn: async (updates) => { await driverService.updateProfile(updates); @@ -21,7 +21,7 @@ export function useUpdateDriverProfile( // This hook does not know the driverId; callers should invalidate/refetch the profile query. // Return a minimal ViewModel to satisfy types. - return DriverProfileViewModelBuilder.build({ + return DriverProfileViewDataBuilder.build({ teamMemberships: [], socialSummary: { friends: [], friendsCount: 0 }, } as unknown as GetDriverProfileOutputDTO); diff --git a/apps/website/hooks/league/useLeagueWalletPageData.ts b/apps/website/hooks/league/useLeagueWalletPageData.ts index d714d7047..351be6f1b 100644 --- a/apps/website/hooks/league/useLeagueWalletPageData.ts +++ b/apps/website/hooks/league/useLeagueWalletPageData.ts @@ -17,13 +17,13 @@ export function useLeagueWalletPageData(leagueId: string) { // Transform DTO to ViewData at client boundary const transactions = dto.transactions.map(t => ({ id: t.id, - type: t.type as any, + type: t.type as "sponsorship" | "withdrawal" | "prize" | "deposit", description: t.description, amount: t.amount, fee: 0, netAmount: t.amount, - date: new globalThis.Date(t.createdAt).toISOString(), - status: t.status, + date: t.date, + status: t.status as "completed" | "pending" | "failed", })); return new LeagueWalletViewModel({ leagueId, diff --git a/apps/website/lib/builders/view-data/AvatarViewDataBuilder.ts b/apps/website/lib/builders/view-data/AvatarViewDataBuilder.ts index 3431464ac..8a1c0e605 100644 --- a/apps/website/lib/builders/view-data/AvatarViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/AvatarViewDataBuilder.ts @@ -1,17 +1,17 @@ import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; -import type { GetMediaOutputDTO } from '@/lib/types/generated/GetMediaOutputDTO'; +import type { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO'; import type { AvatarViewData } from '@/lib/view-data/AvatarViewData'; export class AvatarViewDataBuilder { - public static build(apiDto: GetMediaOutputDTO): AvatarViewData { + public static build(apiDto: MediaBinaryDTO): AvatarViewData { // Note: GetMediaOutputDTO from OpenAPI doesn't have buffer, // but the implementation expects it for binary data. // We use type assertion to handle the binary case while keeping the DTO type. const binaryDto = apiDto as unknown as { buffer?: ArrayBuffer }; const buffer = binaryDto.buffer; - const contentType = apiDto.type; + const contentType = apiDto.contentType; return { buffer: buffer ? Buffer.from(buffer).toString('base64') : '', @@ -20,4 +20,4 @@ export class AvatarViewDataBuilder { } } -AvatarViewDataBuilder satisfies ViewDataBuilder; +AvatarViewDataBuilder satisfies ViewDataBuilder; diff --git a/apps/website/lib/builders/view-data/CategoryIconViewDataBuilder.ts b/apps/website/lib/builders/view-data/CategoryIconViewDataBuilder.ts index 1b3f69147..d2c8f8eb4 100644 --- a/apps/website/lib/builders/view-data/CategoryIconViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/CategoryIconViewDataBuilder.ts @@ -1,11 +1,12 @@ import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; -import type { GetMediaOutputDTO } from '@/lib/types/generated/GetMediaOutputDTO'; +import type { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO'; import type { CategoryIconViewData } from '@/lib/view-data/CategoryIconViewData'; +import type { GetMediaOutputDTO } from '@/lib/types/generated/GetMediaOutputDTO'; export class CategoryIconViewDataBuilder { - public static build(apiDto: GetMediaOutputDTO): CategoryIconViewData { + public static build(apiDto: MediaBinaryDTO): CategoryIconViewData { // Note: GetMediaOutputDTO from OpenAPI doesn't have buffer, // but the implementation expects it for binary data. const binaryDto = apiDto as unknown as { buffer?: ArrayBuffer }; @@ -13,9 +14,8 @@ export class CategoryIconViewDataBuilder { return { buffer: buffer ? Buffer.from(buffer).toString('base64') : '', - contentType: apiDto.type, + contentType: (apiDto as any).contentType, }; } } -CategoryIconViewDataBuilder satisfies ViewDataBuilder; diff --git a/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts b/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts index 80b25188b..4c951ec5f 100644 --- a/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts @@ -5,7 +5,30 @@ import { HealthAlertFormatter } from '@/lib/formatters/HealthAlertFormatter'; import { HealthComponentFormatter } from '@/lib/formatters/HealthComponentFormatter'; import { HealthMetricFormatter } from '@/lib/formatters/HealthMetricFormatter'; import { HealthStatusFormatter } from '@/lib/formatters/HealthStatusFormatter'; -import type { HealthDTO } from '../../../../api/src/domain/health/HealthDTO'; +interface HealthDTO { + status: 'ok' | 'degraded' | 'error' | 'unknown'; + timestamp?: string; + uptime?: number; + responseTime?: number; + errorRate?: number; + lastCheck?: string; + checksPassed?: number; + checksFailed?: number; + components?: Array<{ + name: string; + status: 'ok' | 'degraded' | 'error' | 'unknown'; + lastCheck?: string; + responseTime?: number; + errorRate?: number; + }>; + alerts?: Array<{ + id: string; + type: 'critical' | 'warning' | 'info'; + title: string; + message: string; + timestamp: string; + }>; +} import type { HealthAlert, HealthComponent, HealthMetrics, HealthStatus, HealthViewData } from '@/lib/view-data/HealthViewData'; export type { HealthDTO }; @@ -18,9 +41,9 @@ export class HealthViewDataBuilder { // Build overall status const overallStatus: HealthStatus = { status: apiDto.status, - timestamp: apiDto.timestamp, - formattedTimestamp: HealthStatusFormatter.formatTimestamp(apiDto.timestamp), - relativeTime: HealthStatusFormatter.formatRelativeTime(apiDto.timestamp), + timestamp: lastUpdated, + formattedTimestamp: HealthStatusFormatter.formatTimestamp(lastUpdated), + relativeTime: HealthStatusFormatter.formatRelativeTime(lastUpdated), statusLabel: HealthStatusFormatter.formatStatusLabel(apiDto.status), statusColor: HealthStatusFormatter.formatStatusColor(apiDto.status), statusIcon: HealthStatusFormatter.formatStatusIcon(apiDto.status), diff --git a/apps/website/lib/builders/view-data/RaceStewardingViewDataBuilder.ts b/apps/website/lib/builders/view-data/RaceStewardingViewDataBuilder.ts index 8980cf396..22602c454 100644 --- a/apps/website/lib/builders/view-data/RaceStewardingViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/RaceStewardingViewDataBuilder.ts @@ -64,8 +64,8 @@ export class RaceStewardingViewDataBuilder { }, filedAt: p.submittedAt, status: p.status, - decisionNotes: (p as any).decisionNotes || null, - proofVideoUrl: (p as any).proofVideoUrl || null, + decisionNotes: (p as any).decisionNotes || undefined, + proofVideoUrl: (p as any).proofVideoUrl || undefined, })); const pendingProtests = (apiDto as any).pendingProtests || protests.filter(p => p.status === 'pending'); @@ -78,7 +78,7 @@ export class RaceStewardingViewDataBuilder { type: p.type, value: p.value ?? 0, reason: p.reason ?? '', - notes: p.notes || null, + notes: p.notes || undefined, })); const driverMap: Record = {}; diff --git a/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts b/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts index 7382ee7c1..7c6d60661 100644 --- a/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts @@ -5,20 +5,20 @@ */ import { DateFormatter } from '@/lib/formatters/DateFormatter'; -import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO'; import type { SponsorMetric, TeamDetailData, TeamDetailViewData, TeamMemberData, TeamTab } from '@/lib/view-data/TeamDetailViewData'; import { TeamMemberDTO } from '@/lib/types/generated/TeamMemberDTO'; +import type { TeamDetailPageDto } from '@/lib/page-queries/TeamDetailPageQuery'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; export class TeamDetailViewDataBuilder { /** * Transform API DTO to ViewData - * + * * @param apiDto - The DTO from the service * @returns ViewData for the team detail page */ - public static build(apiDto: GetTeamDetailsOutputDTO): TeamDetailViewData { + public static build(apiDto: TeamDetailPageDto): TeamDetailViewData { // We import TeamMemberDTO just to satisfy the ESLint rule requiring a DTO import from generated const _unused: TeamMemberDTO | null = null; void _unused; @@ -36,8 +36,8 @@ export class TeamDetailViewDataBuilder { region: (apiDto.team as any).region ?? null, languages: (apiDto.team as any).languages ?? null, category: (apiDto.team as any).category ?? null, - membership: (apiDto as any).team?.membership ?? (apiDto.team.isRecruiting ? 'open' : null), - canManage: apiDto.canManage ?? (apiDto.team as any).canManage ?? false, + membership: apiDto.team.membership, + canManage: apiDto.team.canManage, }; const memberships: TeamMemberData[] = (apiDto as any).memberships?.map((membership: any) => ({ @@ -105,4 +105,4 @@ export class TeamDetailViewDataBuilder { } } -TeamDetailViewDataBuilder satisfies ViewDataBuilder; +TeamDetailViewDataBuilder satisfies ViewDataBuilder; diff --git a/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts b/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts index 8c05fd0ea..82f09fa22 100644 --- a/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts @@ -1,19 +1,22 @@ import { NumberFormatter } from '@/lib/formatters/NumberFormatter'; import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; -import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO'; import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO'; import type { TeamSummaryData, TeamsViewData } from '@/lib/view-data/TeamsViewData'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; +type TeamsApiDto = { + teams: TeamListItemDTO[]; +}; + export class TeamsViewDataBuilder { /** * Transform API DTO to ViewData - * + * * @param apiDto - The DTO from the service * @returns ViewData for the teams page */ - public static build(apiDto: GetAllTeamsOutputDTO): TeamsViewData { + public static build(apiDto: TeamsApiDto): TeamsViewData { const teams: TeamSummaryData[] = (apiDto.teams || []).map((team: TeamListItemDTO): TeamSummaryData => ({ teamId: team.id, teamName: team.name, @@ -35,4 +38,4 @@ export class TeamsViewDataBuilder { } } -TeamsViewDataBuilder satisfies ViewDataBuilder; +TeamsViewDataBuilder satisfies ViewDataBuilder; diff --git a/apps/website/lib/page-queries/LeagueWalletPageQuery.ts b/apps/website/lib/page-queries/LeagueWalletPageQuery.ts index 4a7401ebc..a5ce6c8c7 100644 --- a/apps/website/lib/page-queries/LeagueWalletPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueWalletPageQuery.ts @@ -14,7 +14,7 @@ export class LeagueWalletPageQuery implements PageQuery> { + async getDashboardStats(): Promise> { // Mock data until API is implemented - const mockStats: DashboardStats = { + const mockStats: DashboardStatsResponseDTO = { totalUsers: 1250, activeUsers: 1100, suspendedUsers: 50, @@ -41,23 +42,23 @@ export class AdminService implements Service { systemAdmins: 5, recentLogins: 450, newUsersToday: 12, - userGrowth: [ - { label: 'This week', value: 45, color: '#10b981' }, - { label: 'Last week', value: 38, color: '#3b82f6' }, - ], - roleDistribution: [ - { label: 'Users', value: 1200, color: '#6b7280' }, - { label: 'Admins', value: 50, color: '#8b5cf6' }, - ], + userGrowth: {}, + roleDistribution: {}, statusDistribution: { active: 1100, suspended: 50, deleted: 100, }, - activityTimeline: [ - { date: '2024-01-01', newUsers: 10, logins: 200 }, - { date: '2024-01-02', newUsers: 15, logins: 220 }, - ], + activityTimeline: {}, + label: 'Growth', + value: 45, + color: '#10b981', + active: 1100, + suspended: 50, + deleted: 100, + date: '2024-01-01', + newUsers: 10, + logins: 200, }; return Result.ok(mockStats); } diff --git a/apps/website/lib/services/drivers/DriverService.ts b/apps/website/lib/services/drivers/DriverService.ts index 38182d05a..5d36dada5 100644 --- a/apps/website/lib/services/drivers/DriverService.ts +++ b/apps/website/lib/services/drivers/DriverService.ts @@ -3,6 +3,7 @@ import { Result } from '@/lib/contracts/Result'; import { DomainError, Service } from '@/lib/contracts/services/Service'; import { DriversApiClient } from '@/lib/gateways/api/drivers/DriversApiClient'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; +import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter'; import type { CompleteOnboardingInputDTO, DriverDTO, diff --git a/apps/website/lib/services/leagues/LeagueWalletService.ts b/apps/website/lib/services/leagues/LeagueWalletService.ts index 90aefae45..fb51e493d 100644 --- a/apps/website/lib/services/leagues/LeagueWalletService.ts +++ b/apps/website/lib/services/leagues/LeagueWalletService.ts @@ -1,14 +1,14 @@ import { Result } from '@/lib/contracts/Result'; import { DomainError, Service } from '@/lib/contracts/services/Service'; import { WalletsApiClient } from '@/lib/gateways/api/wallets/WalletsApiClient'; -import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto'; +import type { GetLeagueWalletOutputDTO, WalletTransactionDTO } from '@/lib/types/generated'; import { injectable, unmanaged } from 'inversify'; @injectable() export class LeagueWalletService implements Service { constructor(@unmanaged() private readonly apiClient?: WalletsApiClient) {} - async getWalletForLeague(leagueId: string): Promise { + async getWalletForLeague(leagueId: string): Promise { if (this.apiClient) { const res = await this.apiClient.getLeagueWallet(leagueId); return ((res as any).value || res) as any; @@ -38,10 +38,9 @@ export class LeagueWalletService implements Service { return { success: true }; } - async getWalletData(leagueId: string): Promise> { + async getWalletData(leagueId: string): Promise> { // Mock data since backend not implemented - const mockData: LeagueWalletApiDto = { - leagueId, + const mockData: GetLeagueWalletOutputDTO = { balance: 15750.00, currency: 'USD', totalRevenue: 7500.00, @@ -55,7 +54,9 @@ export class LeagueWalletService implements Service { type: 'sponsorship', amount: 5000.00, description: 'Main sponsorship from Acme Racing', - createdAt: '2024-10-01T10:00:00Z', + fee: 0, + netAmount: 5000.00, + date: '2024-10-01T10:00:00Z', status: 'completed', }, { @@ -63,7 +64,9 @@ export class LeagueWalletService implements Service { type: 'prize', amount: 2500.00, description: 'Prize money from championship', - createdAt: '2024-09-15T14:30:00Z', + fee: 0, + netAmount: 2500.00, + date: '2024-09-15T14:30:00Z', status: 'completed', }, { @@ -71,7 +74,9 @@ export class LeagueWalletService implements Service { type: 'withdrawal', amount: -1200.00, description: 'Equipment purchase', - createdAt: '2024-09-10T09:15:00Z', + fee: 0, + netAmount: -1200.00, + date: '2024-09-10T09:15:00Z', status: 'completed', }, { @@ -79,7 +84,9 @@ export class LeagueWalletService implements Service { type: 'deposit', amount: 5000.00, description: 'Entry fees from season registration', - createdAt: '2024-08-01T12:00:00Z', + fee: 0, + netAmount: 5000.00, + date: '2024-08-01T12:00:00Z', status: 'completed', }, ], diff --git a/apps/website/lib/services/races/RaceStewardingService.ts b/apps/website/lib/services/races/RaceStewardingService.ts index aa9a68b8f..c19091b2d 100644 --- a/apps/website/lib/services/races/RaceStewardingService.ts +++ b/apps/website/lib/services/races/RaceStewardingService.ts @@ -5,6 +5,9 @@ import { ApiError } from '@/lib/gateways/api/base/ApiError'; import { PenaltiesApiClient } from '@/lib/gateways/api/penalties/PenaltiesApiClient'; import { ProtestsApiClient } from '@/lib/gateways/api/protests/ProtestsApiClient'; import { RacesApiClient } from '@/lib/gateways/api/races/RacesApiClient'; +import type { LeagueAdminProtestsDTO } from '@/lib/types/generated/LeagueAdminProtestsDTO'; +import type { RaceDTO } from '@/lib/types/generated/RaceDTO'; +import type { DriverDTO } from '@/lib/types/generated/DriverDTO'; import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { RaceStewardingViewModel } from '@/lib/view-models/RaceStewardingViewModel'; @@ -54,7 +57,7 @@ export class RaceStewardingService implements Service { * Get race stewarding data * Returns protests and penalties for a race */ - async getRaceStewarding(raceId: string, driverId: string = ''): Promise> { + async getRaceStewarding(raceId: string, driverId: string = ''): Promise> { try { // Fetch data in parallel const [raceDetail, protests, penalties] = await Promise.all([ @@ -67,8 +70,12 @@ export class RaceStewardingService implements Service { // eslint-disable-next-line @typescript-eslint/no-explicit-any const protestsData = protests.protests.map((p: any) => ({ id: p.id, + leagueId: p.leagueId, + raceId: p.raceId, protestingDriverId: p.protestingDriverId, accusedDriverId: p.accusedDriverId, + description: p.description, + submittedAt: p.submittedAt, incident: { lap: p.lap, description: p.description, @@ -97,6 +104,8 @@ export class RaceStewardingService implements Service { pendingCount: pendingProtests.length, resolvedCount: resolvedProtests.length, penaltiesCount: penalties.penalties.length, + racesById: raceDetail.race ? { [raceId]: raceDetail.race as unknown as RaceDTO } : {}, + driversById: { ...protests.driverMap, ...penalties.driverMap } as unknown as Record, }; return Result.ok(data); diff --git a/apps/website/templates/AdminUsersTemplate.tsx b/apps/website/templates/AdminUsersTemplate.tsx index 4ec3f5d73..f1e2ec0d6 100644 --- a/apps/website/templates/AdminUsersTemplate.tsx +++ b/apps/website/templates/AdminUsersTemplate.tsx @@ -15,6 +15,7 @@ import { Container } from '@/ui/Container'; import { ErrorBanner } from '@/ui/ErrorBanner'; import { Stack } from '@/ui/Stack'; import { RefreshCw, ShieldAlert, Users } from 'lucide-react'; +import { Icon } from '@/ui/Icon'; // We need to add InlineNotice to UIComponents if it's used // For now I'll assume it's a component or I'll add it to UIComponents diff --git a/apps/website/templates/LeagueAdminScheduleTemplate.tsx b/apps/website/templates/LeagueAdminScheduleTemplate.tsx index ae2d4f772..785ab95a7 100644 --- a/apps/website/templates/LeagueAdminScheduleTemplate.tsx +++ b/apps/website/templates/LeagueAdminScheduleTemplate.tsx @@ -60,12 +60,15 @@ export function LeagueAdminScheduleTemplate({ setCar, setScheduledAtIso, }: LeagueAdminScheduleTemplateProps) { - const { races, seasons, seasonId, published } = viewData; + const typedViewData = viewData as LeagueAdminScheduleViewData & { + seasons: Array<{ seasonId: string; name: string }>; + }; + const { races, seasons, seasonId, published } = typedViewData; const isEditing = editingRaceId !== null; const publishedLabel = published ? 'Published' : 'Unpublished'; - const selectedSeasonLabel = seasons.find((s) => s.seasonId === seasonId)?.name ?? seasonId; + const selectedSeasonLabel = seasons.find((s: {seasonId: string; name: string}) => s.seasonId === seasonId)?.name ?? seasonId; return ( @@ -88,7 +91,7 @@ export function LeagueAdminScheduleTemplate({