refactor(products): extract Order Item Select2 to reusable module
Moved order item selection logic from order_form.html to select2-product-search.js for better code reusability and maintainability. Changes: - Extended select2-product-search.js with initOrderItemSelect2() function (~87 lines) - Wraps initProductSelect2 for order item context - Handles product/kit selection and form field updates - Manages custom price indicators - Supports data-ajax-url attribute for URL configuration - Updated order_form.html: - Added data-ajax-url to order item select elements - Removed inline initOrderItemSelect2 function (~73 lines) - Updated dependency check to use initOrderItemSelect2 Benefits: - No code duplication - reuses existing initProductSelect2 - Cleaner template (79 lines removed) - Consistent with existing patterns - Easy to maintain in one place 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -244,7 +244,9 @@
|
|||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Товар или комплект</label>
|
<label class="form-label">Товар или комплект</label>
|
||||||
<select class="form-select select2-order-item" data-form-index="{{ forloop.counter0 }}">
|
<select class="form-select select2-order-item"
|
||||||
|
data-form-index="{{ forloop.counter0 }}"
|
||||||
|
data-ajax-url="{% url 'products:api-search-products-variants' %}">
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
{% if item_form.instance.product %}
|
{% 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 }}">
|
<option value="product_{{ item_form.instance.product.id }}" selected data-type="product" data-price="{{ item_form.instance.product.actual_price }}">
|
||||||
@@ -329,7 +331,10 @@
|
|||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Товар или комплект</label>
|
<label class="form-label">Товар или комплект</label>
|
||||||
<select class="form-select select2-order-item" data-form-index="__prefix__" id="id_items-__prefix__-select">
|
<select class="form-select select2-order-item"
|
||||||
|
data-form-index="__prefix__"
|
||||||
|
data-ajax-url="{% url 'products:api-search-products-variants' %}"
|
||||||
|
id="id_items-__prefix__-select">
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -1167,80 +1172,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
|
||||||
// Глобально определяем initOrderItemSelect2
|
|
||||||
window.initOrderItemSelect2 = function(element) {
|
|
||||||
const $element = $(element);
|
|
||||||
const formIndex = element.dataset.formIndex;
|
|
||||||
|
|
||||||
// Инициализируем Select2 с AJAX поиском
|
|
||||||
window.initProductSelect2(
|
|
||||||
element,
|
|
||||||
'all', // Искать и товары, и комплекты
|
|
||||||
'{% url "products:api-search-products-variants" %}'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Обработка выбора элемента
|
|
||||||
$element.on('select2:select', function(e) {
|
|
||||||
// Проверяем наличие params (может не быть при программном вызове)
|
|
||||||
if (!e.params || !e.params.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = e.params.data;
|
|
||||||
const idParts = data.id.split('_');
|
|
||||||
const type = idParts[0]; // 'product' или 'kit'
|
|
||||||
const id = idParts[1];
|
|
||||||
|
|
||||||
// Найти скрытые поля product и product_kit
|
|
||||||
const form = element.closest('.order-item-form');
|
|
||||||
const productField = form.querySelector('[name$="-product"]');
|
|
||||||
const kitField = form.querySelector('[name$="-product_kit"]');
|
|
||||||
const priceField = form.querySelector('[name$="-price"]');
|
|
||||||
const isCustomPriceField = form.querySelector('[name$="-is_custom_price"]');
|
|
||||||
|
|
||||||
const originalPrice = data.actual_price || data.price || '';
|
|
||||||
|
|
||||||
// Установить значение в правильное поле
|
|
||||||
if (type === 'product') {
|
|
||||||
productField.value = id;
|
|
||||||
kitField.value = '';
|
|
||||||
priceField.value = originalPrice;
|
|
||||||
} else if (type === 'kit') {
|
|
||||||
kitField.value = id;
|
|
||||||
productField.value = '';
|
|
||||||
priceField.value = originalPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сохраняем оригинальную цену в data-атрибуте
|
|
||||||
priceField.dataset.originalPrice = originalPrice;
|
|
||||||
|
|
||||||
// Сбрасываем флаг кастомной цены
|
|
||||||
isCustomPriceField.value = 'false';
|
|
||||||
|
|
||||||
// Скрываем индикатор
|
|
||||||
const badge = form.querySelector('.custom-price-badge');
|
|
||||||
const priceInfo = form.querySelector('.original-price-info');
|
|
||||||
if (badge) badge.style.display = 'none';
|
|
||||||
if (priceInfo) priceInfo.style.display = 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Очистка при удалении выбора
|
|
||||||
$element.on('select2:clear', function() {
|
|
||||||
console.log('[Select2 Clear] Очистка товара в форме');
|
|
||||||
const form = element.closest('.order-item-form');
|
|
||||||
const formIndex = Array.from(document.querySelectorAll('.order-item-form')).indexOf(form);
|
|
||||||
console.log(`[Select2 Clear] Индекс формы: ${formIndex}`);
|
|
||||||
|
|
||||||
form.querySelector('[name$="-product"]').value = '';
|
|
||||||
form.querySelector('[name$="-product_kit"]').value = '';
|
|
||||||
form.querySelector('[name$="-price"]').value = '';
|
|
||||||
form.querySelector('[name$="-quantity"]').value = '';
|
|
||||||
|
|
||||||
console.log('[Select2 Clear] Все поля очищены, форма осталась в DOM');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Customer Select2 Widget -->
|
<!-- Customer Select2 Widget -->
|
||||||
<script src="{% static 'orders/js/customer_select2.js' %}" defer></script>
|
<script src="{% static 'orders/js/customer_select2.js' %}" defer></script>
|
||||||
@@ -1923,8 +1854,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window.initProductSelect2 !== 'function') {
|
if (typeof window.initOrderItemSelect2 !== 'function') {
|
||||||
console.log('[Order Items] Ожидание загрузки initProductSelect2 из select2-product-search.js...');
|
console.log('[Order Items] Ожидание загрузки initOrderItemSelect2 из select2-product-search.js...');
|
||||||
setTimeout(initExistingOrderItems, 100);
|
setTimeout(initExistingOrderItems, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,4 +145,91 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализирует Select2 для позиции заказа (товар или комплект)
|
||||||
|
* Обрабатывает выбор товара/комплекта и обновляет скрытые поля формы
|
||||||
|
*
|
||||||
|
* @param {Element|jQuery} element - DOM элемент select
|
||||||
|
* @param {string} apiUrl - URL API для поиска (по умолчанию из data-ajax-url)
|
||||||
|
*/
|
||||||
|
window.initOrderItemSelect2 = function(element, apiUrl) {
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
var $element = $(element);
|
||||||
|
|
||||||
|
// Получаем URL из параметра или data-атрибута
|
||||||
|
var searchUrl = apiUrl || $element.data('ajax-url');
|
||||||
|
if (!searchUrl) {
|
||||||
|
console.error('[initOrderItemSelect2] API URL not provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализируем базовый Select2 с AJAX поиском (товары + комплекты)
|
||||||
|
window.initProductSelect2(element, 'all', searchUrl);
|
||||||
|
|
||||||
|
// Обработка выбора товара/комплекта
|
||||||
|
$element.on('select2:select', function(e) {
|
||||||
|
if (!e.params || !e.params.data) return;
|
||||||
|
|
||||||
|
var data = e.params.data;
|
||||||
|
var idParts = data.id.split('_');
|
||||||
|
var type = idParts[0]; // 'product' или 'kit'
|
||||||
|
var id = idParts[1];
|
||||||
|
|
||||||
|
// Найти форму и скрытые поля
|
||||||
|
var form = element.closest('.order-item-form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
var productField = form.querySelector('[name$="-product"]');
|
||||||
|
var kitField = form.querySelector('[name$="-product_kit"]');
|
||||||
|
var priceField = form.querySelector('[name$="-price"]');
|
||||||
|
var isCustomPriceField = form.querySelector('[name$="-is_custom_price"]');
|
||||||
|
|
||||||
|
var originalPrice = data.actual_price || data.price || '';
|
||||||
|
|
||||||
|
// Установить значение в правильное поле
|
||||||
|
if (type === 'product') {
|
||||||
|
if (productField) productField.value = id;
|
||||||
|
if (kitField) kitField.value = '';
|
||||||
|
if (priceField) priceField.value = originalPrice;
|
||||||
|
} else if (type === 'kit') {
|
||||||
|
if (kitField) kitField.value = id;
|
||||||
|
if (productField) productField.value = '';
|
||||||
|
if (priceField) priceField.value = originalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем оригинальную цену в data-атрибуте
|
||||||
|
if (priceField) {
|
||||||
|
priceField.dataset.originalPrice = originalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сбрасываем флаг кастомной цены
|
||||||
|
if (isCustomPriceField) {
|
||||||
|
isCustomPriceField.value = 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Скрываем индикатор кастомной цены
|
||||||
|
var badge = form.querySelector('.custom-price-badge');
|
||||||
|
var priceInfo = form.querySelector('.original-price-info');
|
||||||
|
if (badge) badge.style.display = 'none';
|
||||||
|
if (priceInfo) priceInfo.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Очистка при удалении выбора
|
||||||
|
$element.on('select2:clear', function() {
|
||||||
|
var form = element.closest('.order-item-form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
var productField = form.querySelector('[name$="-product"]');
|
||||||
|
var kitField = form.querySelector('[name$="-product_kit"]');
|
||||||
|
var priceField = form.querySelector('[name$="-price"]');
|
||||||
|
var quantityField = form.querySelector('[name$="-quantity"]');
|
||||||
|
|
||||||
|
if (productField) productField.value = '';
|
||||||
|
if (kitField) kitField.value = '';
|
||||||
|
if (priceField) priceField.value = '';
|
||||||
|
if (quantityField) quantityField.value = '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
})(window);
|
})(window);
|
||||||
|
|||||||
Reference in New Issue
Block a user