UI: подтверждение перед отменой незавершённой и удалением сыгранной партии (модалка)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 16:57:35 +03:00
co-authored by Claude Opus 4.8
parent 4d365a373b
commit 7269afcd4e
3 changed files with 91 additions and 1 deletions
+41
View File
@@ -0,0 +1,41 @@
/** Переиспользуемое модальное подтверждение (без нативного confirm()). */
export function ConfirmDialog({
open,
title,
message,
confirmLabel = "Подтвердить",
cancelLabel = "Отмена",
danger = false,
onConfirm,
onCancel,
}: {
open: boolean;
title: string;
message?: string;
confirmLabel?: string;
cancelLabel?: string;
danger?: boolean;
onConfirm: () => void;
onCancel: () => void;
}) {
if (!open) return null;
return (
<div className="modal-overlay" onClick={onCancel}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<div className="modal-title">{title}</div>
{message && <p className="modal-msg">{message}</p>}
<div className="modal-actions">
<button className="btn btn-ghost" onClick={onCancel}>
{cancelLabel}
</button>
<button
className={"btn " + (danger ? "btn-danger-solid" : "btn-primary")}
onClick={onConfirm}
>
{confirmLabel}
</button>
</div>
</div>
</div>
);
}
+25 -1
View File
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { ApiError } from "../api/client";
import { ConfirmDialog } from "../components/ConfirmDialog";
import { MatchMedia } from "../components/MatchMedia";
import { Spinner } from "../components/Spinner";
import { formatDuration, formatTime } from "../domain/format";
@@ -39,6 +40,7 @@ export function MatchDetailPage() {
const [winReason, setWinReason] = useState<WinReason>("objectives");
const [overall, setOverall] = useState("");
const [error, setError] = useState<string | null>(null);
const [confirmRemove, setConfirmRemove] = useState(false);
if (isLoading) return <Spinner />;
if (!match) return <div className="muted">Партия не найдена.</div>;
@@ -271,10 +273,32 @@ export function MatchDetailPage() {
)}
{canModify && (
<button className="btn btn-danger" style={{ width: "100%" }} onClick={remove}>
<button
className="btn btn-danger"
style={{ width: "100%" }}
onClick={() => setConfirmRemove(true)}
>
{inProgress ? "Отменить партию" : "Удалить партию"}
</button>
)}
<ConfirmDialog
open={confirmRemove}
danger
title={inProgress ? "Отменить партию?" : "Удалить партию?"}
message={
inProgress
? "Незавершённая партия будет удалена. Действие необратимо."
: "Сыгранная партия и её результаты будут удалены. Действие необратимо."
}
confirmLabel={inProgress ? "Отменить партию" : "Удалить"}
cancelLabel="Назад"
onCancel={() => setConfirmRemove(false)}
onConfirm={() => {
setConfirmRemove(false);
remove();
}}
/>
</div>
);
}
+25
View File
@@ -68,6 +68,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-ghost { background: transparent; }
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.btn-danger-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn[disabled] { opacity: 0.5; pointer-events: none; }
.row { display: flex; align-items: center; gap: 10px; }
@@ -92,6 +93,30 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); }
.error-text { color: var(--danger); font-size: 13px; }
/* Модальное окно подтверждения */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 300;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
.modal {
width: 100%;
max-width: 360px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 18px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.modal-title { font-weight: 700; font-size: 16px; margin-bottom: 8px; }
.modal-msg { color: var(--text-muted); font-size: 14px; margin: 0 0 16px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
/* «Барабан» рандома фракции — мелькающее название вместо select */
.faction-spin {
display: flex;