feat: Добавлена фильтрация заказов с django-filter и календарный компонент

Основные изменения:
- Установлен и настроен django-filter==24.3
- Создан OrderFilter с фильтрами по дате доставки, статусу, типу, оплате и поиску
- Реализован переиспользуемый компонент календарного фильтра date_range_filter.html
- Добавлены быстрые кнопки выбора дат (Сегодня, Завтра, Неделя)
- Создан templatetag param_replace для сохранения фильтров при пагинации
- Обновлен order_list view для использования django-filter
- Полностью переработан шаблон order_list.html с интеграцией фильтров
- Добавлены стили (date_filter.css) и логика (date_filter.js) для календаря

Структура новых файлов:
- orders/filters.py - FilterSet для заказов
- orders/templatetags/filter_tags.py - кастомные теги для фильтров
- orders/templates/orders/components/date_range_filter.html - компонент календаря
- orders/static/orders/css/date_filter.css - стили
- orders/static/orders/js/date_filter.js - JavaScript логика
- requirements.txt - зависимости проекта

Преимущества:
- Чистая архитектура фильтрации
- Автоматическое сохранение параметров при навигации
- Переиспользуемый календарный компонент
- Улучшенный UX с быстрыми фильтрами
- Готовность к масштабированию на другие модели

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-07 18:22:57 +03:00
parent ec0557c8cf
commit d37a5df482
11 changed files with 576 additions and 59 deletions

View File

@@ -0,0 +1,112 @@
/**
* Стили для календарного фильтра по датам
* Используется в компоненте date_range_filter.html
*/
.date-range-filter {
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.date-range-filter .form-label {
font-weight: 500;
color: #495057;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.date-range-filter .form-label i {
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 {
display: flex;
gap: 0.25rem;
}
.quick-date-btn {
font-size: 0.75rem;
padding: 0.375rem 0.5rem;
border-radius: 4px !important;
transition: all 0.2s ease;
flex: 1;
white-space: nowrap;
}
.quick-date-btn:hover {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.quick-date-btn:active {
transform: translateY(0);
box-shadow: none;
}
.quick-date-btn.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}
/* Адаптивность для мобильных устройств */
@media (max-width: 576px) {
.date-range-filter {
padding: 0.75rem;
}
.quick-filters .btn-group {
flex-wrap: wrap;
}
.quick-date-btn {
flex: 1 1 calc(33.333% - 0.25rem);
min-width: 0;
font-size: 0.7rem;
padding: 0.35rem 0.4rem;
}
}
/* Анимация для визуальной обратной связи */
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.quick-date-btn.clicked {
animation: pulse 0.3s ease;
}

View File

@@ -0,0 +1,131 @@
/**
* Календарный фильтр для выбора диапазона дат
* Поддерживает быстрые фильтры (сегодня, завтра, неделя)
*
* Использование:
* Подключить этот файл в шаблоне после компонента date_range_filter.html
*/
document.addEventListener('DOMContentLoaded', function() {
console.log('Date filter initialized');
const quickDateButtons = document.querySelectorAll('.quick-date-btn');
quickDateButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
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}`);
});
});
/**
* Вычисляет диапазон дат для выбранного периода
* @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 };
}
/**
* Форматирует дату в формат YYYY-MM-DD для input[type="date"]
* @param {Date} date - объект даты
* @returns {string} дата в формате YYYY-MM-DD
*/
function formatDate(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}`;
}
/**
* Валидация диапазона дат (начало <= конец)
*/
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 = '';
}
}
});
});
/**
* Сброс дат при клике на кнопку "Сбросить" формы
*/
const resetButtons = document.querySelectorAll('a[href*="order-list"]:not([href*="?"])');
resetButtons.forEach(button => {
button.addEventListener('click', function() {
// Очищаем все date inputs
dateInputs.forEach(input => {
input.value = '';
});
});
});
});