diff --git a/myproject/orders/static/orders/css/date_filter.css b/myproject/orders/static/orders/css/date_filter.css index 6fe3f23..1198f87 100644 --- a/myproject/orders/static/orders/css/date_filter.css +++ b/myproject/orders/static/orders/css/date_filter.css @@ -1,5 +1,5 @@ /** - * Стили для календарного фильтра по датам + * Стили для календарного фильтра с лентой из 9 дней * Используется в компоненте date_range_filter.html */ @@ -23,78 +23,185 @@ color: #0d6efd; } -.date-range-filter .date-input { - cursor: pointer; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -} - -.date-range-filter .date-input:focus { - border-color: #0d6efd; - box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.15); -} - -.date-range-filter .text-muted.small { - font-size: 0.75rem; - margin-bottom: 0.25rem; -} - -/* Быстрые кнопки фильтров */ -.quick-filters { - margin-top: 0.75rem; -} - -.quick-filters .btn-group { +/* Календарная лента */ +.date-carousel { display: flex; - gap: 0.25rem; + align-items: center; + gap: 0.5rem; } -.quick-date-btn { - font-size: 0.75rem; - padding: 0.375rem 0.5rem; - border-radius: 4px !important; +.carousel-nav-btn { + background: #fff; + border: 1px solid #dee2e6; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; transition: all 0.2s ease; + flex-shrink: 0; +} + +.carousel-nav-btn:hover { + background: #0d6efd; + border-color: #0d6efd; + color: white; + transform: scale(1.1); +} + +.carousel-nav-btn:active { + transform: scale(0.95); +} + +.carousel-nav-btn i { + font-size: 1.2rem; +} + +/* Контейнер с днями */ +.date-carousel-container { + display: flex; + gap: 0.5rem; + overflow-x: auto; flex: 1; - white-space: nowrap; + padding: 0.25rem 0; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ } -.quick-date-btn:hover { - background-color: #0d6efd; - color: white; +.date-carousel-container::-webkit-scrollbar { + display: none; /* Chrome/Safari */ +} + +/* Кнопка дня */ +.date-btn { + background: #fff; + border: 2px solid #dee2e6; + border-radius: 8px; + padding: 0.5rem; + min-width: 70px; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; + display: flex; + flex-direction: column; + gap: 0.25rem; + flex-shrink: 0; +} + +.date-btn:hover { border-color: #0d6efd; - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background: #f0f7ff; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } -.quick-date-btn:active { - transform: translateY(0); - box-shadow: none; +.date-btn-label { + font-size: 0.7rem; + font-weight: 600; + color: #6c757d; + text-transform: uppercase; } -.quick-date-btn.active { - background-color: #0d6efd; - color: white; +.date-btn-day { + font-size: 1.5rem; + font-weight: 700; + color: #212529; + line-height: 1; +} + +.date-btn-weekday { + font-size: 0.75rem; + font-weight: 500; + color: #6c757d; + text-transform: uppercase; +} + +/* Сегодняшний день (активный) */ +.date-btn.today { + background: #0d6efd; border-color: #0d6efd; + color: white; +} + +.date-btn.today .date-btn-label, +.date-btn.today .date-btn-day, +.date-btn.today .date-btn-weekday { + color: white; +} + +.date-btn.today:hover { + background: #0b5ed7; + border-color: #0b5ed7; +} + +/* Выбранный день */ +.date-btn.selected { + background: #198754; + border-color: #198754; + color: white; +} + +.date-btn.selected .date-btn-label, +.date-btn.selected .date-btn-day, +.date-btn.selected .date-btn-weekday { + color: white; } /* Адаптивность для мобильных устройств */ -@media (max-width: 576px) { +@media (max-width: 768px) { .date-range-filter { padding: 0.75rem; } - .quick-filters .btn-group { - flex-wrap: wrap; + .date-carousel { + gap: 0.25rem; } - .quick-date-btn { - flex: 1 1 calc(33.333% - 0.25rem); - min-width: 0; + .carousel-nav-btn { + width: 32px; + height: 32px; + } + + .date-btn { + min-width: 60px; + padding: 0.4rem; + } + + .date-btn-label { + font-size: 0.65rem; + } + + .date-btn-day { + font-size: 1.3rem; + } + + .date-btn-weekday { font-size: 0.7rem; - padding: 0.35rem 0.4rem; } } -/* Анимация для визуальной обратной связи */ +@media (max-width: 576px) { + .date-btn { + min-width: 50px; + padding: 0.3rem; + } + + .date-btn-label { + font-size: 0.6rem; + } + + .date-btn-day { + font-size: 1.1rem; + } + + .date-btn-weekday { + font-size: 0.65rem; + } +} + +/* Анимация при клике */ @keyframes pulse { 0% { transform: scale(1); @@ -107,6 +214,6 @@ } } -.quick-date-btn.clicked { +.date-btn.clicked { animation: pulse 0.3s ease; } diff --git a/myproject/orders/static/orders/js/date_filter.js b/myproject/orders/static/orders/js/date_filter.js index 2983cba..4055df8 100644 --- a/myproject/orders/static/orders/js/date_filter.js +++ b/myproject/orders/static/orders/js/date_filter.js @@ -1,89 +1,144 @@ /** - * Календарный фильтр для выбора диапазона дат - * Поддерживает быстрые фильтры (сегодня, завтра, неделя) - * - * Использование: - * Подключить этот файл в шаблоне после компонента date_range_filter.html + * Календарная лента с 9 днями для фильтрации заказов + * Сегодня в центре, навигация стрелками */ document.addEventListener('DOMContentLoaded', function() { - console.log('Date filter initialized'); + console.log('Date carousel initialized'); - const quickDateButtons = document.querySelectorAll('.quick-date-btn'); + // Инициализация всех календарных лент на странице + const carousels = document.querySelectorAll('.date-carousel-container'); - quickDateButtons.forEach(button => { - button.addEventListener('click', function(e) { - e.preventDefault(); + carousels.forEach(container => { + const minInputId = container.getAttribute('data-min-input'); + const maxInputId = container.getAttribute('data-max-input'); - const period = this.getAttribute('data-period'); - const minInputId = this.getAttribute('data-min-input'); - const maxInputId = this.getAttribute('data-max-input'); - - const minInput = document.getElementById(minInputId); - const maxInput = document.getElementById(maxInputId); - - if (!minInput || !maxInput) { - console.error('Date inputs not found:', minInputId, maxInputId); - return; - } - - const dates = getDateRange(period); - - minInput.value = dates.min; - maxInput.value = dates.max; - - // Визуальная обратная связь - this.classList.add('clicked'); - setTimeout(() => this.classList.remove('clicked'), 300); - - console.log(`Set date range: ${dates.min} - ${dates.max}`); - }); + // Инициализация с сегодняшней датой в центре + const carousel = new DateCarousel(container, minInputId, maxInputId); + carousel.init(); }); +}); - /** - * Вычисляет диапазон дат для выбранного периода - * @param {string} period - период (today, tomorrow, week) - * @returns {Object} объект с min и max датами в формате YYYY-MM-DD - */ - function getDateRange(period) { - const today = new Date(); - const tomorrow = new Date(today); - tomorrow.setDate(tomorrow.getDate() + 1); - - let minDate, maxDate; - - switch(period) { - case 'today': - minDate = maxDate = formatDate(today); - break; - case 'tomorrow': - minDate = maxDate = formatDate(tomorrow); - break; - case 'week': - minDate = formatDate(today); - const weekEnd = new Date(today); - weekEnd.setDate(weekEnd.getDate() + 6); - maxDate = formatDate(weekEnd); - break; - case 'month': - minDate = formatDate(today); - const monthEnd = new Date(today); - monthEnd.setMonth(monthEnd.getMonth() + 1); - maxDate = formatDate(monthEnd); - break; - default: - minDate = maxDate = ''; - } - - return { min: minDate, max: maxDate }; +/** + * Класс для управления календарной лентой + */ +class DateCarousel { + constructor(container, minInputId, maxInputId) { + this.container = container; + this.minInputId = minInputId; + this.maxInputId = maxInputId; + this.minInput = document.getElementById(minInputId); + this.maxInput = document.getElementById(maxInputId); + this.centerDate = new Date(); // Центральная дата (по умолчанию сегодня) + this.today = new Date(); + this.today.setHours(0, 0, 0, 0); } /** - * Форматирует дату в формат YYYY-MM-DD для input[type="date"] - * @param {Date} date - объект даты - * @returns {string} дата в формате YYYY-MM-DD + * Инициализация календарной ленты */ - function formatDate(date) { + init() { + this.render(); + this.attachNavHandlers(); + } + + /** + * Генерация и отображение 9 дней + */ + render() { + this.container.innerHTML = ''; + const days = this.generateDays(); + + days.forEach(dayData => { + const btn = this.createDayButton(dayData); + this.container.appendChild(btn); + }); + } + + /** + * Генерация массива из 9 дней (±4 от центральной даты) + */ + generateDays() { + const days = []; + + for (let i = -4; i <= 4; i++) { + const date = new Date(this.centerDate); + date.setDate(date.getDate() + i); + date.setHours(0, 0, 0, 0); + + days.push({ + date: date, + label: this.getDateLabel(date, i), + isToday: date.getTime() === this.today.getTime(), + isCenter: i === 0 + }); + } + + return days; + } + + /** + * Определение текстовой метки для даты + */ + getDateLabel(date, offset) { + if (offset === -1) return 'Вчера'; + if (offset === 0 && date.getTime() === this.today.getTime()) return 'Сегодня'; + if (offset === 1 && date.getTime() === new Date(this.today.getTime() + 86400000).getTime()) return 'Завтра'; + return ''; + } + + /** + * Создание кнопки дня + */ + createDayButton(dayData) { + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'date-btn'; + + if (dayData.isToday) { + btn.classList.add('today'); + } + + // Проверка, выбрана ли эта дата + if (this.isDateSelected(dayData.date)) { + btn.classList.add('selected'); + } + + // Структура кнопки + const label = document.createElement('div'); + label.className = 'date-btn-label'; + label.textContent = dayData.label; + + const day = document.createElement('div'); + day.className = 'date-btn-day'; + day.textContent = String(dayData.date.getDate()).padStart(2, '0'); + + const weekday = document.createElement('div'); + weekday.className = 'date-btn-weekday'; + weekday.textContent = this.getWeekdayShort(dayData.date); + + btn.appendChild(label); + btn.appendChild(day); + btn.appendChild(weekday); + + // Обработчик клика + btn.addEventListener('click', () => this.selectDate(dayData.date, btn)); + + return btn; + } + + /** + * Получить короткое название дня недели (ПН, ВТ, СР, ЧТ, ПТ, СБ, ВС) + */ + getWeekdayShort(date) { + const weekdays = ['ВС', 'ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ']; + return weekdays[date.getDay()]; + } + + /** + * Форматирование даты в YYYY-MM-DD + */ + formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); @@ -91,41 +146,53 @@ document.addEventListener('DOMContentLoaded', function() { } /** - * Валидация диапазона дат (начало <= конец) + * Проверка, выбрана ли дата */ - const dateInputs = document.querySelectorAll('.date-input'); - dateInputs.forEach(input => { - input.addEventListener('change', function() { - const container = this.closest('.date-range-filter'); - if (!container) return; - - const minInput = container.querySelector('.date-input[id$="_after"]'); - const maxInput = container.querySelector('.date-input[id$="_before"]'); - - if (!minInput || !maxInput) return; - - if (minInput.value && maxInput.value) { - const minDate = new Date(minInput.value); - const maxDate = new Date(maxInput.value); - - if (minDate > maxDate) { - alert('Дата начала не может быть позже даты окончания'); - this.value = ''; - } - } - }); - }); + isDateSelected(date) { + const formattedDate = this.formatDate(date); + return this.minInput.value === formattedDate && this.maxInput.value === formattedDate; + } /** - * Сброс дат при клике на кнопку "Сбросить" формы + * Выбор даты (установка в оба поля фильтра) */ - const resetButtons = document.querySelectorAll('a[href*="order-list"]:not([href*="?"])'); - resetButtons.forEach(button => { - button.addEventListener('click', function() { - // Очищаем все date inputs - dateInputs.forEach(input => { - input.value = ''; - }); - }); - }); -}); + selectDate(date, btn) { + const formattedDate = this.formatDate(date); + + this.minInput.value = formattedDate; + this.maxInput.value = formattedDate; + + // Визуальная обратная связь + btn.classList.add('clicked'); + setTimeout(() => btn.classList.remove('clicked'), 300); + + // Обновление визуального состояния + this.render(); + + console.log(`Selected date: ${formattedDate}`); + } + + /** + * Подключение обработчиков для стрелок навигации + */ + attachNavHandlers() { + const prevBtn = this.container.parentElement.querySelector('.carousel-prev'); + const nextBtn = this.container.parentElement.querySelector('.carousel-next'); + + if (prevBtn) { + prevBtn.addEventListener('click', () => this.shiftDays(-1)); + } + + if (nextBtn) { + nextBtn.addEventListener('click', () => this.shiftDays(1)); + } + } + + /** + * Сдвиг диапазона дней на N дней + */ + shiftDays(offset) { + this.centerDate.setDate(this.centerDate.getDate() + offset); + this.render(); + } +} diff --git a/myproject/orders/templates/orders/components/date_range_filter.html b/myproject/orders/templates/orders/components/date_range_filter.html index 599fcef..7e8af70 100644 --- a/myproject/orders/templates/orders/components/date_range_filter.html +++ b/myproject/orders/templates/orders/components/date_range_filter.html @@ -1,5 +1,5 @@ {% comment %} -Переиспользуемый компонент для фильтрации по диапазону дат +Переиспользуемый компонент для фильтрации по диапазону дат с календарной лентой Параметры: - field_after: поле фильтра "от" (например, filter.form.delivery_date_after) @@ -19,39 +19,33 @@ {{ label }} -