166 lines
5.7 KiB
TypeScript
166 lines
5.7 KiB
TypeScript
import { useQueryClient } from "@tanstack/react-query";
|
|
import { useServerMutation, useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
|
import { Match } from "@/features/matches/types";
|
|
import {
|
|
getMatchesBetweenTeams,
|
|
getMatchesBetweenPlayers,
|
|
reportMatchScore,
|
|
confirmMatchScore,
|
|
clearMatchReport,
|
|
} from "./server";
|
|
|
|
export const matchKeys = {
|
|
headToHeadTeams: (team1Id: string, team2Id: string) => ['matches', 'headToHead', 'teams', team1Id, team2Id] as const,
|
|
headToHeadPlayers: (player1Id: string, player2Id: string) => ['matches', 'headToHead', 'players', player1Id, player2Id] as const,
|
|
};
|
|
|
|
export const matchQueries = {
|
|
headToHeadTeams: (team1Id: string, team2Id: string) => ({
|
|
queryKey: matchKeys.headToHeadTeams(team1Id, team2Id),
|
|
queryFn: () => getMatchesBetweenTeams({ data: { team1Id, team2Id } }),
|
|
}),
|
|
headToHeadPlayers: (player1Id: string, player2Id: string) => ({
|
|
queryKey: matchKeys.headToHeadPlayers(player1Id, player2Id),
|
|
queryFn: () => getMatchesBetweenPlayers({ data: { player1Id, player2Id } }),
|
|
}),
|
|
};
|
|
|
|
export const useTeamHeadToHead = (team1Id: string, team2Id: string, enabled = true) =>
|
|
useServerSuspenseQuery({
|
|
...matchQueries.headToHeadTeams(team1Id, team2Id),
|
|
enabled,
|
|
});
|
|
|
|
export const usePlayerHeadToHead = (player1Id: string, player2Id: string, enabled = true) =>
|
|
useServerSuspenseQuery({
|
|
...matchQueries.headToHeadPlayers(player1Id, player2Id),
|
|
enabled,
|
|
});
|
|
|
|
const tournamentsRoot = { queryKey: ["tournaments"] as const };
|
|
|
|
const reportTeamId = (t: Match["home"]): string | undefined =>
|
|
!t ? undefined : typeof t === "string" ? t : t.id;
|
|
|
|
const reportTeamPlayerIds = (t: Match["home"]): string[] =>
|
|
t && typeof t !== "string" ? (t.players ?? []).map((p) => p.id) : [];
|
|
|
|
function patchTournamentsData(
|
|
data: unknown,
|
|
matchId: string,
|
|
patch: (match: Match) => Partial<Match>
|
|
): unknown {
|
|
if (!data) return data;
|
|
if (Array.isArray(data)) {
|
|
return data.map((entry) => patchTournamentsData(entry, matchId, patch));
|
|
}
|
|
const tournament = data as { matches?: Match[] };
|
|
if (!Array.isArray(tournament.matches)) return data;
|
|
|
|
let changed = false;
|
|
const matches = tournament.matches.map((m) => {
|
|
if (m.id !== matchId) return m;
|
|
changed = true;
|
|
return { ...m, ...patch(m) };
|
|
});
|
|
return changed ? { ...tournament, matches } : data;
|
|
}
|
|
|
|
type MatchVariables = { data: { matchId: string } };
|
|
type TournamentsSnapshot = [readonly unknown[], unknown][];
|
|
|
|
function useOptimisticMatchMutation<TData, TVariables extends MatchVariables>(
|
|
options: Parameters<typeof useServerMutation<TData, TVariables>>[0] & {
|
|
buildPatch: (match: Match, variables: TVariables) => Partial<Match>;
|
|
}
|
|
) {
|
|
const queryClient = useQueryClient();
|
|
const { buildPatch, ...mutationOptions } = options;
|
|
|
|
return useServerMutation<TData, TVariables>({
|
|
...mutationOptions,
|
|
onMutate: async (variables) => {
|
|
const { matchId } = variables.data;
|
|
await queryClient.cancelQueries(tournamentsRoot);
|
|
|
|
const previous = queryClient.getQueriesData(tournamentsRoot);
|
|
queryClient.setQueriesData(tournamentsRoot, (data: unknown) =>
|
|
patchTournamentsData(data, matchId, (match) => buildPatch(match, variables))
|
|
);
|
|
|
|
return { previous };
|
|
},
|
|
onError: (error, variables, onMutateResult, context) => {
|
|
if (context && typeof context === "object" && "previous" in context) {
|
|
const snapshot = (context as { previous: TournamentsSnapshot }).previous;
|
|
for (const [key, data] of snapshot) {
|
|
queryClient.setQueryData(key, data);
|
|
}
|
|
}
|
|
mutationOptions.onError?.(error, variables, onMutateResult, context);
|
|
},
|
|
onSettled: (data, error, variables, onMutateResult, context) => {
|
|
queryClient.invalidateQueries(tournamentsRoot);
|
|
mutationOptions.onSettled?.(data, error, variables, onMutateResult, context);
|
|
},
|
|
});
|
|
}
|
|
|
|
type MatchMutationOptions = {
|
|
onSuccess?: Parameters<typeof useServerMutation>[0]["onSuccess"];
|
|
};
|
|
|
|
export const useReportMatchScore = (userId?: string, options?: MatchMutationOptions) =>
|
|
useOptimisticMatchMutation({
|
|
mutationFn: reportMatchScore,
|
|
showSuccessToast: false,
|
|
...options,
|
|
buildPatch: (match, variables) => {
|
|
const onHome = !!userId && reportTeamPlayerIds(match.home).includes(userId);
|
|
const onAway = !!userId && reportTeamPlayerIds(match.away).includes(userId);
|
|
const callerTeamId = onHome
|
|
? reportTeamId(match.home)
|
|
: onAway
|
|
? reportTeamId(match.away)
|
|
: undefined;
|
|
return {
|
|
reported_home_cups: variables.data.home_cups,
|
|
reported_away_cups: variables.data.away_cups,
|
|
reported_ot_count: variables.data.ot_count,
|
|
reported_by_team: callerTeamId,
|
|
reported_by_player: userId,
|
|
};
|
|
},
|
|
});
|
|
|
|
export const useConfirmMatchScore = (options?: MatchMutationOptions) =>
|
|
useOptimisticMatchMutation({
|
|
mutationFn: confirmMatchScore,
|
|
showSuccessToast: false,
|
|
...options,
|
|
buildPatch: (match) => ({
|
|
status: "ended",
|
|
home_cups: match.reported_home_cups ?? match.home_cups,
|
|
away_cups: match.reported_away_cups ?? match.away_cups,
|
|
ot_count: match.reported_ot_count ?? match.ot_count,
|
|
reported_home_cups: undefined,
|
|
reported_away_cups: undefined,
|
|
reported_ot_count: undefined,
|
|
reported_by_team: undefined,
|
|
reported_by_player: undefined,
|
|
}),
|
|
});
|
|
|
|
export const useClearMatchReport = (options?: MatchMutationOptions) =>
|
|
useOptimisticMatchMutation({
|
|
mutationFn: clearMatchReport,
|
|
...options,
|
|
buildPatch: () => ({
|
|
reported_home_cups: undefined,
|
|
reported_away_cups: undefined,
|
|
reported_ot_count: undefined,
|
|
reported_by_team: undefined,
|
|
reported_by_player: undefined,
|
|
}),
|
|
});
|