9f54b459f3
computeRanksBy(items, comparator) remplace la logique de classement "ex-aequo partagent le rang" en la rendant générique ; computeRanks devient un simple appel avec le comparateur points desc, sans rien changer pour l'appelant existant. Podium accepte maintenant un prop renderValue (+ valueLabel optionnel) à la place du "points/gloires" en dur, pour pouvoir afficher un classement par temps (Course du Char) avec le même composant que le classement par points.
56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
export type Ranked<T> = T & { rank: number };
|
|
export type RankedProfile<T extends { points: number }> = Ranked<T>;
|
|
|
|
// Classement "compétition" générique : les ex-aequo (selon le comparateur)
|
|
// partagent le même rang, et le rang suivant saute en conséquence
|
|
// (1, 2, 2, 4 — pas 1, 2, 2, 3).
|
|
export function computeRanksBy<T>(items: T[], compare: (a: T, b: T) => number): Ranked<T>[] {
|
|
const sorted = [...items].sort(compare);
|
|
let lastItem: T | null = null;
|
|
let lastRank = 0;
|
|
|
|
return sorted.map((item, index) => {
|
|
if (lastItem === null || compare(item, lastItem) !== 0) {
|
|
lastRank = index + 1;
|
|
lastItem = item;
|
|
}
|
|
return { ...item, rank: lastRank };
|
|
});
|
|
}
|
|
|
|
export function computeRanks<T extends { points: number }>(profiles: T[]): RankedProfile<T>[] {
|
|
return computeRanksBy(profiles, (a, b) => b.points - a.points);
|
|
}
|
|
|
|
const ROMAN_DIGITS: [number, string][] = [
|
|
[10, "X"],
|
|
[9, "IX"],
|
|
[5, "V"],
|
|
[4, "IV"],
|
|
[1, "I"],
|
|
];
|
|
|
|
export function toRoman(value: number): string {
|
|
let remaining = value;
|
|
let result = "";
|
|
for (const [digit, symbol] of ROMAN_DIGITS) {
|
|
while (remaining >= digit) {
|
|
result += symbol;
|
|
remaining -= digit;
|
|
}
|
|
}
|
|
return result || "0";
|
|
}
|
|
|
|
export type Progress = { direction: "up" | "down" | "same" | "none"; amount: number };
|
|
|
|
export function computeProgress(currentRank: number, previousRank: number | null): Progress {
|
|
if (previousRank == null) {
|
|
return { direction: "none", amount: 0 };
|
|
}
|
|
const amount = previousRank - currentRank;
|
|
if (amount > 0) return { direction: "up", amount };
|
|
if (amount < 0) return { direction: "down", amount: -amount };
|
|
return { direction: "same", amount: 0 };
|
|
}
|