$(document).ready(function () { function initMobileDatePicker() { const mobileQuery = window.matchMedia('(max-width: 767.98px)'); const coarsePointer = window.matchMedia('(pointer: coarse)'); if (!mobileQuery.matches || !coarsePointer.matches) { return; } const dateInputs = Array.from(document.querySelectorAll('input[type="date"]')).filter(function (input) { return !input.disabled && !input.dataset.mobileDateEnhanced; }); if (!dateInputs.length) { return; } const monthFormatter = new Intl.DateTimeFormat('ru-RU', { month: 'long', year: 'numeric', }); const displayFormatter = new Intl.DateTimeFormat('ru-RU'); const weekdayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']; const overlay = document.createElement('div'); overlay.className = 'mobile-date-picker'; overlay.hidden = true; overlay.innerHTML = '' + ''; document.body.appendChild(overlay); const titleEl = overlay.querySelector('.mobile-date-picker__title'); const monthLabelEl = overlay.querySelector('.mobile-date-picker__month-label'); const weekdaysEl = overlay.querySelector('.mobile-date-picker__weekdays'); const gridEl = overlay.querySelector('.mobile-date-picker__grid'); const closeBtn = overlay.querySelector('.mobile-date-picker__close'); const todayBtn = overlay.querySelector('.mobile-date-picker__today'); const clearBtn = overlay.querySelector('.mobile-date-picker__clear'); const applyBtn = overlay.querySelector('.mobile-date-picker__apply'); const prevBtn = overlay.querySelector('.mobile-date-picker__nav--prev'); const nextBtn = overlay.querySelector('.mobile-date-picker__nav--next'); weekdaysEl.innerHTML = weekdayLabels.map(function (day) { return '
' + day + '
'; }).join(''); function parseIsoDate(value) { if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) { return null; } const parts = value.split('-').map(Number); const date = new Date(parts[0], parts[1] - 1, parts[2]); if (Number.isNaN(date.getTime())) { return null; } return date; } function formatIsoDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return year + '-' + month + '-' + day; } function formatDisplayDate(value) { const date = parseIsoDate(value); return date ? displayFormatter.format(date) : ''; } function normalizeMonthLabel(date) { const label = monthFormatter.format(date); return label.charAt(0).toUpperCase() + label.slice(1); } function startOfMonth(date) { return new Date(date.getFullYear(), date.getMonth(), 1); } function isSameDate(a, b) { return a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); } const state = { activeInput: null, hiddenInput: null, displayInput: null, selectedDate: null, draftDate: null, viewDate: startOfMonth(new Date()), }; function syncDisplay(hiddenInput, displayInput) { displayInput.value = formatDisplayDate(hiddenInput.value); displayInput.classList.toggle('mobile-date-input--empty', !hiddenInput.value); } function closePicker() { overlay.hidden = true; document.body.classList.remove('mobile-date-picker-open'); state.activeInput = null; state.hiddenInput = null; state.displayInput = null; state.selectedDate = null; state.draftDate = null; } function renderCalendar() { const today = new Date(); const monthStart = startOfMonth(state.viewDate); const gridStart = new Date(monthStart); const firstWeekday = (monthStart.getDay() + 6) % 7; gridStart.setDate(monthStart.getDate() - firstWeekday); monthLabelEl.textContent = normalizeMonthLabel(monthStart); gridEl.innerHTML = ''; for (let index = 0; index < 42; index += 1) { const day = new Date(gridStart); day.setDate(gridStart.getDate() + index); const button = document.createElement('button'); button.type = 'button'; button.className = 'mobile-date-picker__day'; button.textContent = String(day.getDate()); button.dataset.value = formatIsoDate(day); if (day.getMonth() !== monthStart.getMonth()) { button.classList.add('is-outside'); } if (isSameDate(day, today)) { button.classList.add('is-today'); } if (state.draftDate && isSameDate(day, state.draftDate)) { button.classList.add('is-selected'); } button.addEventListener('click', function () { state.draftDate = parseIsoDate(button.dataset.value); renderCalendar(); }); gridEl.appendChild(button); } } function openPicker(hiddenInput, displayInput) { state.activeInput = hiddenInput; state.hiddenInput = hiddenInput; state.displayInput = displayInput; state.selectedDate = parseIsoDate(hiddenInput.value); state.draftDate = state.selectedDate; state.viewDate = startOfMonth(state.selectedDate || new Date()); titleEl.textContent = hiddenInput.dataset.mobileDateTitle || 'Выбор даты'; overlay.hidden = false; document.body.classList.add('mobile-date-picker-open'); renderCalendar(); } closeBtn.addEventListener('click', closePicker); overlay.addEventListener('click', function (event) { if (event.target === overlay) { closePicker(); } }); prevBtn.addEventListener('click', function () { state.viewDate = new Date(state.viewDate.getFullYear(), state.viewDate.getMonth() - 1, 1); renderCalendar(); }); nextBtn.addEventListener('click', function () { state.viewDate = new Date(state.viewDate.getFullYear(), state.viewDate.getMonth() + 1, 1); renderCalendar(); }); todayBtn.addEventListener('click', function () { state.draftDate = startOfMonth(new Date()); state.draftDate = new Date(state.draftDate.getFullYear(), state.draftDate.getMonth(), new Date().getDate()); state.viewDate = startOfMonth(state.draftDate); renderCalendar(); }); clearBtn.addEventListener('click', function () { if (!state.hiddenInput || !state.displayInput) { return; } state.hiddenInput.value = ''; state.hiddenInput.dispatchEvent(new Event('input', { bubbles: true })); state.hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); syncDisplay(state.hiddenInput, state.displayInput); closePicker(); }); applyBtn.addEventListener('click', function () { if (!state.hiddenInput || !state.displayInput || !state.draftDate) { closePicker(); return; } state.hiddenInput.value = formatIsoDate(state.draftDate); state.hiddenInput.dispatchEvent(new Event('input', { bubbles: true })); state.hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); syncDisplay(state.hiddenInput, state.displayInput); closePicker(); }); document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && !overlay.hidden) { closePicker(); } }); dateInputs.forEach(function (input) { input.dataset.mobileDateEnhanced = '1'; input.dataset.mobileDateTitle = input.closest('.row')?.querySelector('label[for="' + input.id + '"]')?.textContent.trim() || 'Выбор даты'; const displayInput = input.cloneNode(false); displayInput.type = 'text'; displayInput.removeAttribute('name'); displayInput.value = ''; displayInput.readOnly = true; displayInput.dataset.mobileDateDisplay = '1'; displayInput.classList.add('mobile-date-input'); displayInput.placeholder = 'Выберите дату'; input.type = 'hidden'; input.dataset.mobileDateHidden = '1'; input.parentNode.insertBefore(displayInput, input.nextSibling); syncDisplay(input, displayInput); displayInput.addEventListener('click', function () { openPicker(input, displayInput); }); }); } function cleanupStaleModalState() { if (!document.querySelector('.modal.show')) { document.querySelectorAll('.modal-backdrop').forEach(function (backdrop) { backdrop.remove(); }); document.body.classList.remove('modal-open'); document.body.style.removeProperty('padding-right'); document.body.style.removeProperty('overflow'); } } function getNotificationBadge() { return document.getElementById('notification-badge'); } function updateNotificationBadge(count) { const badge = getNotificationBadge(); if (!badge) { return; } const safeCount = Math.max(0, parseInt(count || 0, 10)); badge.dataset.count = String(safeCount); badge.classList.toggle('d-none', safeCount === 0); } function incrementNotificationBadge() { const badge = getNotificationBadge(); if (!badge) { return; } const current = parseInt(badge.dataset.count || '0', 10); updateNotificationBadge(current + 1); } function markNotificationRead(notificationId) { if (!notificationId || !window.notificationsReadUrlTemplate) { return; } fetch(window.notificationsReadUrlTemplate.replace('__id__', String(notificationId)), { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Accept': 'application/json', }, keepalive: true, }) .then(function (response) { if (!response.ok) { return null; } return response.json(); }) .then(function (data) { if (data && typeof data.unread !== 'undefined') { updateNotificationBadge(data.unread); } }) .catch(function () { // noop }); } function appendWsPopup(notification) { const container = document.getElementById('ws-notification-container'); if (!container || !notification) { return; } const popup = document.createElement('div'); popup.className = 'ws-notification-popup'; popup.dataset.id = String(notification.id || ''); const safeTitle = notification.title || 'Уведомление'; const bodyHtml = notification.message_html || notification.message || ''; popup.innerHTML = '
' + '' + safeTitle + '' + '' + '
' + '
' + bodyHtml + '
'; const closeBtn = popup.querySelector('.ws-notification-close'); if (closeBtn) { closeBtn.addEventListener('click', function (event) { event.stopPropagation(); markNotificationRead(notification.id); popup.remove(); }); } container.appendChild(popup); } cleanupStaleModalState(); initMobileDatePicker(); window.addEventListener('pageshow', cleanupStaleModalState); if ($('.main-alert').length) { setTimeout(function () { $('.main-alert').fadeTo(2000, 500).slideUp(500, function () { $('.main-alert').slideUp(500); }); }, 3000); } const user = localStorage.getItem('user'); if (user > 0) { const socket = new WebSocket(localStorage.getItem('socketAddress')); socket.onopen = function () { console.log('[WS] Connected. Listen messages for user ' + user); }; socket.onmessage = function (event) { const received = JSON.parse(event.data); if (parseInt(received.data.user_id, 10) !== parseInt(user, 10)) { return; } const action = received.data.action; if (action === 'persistent_notification') { incrementNotificationBadge(); appendWsPopup(received.data.notification); return; } if (action !== 'message') { return; } if (received.data.payload.download) { document.location.href = '/storage/export/' + received.data.payload.download; } if (received.data.payload.link) { document.location.href = received.data.payload.link; setTimeout(function () { document.location.reload(); }, 2000); } setTimeout(function () { if (received.data.payload.error) { $('.alerts').append(''); } else { $('.alerts').append(''); } setTimeout(function () { $('.main-alert2').fadeTo(2000, 500).slideUp(500, function () { $('.main-alert2').slideUp(500); }); }, 3000); }, 1000); }; socket.onclose = function (event) { if (event.wasClean) { console.log('[WS] Closed clear, code=' + event.code + ' reason=' + event.reason); } else { console.log('[WS] Connection lost', event); } }; socket.onerror = function (error) { console.log('[error]', error); }; } });