- В панели отображается общее число файлов и догружаются следующие страницы (при прокрутке). - Опрос статусов обработки файлов (раз в 5 секунд) не сбрасывает уже подгруженные страницы. - Тот же список используется на экране проверки распознавания
88 lines
3.8 KiB
TypeScript
88 lines
3.8 KiB
TypeScript
import { ApiError, api, apiFetch, apiFetchBlob } from '../../api/client';
|
||
import type { FileOut, Page, UploadResult } from '../../types/api';
|
||
|
||
export function isUploadResult(body: unknown): body is UploadResult {
|
||
return (
|
||
typeof body === 'object' &&
|
||
body !== null &&
|
||
Array.isArray((body as UploadResult).accepted) &&
|
||
Array.isArray((body as UploadResult).rejected)
|
||
);
|
||
}
|
||
|
||
/** Маршрут файлов ЮЛ: с консолидацией — файлы в контексте консолидации, без неё — файлы ЮЛ (design D-2). */
|
||
export function filesPath(legalEntityId: string, consolidationId: string | null): string {
|
||
return consolidationId
|
||
? `/consolidations/${encodeURIComponent(consolidationId)}/legal-entities/${legalEntityId}/files`
|
||
: `/legal-entities/${legalEntityId}/files`;
|
||
}
|
||
|
||
/** Загрузка файлов ЮЛ одним запросом (в консолидацию или без контекста); 422 «ни один не принят» возвращается как результат, прочие ошибки — throw. */
|
||
export async function uploadFiles(legalEntityId: string, files: File[], consolidationId: string | null): Promise<UploadResult> {
|
||
const form = new FormData();
|
||
for (const file of files) {
|
||
form.append('files', file);
|
||
}
|
||
try {
|
||
return await apiFetch<UploadResult>(filesPath(legalEntityId, consolidationId), { method: 'POST', body: form });
|
||
} catch (e: unknown) {
|
||
if (e instanceof ApiError && e.status === 422 && isUploadResult(e.body)) {
|
||
return e.body;
|
||
}
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
export function summarizeUpload(result: UploadResult): { accepted: string[]; rejected: { name: string; reason: string }[] } {
|
||
return {
|
||
accepted: result.accepted.map((a) => a.name),
|
||
rejected: result.rejected.map((r) => ({ name: r.name, reason: r.message })),
|
||
};
|
||
}
|
||
|
||
/** Размер страницы: столько файлов панель загружает сразу и при каждой прокрутке до конца списка. Не больше 200 — `MAX_PAGE_LIMIT` бэка. */
|
||
export const FILES_PAGE_LIMIT = 200;
|
||
|
||
/** Страница файлов ЮЛ одним запросом. Бэк сортирует по дате загрузки и `id` — порядок стабилен. */
|
||
export function listLegalEntityFiles(le: string, cons: string | null, offset = 0, limit = FILES_PAGE_LIMIT) {
|
||
return apiFetch<Page<FileOut>>(`${filesPath(le, cons)}?limit=${limit}&offset=${offset}`);
|
||
}
|
||
|
||
/**
|
||
* Файлы ЮЛ начиная с номера `from` запросами по `FILES_PAGE_LIMIT`, пока не наберём `count`
|
||
* или файлы не кончатся. Пустая страница раньше `total` — файлы удалили между запросами, дальше не идём.
|
||
*/
|
||
export async function listLegalEntityFilesFrom(
|
||
le: string,
|
||
cons: string | null,
|
||
from: number,
|
||
count: number,
|
||
): Promise<{ items: FileOut[]; total: number }> {
|
||
const items: FileOut[] = [];
|
||
let total = 0;
|
||
let lastPageEmpty = false;
|
||
do {
|
||
const page = await listLegalEntityFiles(le, cons, from + items.length);
|
||
items.push(...page.items);
|
||
total = page.total;
|
||
lastPageEmpty = page.items.length === 0;
|
||
} while (items.length < count && from + items.length < total && !lastPageEmpty);
|
||
return { items, total };
|
||
}
|
||
|
||
export function hasPendingFiles(files: FileOut[]): boolean {
|
||
return files.some((f) => f.status === 'UPLOADED' || f.status === 'PROCESSING');
|
||
}
|
||
|
||
export function fetchFileContent(fileId: string): Promise<{ blob: Blob; filename: string | null }> {
|
||
return apiFetchBlob(`/files/${fileId}/content?disposition=inline`);
|
||
}
|
||
|
||
export function getFile(id: string): Promise<FileOut> {
|
||
return apiFetch<FileOut>(`/files/${id}`);
|
||
}
|
||
|
||
export function removeFile(fileId: string) {
|
||
return api.del(`/files/${fileId}`);
|
||
}
|