Files
ForbiddenStarsApp/frontend/src/hooks/groups.ts
T
NotBigGhostandClaude Opus 5 ba69b9f21d Группа: в рейтинге группы только текущие участники
Удалённый из группы игрок больше не остаётся в «Списке игроков»: leaderboard
получает набор текущих участников, и group_stats фильтрует по нему вывод до
сортировки, так что места нумеруются без пропусков. Расчёт не меняется —
групповая цепочка проигрывает все партии группы, и партии с ушедшим по-прежнему
влияют на рейтинг оставшихся; счётчик партий и статистика фракций группы тоже
прежние. Общий топ и главная без изменений. После удаления участника фронт
сразу перезапрашивает статистику группы.

Тесты: удалённый пропадает из группы, но остаётся в общем топе, рейтинг
оставшегося не меняется, после возврата игрок снова в списке; ранги после
удаления перенумерованы. #76

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
2026-09-14 23:47:24 +03:00

147 lines
4.9 KiB
TypeScript

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, unwrap } from "../api/client";
import { qk } from "../api/queryKeys";
export function useMyGroups() {
return useQuery({
queryKey: qk.groups,
queryFn: async () => unwrap(await api.GET("/api/groups")),
});
}
export function useGroup(groupId: number | null) {
return useQuery({
queryKey: groupId ? qk.group(groupId) : ["group", "none"],
enabled: groupId != null,
queryFn: async () =>
unwrap(
await api.GET("/api/groups/{group_id}", {
params: { path: { group_id: groupId as number } },
}),
),
});
}
export function useGroupMembers(groupId: number | null) {
return useQuery({
queryKey: groupId ? qk.groupMembers(groupId) : ["group", "none", "members"],
enabled: groupId != null,
queryFn: async () =>
unwrap(
await api.GET("/api/groups/{group_id}/members", {
params: { path: { group_id: groupId as number } },
}),
),
});
}
export function useGroupStats(groupId: number | null) {
return useQuery({
queryKey: groupId ? qk.groupStats(groupId) : ["group", "none", "stats"],
enabled: groupId != null,
queryFn: async () =>
unwrap(
await api.GET("/api/groups/{group_id}/stats", {
params: { path: { group_id: groupId as number } },
}),
),
});
}
export function useGroupMatches(groupId: number | null, limit = 20, offset = 0) {
return useQuery({
queryKey: groupId ? [...qk.groupMatches(groupId), limit, offset] : ["group", "none", "matches"],
enabled: groupId != null,
queryFn: async () =>
unwrap(
await api.GET("/api/groups/{group_id}/matches", {
params: { path: { group_id: groupId as number }, query: { limit, offset } },
}),
),
});
}
export function useCreateGroup() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (body: { name: string; expansion_ids: number[] }) =>
unwrap(await api.POST("/api/groups", { body })),
onSuccess: () => {
qc.invalidateQueries({ queryKey: qk.groups });
qc.invalidateQueries({ queryKey: qk.me });
qc.invalidateQueries({ queryKey: qk.home });
},
});
}
export function useUpdateGroup(groupId: number) {
const qc = useQueryClient();
return useMutation({
// Частичная правка: название и/или домашние правила (например, 9 раундов).
mutationFn: async (body: { name?: string; nine_rounds_rule?: boolean }) =>
unwrap(
await api.PATCH("/api/groups/{group_id}", {
params: { path: { group_id: groupId } },
body,
}),
),
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) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (expansion_ids: number[]) =>
unwrap(
await api.PUT("/api/groups/{group_id}/expansions", {
params: { path: { group_id: groupId } },
body: { expansion_ids },
}),
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: qk.group(groupId) });
qc.invalidateQueries({ queryKey: qk.groupFactions(groupId) });
},
});
}
// Приглашение игрока в группу по нику (вместо прямого добавления). groupId передаётся
// при вызове — чтобы можно было приглашать в выбранную группу (со страницы чужого профиля).
export function useInviteMember() {
return useMutation({
mutationFn: async ({ groupId, nickname }: { groupId: number; nickname: string }) =>
unwrap(
await api.POST("/api/groups/{group_id}/invitations", {
params: { path: { group_id: groupId } },
body: { nickname },
}),
),
});
}
export function useRemoveMember(groupId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (userId: number) =>
unwrap(
await api.DELETE("/api/groups/{group_id}/members/{user_id}", {
params: { path: { group_id: groupId, user_id: userId } },
}),
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: qk.groupMembers(groupId) });
// Список игроков группы показывает только текущий состав — удалённый из него уходит.
qc.invalidateQueries({ queryKey: qk.groupStats(groupId) });
qc.invalidateQueries({ queryKey: qk.me });
},
});
}