37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
import { formatCash } from '@/lib/format';
|
|
import type { PlayerSummary } from '@/lib/game/state';
|
|
|
|
// v1.6.0 (ISS-115): leaderboard panel — every player in the world with cash, reputation and
|
|
// plane count, rendered straight off the shared snapshot (no endpoint of its own). Sorted by
|
|
// reputation desc; cash then username break ties deterministically so rows don't shuffle
|
|
// between snapshots when two players share a reputation.
|
|
export default function Leaderboard({ players }: { players: PlayerSummary[] }) {
|
|
const sorted = [...players].sort(
|
|
(a, b) => b.reputation - a.reputation || b.cash - a.cash || a.username.localeCompare(b.username),
|
|
);
|
|
return (
|
|
<div className="leaderboard">
|
|
<table data-testid="leaderboard-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Player</th>
|
|
<th>Cash</th>
|
|
<th>Reputation</th>
|
|
<th>Planes</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{sorted.map((p) => (
|
|
<tr key={p.id} data-testid={`leaderboard-row-${p.id}`}>
|
|
<td>{p.username}</td>
|
|
<td>{formatCash(p.cash)}</td>
|
|
<td>{p.reputation}</td>
|
|
<td>{p.planeCount}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|