refactor: отчёт сессии загружается одним hook useSessionReport с повтором 409
This commit is contained in:
parent
93c5f063f3
commit
f6d0653e68
7 changed files with 106 additions and 79 deletions
|
|
@ -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 }) =>
|
||||
|
|
|
|||
4
frontend/src/shared/api/report.d.mts
Normal file
4
frontend/src/shared/api/report.d.mts
Normal 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;
|
||||
28
frontend/src/shared/api/report.mjs
Normal file
28
frontend/src/shared/api/report.mjs
Normal 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);
|
||||
}
|
||||
Loading…
Reference in a new issue