refactor: отчёт сессии загружается одним hook useSessionReport с повтором 409

This commit is contained in:
gglamer 2026-09-26 20:59:50 +00:00
commit f6d0653e68
7 changed files with 106 additions and 79 deletions

View file

@ -3,7 +3,9 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { Level, SessionMode, StationSnapshot } from "@/shared/types/generated";
import type { Level, SessionMode, SessionReport, StationSnapshot } from "@/shared/types/generated";
import { reportErrorMessage, reportRetryDelay, shouldRetryReport } from "./report.mjs";
export class ApiError extends Error {
constructor(
@ -328,6 +330,30 @@ export const useProfile = (traineeId: string | null) =>
enabled: Boolean(traineeId),
});
const errorStatus = (error: unknown) => (error instanceof ApiError ? error.status : null);
/**
* Отчёт сессии. `ready = false` — запроса нет и отчёт не показывается: его
* просят после `score.ready` или у уже завершённой сессии. 409 (оценка ещё
* считается) повторяется сам с ограниченным числом попыток.
*/
export function useSessionReport(sessionId: string | null, ready: boolean) {
const enabled = Boolean(sessionId) && ready;
const query = useQuery({
queryKey: ["session-report", sessionId],
queryFn: () => request<SessionReport>(`/api/sessions/${sessionId}/report`),
enabled,
retry: (failureCount, error) => shouldRetryReport(failureCount, errorStatus(error)),
retryDelay: reportRetryDelay,
});
return {
report: enabled ? query.data ?? null : null,
loading: enabled && query.isFetching,
error: enabled && query.isError ? reportErrorMessage(errorStatus(query.error)) : "",
refetch: query.refetch,
};
}
export const useCreateSession = () =>
useMutation({
mutationFn: (body: { scenario_id: string; mode: SessionMode; trainee?: string; group?: string }) =>

View file

@ -0,0 +1,4 @@
export const REPORT_RETRY_LIMIT: number;
export function reportErrorMessage(status: number | null): string;
export function shouldRetryReport(failureCount: number, status: number | null): boolean;
export function reportRetryDelay(attempt: number): number;

View file

@ -0,0 +1,28 @@
// Правила чтения отчёта сессии — одни для пульта ДДС, звонка 112 и наблюдения.
// Отдельный .mjs, чтобы проверять их node-скриптом без сборки.
/** Сколько раз повторять 409: оценка к этому моменту обычно уже посчитана. */
export const REPORT_RETRY_LIMIT = 6;
/** @param {number | null} status HTTP-статус; `null` — ответа не было. */
export function reportErrorMessage(status) {
if (status === 403) return "Отчёт недоступен: это занятие другого обучающегося.";
if (status === 409) return "Оценка ещё не готова. Попробуйте получить разбор чуть позже.";
if (status == null) return "Не удалось получить отчёт.";
return `Не удалось получить отчёт (HTTP ${status}).`;
}
/**
* 409 — оценка ещё считается, она появится сама. 403 и прочее от повтора
* не изменятся.
* @param {number} failureCount
* @param {number | null} status
*/
export function shouldRetryReport(failureCount, status) {
return status === 409 && failureCount < REPORT_RETRY_LIMIT;
}
/** @param {number} attempt */
export function reportRetryDelay(attempt) {
return Math.min(1_000 * 2 ** attempt, 5_000);
}