import { onBeforeUnmount, onMounted } from 'vue'; /** * Polling-composable для авто-обновления view-данных. * * Используется в DealsView/KanbanView/AdminTenants/Billing/Incidents как * замена manual reload-btn — каждые `intervalMs` миллисекунд вызывает * переданный `loader()`. На MVP это покрывает «real-time» pattern до * приезда SSE/WebSocket в production. * * Pause при скрытой вкладке (`document.hidden=true`) через Page Visibility * API — не делаем лишних запросов когда пользователь смотрит другую * вкладку. Resume на visibilitychange-event. * * Cleanup на onBeforeUnmount: clearInterval + removeEventListener. */ export interface PollingOptions { /** Период polling в миллисекундах. По умолчанию 30_000. */ intervalMs?: number; /** Если false — composable не стартует interval (для disable-флага). */ enabled?: boolean; } export function usePolling(loader: () => void | Promise, options: PollingOptions = {}): void { const intervalMs = options.intervalMs ?? 30_000; const enabled = options.enabled ?? true; if (!enabled) return; let timerId: ReturnType | null = null; let visibilityListener: (() => void) | null = null; function start() { if (timerId !== null) return; timerId = setInterval(() => { // Skip если вкладка скрыта — экономим запросы. if (typeof document !== 'undefined' && document.hidden) return; void loader(); }, intervalMs); } function stop() { if (timerId !== null) { clearInterval(timerId); timerId = null; } } onMounted(() => { start(); if (typeof document !== 'undefined') { visibilityListener = () => { if (document.hidden) { stop(); } else { start(); // Сразу подгрузить свежее на возврат во вкладку (не ждать interval). void loader(); } }; document.addEventListener('visibilitychange', visibilityListener); } }); onBeforeUnmount(() => { stop(); if (visibilityListener !== null && typeof document !== 'undefined') { document.removeEventListener('visibilitychange', visibilityListener); visibilityListener = null; } }); }