Профиль: блок истории игр с переключателями режима и подробности

В «Аккаунте» появился блок «История игр» с двумя тумблерами — «Только
лучшая партия» и «Подробные карточки»; выбор уходит в профиль, поэтому
переживает перезаход и другое устройство.

Новый компонент MatchHistory: подробный режим переиспользует список партий
группы (MatchListView со всеми участниками), компактный рисует строку с
результатом самого игрока — место, фракция, дата и длительность.

Публичный профиль показывает ту же историю в режиме владельца и без
контролов: страница намеренно read-only.

schema.d.ts пересобран с живого бэкенда (npm run gen:api).

#1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 19:56:15 +03:00
co-authored by Claude Opus 5
parent be80891449
commit 9dda7de067
7 changed files with 250 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
import { useNavigate } from "react-router-dom";
import { formatDate, formatDuration } from "../domain/format";
import type { MatchListItem } from "../domain/types";
import { MatchListView } from "./MatchList";
/**
* История партий игрока в профиле. Подробный режим — тот же список, что у группы
* (MatchListView со всеми участниками); компактный — строка с результатом самого
* владельца профиля, поэтому компонент и принимает его userId.
*/
export function MatchHistory({
items,
userId,
detail,
}: {
items: MatchListItem[];
userId: number;
detail: string;
}) {
const navigate = useNavigate();
if (items.length === 0) return <div className="muted small">Партий пока нет.</div>;
if (detail === "full") return <MatchListView items={items} />;
return (
<div className="stack">
{items.map((m) => {
const mine = m.participants.find((p) => p.user_id === userId);
return (
<button
key={m.id}
className="card row-between"
style={{ margin: 0, width: "100%", textAlign: "left" }}
onClick={() => navigate(`/match/${m.id}`)}
>
<div className="row" style={{ gap: 10, minWidth: 0 }}>
<span
className={
"place-badge" +
(mine?.place === 1 && !mine?.eliminated ? " first" : "") +
(mine?.eliminated ? " eliminated" : "")
}
>
{mine?.eliminated ? "✕" : (mine?.place ?? "—")}
</span>
<span
style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
>
{mine?.faction_name ?? "—"}
</span>
</div>
<span className="muted small" style={{ flexShrink: 0 }}>
{formatDate(m.played_at)} · {formatDuration(m.duration_minutes)}
</span>
</button>
);
})}
</div>
);
}