import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api, unwrap } from "../api/client"; import { invalidateRatingViews, qk } from "../api/queryKeys"; import type { FactionRead, MatchCreate, MatchFinish, MatchFinishDraftData, MatchRead, MatchUpdate, } from "../domain/types"; import { uploadFile } from "../lib/upload"; export function useMatch(matchId: number | null) { return useQuery({ // Ключ и enabled должны сходиться: иначе запрос без валидного id кэшировал бы // свою ошибку под общим ключом-заглушкой. queryKey: matchId != null ? qk.match(matchId) : ["match", "none"], enabled: matchId != null, queryFn: async () => unwrap( await api.GET("/api/matches/{match_id}", { params: { path: { match_id: matchId as number } }, }), ), }); } /** * Общий черновик формы завершения: то, что видят все, кто заполняет партию. * Ответ — свежая партия целиком, поэтому кладём его прямо в кэш, без инвалидации: * перезапрос на каждое движение тайла был бы расточительным. */ export function useSaveFinishDraft(matchId: number | null) { const qc = useQueryClient(); return useMutation({ mutationFn: async (data: MatchFinishDraftData): Promise => unwrap( await api.PUT("/api/matches/{match_id}/finish-draft", { params: { path: { match_id: matchId as number } }, body: data, }), ), onSuccess: (m) => qc.setQueryData(qk.match(m.id), m), }); } export function useRandomizeFaction() { return useMutation({ mutationFn: async (args: { group_id: number; exclude_faction_ids: number[] }): Promise => { const res = unwrap(await api.POST("/api/matches/randomize-faction", { body: args })); return res.faction; }, }); } /** Этап 1: старт партии (игроки + фракции, без мест). */ export function useStartMatch() { const qc = useQueryClient(); return useMutation({ mutationFn: async (body: MatchCreate): Promise => unwrap(await api.POST("/api/matches", { body })), onSuccess: (m) => { qc.invalidateQueries({ queryKey: qk.groupMatches(m.group_id) }); qc.invalidateQueries({ queryKey: qk.home }); }, }); } /** Этап 2: завершение партии (места, причина победы). */ export function useFinishMatch() { const qc = useQueryClient(); return useMutation({ mutationFn: async (args: { matchId: number; body: MatchFinish }): Promise => unwrap( await api.POST("/api/matches/{match_id}/finish", { params: { path: { match_id: args.matchId } }, body: args.body, }), ), onSuccess: (m) => { qc.invalidateQueries({ queryKey: qk.match(m.id) }); qc.invalidateQueries({ queryKey: qk.groupMatches(m.group_id) }); invalidateRatingViews(qc); }, }); } /** Правка завершённой партии участником группы (у админки свой эндпойнт). */ export function useUpdateMatch() { const qc = useQueryClient(); return useMutation({ mutationFn: async (args: { matchId: number; body: MatchUpdate }): Promise => unwrap( await api.PATCH("/api/matches/{match_id}", { params: { path: { match_id: args.matchId } }, body: args.body, }), ), onSuccess: (m) => { qc.setQueryData(qk.match(m.id), m); qc.invalidateQueries({ queryKey: qk.groupMatches(m.group_id) }); // Места изменились — значит изменились рейтинги, топ, истории и профили. invalidateRatingViews(qc); }, }); } export function useDeleteMatch() { const qc = useQueryClient(); return useMutation({ // expectedVersion — оптимистичная блокировка: отмена устаревшей версии вернёт 409 STALE_WRITE. mutationFn: async (args: { matchId: number; expectedVersion?: string }) => unwrap( await api.DELETE("/api/matches/{match_id}", { params: { path: { match_id: args.matchId }, query: args.expectedVersion ? { expected_version: args.expectedVersion } : {}, }, }), ), onSuccess: () => qc.invalidateQueries(), }); } // Медиа партии: фото грузим multipart'ом отдельным fetch (с ресайзом и CSRF из cookie). export function useUploadMatchAttachment(matchId: number) { const qc = useQueryClient(); return useMutation({ mutationFn: async (file: File) => uploadFile(`/api/matches/${matchId}/attachments`, file), onSuccess: () => qc.invalidateQueries({ queryKey: qk.match(matchId) }), }); } export function useDeleteMatchAttachment(matchId: number) { const qc = useQueryClient(); return useMutation({ mutationFn: async (attachmentId: number) => unwrap( await api.DELETE("/api/matches/{match_id}/attachments/{attachment_id}", { params: { path: { match_id: matchId, attachment_id: attachmentId } }, }), ), onSuccess: () => qc.invalidateQueries({ queryKey: qk.match(matchId) }), }); }