64 lines
2.7 KiB
JavaScript
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 { useEffect } from 'react';
import { toast } from 'react-toastify';
import { useAuth } from '../app/context/AuthProvider';
import { downloadExport, getExportFilename, startExportUpdates, subscribeExports } from '../api/exports';
import { downloadFile } from '../utils/downloadFile';
import { getApiErrorMessage } from '../utils/getApiErrorMessage';
export default function ExportJobs() {
const { user, isAuthenticated } = useAuth();
useEffect(() => {
if (!isAuthenticated || !user?.id) return;
let stopped = false;
const processing = new Set();
const handled = new Set();
const toastIds = new Set();
const key = (id) => `export-handled:${user.id}:${id}`;
const wasHandled = (id) => {
try { return handled.has(id) || Boolean(localStorage.getItem(key(id))); }
catch { return handled.has(id); }
};
const markHandled = (id) => {
handled.add(id);
try { localStorage.setItem(key(id), String(Date.now())); } catch { /* Memory fallback. */ }
};
const showLoading = (id, message) => {
toastIds.add(id);
if (!toast.isActive(id)) toast.loading(message, { toastId: id, autoClose: false });
else toast.update(id, { render: message, isLoading: true, autoClose: false });
};
const observe = async (job) => {
if (stopped || wasHandled(job.id) || processing.has(job.id)) return;
const toastId = `export:${user.id}:${job.id}`;
if (['queued', 'processing'].includes(job.status)) {
showLoading(toastId, 'Идёт формирование файла…');
return;
}
processing.add(job.id);
showLoading(toastId, 'Скачивание файла…');
try {
if (job.status === 'failed') throw new Error(job.error || 'Ошибка экспорта');
const response = await downloadExport(job.id);
if (stopped) return;
await downloadFile(response, getExportFilename(job), 'xlsx');
markHandled(job.id);
toast.update(toastId, { render: 'Файл сформирован, скачивание началось', type: 'success', isLoading: false, autoClose: 3000 });
} catch (error) {
if (stopped) return;
if (job.status === 'failed' || error?.response?.status === 410) markHandled(job.id);
const message = await getApiErrorMessage(error, 'Не удалось скачать файл');
if (!stopped) toast.update(toastId, { render: message, type: 'error', isLoading: false, autoClose: 5000 });
} finally { processing.delete(job.id); }
};
const unsubscribe = subscribeExports((jobs) => { for (const job of jobs) void observe(job); });
const stop = startExportUpdates();
return () => {
stopped = true;
unsubscribe();
stop();
for (const id of toastIds) toast.dismiss(id);
};
}, [isAuthenticated, user?.id]);
return null;
}