arm-core/web/src/widgets/ConsolidatedOsv/useConsolidatedOsv.ts
2026-10-27 21:56:59 +04:00

108 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { message } from 'antd';
import { useCallback, useEffect, useRef, useState } from 'react';
import { ApiError } from '../../api/client';
import { getConsolidatedOsv, getOsvSummary } from '../../services/api/osv';
import type { ConsolidatedRowOut, OsvSummaryOut } from '../../types/api';
import { type OsvFilterDraft, type OsvFilterState, applyFilterPatch } from './osvFilterState';
export const OSV_PAGE_SIZE = 500;
/** Понятный русский текст ошибки загрузки свода. */
export function osvErrorText(e: unknown): string {
if (e instanceof ApiError) {
if (e.status === 404) return 'Версия не найдена или недоступна';
if (e.status === 409) return `Сводная ОСВ сейчас недоступна: ${e.message}`;
return e.message;
}
return 'Не удалось получить сводную ОСВ';
}
/**
* @param pairsMode режим «Встречные записи»: строки свода не нужны и не запрашиваются (сводка — запрашивается).
* Фильтры и страница — одно состояние: смена фильтра с возвратом на 1-ю страницу даёт один запрос, а не два.
*/
export function useConsolidatedOsv(versionId: string, pairsMode = false) {
const [query, setQuery] = useState<OsvFilterState>({ filters: {}, page: 1 });
const { filters, page } = query;
const setPage = useCallback((p: number) => setQuery((q) => ({ ...q, page: p })), []);
const [rows, setRows] = useState<ConsolidatedRowOut[]>([]);
const [total, setTotal] = useState(0);
const [summary, setSummary] = useState<OsvSummaryOut | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// ошибка загрузки сводки (для режима пар: без неё там не на что смотреть)
const [summaryError, setSummaryError] = useState<string | null>(null);
// номер последнего запроса: ответы более старых запросов игнорируются
const requestRef = useRef(0);
// номер последнего запроса сводки: перечитывание после сопоставления не перетирается более старым ответом
const summaryRef = useRef(0);
const loadSummary = useCallback(async () => {
const requestId = ++summaryRef.current;
try {
const s = await getOsvSummary(versionId);
if (requestId === summaryRef.current) {
setSummary(s);
setSummaryError(null);
}
} catch (e: unknown) {
if (requestId === summaryRef.current) {
message.error(osvErrorText(e));
setSummaryError(osvErrorText(e));
}
}
}, [versionId]);
useEffect(() => {
void loadSummary();
return () => {
// размонтирование: ответ уже отправленного запроса игнорируется
summaryRef.current++;
};
}, [loadSummary]);
const load = useCallback(async () => {
const requestId = ++requestRef.current;
setLoading(true);
try {
const result = await getConsolidatedOsv(versionId, filters, OSV_PAGE_SIZE, (page - 1) * OSV_PAGE_SIZE);
if (requestId === requestRef.current) {
setRows(result.items);
setTotal(result.total);
setError(null);
}
} catch (e: unknown) {
if (requestId === requestRef.current) {
setRows([]);
setTotal(0);
setError(osvErrorText(e));
}
} finally {
if (requestId === requestRef.current) setLoading(false);
}
}, [versionId, filters, page]);
useEffect(() => {
if (pairsMode) {
// ответ ещё летящего запроса строк больше не нужен
requestRef.current++;
setLoading(false);
return;
}
void load();
}, [load, pairsMode]);
const patchFilters = (patch: OsvFilterDraft) => {
setQuery((q) => applyFilterPatch(q, patch));
};
/** После ручного сопоставления: «Осталось N» и строки меняются сразу у нескольких строк (автоприменение) — перечитать обе. */
const refresh = () => {
if (!pairsMode) void load();
void loadSummary();
};
return { filters, patchFilters, page, setPage, rows, total, summary, summaryError, loadSummary, loading, error, refresh };
}