109 lines
2.9 KiB
TypeScript
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);
|
|
} |