Улучшения UX формы заказа и создания клиента
- Изменен порядок секций формы: товары перемещены выше доставки - Добавлена защита от двойного создания клиента - Улучшена валидация при создании клиента с детализацией ошибок - Добавлен индикатор загрузки при сохранении клиента - Исправлена логика обработки специальной опции создания клиента 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -338,7 +338,7 @@ def api_search_customers(request):
|
|||||||
# Если ничего не найдено, предлагаем создать нового клиента
|
# Если ничего не найдено, предлагаем создать нового клиента
|
||||||
if not results:
|
if not results:
|
||||||
results.append({
|
results.append({
|
||||||
'id': None,
|
'id': '__create_new__', # Специальный ID для опции создания
|
||||||
'text': f'Создать клиента: "{query}"',
|
'text': f'Создать клиента: "{query}"',
|
||||||
'is_create_option': True,
|
'is_create_option': True,
|
||||||
'search_text': query,
|
'search_text': query,
|
||||||
@@ -390,6 +390,7 @@ def api_create_customer(request):
|
|||||||
'name': name,
|
'name': name,
|
||||||
'phone': phone if phone else None,
|
'phone': phone if phone else None,
|
||||||
'email': email if email else None,
|
'email': email if email else None,
|
||||||
|
'loyalty_tier': 'no_discount', # Значение по умолчанию для новых клиентов
|
||||||
}
|
}
|
||||||
|
|
||||||
# Используем форму для валидации и создания
|
# Используем форму для валидации и создания
|
||||||
@@ -409,18 +410,26 @@ def api_create_customer(request):
|
|||||||
'email': customer.email if customer.email else '',
|
'email': customer.email if customer.email else '',
|
||||||
}, status=201)
|
}, status=201)
|
||||||
else:
|
else:
|
||||||
# Собираем ошибки валидации
|
# Собираем ошибки валидации с указанием полей
|
||||||
errors = []
|
errors = []
|
||||||
for field, field_errors in form.errors.items():
|
field_labels = {
|
||||||
for error in field_errors:
|
'name': 'Имя клиента',
|
||||||
errors.append(error)
|
'phone': 'Телефон',
|
||||||
|
'email': 'Email',
|
||||||
|
}
|
||||||
|
|
||||||
# Возвращаем первую ошибку
|
for field, field_errors in form.errors.items():
|
||||||
error_message = errors[0] if errors else 'Ошибка валидации данных'
|
field_label = field_labels.get(field, field)
|
||||||
|
for error in field_errors:
|
||||||
|
errors.append(f'{field_label}: {error}')
|
||||||
|
|
||||||
|
# Возвращаем все ошибки
|
||||||
|
error_message = '<br>'.join(errors) if errors else 'Ошибка валидации данных'
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': error_message
|
'error': error_message,
|
||||||
|
'errors': form.errors # Добавляем детальную информацию об ошибках
|
||||||
}, status=400)
|
}, status=400)
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
|
|||||||
@@ -181,6 +181,137 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Товары в заказе -->
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Товары в заказе</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{{ formset.management_form }}
|
||||||
|
<div id="order-items-container">
|
||||||
|
{% for item_form in formset %}
|
||||||
|
<div class="order-item-form border rounded p-3 mb-3" data-form-index="{{ forloop.counter0 }}">
|
||||||
|
{{ item_form.id }}
|
||||||
|
{{ item_form.product }} <!-- Hidden field -->
|
||||||
|
{{ item_form.product_kit }} <!-- Hidden field -->
|
||||||
|
{{ item_form.is_custom_price }} <!-- Hidden field -->
|
||||||
|
|
||||||
|
<div class="row align-items-end">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Товар или комплект</label>
|
||||||
|
<select class="form-select select2-order-item" data-form-index="{{ forloop.counter0 }}">
|
||||||
|
<option value=""></option>
|
||||||
|
{% if item_form.instance.product %}
|
||||||
|
<option value="product_{{ item_form.instance.product.id }}" selected data-type="product" data-price="{{ item_form.instance.product.actual_price }}">
|
||||||
|
{{ item_form.instance.product.name }}{% if item_form.instance.product.sku %} ({{ item_form.instance.product.sku }}){% endif %}
|
||||||
|
</option>
|
||||||
|
{% elif item_form.instance.product_kit %}
|
||||||
|
<option value="kit_{{ item_form.instance.product_kit.id }}" selected data-type="kit" data-price="{{ item_form.instance.product_kit.actual_price }}">
|
||||||
|
{{ item_form.instance.product_kit.name }}{% if item_form.instance.product_kit.sku %} ({{ item_form.instance.product_kit.sku }}){% endif %}
|
||||||
|
</option>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Количество</label>
|
||||||
|
{{ item_form.quantity }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Цена</label>
|
||||||
|
<div class="position-relative">
|
||||||
|
{{ item_form.price }}
|
||||||
|
<span class="custom-price-badge badge bg-warning position-absolute top-0 end-0 mt-1 me-1" style="display: none;">
|
||||||
|
Изменена
|
||||||
|
</span>
|
||||||
|
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
||||||
|
Оригинальная: <span class="original-price-value"></span> руб.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-end">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label d-block"> </label>
|
||||||
|
{% if formset.can_delete %}
|
||||||
|
{{ item_form.DELETE }}
|
||||||
|
{% endif %}
|
||||||
|
<button type="button" class="btn btn-danger btn-sm remove-item-btn">
|
||||||
|
<i class="bi bi-trash"></i> Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if item_form.errors %}
|
||||||
|
<div class="alert alert-danger mt-2">{{ item_form.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Скрытый шаблон для новых форм -->
|
||||||
|
<template id="empty-form-template">
|
||||||
|
<div class="order-item-form border rounded p-3 mb-3" data-form-index="__prefix__">
|
||||||
|
<input type="hidden" name="items-__prefix__-id" id="id_items-__prefix__-id">
|
||||||
|
<input type="hidden" name="items-__prefix__-product" id="id_items-__prefix__-product">
|
||||||
|
<input type="hidden" name="items-__prefix__-product_kit" id="id_items-__prefix__-product_kit">
|
||||||
|
<input type="hidden" name="items-__prefix__-is_custom_price" id="id_items-__prefix__-is_custom_price" value="false">
|
||||||
|
|
||||||
|
<div class="row align-items-end">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Товар или комплект</label>
|
||||||
|
<select class="form-select select2-order-item" data-form-index="__prefix__" id="id_items-__prefix__-select">
|
||||||
|
<option value=""></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Количество</label>
|
||||||
|
<input type="number" name="items-__prefix__-quantity" step="1" min="1" value="1" class="form-control" id="id_items-__prefix__-quantity">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Цена</label>
|
||||||
|
<div class="position-relative">
|
||||||
|
<input type="number" name="items-__prefix__-price" step="0.01" min="0" class="form-control" id="id_items-__prefix__-price">
|
||||||
|
<span class="custom-price-badge badge bg-warning position-absolute top-0 end-0 mt-1 me-1" style="display: none;">
|
||||||
|
Изменена
|
||||||
|
</span>
|
||||||
|
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
||||||
|
Оригинальная: <span class="original-price-value"></span> руб.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-end">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label d-block"> </label>
|
||||||
|
<input type="checkbox" name="items-__prefix__-DELETE" id="id_items-__prefix__-DELETE">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm remove-item-btn">
|
||||||
|
<i class="bi bi-trash"></i> Удалить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-success" id="add-item-btn">
|
||||||
|
<i class="bi bi-plus-circle"></i> Добавить товар
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary ms-2" id="create-temp-kit-btn" data-bs-toggle="modal" data-bs-target="#tempKitModal">
|
||||||
|
<i class="bi bi-flower1"></i> Создать и добавить комплект
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Доставка -->
|
<!-- Доставка -->
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -324,28 +455,6 @@
|
|||||||
<div class="border-top pt-3 mt-3">
|
<div class="border-top pt-3 mt-3">
|
||||||
<h6 class="mb-3">Получатель</h6>
|
<h6 class="mb-3">Получатель</h6>
|
||||||
|
|
||||||
<!-- Поля получателя из модели Address -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="id_address_recipient_name" class="form-label">
|
|
||||||
Имя получателя
|
|
||||||
</label>
|
|
||||||
<input type="text" name="address_recipient_name" id="id_address_recipient_name"
|
|
||||||
class="form-control" placeholder="Имя получателя">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="id_address_recipient_phone" class="form-label">
|
|
||||||
Телефон получателя
|
|
||||||
</label>
|
|
||||||
<input type="text" name="address_recipient_phone" id="id_address_recipient_phone"
|
|
||||||
class="form-control" placeholder="Телефон получателя">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Крупный переключатель "Покупатель = получатель" -->
|
<!-- Крупный переключатель "Покупатель = получатель" -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-check form-switch" style="padding-left: 3.5em;">
|
<div class="form-check form-switch" style="padding-left: 3.5em;">
|
||||||
@@ -425,136 +534,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Товары в заказе -->
|
|
||||||
<div class="card mb-3">
|
|
||||||
<div class="card-header">
|
|
||||||
<h5 class="mb-0">Товары в заказе</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{{ formset.management_form }}
|
|
||||||
<div id="order-items-container">
|
|
||||||
{% for item_form in formset %}
|
|
||||||
<div class="order-item-form border rounded p-3 mb-3" data-form-index="{{ forloop.counter0 }}">
|
|
||||||
{{ item_form.id }}
|
|
||||||
{{ item_form.product }} <!-- Hidden field -->
|
|
||||||
{{ item_form.product_kit }} <!-- Hidden field -->
|
|
||||||
{{ item_form.is_custom_price }} <!-- Hidden field -->
|
|
||||||
|
|
||||||
<div class="row align-items-end">
|
|
||||||
<div class="col-md-5">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">Товар или комплект</label>
|
|
||||||
<select class="form-select select2-order-item" data-form-index="{{ forloop.counter0 }}">
|
|
||||||
<option value=""></option>
|
|
||||||
{% if item_form.instance.product %}
|
|
||||||
<option value="product_{{ item_form.instance.product.id }}" selected data-type="product" data-price="{{ item_form.instance.product.actual_price }}">
|
|
||||||
{{ item_form.instance.product.name }}{% if item_form.instance.product.sku %} ({{ item_form.instance.product.sku }}){% endif %}
|
|
||||||
</option>
|
|
||||||
{% elif item_form.instance.product_kit %}
|
|
||||||
<option value="kit_{{ item_form.instance.product_kit.id }}" selected data-type="kit" data-price="{{ item_form.instance.product_kit.actual_price }}">
|
|
||||||
{{ item_form.instance.product_kit.name }}{% if item_form.instance.product_kit.sku %} ({{ item_form.instance.product_kit.sku }}){% endif %}
|
|
||||||
</option>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">Количество</label>
|
|
||||||
{{ item_form.quantity }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">Цена</label>
|
|
||||||
<div class="position-relative">
|
|
||||||
{{ item_form.price }}
|
|
||||||
<span class="custom-price-badge badge bg-warning position-absolute top-0 end-0 mt-1 me-1" style="display: none;">
|
|
||||||
Изменена
|
|
||||||
</span>
|
|
||||||
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
|
||||||
Оригинальная: <span class="original-price-value"></span> руб.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2 text-end">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label d-block"> </label>
|
|
||||||
{% if formset.can_delete %}
|
|
||||||
{{ item_form.DELETE }}
|
|
||||||
{% endif %}
|
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-item-btn">
|
|
||||||
<i class="bi bi-trash"></i> Удалить
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if item_form.errors %}
|
|
||||||
<div class="alert alert-danger mt-2">{{ item_form.errors }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Скрытый шаблон для новых форм -->
|
|
||||||
<template id="empty-form-template">
|
|
||||||
<div class="order-item-form border rounded p-3 mb-3" data-form-index="__prefix__">
|
|
||||||
<input type="hidden" name="items-__prefix__-id" id="id_items-__prefix__-id">
|
|
||||||
<input type="hidden" name="items-__prefix__-product" id="id_items-__prefix__-product">
|
|
||||||
<input type="hidden" name="items-__prefix__-product_kit" id="id_items-__prefix__-product_kit">
|
|
||||||
<input type="hidden" name="items-__prefix__-is_custom_price" id="id_items-__prefix__-is_custom_price" value="false">
|
|
||||||
|
|
||||||
<div class="row align-items-end">
|
|
||||||
<div class="col-md-5">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">Товар или комплект</label>
|
|
||||||
<select class="form-select select2-order-item" data-form-index="__prefix__" id="id_items-__prefix__-select">
|
|
||||||
<option value=""></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">Количество</label>
|
|
||||||
<input type="number" name="items-__prefix__-quantity" step="1" min="1" value="1" class="form-control" id="id_items-__prefix__-quantity">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label">Цена</label>
|
|
||||||
<div class="position-relative">
|
|
||||||
<input type="number" name="items-__prefix__-price" step="0.01" min="0" class="form-control" id="id_items-__prefix__-price">
|
|
||||||
<span class="custom-price-badge badge bg-warning position-absolute top-0 end-0 mt-1 me-1" style="display: none;">
|
|
||||||
Изменена
|
|
||||||
</span>
|
|
||||||
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
|
||||||
Оригинальная: <span class="original-price-value"></span> руб.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2 text-end">
|
|
||||||
<div class="mb-2">
|
|
||||||
<label class="form-label d-block"> </label>
|
|
||||||
<input type="checkbox" name="items-__prefix__-DELETE" id="id_items-__prefix__-DELETE">
|
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-item-btn">
|
|
||||||
<i class="bi bi-trash"></i> Удалить
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" id="add-item-btn">
|
|
||||||
<i class="bi bi-plus-circle"></i> Добавить товар
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-primary ms-2" id="create-temp-kit-btn" data-bs-toggle="modal" data-bs-target="#tempKitModal">
|
|
||||||
<i class="bi bi-flower1"></i> Создать и добавить комплект
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Оплата и дополнительно -->
|
<!-- Оплата и дополнительно -->
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
@@ -672,19 +651,7 @@ function initCustomerSelect2() {
|
|||||||
},
|
},
|
||||||
templateResult: formatCustomerOption,
|
templateResult: formatCustomerOption,
|
||||||
templateSelection: formatCustomerSelection,
|
templateSelection: formatCustomerSelection,
|
||||||
escapeMarkup: function(markup) { return markup; },
|
escapeMarkup: function(markup) { return markup; }
|
||||||
// Очень важно: указываем как отображать уже выбранное значение
|
|
||||||
matcher: function(params, data) {
|
|
||||||
// Если нет поискового термина, показываем все результаты
|
|
||||||
if ($.trim(params.term) === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
// Иначе ищем совпадение
|
|
||||||
if (data.text.toUpperCase().indexOf(params.term.toUpperCase()) > -1) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('6. Select2 инициализирован');
|
console.log('6. Select2 инициализирован');
|
||||||
@@ -726,10 +693,12 @@ function initCustomerSelect2() {
|
|||||||
const data = e.params.data;
|
const data = e.params.data;
|
||||||
console.log('9b. Попытка выбрать элемент (перед выбором):', data);
|
console.log('9b. Попытка выбрать элемент (перед выбором):', data);
|
||||||
|
|
||||||
if (data.is_create_option) {
|
if (data.is_create_option || data.id === '__create_new__') {
|
||||||
console.log('9c. Это опция создания клиента - предотвращаем выбор и открываем модаль');
|
console.log('9c. Это опция создания клиента - предотвращаем выбор и открываем модаль');
|
||||||
// Предотвращаем выбор этой опции
|
// Предотвращаем выбор этой опции
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// Закрываем dropdown
|
||||||
|
$customerSelect.select2('close');
|
||||||
// Очищаем значение
|
// Очищаем значение
|
||||||
$customerSelect.val(null).trigger('change.select2');
|
$customerSelect.val(null).trigger('change.select2');
|
||||||
// Открываем модаль
|
// Открываем модаль
|
||||||
@@ -752,7 +721,7 @@ function initCustomerSelect2() {
|
|||||||
const data = e.params.data;
|
const data = e.params.data;
|
||||||
console.log('10b. Выбран элемент:', data);
|
console.log('10b. Выбран элемент:', data);
|
||||||
|
|
||||||
if (data.is_create_option) {
|
if (data.is_create_option || data.id === '__create_new__') {
|
||||||
console.log('11. Открываем модальное окно для создания клиента');
|
console.log('11. Открываем модальное окно для создания клиента');
|
||||||
this.value = '';
|
this.value = '';
|
||||||
// Триггерим нативное change событие для draft-creator.js
|
// Триггерим нативное change событие для draft-creator.js
|
||||||
@@ -769,12 +738,13 @@ function initCustomerSelect2() {
|
|||||||
|
|
||||||
// Форматирование опции в списке
|
// Форматирование опции в списке
|
||||||
function formatCustomerOption(option) {
|
function formatCustomerOption(option) {
|
||||||
if (!option.id) {
|
// ВАЖНО: Проверяем is_create_option или специальный ID ПЕРЕД проверкой !option.id
|
||||||
return option.text;
|
if (option.is_create_option || option.id === '__create_new__') {
|
||||||
|
return '<div class="customer-create-option"><i class="bi bi-plus-circle"></i> ' + option.text + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.is_create_option) {
|
if (!option.id) {
|
||||||
return '<div class="customer-create-option"><i class="bi bi-plus-circle"></i> ' + option.text + '</div>';
|
return option.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
let html = '<div class="customer-option">';
|
let html = '<div class="customer-option">';
|
||||||
@@ -791,10 +761,10 @@ function initCustomerSelect2() {
|
|||||||
|
|
||||||
// Форматирование выбранного значения
|
// Форматирование выбранного значения
|
||||||
function formatCustomerSelection(option) {
|
function formatCustomerSelection(option) {
|
||||||
if (!option.id) {
|
if (option.is_create_option || option.id === '__create_new__') {
|
||||||
return option.text;
|
return option.text;
|
||||||
}
|
}
|
||||||
if (option.is_create_option) {
|
if (!option.id) {
|
||||||
return option.text;
|
return option.text;
|
||||||
}
|
}
|
||||||
// Возвращаем name если есть (из AJAX), иначе text (из DOM опции)
|
// Возвращаем name если есть (из AJAX), иначе text (из DOM опции)
|
||||||
@@ -1444,6 +1414,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<!-- Обработчик сохранения нового клиента (ДОЛЖЕН быть ПОСЛЕ модального окна в HTML) -->
|
<!-- Обработчик сохранения нового клиента (ДОЛЖЕН быть ПОСЛЕ модального окна в HTML) -->
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('save-customer-btn').addEventListener('click', function() {
|
document.getElementById('save-customer-btn').addEventListener('click', function() {
|
||||||
|
const saveBtn = this;
|
||||||
|
|
||||||
|
// Защита от двойного клика
|
||||||
|
if (saveBtn.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const name = document.getElementById('customer-name').value.trim();
|
const name = document.getElementById('customer-name').value.trim();
|
||||||
const phone = document.getElementById('customer-phone').value.trim();
|
const phone = document.getElementById('customer-phone').value.trim();
|
||||||
const email = document.getElementById('customer-email').value.trim();
|
const email = document.getElementById('customer-email').value.trim();
|
||||||
@@ -1461,6 +1438,11 @@ document.getElementById('save-customer-btn').addEventListener('click', function(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Блокируем кнопку и меняем текст
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
const originalHTML = saveBtn.innerHTML;
|
||||||
|
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Создание...';
|
||||||
|
|
||||||
// Отправляем AJAX запрос
|
// Отправляем AJAX запрос
|
||||||
fetch('{% url "customers:api-create-customer" %}', {
|
fetch('{% url "customers:api-create-customer" %}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1528,6 +1510,10 @@ document.getElementById('save-customer-btn').addEventListener('click', function(
|
|||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
|
// Разблокируем кнопку при ошибке
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = originalHTML;
|
||||||
|
|
||||||
const errorDiv = document.getElementById('customer-form-errors');
|
const errorDiv = document.getElementById('customer-form-errors');
|
||||||
errorDiv.innerHTML = '<div class="alert alert-danger mb-0">' + data.error + '</div>';
|
errorDiv.innerHTML = '<div class="alert alert-danger mb-0">' + data.error + '</div>';
|
||||||
errorDiv.style.display = 'block';
|
errorDiv.style.display = 'block';
|
||||||
@@ -1535,6 +1521,11 @@ document.getElementById('save-customer-btn').addEventListener('click', function(
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
|
|
||||||
|
// Разблокируем кнопку при ошибке
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = originalHTML;
|
||||||
|
|
||||||
const errorDiv = document.getElementById('customer-form-errors');
|
const errorDiv = document.getElementById('customer-form-errors');
|
||||||
errorDiv.innerHTML = '<div class="alert alert-danger mb-0">Ошибка при создании клиента: ' + error.message + '</div>';
|
errorDiv.innerHTML = '<div class="alert alert-danger mb-0">Ошибка при создании клиента: ' + error.message + '</div>';
|
||||||
errorDiv.style.display = 'block';
|
errorDiv.style.display = 'block';
|
||||||
|
|||||||
Reference in New Issue
Block a user