Добавлена обработка ValidationError в AJAX API и Bootstrap alert на странице списка заказов

Проблема:
На странице списка заказов (order_list) при изменении статуса 'на лету':
- ValidationError показывался через alert() - страшно
- Сообщение содержало служебные элементы
- Статус всё равно менялся визуально (нет отката)

Решение Backend (views.py):
- В set_order_status добавлена обработка ValidationError ПЕРЕД ValueError
- Извлекается чистое сообщение (e.messages[0] или str(e))
- Возвращается JSON: {success: false, error: 'чистое сообщение'}

Решение Frontend (order_list.html):
- Добавлен контейнер для динамических Bootstrap alert
- Создана функция showAlert() для показа красивых alert-danger
- При ошибке:
  * Показывается Bootstrap alert с иконкой
  * Прокрутка к верху страницы
  * Автоскрытие через 5 секунд
  * Возврат select к предыдущему значению (откат визуально)
- Больше НЕТ страшных alert()

Теперь пользователь видит:
[красный Bootstrap alert вверху страницы]
⚠️ Заказ 134 был отменён, товары проданы в другом заказе.
Невозможно изменить статус. Для новой продажи создайте новый заказ.
[X]

User-friendly на обеих страницах (форма редактирования + список)!
This commit is contained in:
2025-12-12 00:18:09 +03:00
parent 49cfec3088
commit 2d253584ba
2 changed files with 46 additions and 2 deletions

View File

@@ -8,6 +8,9 @@
{% endblock %}
{% block content %}
<!-- Контейнер для динамических alert-сообщений -->
<div id="js-alert-container"></div>
<!-- Фильтры сверху -->
<div class="card mb-4">
<div class="card-header">
@@ -242,6 +245,28 @@
(function() {
const csrfToken = '{{ csrf_token }}';
// Функция для показа Bootstrap alert
function showAlert(message, type = 'danger') {
const container = document.getElementById('js-alert-container');
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.setAttribute('role', 'alert');
alertDiv.innerHTML = `
<i class="bi bi-exclamation-triangle-fill"></i> ${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
container.appendChild(alertDiv);
// Авто-скрытие через 5 секунд
setTimeout(() => {
alertDiv.classList.remove('show');
setTimeout(() => alertDiv.remove(), 150);
}, 5000);
// Прокрутка к верху страницы
window.scrollTo({ top: 0, behavior: 'smooth' });
}
async function updateStatus(orderNumber, statusId) {
const body = new URLSearchParams({ status_id: statusId }).toString();
const resp = await fetch(`/orders/api/${orderNumber}/set-status/`, {
@@ -278,6 +303,7 @@
select.addEventListener('change', async function() {
const statusId = select.value || '';
const selectedOption = select.options[select.selectedIndex];
const originalIndex = select.selectedIndex;
select.disabled = true;
try {
@@ -294,10 +320,20 @@
select.style.display = 'none';
badge.style.display = 'inline-block';
} else {
alert(result.error || 'Не удалось обновить статус');
// Показываем красивый Bootstrap alert
showAlert(result.error || 'Не удалось обновить статус', 'danger');
// Возвращаем предыдущее значение
select.selectedIndex = originalIndex;
// Прячем select, показываем badge
select.style.display = 'none';
badge.style.display = 'inline-block';
}
} catch (e) {
alert('Ошибка сервера при обновлении статуса');
showAlert('Ошибка сервера при обновлении статуса', 'danger');
// Возвращаем предыдущее значение
select.selectedIndex = originalIndex;
select.style.display = 'none';
badge.style.display = 'inline-block';
} finally {
select.disabled = false;
}