/** * Компактный Date Range Picker для /reports. * Сохраняет #date-from / #date-to для совместимости с остальным кодом. */ (function (global) { const MONTHS = [ "янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек", ]; let draftFrom = null; let draftTo = null; let viewMonth = null; let activePreset = "day"; let panelEl = null; let triggerEl = null; function pad(n) { return String(n).padStart(2, "0"); } function toIso(d) { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } function parseIso(s) { if (!s) return null; const p = String(s).split("-"); if (p.length !== 3) return null; const d = new Date(Number(p[0]), Number(p[1]) - 1, Number(p[2])); d.setHours(0, 0, 0, 0); return d; } function startOfDay(d) { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; } function today() { return startOfDay(new Date()); } function sameDay(a, b) { return a && b && toIso(a) === toIso(b); } function formatLabel(from, to) { if (!from || !to) return "Выберите даты"; const t = today(); const fmt = (d) => `${pad(d.getDate())}.${pad(d.getMonth() + 1)}`; if (sameDay(from, to) && sameDay(from, t)) return "Сегодня"; if (sameDay(from, to)) return fmt(from); return `${fmt(from)} — ${fmt(to)}`; } function syncHiddenInputs(from, to) { const fromEl = document.getElementById("date-from"); const toEl = document.getElementById("date-to"); if (fromEl) fromEl.value = from ? toIso(from) : ""; if (toEl) toEl.value = to ? toIso(to) : ""; } function readCommittedRange() { const fromEl = document.getElementById("date-from"); const toEl = document.getElementById("date-to"); return { from: parseIso(fromEl?.value), to: parseIso(toEl?.value), }; } function updateTriggerLabel() { const label = document.getElementById("reportsDateRangeLabel"); if (!label) return; const { from, to } = readCommittedRange(); label.textContent = formatLabel(from, to); } function setPresetActive(preset) { activePreset = preset || ""; document.querySelectorAll("[data-drp-preset]").forEach((btn) => { btn.classList.toggle("active", btn.getAttribute("data-drp-preset") === activePreset); }); } function computePresetRange(preset) { const t = today(); let from = t; let to = t; if (preset === "week") { from = new Date(t); from.setDate(t.getDate() - ((t.getDay() + 6) % 7)); } else if (preset === "month") { from = new Date(t.getFullYear(), t.getMonth(), 1); } return { from: startOfDay(from), to: startOfDay(to) }; } function commitDraft() { if (draftFrom && !draftTo) draftTo = draftFrom; if (!draftFrom || !draftTo) return readCommittedRange(); syncHiddenInputs(draftFrom, draftTo); setPresetActive(""); updateTriggerLabel(); return { from: draftFrom, to: draftTo }; } function applyRange(from, to, preset) { syncHiddenInputs(from, to); draftFrom = from; draftTo = to; setPresetActive(preset || ""); updateTriggerLabel(); } function commitSelection() { commitDraft(); closePanel(); } function positionPanel() { if (!triggerEl || !panelEl || panelEl.hidden) return; const rect = triggerEl.getBoundingClientRect(); const width = Math.max(rect.width, 304); let left = rect.left; if (left + width > window.innerWidth - 12) { left = Math.max(12, window.innerWidth - width - 12); } let top = rect.bottom + 6; const panelHeight = panelEl.offsetHeight || 360; if (top + panelHeight > window.innerHeight - 12) { top = Math.max(12, rect.top - panelHeight - 6); } panelEl.style.left = `${left}px`; panelEl.style.top = `${top}px`; panelEl.style.width = `${width}px`; } function closePanel() { if (panelEl) panelEl.hidden = true; if (triggerEl) triggerEl.setAttribute("aria-expanded", "false"); window.removeEventListener("resize", positionPanel); window.removeEventListener("scroll", positionPanel, true); } function openPanel() { const committed = readCommittedRange(); draftFrom = committed.from || today(); draftTo = committed.to || draftFrom; viewMonth = new Date(draftFrom.getFullYear(), draftFrom.getMonth(), 1); renderCalendar(); if (panelEl) panelEl.hidden = false; if (triggerEl) triggerEl.setAttribute("aria-expanded", "true"); positionPanel(); window.addEventListener("resize", positionPanel); window.addEventListener("scroll", positionPanel, true); } function togglePanel() { if (panelEl?.hidden) openPanel(); else closePanel(); } function inRange(d, a, b) { if (!a || !b) return false; const t = d.getTime(); const lo = Math.min(a.getTime(), b.getTime()); const hi = Math.max(a.getTime(), b.getTime()); return t >= lo && t <= hi; } function onDayClick(d) { if (!draftFrom || (draftFrom && draftTo)) { draftFrom = d; draftTo = null; setPresetActive(""); renderCalendar(); return; } if (d < draftFrom) { draftTo = draftFrom; draftFrom = d; } else { draftTo = d; } setPresetActive(""); commitSelection(); } function renderCalendar() { const grid = document.getElementById("reportsDateRangeGrid"); const title = document.getElementById("reportsDateRangeMonth"); if (!grid || !viewMonth) return; const y = viewMonth.getFullYear(); const m = viewMonth.getMonth(); if (title) title.textContent = `${MONTHS[m]} ${y}`; const first = new Date(y, m, 1); const startOffset = (first.getDay() + 6) % 7; const daysInMonth = new Date(y, m + 1, 0).getDate(); let html = ""; for (let i = 0; i < startOffset; i++) { html += ''; } for (let day = 1; day <= daysInMonth; day++) { const d = new Date(y, m, day); const classes = ["reports-drp__day"]; if (sameDay(d, today())) classes.push("reports-drp__day--today"); if (draftFrom && sameDay(d, draftFrom)) classes.push("reports-drp__day--edge"); if (draftTo && sameDay(d, draftTo)) classes.push("reports-drp__day--edge"); if (inRange(d, draftFrom, draftTo)) classes.push("reports-drp__day--in-range"); html += ``; } grid.innerHTML = html; grid.querySelectorAll("[data-drp-day]").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const d = parseIso(btn.getAttribute("data-drp-day")); if (d) onDayClick(d); }); }); } function setPeriod(period) { const { from, to } = computePresetRange(period); applyRange(from, to, period); closePanel(); } function init(options) { triggerEl = document.getElementById("reportsDateRangeTrigger"); panelEl = document.getElementById("reportsDateRangePanel"); if (!triggerEl || !panelEl) return; if (panelEl.parentElement !== document.body) { document.body.appendChild(panelEl); } panelEl.addEventListener("mousedown", (e) => e.stopPropagation()); panelEl.addEventListener("click", (e) => e.stopPropagation()); document.querySelectorAll("[data-drp-preset]").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); setPeriod(btn.getAttribute("data-drp-preset") || "day"); }); }); document.getElementById("reportsDateRangePrev")?.addEventListener("click", (e) => { e.stopPropagation(); if (!viewMonth) viewMonth = today(); viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() - 1, 1); renderCalendar(); positionPanel(); }); document.getElementById("reportsDateRangeNext")?.addEventListener("click", (e) => { e.stopPropagation(); if (!viewMonth) viewMonth = today(); viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 1); renderCalendar(); positionPanel(); }); triggerEl.addEventListener("click", (e) => { e.stopPropagation(); togglePanel(); }); document.addEventListener("click", () => { if (!panelEl.hidden) closePanel(); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") closePanel(); }); const urlParams = new URLSearchParams(window.location.search); const highlightReportId = urlParams.get("report"); if (highlightReportId) { global.__wespHighlightReportId = highlightReportId; setPeriod("month"); } else { setPeriod("day"); } if (options?.onReady) options.onReady(); } global.WespReportsDateRange = { init, setPeriod, commit: commitDraft, updateTriggerLabel, formatLabel, parseIso, toIso, }; global.setPeriod = setPeriod; })(typeof window !== "undefined" ? window : globalThis);