// Team-wide support waits. Customer-wait tickets never enter this queue. function supportWaitMinutes(since, now = Date.now()) { const start = Date.parse(since); return Number.isFinite(start) ? Math.max(0, Math.floor((now - start) / 60000)) : 0; } function supportWaitColor(minutes) { return minutes >= 120 ? 'rgb(var(--hd-wait-long))' : minutes >= 60 ? 'rgb(var(--hd-wait-medium))' : 'rgb(var(--hd-wait-short))'; } function supportWaitDuration(minutes) { return `${minutes >= 60 ? `${Math.floor(minutes / 60)} ч ` : ''}${minutes % 60} мин`; } function supportWaitingDialogs(conversations) { return conversations.filter(c => c.status === 'waiting' && c.waitingReason === 'manual') .sort((a, b) => (Date.parse(a.waitingSince) || 0) - (Date.parse(b.waitingSince) || 0) || String(a.id).localeCompare(String(b.id))); } function TeamWaiting({ conversations, onOpenDialog }) { const [now, setNow] = React.useState(Date.now()); const [config, setConfig] = React.useState(null); const [listOpen, setListOpen] = React.useState(false); const [toastOpen, setToastOpen] = React.useState(false); const bucket = React.useRef(null); const root = React.useRef(null); const trigger = React.useRef(null); const closeButton = React.useRef(null); const items = supportWaitingDialogs(conversations); const count = items.length; React.useEffect(() => { let live = true; const load = () => window.apiFetch('GET', '/api/waiting-reminders/config').then(value => { if (live) setConfig({ interval: value.intervalSeconds * 1000, offset: value.serverTime * 1000 - Date.now() }); }).catch(() => {}); load(); const retry = setInterval(load, 60000); const tick = setInterval(() => setNow(Date.now()), 15000); return () => { live = false; clearInterval(retry); clearInterval(tick); }; }, []); React.useEffect(() => { if (!count) { bucket.current = null; setToastOpen(false); setListOpen(false); return; } if (!config) return; const next = Math.floor((now + config.offset) / config.interval); if (bucket.current !== null && bucket.current !== next) setToastOpen(true); bucket.current = next; }, [count, now, config]); React.useEffect(() => { if (!listOpen) return; closeButton.current?.focus(); const dismiss = event => { if (event.type === 'keydown' && event.key === 'Escape') { setListOpen(false); trigger.current?.focus(); } else if (event.type === 'pointerdown' && !root.current?.contains(event.target)) setListOpen(false); }; document.addEventListener('keydown', dismiss); document.addEventListener('pointerdown', dismiss); return () => { document.removeEventListener('keydown', dismiss); document.removeEventListener('pointerdown', dismiss); }; }, [listOpen]); if (!count) return null; const longest = items[0]; const mins = supportWaitMinutes(longest.waitingSince, now); const color = supportWaitColor(mins); return