103 lines
6.4 KiB
JavaScript
103 lines
6.4 KiB
JavaScript
/* 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');
|
||
},
|
||
};
|