81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
import type { Driver } from '@gridpilot/racing/domain/entities/Driver';
|
|
import type { SkillLevel } from '@gridpilot/racing/domain/services/SkillLevelService';
|
|
import { SkillLevelService } from '@gridpilot/racing/domain/services/SkillLevelService';
|
|
import type {
|
|
IDriversLeaderboardPresenter,
|
|
DriverLeaderboardItemViewModel,
|
|
DriversLeaderboardViewModel,
|
|
} from '@gridpilot/racing/application/presenters/IDriversLeaderboardPresenter';
|
|
|
|
export class DriversLeaderboardPresenter implements IDriversLeaderboardPresenter {
|
|
private viewModel: DriversLeaderboardViewModel | null = null;
|
|
|
|
present(
|
|
drivers: Driver[],
|
|
rankings: Array<{ driverId: string; rating: number; overallRank: number }>,
|
|
stats: Record<string, { rating: number; wins: number; podiums: number; totalRaces: number; overallRank: number }>,
|
|
avatarUrls: Record<string, string>
|
|
): DriversLeaderboardViewModel {
|
|
const items: DriverLeaderboardItemViewModel[] = drivers.map((driver) => {
|
|
const driverStats = stats[driver.id];
|
|
const rating = driverStats?.rating ?? 0;
|
|
const wins = driverStats?.wins ?? 0;
|
|
const podiums = driverStats?.podiums ?? 0;
|
|
const totalRaces = driverStats?.totalRaces ?? 0;
|
|
|
|
let effectiveRank = Number.POSITIVE_INFINITY;
|
|
if (typeof driverStats?.overallRank === 'number' && driverStats.overallRank > 0) {
|
|
effectiveRank = driverStats.overallRank;
|
|
} else {
|
|
const indexInGlobal = rankings.findIndex((entry) => entry.driverId === driver.id);
|
|
if (indexInGlobal !== -1) {
|
|
effectiveRank = indexInGlobal + 1;
|
|
}
|
|
}
|
|
|
|
const skillLevel = SkillLevelService.getSkillLevel(rating);
|
|
const isActive = rankings.some((r) => r.driverId === driver.id);
|
|
|
|
return {
|
|
id: driver.id,
|
|
name: driver.name,
|
|
rating,
|
|
skillLevel,
|
|
nationality: driver.country,
|
|
racesCompleted: totalRaces,
|
|
wins,
|
|
podiums,
|
|
isActive,
|
|
rank: effectiveRank,
|
|
avatarUrl: avatarUrls[driver.id] ?? '',
|
|
};
|
|
});
|
|
|
|
items.sort((a, b) => {
|
|
const rankA = Number.isFinite(a.rank) && a.rank > 0 ? a.rank : Number.POSITIVE_INFINITY;
|
|
const rankB = Number.isFinite(b.rank) && b.rank > 0 ? b.rank : Number.POSITIVE_INFINITY;
|
|
if (rankA !== rankB) return rankA - rankB;
|
|
return b.rating - a.rating;
|
|
});
|
|
|
|
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;
|
|
|
|
this.viewModel = {
|
|
drivers: items,
|
|
totalRaces,
|
|
totalWins,
|
|
activeCount,
|
|
};
|
|
|
|
return this.viewModel;
|
|
}
|
|
|
|
getViewModel(): DriversLeaderboardViewModel {
|
|
if (!this.viewModel) {
|
|
throw new Error('Presenter has not been called yet');
|
|
}
|
|
return this.viewModel;
|
|
}
|
|
} |