Рейтинг: ввод деталей партии и предупреждения в формах
Форма завершения и правка результатов (MatchDetailPage): у каждого игрока
в PlaceEditor поля «цели» и «миры» на конец партии (у выбывшего миры
заблокированы нулём), общий блок «Итог партии» — причина победы и раунд
окончания из max_rounds партии. Всё новое уходит в общий черновик.
Причина «последний выживший» ставится сама при одном невыбывшем, выбор
заблокирован; вернули второго — причина сбрасывается и её нужно выбрать.
То же в админской правке партии.
Предупреждения (domain/finishWarnings.ts, отправку не блокируют): тип победы
и цели/миры лидеров не согласуются, досрочный конец без N целей у победителя,
у соперника целей больше, чем у победителя. Настройки группы: переключатель
«9 раундов при 5–6 игроках» через PATCH /groups/{id}. schema.d.ts
перегенерирован. #23
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { PickerSelect } from "./PickerSelect";
|
||||
import { LAST_STANDING, WIN_REASONS, type WinReason, winReasonLabel } from "../domain/winReasons";
|
||||
|
||||
const REASON_OPTIONS = WIN_REASONS.map((w) => ({ id: w.code, label: w.label }));
|
||||
const NO_ROUND = 0;
|
||||
|
||||
/**
|
||||
* Итог партии в форме завершения и правки результатов: причина победы, раунд окончания
|
||||
* и предупреждения о несогласованном вводе. «Последний выживший» не выбирается —
|
||||
* пока выживший один, причина зафиксирована (её ставит родитель, см. reasonForSurvivors).
|
||||
*/
|
||||
export function MatchOutcomeFields({
|
||||
winReason,
|
||||
onReason,
|
||||
endRound,
|
||||
onEndRound,
|
||||
maxRounds,
|
||||
warnings,
|
||||
}: {
|
||||
winReason: WinReason | null;
|
||||
onReason: (reason: WinReason) => void;
|
||||
endRound: number | null;
|
||||
onEndRound: (round: number | null) => void;
|
||||
maxRounds: number;
|
||||
warnings: string[];
|
||||
}) {
|
||||
const roundOptions = [
|
||||
{ id: NO_ROUND, label: "не указан" },
|
||||
...Array.from({ length: maxRounds }, (_, i) => ({ id: i + 1, label: `${i + 1}-й` })),
|
||||
];
|
||||
return (
|
||||
<div className="card">
|
||||
<h3>Итог партии</h3>
|
||||
<div className="field">
|
||||
<label className="label">Причина победы</label>
|
||||
{winReason === LAST_STANDING ? (
|
||||
<div className="picker-trigger" aria-disabled>
|
||||
{winReasonLabel(LAST_STANDING)}
|
||||
<span className="muted small" style={{ marginLeft: "auto" }}>
|
||||
все соперники выбыли
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<PickerSelect
|
||||
selected={REASON_OPTIONS.find((o) => o.id === winReason) ?? null}
|
||||
options={REASON_OPTIONS}
|
||||
placeholder="— выберите причину —"
|
||||
renderOption={(o) => o.label}
|
||||
onPick={(o) => onReason(o.id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="field" style={{ marginBottom: 0 }}>
|
||||
<label className="label">Раунд окончания (из {maxRounds})</label>
|
||||
<PickerSelect
|
||||
selected={roundOptions.find((o) => o.id === (endRound ?? NO_ROUND)) ?? null}
|
||||
options={roundOptions}
|
||||
placeholder="не указан"
|
||||
renderOption={(o) => o.label}
|
||||
onPick={(o) => onEndRound(o.id === NO_ROUND ? null : o.id)}
|
||||
/>
|
||||
</div>
|
||||
<p className="muted small" style={{ marginBottom: 0 }}>
|
||||
Раунд, цели и миры необязательны, но делают рейтинг точнее: быстрая и крупная победа
|
||||
весит больше.
|
||||
</p>
|
||||
{warnings.length > 0 && (
|
||||
<ul className="warn-list">
|
||||
{warnings.map((w) => (
|
||||
<li key={w}>{w}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user