Настройки группы: переименование группы

Новый хук useRenameGroup (PATCH /api/groups/{id} — эндпойнт уже был на
бэкенде) с инвалидацией всех мест, где видно имя группы (шапка группы,
переключатель групп, главная). В настройках — карточка «Название» с
полем и кнопкой «Сохранить» (по образцу смены ника в аккаунте).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 17:19:07 +03:00
co-authored by Claude Fable 5
parent 378094c366
commit c4f75d5de0
2 changed files with 64 additions and 1 deletions
+20
View File
@@ -75,6 +75,26 @@ export function useCreateGroup() {
}); });
} }
export function useRenameGroup(groupId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (name: string) =>
unwrap(
await api.PATCH("/api/groups/{group_id}", {
params: { path: { group_id: groupId } },
body: { name },
}),
),
onSuccess: () => {
// Имя группы видно в шапке группы, переключателе групп (me), на главной.
qc.invalidateQueries({ queryKey: qk.group(groupId) });
qc.invalidateQueries({ queryKey: qk.groups });
qc.invalidateQueries({ queryKey: qk.me });
qc.invalidateQueries({ queryKey: qk.home });
},
});
}
export function useSetGroupExpansions(groupId: number) { export function useSetGroupExpansions(groupId: number) {
const qc = useQueryClient(); const qc = useQueryClient();
return useMutation({ return useMutation({
+44 -1
View File
@@ -9,7 +9,13 @@ import { Switch } from "../components/Switch";
import { MAX_GROUP_SIZE } from "../domain/constants"; import { MAX_GROUP_SIZE } from "../domain/constants";
import { useToast } from "../context/ToastContext"; import { useToast } from "../context/ToastContext";
import { useMe } from "../hooks/auth"; import { useMe } from "../hooks/auth";
import { useGroup, useGroupMembers, useRemoveMember, useSetGroupExpansions } from "../hooks/groups"; import {
useGroup,
useGroupMembers,
useRemoveMember,
useRenameGroup,
useSetGroupExpansions,
} from "../hooks/groups";
import { useExpansions } from "../hooks/reference"; import { useExpansions } from "../hooks/reference";
export function GroupSettingsPage() { export function GroupSettingsPage() {
@@ -19,10 +25,13 @@ export function GroupSettingsPage() {
const { data: expansions } = useExpansions(); const { data: expansions } = useExpansions();
const { data: members } = useGroupMembers(groupId); const { data: members } = useGroupMembers(groupId);
const setExpansions = useSetGroupExpansions(groupId ?? 0); const setExpansions = useSetGroupExpansions(groupId ?? 0);
const renameGroup = useRenameGroup(groupId ?? 0);
const removeMember = useRemoveMember(groupId ?? 0); const removeMember = useRemoveMember(groupId ?? 0);
const toast = useToast(); const toast = useToast();
const [selected, setSelected] = useState<Set<number>>(new Set()); const [selected, setSelected] = useState<Set<number>>(new Set());
// Название редактируется локально; до первого ввода берём значение из группы.
const [name, setName] = useState<string | null>(null);
const [removeTarget, setRemoveTarget] = useState<{ userId: number; nickname: string } | null>(null); const [removeTarget, setRemoveTarget] = useState<{ userId: number; nickname: string } | null>(null);
useEffect(() => { useEffect(() => {
@@ -40,6 +49,20 @@ export function GroupSettingsPage() {
return next; return next;
}); });
const nameValue = name ?? group.name;
const saveName = async () => {
const trimmed = nameValue.trim();
if (!trimmed || trimmed === group.name) return;
try {
await renameGroup.mutateAsync(trimmed);
setName(null);
toast.show("Название сохранено");
} catch (e) {
toast.show(e instanceof ApiError ? e.message : "Ошибка");
}
};
const saveExpansions = async () => { const saveExpansions = async () => {
try { try {
await setExpansions.mutateAsync([...selected]); await setExpansions.mutateAsync([...selected]);
@@ -59,6 +82,26 @@ export function GroupSettingsPage() {
return ( return (
<div> <div>
<div className="card">
<h3>Название</h3>
<div className="row">
<input
value={nameValue}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") saveName();
}}
/>
<button
className="btn btn-primary"
onClick={saveName}
disabled={renameGroup.isPending || !nameValue.trim() || nameValue.trim() === group.name}
>
Сохранить
</button>
</div>
</div>
<div className="card"> <div className="card">
<h3>Дополнения</h3> <h3>Дополнения</h3>
<div className="stack"> <div className="stack">