/* Full-viewport presentation of the real weekly-report table. No demo periods, metrics or contact numbers: WeeklyDashboard owns all data. */ const WeeklyFullscreen = { ensure() { if (this.dialog) return; const dialog = el('dialog', { id: 'weekly-fullscreen', className: 'weekly-experiment', ariaLabel: ReportClinic.brand + ' — маркетинговая аналитика по неделям' }); const layout = el('div', { className: 'wke-layout' }); const toolbar = el('div', { className: 'wke-toolbar' }); const modes = el('div', { className: 'wke-modes', role: 'group', ariaLabel: 'Периодичность отчёта' }); for (const [mode, label] of [['weeks', 'По неделям'], ['months', 'По месяцам']]) { const button = el('button', { type: 'button', className: 'wke-mode', textContent: label }); button.dataset.mode = mode; button.onclick = () => WeeklyDashboard.setMode(mode); modes.append(button); } const status = el('span', { className: 'wke-status', role: 'status', ariaLive: 'polite' }); const refresh = el('button', { type: 'button', className: 'wke-refresh', textContent: 'Обновить', title: 'Загрузить актуальные данные отчётов' }); refresh.onclick = () => WeeklyDashboard.load(); const close = el('button', { type: 'button', className: 'wke-close', ariaLabel: 'Вернуться в админку', title: 'Вернуться в админку · Esc', textContent: '×' }); close.onclick = () => dialog.close(); toolbar.append(modes, status, refresh, close); const scroll = el('div', { className: 'wke-scroll' }); const empty = el('div', { className: 'wke-empty', role: 'status', hidden: true }); layout.append(toolbar, empty, scroll); dialog.append(layout); scroll.addEventListener('scroll', () => { dialog.classList.toggle('wke-scrolled', scroll.scrollLeft > 0); dialog.classList.toggle('wke-scrolled-y', scroll.scrollTop > 0); }, { passive: true }); dialog.addEventListener('close', () => { // Return the actual nodes (including budget/filter handlers), not copies. this.tableHome.append(this.table); this.filterHome.append(this.filter); document.body.style.overflow = this.savedOverflow; document.title = this.savedTitle; }); document.body.append(dialog); Object.assign(this, { dialog, toolbar, modes, status, refresh, close, scroll, empty }); this.resizeObserver = new ResizeObserver(() => this.resize()); this.resizeObserver.observe(scroll); }, async open() { this.ensure(); if (this.dialog.open) return; this.table = $('#wk-table'); this.filter = $('#wk-filter'); this.tableHome = this.table.parentElement; this.filterHome = this.filter.parentElement; this.savedOverflow = document.body.style.overflow; this.savedTitle = document.title; this.scroll.append(this.table); this.toolbar.insertBefore(this.filter, this.refresh); this.scroll.scrollTop = 0; this.scroll.scrollLeft = 0; this.dialog.classList.remove('wke-scrolled', 'wke-scrolled-y'); document.body.style.overflow = 'hidden'; document.title = ReportClinic.brand + ' — маркетинговая аналитика'; this.dialog.showModal(); this.close.focus({ preventScroll: true }); await WeeklyDashboard.load(); }, sync() { if (!this.dialog?.open) return; const { list, loading, error } = WeeklyDashboard; const periods = WeeklyDashboard.periods(); const monthly = WeeklyDashboard.mode === 'months'; for (const button of this.modes.children) button.setAttribute('aria-pressed', String(button.dataset.mode === WeeklyDashboard.mode)); // Keep the page's top tabs in sync when the mode changes inside the report. for (const button of document.querySelectorAll('#rp-tabs button[data-mode]')) button.setAttribute('aria-selected', String(button.dataset.mode === WeeklyDashboard.mode)); const heading = $('#tab-reports .rp-head h1'); if (heading) heading.textContent = monthly ? 'Дашборд по месяцам' : 'Дашборд по неделям'; this.dialog.setAttribute('aria-label', ReportClinic.brand + ' — маркетинговая аналитика ' + (monthly ? 'по месяцам' : 'по неделям')); const visible = WeeklyDashboard.filtered(); this.table.style.setProperty('--wk-period-count', visible.length); this.refresh.disabled = loading; this.dialog.setAttribute('aria-busy', String(loading)); this.status.textContent = loading ? 'Загрузка отчётов…' : error ? `${error}${list.length ? ' · показаны ранее загруженные данные' : ''}` : `Периодов: ${periods.length}${visible.length !== periods.length ? ' · показано: ' + visible.length : ''}`; this.status.classList.toggle('wke-status--error', Boolean(error)); this.scroll.hidden = false; this.empty.hidden = Boolean(visible.length); this.empty.textContent = loading ? 'Загрузка реальных данных…' : error ? 'Не удалось загрузить отчёты. Нажмите «Обновить», чтобы повторить.' : periods.length ? 'За выбранные даты отчётов нет. Измените даты или нажмите «Показать все».' : monthly ? 'Месячных отчётов пока нет. Загрузите выгрузку 1С за полный календарный месяц во вкладке «Новый отчёт». Недельные отчёты не суммируются, чтобы не задвоить пациентов.' : 'Недельных отчётов пока нет. Загрузите выгрузку 1С с указанным периодом во вкладке «Новый отчёт».'; this.resize(); }, resize() { if (!this.dialog?.open) return; const style = getComputedStyle(this.dialog); const frozenWidth = parseFloat(style.getPropertyValue('--wk-channel-width')) + parseFloat(style.getPropertyValue('--wk-metric-width')); const count = Math.max(WeeklyDashboard.filtered().length, 1); // Few actual weeks must not stretch into enormous columns or get fake dates. const width = Math.max(112, Math.min(160, (this.scroll.clientWidth - frozenWidth - 24) / count)); this.table.style.setProperty('--wk-period-width', width + 'px'); }, };