Улучшен интерфейс ввода даты и времени доставки

- Исправлены имена полей времени (time_from/time_to вместо delivery_time_start/end)
- Поля времени сделаны необязательными (дата остается обязательной)
- Добавлен улучшенный UI с быстрыми кнопками для даты и времени
- Поля ввода расположены в один ряд, кнопки быстрого выбора ниже
- Добавлены CSS и JS файлы для улучшенного интерфейса
- Обновлена валидация: время необязательно, но если указано одно - должно быть и другое
This commit is contained in:
2025-12-24 18:25:20 +03:00
parent d62caa924b
commit 61ce3f550d
7 changed files with 459 additions and 46 deletions

View 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;
}
}

View 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();
})();