import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { ApiError } from "../../api/client"; import { MatchMedia } from "../../components/MatchMedia"; import { Spinner } from "../../components/Spinner"; import { Switch } from "../../components/Switch"; import { finishWarnings } from "../../domain/finishWarnings"; import { MAX_COUNT, parseCount } from "../../domain/matchCounts"; import { LAST_STANDING, reasonForSurvivors, WIN_REASONS, type WinReason, winReasonLabel, } from "../../domain/winReasons"; import { useAdminDeleteAttachment, useAdminFactions, useAdminMatch, useAdminMatchAttachments, useAdminUpdateMatch, useAdminUploadAttachment, } from "../../hooks/admin"; interface Row { user_id: number; nickname: string; faction_id: number; place: number; eliminated: boolean; was_random: boolean; comment: string; objectives: number | null; worlds: number | null; } export function AdminMatchEdit({ matchId, onClose }: { matchId: number; onClose: () => void }) { const { data: match, isLoading } = useAdminMatch(matchId); const { data: factions } = useAdminFactions(); const { data: attachments } = useAdminMatchAttachments(matchId); const update = useAdminUpdateMatch(); const uploadAtt = useAdminUploadAttachment(matchId); const deleteAtt = useAdminDeleteAttachment(matchId); const navigate = useNavigate(); const [rows, setRows] = useState([]); const [playedAt, setPlayedAt] = useState(""); const [overall, setOverall] = useState(""); const [winReason, setWinReason] = useState("objectives"); const [endRound, setEndRound] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (match) { setRows( match.participants.map((p) => ({ user_id: p.user_id, nickname: p.nickname, faction_id: p.faction_id, place: p.place ?? 1, eliminated: p.eliminated, was_random: p.was_random, comment: p.comment ?? "", objectives: p.objectives ?? null, worlds: p.worlds ?? null, })), ); setPlayedAt(match.played_at); setOverall(match.overall_comment ?? ""); setWinReason(match.win_reason ?? null); setEndRound(match.end_round ?? null); } }, [match]); const upd = (i: number, patch: Partial) => { const next = rows.map((r, idx) => (idx === i ? { ...r, ...patch } : r)); setRows(next); // Выбывание меняет число выживших: «последний выживший» ставится и снимается сам. if ("eliminated" in patch) { setWinReason(reasonForSurvivors(winReason, next.filter((r) => !r.eliminated).length)); } }; const save = async () => { setError(null); if (!winReason) { setError("Выберите причину победы."); return; } try { await update.mutateAsync({ matchId, body: { played_at: playedAt, overall_comment: overall.trim() || null, win_reason: winReason, end_round: endRound, participants: rows.map((r) => ({ user_id: r.user_id, faction_id: r.faction_id, place: r.eliminated ? null : r.place, eliminated: r.eliminated, was_random: r.was_random, comment: r.comment.trim() || null, objectives: r.objectives, worlds: r.eliminated ? null : r.worlds, })), }, }); onClose(); } catch (e) { setError(e instanceof ApiError ? e.message : "Не удалось сохранить"); } }; const warnings = match ? finishWarnings({ seats: rows.map((r) => ({ userId: r.user_id, nickname: r.nickname, place: r.place, eliminated: r.eliminated, objectives: r.objectives, worlds: r.eliminated ? 0 : r.worlds, })), winReason, endRound, maxRounds: match.max_rounds, }) : []; return ( // Полноэкранная страница правки (не нижний «лист»): занимает весь экран.

Правка партии #{matchId}

{isLoading || !match ? ( ) : match.status === "in_progress" ? ( // Результаты идущей партии сервер не примет (409): места без завершения // оставляли бы партию вне статистики. Объясняем это здесь, а не ошибкой.

Партия ещё идёт

Результаты незавершённой партии правкой не вносятся — их проставляют при завершении, на странице самой партии. Там же участники заполняют места совместно.

) : ( <>
setPlayedAt(e.target.value)} />
{rows.map((r, i) => (
{r.nickname}
upd(i, { place: Math.max(1, Number(e.target.value) || 1) })} />
upd(i, { comment: e.target.value })} /> upd(i, { objectives: parseCount(e.target.value) })} /> upd(i, { worlds: parseCount(e.target.value) })} />
))}
{winReason === LAST_STANDING ? ( ) : ( )}
{warnings.length > 0 && (
    {warnings.map((w) => (
  • {w}
  • ))}
)}