Files
gridpilot.gg/apps/website/lib/presenters/DriversLeaderboardPresenter.ts
2025-12-16 10:50:15 +01:00

109 lines
2.9 KiB
TypeScript

/**
* DriversLeaderboardPresenter - Pure data transformer
* Transforms API response to view model without DI dependencies.
*/
import { apiClient, type DriversLeaderboardViewModel as ApiDriversLeaderboardViewModel } from '@/lib/apiClient';
export type SkillLevel = 'rookie' | 'amateur' | 'pro' | 'elite' | 'legend';
export interface DriverLeaderboardItemViewModel {
id: string;
name: string;
rating: number;
skillLevel: SkillLevel;
nationality?: string | undefined;
racesCompleted: number;
wins: number;
podiums: number;
isActive: boolean;
rank: number;
avatarUrl?: string | undefined;
}
export interface DriversLeaderboardViewModel {
drivers: DriverLeaderboardItemViewModel[];
totalRaces: number;
totalWins: number;
activeCount: number;
}
export interface IDriversLeaderboardPresenter {
reset(): void;
getViewModel(): DriversLeaderboardViewModel | null;
}
/**
* Calculate skill level from rating
*/
function getSkillLevel(rating: number): SkillLevel {
if (rating >= 5000) return 'legend';
if (rating >= 3500) return 'elite';
if (rating >= 2000) return 'pro';
if (rating >= 1000) return 'amateur';
return 'rookie';
}
/**
* Transform API response to view model
*/
function transformApiResponse(apiResponse: ApiDriversLeaderboardViewModel): DriversLeaderboardViewModel {
const items: DriverLeaderboardItemViewModel[] = apiResponse.drivers.map((driver, index) => {
const rating = driver.rating ?? 0;
const skillLevel = getSkillLevel(rating);
const viewModel: DriverLeaderboardItemViewModel = {
id: driver.id,
name: driver.name,
rating,
skillLevel,
racesCompleted: driver.races ?? 0,
wins: driver.wins ?? 0,
podiums: 0, // API may not provide this, default to 0
isActive: true,
rank: index + 1,
};
if (driver.avatarUrl) {
viewModel.avatarUrl = driver.avatarUrl;
}
return viewModel;
});
const totalRaces = items.reduce((sum, d) => sum + d.racesCompleted, 0);
const totalWins = items.reduce((sum, d) => sum + d.wins, 0);
const activeCount = items.filter((d) => d.isActive).length;
return {
drivers: items,
totalRaces,
totalWins,
activeCount,
};
}
export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter {
private viewModel: DriversLeaderboardViewModel | null = null;
reset(): void {
this.viewModel = null;
}
async fetchAndPresent(): Promise<void> {
const apiResponse = await apiClient.drivers.getLeaderboard();
this.viewModel = transformApiResponse(apiResponse);
}
getViewModel(): DriversLeaderboardViewModel | null {
return this.viewModel;
}
}
/**
* Convenience function to fetch and transform drivers leaderboard
*/
export async function fetchDriversLeaderboard(): Promise<DriversLeaderboardViewModel> {
const apiResponse = await apiClient.drivers.getLeaderboard();
return transformApiResponse(apiResponse);
}