v0.1 - макет интерфейса, аутентификация через логин, аккаунт админа, создание партии в 2 этапа, базовые настройки профиля и группы, переключение между группами, статистика

This commit is contained in:
2026-06-16 17:41:06 +03:00
parent 6ab74f01aa
commit 56b5d09a4d
123 changed files with 13572 additions and 21 deletions
@@ -0,0 +1,67 @@
import { useState } from "react";
import { ApiError } from "../api/client";
import { useToast } from "../context/ToastContext";
import { useCreateGroup } from "../hooks/groups";
import { useExpansions } from "../hooks/reference";
export function CreateGroupForm({ onCreated }: { onCreated?: () => void }) {
const { data: expansions } = useExpansions();
const createGroup = useCreateGroup();
const toast = useToast();
const [name, setName] = useState("");
const [selected, setSelected] = useState<Set<number>>(new Set());
const [error, setError] = useState<string | null>(null);
const toggle = (id: number) =>
setSelected((s) => {
const next = new Set(s);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
const submit = async () => {
setError(null);
try {
await createGroup.mutateAsync({ name: name.trim(), expansion_ids: [...selected] });
toast.show("Группа создана");
onCreated?.();
} catch (e) {
setError(e instanceof ApiError ? e.message : "Не удалось создать группу");
}
};
return (
<div className="card">
<h3>Новая группа</h3>
<div className="field">
<label className="label">Название</label>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Например: Вечер 40k" />
</div>
<div className="field">
<label className="label">Дополнения группы</label>
<div className="stack">
{(expansions ?? []).map((e) => (
<label key={e.id} className="row" style={{ justifyContent: "space-between" }}>
<span>
{e.name_ru}
{e.is_base && <span className="muted small"> (всегда включено)</span>}
</span>
<input
type="checkbox"
style={{ width: "auto" }}
disabled={e.is_base}
checked={e.is_base || selected.has(e.id)}
onChange={() => toggle(e.id)}
/>
</label>
))}
</div>
</div>
{error && <p className="error-text">{error}</p>}
<button className="btn btn-primary" style={{ width: "100%" }} onClick={submit} disabled={createGroup.isPending}>
Создать
</button>
</div>
);
}