64 lines
2.7 KiB
JavaScript
64 lines
2.7 KiB
JavaScript
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;
|
||
}
|