108 lines
4.3 KiB
TypeScript
108 lines
4.3 KiB
TypeScript
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 };
|
||
}
|