Улучшен интерфейс ввода даты и времени доставки
- Исправлены имена полей времени (time_from/time_to вместо delivery_time_start/end) - Поля времени сделаны необязательными (дата остается обязательной) - Добавлен улучшенный UI с быстрыми кнопками для даты и времени - Поля ввода расположены в один ряд, кнопки быстрого выбора ниже - Добавлены CSS и JS файлы для улучшенного интерфейса - Обновлена валидация: время необязательно, но если указано одно - должно быть и другое
This commit is contained in:
161
myproject/orders/static/orders/css/delivery_datetime.css
Normal file
161
myproject/orders/static/orders/css/delivery_datetime.css
Normal file
@@ -0,0 +1,161 @@
|
||||
/* Стили для улучшенного интерфейса даты и времени доставки */
|
||||
|
||||
.delivery-datetime-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Контейнер для всех кнопок быстрого выбора */
|
||||
#delivery-datetime-quick-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Быстрые кнопки для даты */
|
||||
.delivery-date-quick-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.delivery-date-quick-btn {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
border: 1px solid #dee2e6;
|
||||
background-color: #fff;
|
||||
color: #495057;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.delivery-date-quick-btn:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #0d6efd;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.delivery-date-quick-btn.active {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.delivery-date-quick-btn i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
/* Предустановленные интервалы времени */
|
||||
.delivery-time-presets {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Дополнительный отступ для группы времени */
|
||||
.delivery-time-presets {
|
||||
margin-left: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
border-left: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.delivery-time-preset-btn {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
border: 1px solid #dee2e6;
|
||||
background-color: #fff;
|
||||
color: #495057;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.delivery-time-preset-btn:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #0d6efd;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.delivery-time-preset-btn.active {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.delivery-time-preset-btn i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
/* Группа полей времени */
|
||||
.delivery-time-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.delivery-time-group .form-control {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.delivery-time-separator {
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Улучшенные поля ввода */
|
||||
.delivery-datetime-wrapper .form-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.delivery-datetime-wrapper .form-control:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
/* Подсказки под полями */
|
||||
.delivery-datetime-hint {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
margin-top: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.delivery-datetime-hint i {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Адаптивность */
|
||||
@media (max-width: 768px) {
|
||||
.delivery-date-quick-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.delivery-date-quick-btn {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delivery-time-presets {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.delivery-time-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.delivery-time-separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
203
myproject/orders/static/orders/js/delivery_datetime.js
Normal file
203
myproject/orders/static/orders/js/delivery_datetime.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Улучшенный интерфейс для ввода даты и времени доставки
|
||||
* Добавляет быстрые кнопки для даты и предустановленные интервалы времени
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Инициализация улучшенного интерфейса даты и времени
|
||||
* @param {string} dateFieldId - ID поля даты
|
||||
* @param {string} timeFromFieldId - ID поля времени "от"
|
||||
* @param {string} timeToFieldId - ID поля времени "до"
|
||||
*/
|
||||
function initDeliveryDateTime(dateFieldId, timeFromFieldId, timeToFieldId) {
|
||||
const dateField = document.getElementById(dateFieldId);
|
||||
const timeFromField = document.getElementById(timeFromFieldId);
|
||||
const timeToField = document.getElementById(timeToFieldId);
|
||||
|
||||
if (!dateField || !timeFromField || !timeToField) {
|
||||
console.warn('[Delivery DateTime] Поля не найдены');
|
||||
return;
|
||||
}
|
||||
|
||||
// Находим общий контейнер для кнопок
|
||||
const buttonsContainer = document.getElementById('delivery-datetime-quick-buttons');
|
||||
if (!buttonsContainer) {
|
||||
console.warn('[Delivery DateTime] Контейнер для кнопок не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавляем быстрые кнопки для даты
|
||||
addDateQuickButtons(buttonsContainer, dateField);
|
||||
|
||||
// Добавляем предустановленные интервалы времени
|
||||
addTimePresets(buttonsContainer, timeFromField, timeToField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет быстрые кнопки для выбора даты
|
||||
*/
|
||||
function addDateQuickButtons(container, dateField) {
|
||||
const quickButtonsContainer = document.createElement('div');
|
||||
quickButtonsContainer.className = 'delivery-date-quick-buttons';
|
||||
|
||||
const buttons = [
|
||||
{ label: 'Сегодня', days: 0, icon: 'bi-calendar-day' },
|
||||
{ label: 'Завтра', days: 1, icon: 'bi-calendar-check' }
|
||||
];
|
||||
|
||||
buttons.forEach(button => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'delivery-date-quick-btn';
|
||||
btn.innerHTML = `<i class="bi ${button.icon}"></i>${button.label}`;
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + button.days);
|
||||
const dateString = formatDateForInput(date);
|
||||
dateField.value = dateString;
|
||||
|
||||
// Обновляем активное состояние кнопок
|
||||
quickButtonsContainer.querySelectorAll('.delivery-date-quick-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
|
||||
// Триггерим событие change для валидации
|
||||
dateField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
quickButtonsContainer.appendChild(btn);
|
||||
});
|
||||
|
||||
// Проверяем текущую дату и активируем соответствующую кнопку
|
||||
if (dateField.value) {
|
||||
const currentDate = new Date(dateField.value);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
currentDate.setHours(0, 0, 0, 0);
|
||||
|
||||
const diffDays = Math.round((currentDate - today) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
quickButtonsContainer.children[0].classList.add('active');
|
||||
} else if (diffDays === 1) {
|
||||
quickButtonsContainer.children[1].classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Слушаем изменения даты вручную
|
||||
dateField.addEventListener('change', function() {
|
||||
quickButtonsContainer.querySelectorAll('.delivery-date-quick-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
container.appendChild(quickButtonsContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет предустановленные интервалы времени
|
||||
*/
|
||||
function addTimePresets(container, timeFromField, timeToField) {
|
||||
// Создаем контейнер для пресетов
|
||||
const presetsContainer = document.createElement('div');
|
||||
presetsContainer.className = 'delivery-time-presets';
|
||||
|
||||
const presets = [
|
||||
{ label: 'Утро', from: '09:00', to: '12:00', icon: 'bi-sunrise' },
|
||||
{ label: 'День', from: '12:00', to: '15:00', icon: 'bi-sun' },
|
||||
{ label: 'Вечер', from: '15:00', to: '18:00', icon: 'bi-sunset' },
|
||||
{ label: 'Поздно', from: '18:00', to: '21:00', icon: 'bi-moon' }
|
||||
];
|
||||
|
||||
presets.forEach(preset => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'delivery-time-preset-btn';
|
||||
btn.innerHTML = `<i class="bi ${preset.icon}"></i>${preset.label}`;
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
timeFromField.value = preset.from;
|
||||
timeToField.value = preset.to;
|
||||
|
||||
// Обновляем активное состояние кнопок
|
||||
presetsContainer.querySelectorAll('.delivery-time-preset-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
|
||||
// Триггерим события change для валидации
|
||||
timeFromField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
timeToField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
presetsContainer.appendChild(btn);
|
||||
});
|
||||
|
||||
// Проверяем текущие значения и активируем соответствующий пресет
|
||||
if (timeFromField.value && timeToField.value) {
|
||||
presets.forEach((preset, index) => {
|
||||
if (timeFromField.value === preset.from && timeToField.value === preset.to) {
|
||||
presetsContainer.children[index].classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Слушаем изменения времени вручную
|
||||
const clearPresets = function() {
|
||||
presetsContainer.querySelectorAll('.delivery-time-preset-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
};
|
||||
timeFromField.addEventListener('change', clearPresets);
|
||||
timeToField.addEventListener('change', clearPresets);
|
||||
|
||||
// Вставляем пресеты в контейнер
|
||||
container.appendChild(presetsContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирует дату для input type="date" (YYYY-MM-DD)
|
||||
*/
|
||||
function formatDateForInput(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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматическая инициализация при загрузке DOM
|
||||
*/
|
||||
function autoInit() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Ищем поля по стандартным ID Django форм
|
||||
const dateField = document.getElementById('id_delivery_date');
|
||||
const timeFromField = document.getElementById('id_time_from');
|
||||
const timeToField = document.getElementById('id_time_to');
|
||||
|
||||
if (dateField && timeFromField && timeToField) {
|
||||
initDeliveryDateTime(
|
||||
'id_delivery_date',
|
||||
'id_time_from',
|
||||
'id_time_to'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Экспортируем функции для глобального использования
|
||||
window.DeliveryDateTime = {
|
||||
init: initDeliveryDateTime,
|
||||
autoInit: autoInit
|
||||
};
|
||||
|
||||
// Автоматическая инициализация
|
||||
autoInit();
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user