Удалённый из группы игрок больше не остаётся в «Списке игроков»: leaderboard получает набор текущих участников, и group_stats фильтрует по нему вывод до сортировки, так что места нумеруются без пропусков. Расчёт не меняется — групповая цепочка проигрывает все партии группы, и партии с ушедшим по-прежнему влияют на рейтинг оставшихся; счётчик партий и статистика фракций группы тоже прежние. Общий топ и главная без изменений. После удаления участника фронт сразу перезапрашивает статистику группы. Тесты: удалённый пропадает из группы, но остаётся в общем топе, рейтинг оставшегося не меняется, после возврата игрок снова в списке; ранги после удаления перенумерованы. #76 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
147 lines
4.9 KiB
TypeScript
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 });
|
|
},
|
|
});
|
|
}
|