UI: согласованные тексты и терминология
- «Winrate» -> «Винрейт» в карточке статистики; в справке добавлена расшифровка «Винрейт (WR)» — WR остаётся в компактных таблицах; - кнопки сохранения: «ОК» -> «Сохранить» (аккаунт, фракции, аккаунты в админке); отмена в модалках/формах — везде «Отмена», не «Назад»; - пустые состояния приведены к шаблону «X пока нет.»; - роли участников группы переведены: owner/member -> владелец/участник, бейдж admin -> «админ»; «Имя обновлено» -> «Ник обновлён»; - «N игр.» в значении числа игроков -> «N игроков» со склонением (новый хелпер plural в domain/format.ts); - «минимум 2 участника» -> «минимум 2 игрока» (в партии — игроки, в группе — участники). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -145,7 +145,7 @@ export function AccountPage() {
|
||||
<div className="row">
|
||||
<input value={nickname} onChange={(e) => setNickname(e.target.value)} placeholder={me.nickname} />
|
||||
<button className="btn btn-primary" onClick={saveNick} disabled={updateNick.isPending}>
|
||||
ОК
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -134,7 +134,7 @@ export function CreateMatchPage() {
|
||||
setError(null);
|
||||
const filled = rows.filter((r) => r.user_id !== "" && r.faction_id !== "");
|
||||
if (filled.length < 2) {
|
||||
setError("Нужно минимум 2 участника с выбранным игроком и фракцией.");
|
||||
setError("Нужно минимум 2 игрока с выбранной фракцией.");
|
||||
return;
|
||||
}
|
||||
if (filled.length > MAX_MATCH_PLAYERS) {
|
||||
|
||||
@@ -120,7 +120,8 @@ export function GroupSettingsPage() {
|
||||
<div key={m.user_id} className="row-between">
|
||||
{/* Кликабельна вся левая часть строки, не только текст ника. */}
|
||||
<PlayerLink userId={m.user_id} style={{ flex: 1 }}>
|
||||
{m.nickname} <span className="muted small">· {m.role}</span>
|
||||
{m.nickname}{" "}
|
||||
<span className="muted small">· {m.role === "owner" ? "владелец" : "участник"}</span>
|
||||
</PlayerLink>
|
||||
{isOwner && m.role !== "owner" && (
|
||||
<button
|
||||
|
||||
@@ -63,7 +63,7 @@ export function HelpPage() {
|
||||
<div className="card">
|
||||
<h3>Другие показатели</h3>
|
||||
<ul className="small" style={{ margin: 0, paddingLeft: 18 }}>
|
||||
<li><b>Винрейт</b> — доля партий, в которых игрок занял 1-е место.</li>
|
||||
<li><b>Винрейт (WR)</b> — доля партий, в которых игрок занял 1-е место.</li>
|
||||
<li><b>Среднее место</b> — среднее по завершённым партиям.</li>
|
||||
<li>
|
||||
При равенстве рейтинга топ сортируется по: винрейту, числу игр, среднему месту
|
||||
|
||||
@@ -154,7 +154,7 @@ function TelegramNicknamePicker({
|
||||
</div>
|
||||
{error && <p className="error-text">{error}</p>}
|
||||
<button className="btn btn-ghost small" onClick={onCancel}>
|
||||
Назад
|
||||
Отмена
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -293,7 +293,6 @@ export function MatchDetailPage() {
|
||||
: "Сыгранная партия и её результаты будут удалены. Действие необратимо."
|
||||
}
|
||||
confirmLabel={inProgress ? "Отменить партию" : "Удалить"}
|
||||
cancelLabel="Назад"
|
||||
onCancel={() => setConfirmRemove(false)}
|
||||
onConfirm={() => {
|
||||
setConfirmRemove(false);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function AdminAccountsPage() {
|
||||
if (editId == null || !name) return;
|
||||
try {
|
||||
await update.mutateAsync({ userId: editId, nickname: name });
|
||||
toast.show("Имя обновлено");
|
||||
toast.show("Ник обновлён");
|
||||
setEditId(null);
|
||||
} catch (e) {
|
||||
toast.show(e instanceof ApiError ? e.message : "Не удалось переименовать");
|
||||
@@ -63,7 +63,7 @@ export function AdminAccountsPage() {
|
||||
disabled={update.isPending || !editName.trim() || editName.trim() === u.nickname}
|
||||
onClick={saveName}
|
||||
>
|
||||
ОК
|
||||
Сохранить
|
||||
</button>
|
||||
<button className="btn btn-ghost small" onClick={() => setEditId(null)}>
|
||||
Отмена
|
||||
@@ -72,7 +72,7 @@ export function AdminAccountsPage() {
|
||||
) : (
|
||||
<div className="row-between">
|
||||
<div>
|
||||
<b>{u.nickname}</b> {u.role === "admin" && <span className="badge">admin</span>}
|
||||
<b>{u.nickname}</b> {u.role === "admin" && <span className="badge">админ</span>}
|
||||
<div className="muted small">
|
||||
#{u.id} · {u.auth_provider} · {u.is_active ? "активен" : "отключён"}
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function AdminFactionsPage() {
|
||||
disabled={!names[f.id] || names[f.id] === f.name_ru}
|
||||
onClick={() => save(f.id)}
|
||||
>
|
||||
ОК
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export function AdminLogsPage() {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{(data?.items ?? []).length === 0 && <div className="muted small">Событий нет.</div>}
|
||||
{(data?.items ?? []).length === 0 && <div className="muted small">Событий пока нет.</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMemo, useState } from "react";
|
||||
|
||||
import { ApiError } from "../../api/client";
|
||||
import { ConfirmDialog } from "../../components/ConfirmDialog";
|
||||
import { formatDate } from "../../domain/format";
|
||||
import { formatDate, plural } from "../../domain/format";
|
||||
import { Spinner } from "../../components/Spinner";
|
||||
import { Switch } from "../../components/Switch";
|
||||
import { useToast } from "../../context/ToastContext";
|
||||
@@ -58,7 +58,8 @@ export function AdminRecordsPage() {
|
||||
<b>#{m.id}</b> · {formatDate(m.played_at)}
|
||||
{!grouped && <span className="muted small"> · {m.group_name}</span>}
|
||||
<div className="muted small">
|
||||
{m.player_count} игр. · автор {m.created_by}
|
||||
{m.player_count} {plural(m.player_count, "игрок", "игрока", "игроков")} · автор{" "}
|
||||
{m.created_by}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row" style={{ gap: 4 }}>
|
||||
@@ -120,7 +121,7 @@ export function AdminRecordsPage() {
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (matches ?? []).length === 0 ? (
|
||||
<div className="muted small">Записей нет.</div>
|
||||
<div className="muted small">Записей пока нет.</div>
|
||||
) : grouped ? (
|
||||
byGroup.map(([gid, { name, items }]) => (
|
||||
<div key={gid} style={{ marginBottom: 16 }}>
|
||||
|
||||
Reference in New Issue
Block a user