diff --git a/frontend/src/stores/fleetDashboardStore.js b/frontend/src/stores/fleetDashboardStore.js index 9318966..bee4bbf 100644 --- a/frontend/src/stores/fleetDashboardStore.js +++ b/frontend/src/stores/fleetDashboardStore.js @@ -28,6 +28,9 @@ const DASHBOARD_FETCH_TTL_MS = 30_000; // 30 sekundi let dbPromise = null; let syncListenerStarted = false; let isSyncInProgress = false; +const archiveNotificationPollers = new Map(); +const ARCHIVE_NOTIFICATION_POLL_INTERVAL_MS = 10_000; +const ARCHIVE_NOTIFICATION_POLL_TIMEOUT_MS = 15 * 60 * 1000; function isBrowser() { return typeof window !== 'undefined'; @@ -866,6 +869,70 @@ function _schedulePdfNotificationPolling() { }); } +function _findCompletedArchiveNotification(notifications, generatedArchiveId) { + if (!generatedArchiveId || !Array.isArray(notifications)) { + return null; + } + return notifications.find((notification) => { + const metadata = notification?.metadata || {}; + return metadata.entity_type === 'fleet_archive' + && String(metadata.generated_archive_id || '') === String(generatedArchiveId) + && ['completed', 'failed'].includes(String(metadata.stage || '')); + }) || null; +} + +function _clearArchiveNotificationPoller(generatedArchiveId) { + const key = String(generatedArchiveId || ''); + const handles = archiveNotificationPollers.get(key); + if (!handles) { + return; + } + window.clearInterval(handles.intervalId); + window.clearTimeout(handles.timeoutId); + archiveNotificationPollers.delete(key); +} + +function _scheduleArchiveNotificationPolling(generatedArchiveId = null) { + if (!isBrowser()) return; + if (!generatedArchiveId) { + [5000, 20000, 60000].forEach((delay) => { + setTimeout(() => _refreshNotificationsAsync(), delay); + }); + return; + } + + const key = String(generatedArchiveId); + if (archiveNotificationPollers.has(key)) { + return; + } + + const pollOnce = async () => { + try { + const notificationModule = await import('./notificationStore.js'); + await notificationModule.fetchNotifications(); + const resolvedNotification = _findCompletedArchiveNotification( + notificationModule.$notifications.get(), + key + ); + if (resolvedNotification) { + _clearArchiveNotificationPoller(key); + } + } catch (_) { + // silent — korisnik će i dalje vidjeti toast ili ručno osvježiti notifikacije + } + }; + + const intervalId = window.setInterval(() => { + void pollOnce(); + }, ARCHIVE_NOTIFICATION_POLL_INTERVAL_MS); + const timeoutId = window.setTimeout(() => { + _clearArchiveNotificationPoller(key); + }, ARCHIVE_NOTIFICATION_POLL_TIMEOUT_MS); + + archiveNotificationPollers.set(key, { intervalId, timeoutId }); + void pollOnce(); +} + export async function downloadWorkOrderPdf(workOrderId) { if (!workOrderId) { throw new Error('Work order ID je obavezan.'); @@ -1030,7 +1097,7 @@ export async function downloadMonthlyServiceTasksArchive(year, month) { return payload; } showToast('ZIP arhiva servisnih taskova se generira u pozadini. Preuzimanje je dostupno kroz notifikaciju.', 'info'); - _schedulePdfNotificationPolling(); + _scheduleArchiveNotificationPolling(payload?.generated_archive_id || null); return payload; } catch (err) { showToast(err?.message || 'Pokretanje generiranja ZIP arhive servisnih taskova nije uspjelo.', 'error'); @@ -1047,7 +1114,7 @@ export async function downloadMonthlyWorkOrdersArchive(year, month) { return payload; } showToast('ZIP arhiva putnih naloga se generira u pozadini. Preuzimanje je dostupno kroz notifikaciju.', 'info'); - _schedulePdfNotificationPolling(); + _scheduleArchiveNotificationPolling(payload?.generated_archive_id || null); return payload; } catch (err) { showToast(err?.message || 'Pokretanje generiranja ZIP arhive putnih naloga nije uspjelo.', 'error');