Files
ForbiddenStarsApp/frontend/src/components/Leaderboard.tsx
T
NotBigGhostandClaude Fable 5 fc1b1fd912 UI: в топе игроков на главной ники некликабельны
Карточка топа целиком ведёт в общую статистику — вложенные ссылки на
профили конфликтовали с этим переходом. У Leaderboard появился флаг
profileLinks: на главной выключен, в «Списке игроков» группы (карточка
никуда не ведёт) строки-ссылки остаются, как и в общей статистике.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 17:28:21 +03:00

89 lines
2.6 KiB
TypeScript

import type { LeaderboardEntry } from "../domain/types";
import { Avatar } from "./Avatar";
import { PlayerLink } from "./PlayerLink";
function pct(v: number | null | undefined): string {
return v == null ? "—" : `${Math.round(v * 100)}%`;
}
function Row({
entry,
provisional,
profileLinks,
}: {
entry: LeaderboardEntry;
provisional?: boolean;
profileLinks: boolean;
}) {
const content = (
<>
<div className={"lb-rank" + (entry.rank && entry.rank <= 3 ? " top" : "")}>
{entry.rank ?? "—"}
</div>
<Avatar url={entry.avatar_url} nickname={entry.nickname} size={32} />
<div>
<div style={{ fontWeight: 600 }}>{entry.nickname}</div>
<div className="lb-metrics">
<span>Игр: {entry.games}</span>
<span>Побед: {entry.wins}</span>
<span>WR: {pct(entry.win_rate)}</span>
</div>
</div>
<div className={"lb-score" + (provisional ? " provisional" : "")}>{entry.score ?? "—"}</div>
</>
);
// Строка-ссылка на профиль — только там, где сама карточка никуда не ведёт
// (на главной карточка топа целиком открывает общую статистику).
if (!profileLinks) return <div className="lb-row">{content}</div>;
return (
<PlayerLink userId={entry.user_id} className="lb-row">
{content}
</PlayerLink>
);
}
export function Leaderboard({
entries,
provisional,
inactive,
profileLinks = true,
}: {
entries: LeaderboardEntry[];
provisional?: LeaderboardEntry[];
inactive?: LeaderboardEntry[];
profileLinks?: boolean;
}) {
const empty =
entries.length === 0 && !provisional?.length && !inactive?.length;
if (empty) {
return <div className="muted small">Сыгранных партий пока нет.</div>;
}
return (
<div>
{entries.map((e) => (
<Row key={e.user_id} entry={e} profileLinks={profileLinks} />
))}
{provisional && provisional.length > 0 && (
<>
<div className="label" style={{ marginTop: 10 }}>
Новички (мало игр)
</div>
{provisional.map((e) => (
<Row key={e.user_id} entry={e} provisional profileLinks={profileLinks} />
))}
</>
)}
{inactive && inactive.length > 0 && (
<>
<div className="label" style={{ marginTop: 10 }}>
Ещё не играли
</div>
{inactive.map((e) => (
<Row key={e.user_id} entry={e} profileLinks={profileLinks} />
))}
</>
)}
</div>
);
}