diff --git a/myproject/inventory/templates/inventory/writeoff_document/detail.html b/myproject/inventory/templates/inventory/writeoff_document/detail.html
index eba43b1..165e4fa 100644
--- a/myproject/inventory/templates/inventory/writeoff_document/detail.html
+++ b/myproject/inventory/templates/inventory/writeoff_document/detail.html
@@ -107,7 +107,7 @@
- {% include 'products/components/product_search_picker.html' with container_id='writeoff-document-picker' title='Найти товар для списания' warehouse_id=document.warehouse.id filter_in_stock_only=True categories=categories tags=tags add_button_text='Выбрать товар' multi_select=False show_select_all=False content_height='250px' %}
+ {% include 'products/components/product_search_picker.html' with container_id='writeoff-document-picker' title='Найти товар для списания' warehouse_id=document.warehouse.id filter_in_stock_only=True categories=categories tags=tags add_button_text='Выбрать товар' content_height='250px' %}
@@ -291,9 +291,9 @@ document.addEventListener('DOMContentLoaded', function() {
// Инициализация компонента поиска товаров
const picker = ProductSearchPicker.init('#writeoff-document-picker', {
- onAddSelected: function(products, instance) {
- if (products.length > 0) {
- selectProduct(products[0]);
+ onAddSelected: function(product, instance) {
+ if (product) {
+ selectProduct(product);
instance.clearSelection();
}
}
diff --git a/myproject/products/static/products/js/product-search-picker.js b/myproject/products/static/products/js/product-search-picker.js
index b64dbcf..375e928 100644
--- a/myproject/products/static/products/js/product-search-picker.js
+++ b/myproject/products/static/products/js/product-search-picker.js
@@ -1,19 +1,18 @@
/**
- * ProductSearchPicker - переиспользуемый модуль для поиска и выбора товаров
+ * ProductSearchPicker - переиспользуемый модуль для поиска и выбора товара
+ * Только single-select режим.
*
* API:
* - ProductSearchPicker.init(containerSelector, options) - инициализация
- * - instance.getSelected() - получить выбранные товары (массив объектов)
- * - instance.getSelectedIds() - получить ID выбранных товаров (массив строк)
+ * - instance.getSelected() - получить выбранный товар (объект или null)
* - instance.clearSelection() - сбросить выбор
- * - instance.setSelection(productIds) - установить выбор программно
* - instance.refresh() - обновить список товаров
* - instance.destroy() - уничтожить экземпляр
*
* Пример использования:
* const picker = ProductSearchPicker.init('#my-picker', {
- * onAddSelected: function(products, instance) {
- * console.log('Добавлены товары:', products);
+ * onAddSelected: function(product, instance) {
+ * console.log('Выбран товар:', product);
* }
* });
*/
@@ -39,23 +38,20 @@
// Настройки по умолчанию
this.options = Object.assign({
apiUrl: this.container.dataset.apiUrl || '/products/api/search-products-variants/',
- multiSelect: this.container.dataset.multiSelect !== 'false',
- maxSelection: parseInt(this.container.dataset.maxSelection) || null,
excludeKits: this.container.dataset.excludeKits !== 'false',
debounceDelay: 300,
pageSize: 30,
// Callbacks
- onSelect: null, // (products, instance) => {}
- onDeselect: null, // (products, instance) => {}
- onSelectionChange: null, // (selectedProducts, instance) => {}
- onAddSelected: null // (selectedProducts, instance) => {}
+ onSelect: null, // (product, instance) => {}
+ onDeselect: null, // (instance) => {}
+ onAddSelected: null // (product, instance) => {}
}, options || {});
// Состояние
this.state = {
products: [],
- selected: {}, // {productId: productData}
+ selected: null, // Единичный выбранный товар (объект или null)
currentPage: 1,
hasMore: false,
isLoading: false,
@@ -94,9 +90,6 @@
loading: c.querySelector('.product-picker-loading'),
empty: c.querySelector('.product-picker-empty'),
content: c.querySelector('.product-picker-content'),
- selectedCount: c.querySelector('.product-picker-selected-count'),
- selectAllBtn: c.querySelector('.product-picker-select-all'),
- clearSelectionBtn: c.querySelector('.product-picker-clear-selection'),
addSelectedBtn: c.querySelector('.product-picker-add-selected'),
viewButtons: c.querySelectorAll('.product-picker-view')
};
@@ -210,25 +203,11 @@
});
}
- // Выбрать все
- if (this.elements.selectAllBtn) {
- this.elements.selectAllBtn.addEventListener('click', function() {
- self._selectAll();
- });
- }
-
- // Сбросить выбор
- if (this.elements.clearSelectionBtn) {
- this.elements.clearSelectionBtn.addEventListener('click', function() {
- self.clearSelection();
- });
- }
-
- // Добавить выбранные
+ // Добавить выбранный
if (this.elements.addSelectedBtn) {
this.elements.addSelectedBtn.addEventListener('click', function() {
- if (self.options.onAddSelected) {
- self.options.onAddSelected(self.getSelected(), self);
+ if (self.state.selected && self.options.onAddSelected) {
+ self.options.onAddSelected(self.state.selected, self);
}
});
}
@@ -348,7 +327,7 @@
products.forEach(function(product) {
// Извлекаем ID (убираем префикс "product_")
var productId = String(product.id).replace('product_', '');
- var isSelected = self.state.selected.hasOwnProperty(productId);
+ var isSelected = self.state.selected && self.state.selected.id === productId;
var html = self.state.currentView === 'grid'
? self._renderGridCard(product, productId, isSelected)
@@ -446,7 +425,7 @@
};
/**
- * Переключение выбора товара
+ * Переключение выбора товара (single-select)
*/
ProductSearchPicker.prototype._toggleProduct = function(productId) {
var self = this;
@@ -457,47 +436,40 @@
var p = this.state.products[i];
if (String(p.id).replace('product_', '') === productId) {
product = p;
+ product.id = productId; // Сохраняем очищенный ID
break;
}
}
if (!product) return;
- var isSelected = this.state.selected.hasOwnProperty(productId);
+ var isSelected = this.state.selected && this.state.selected.id === productId;
if (isSelected) {
// Снять выбор
- delete this.state.selected[productId];
+ var oldProductId = this.state.selected.id;
+ this.state.selected = null;
+ this._updateProductUI(oldProductId);
if (this.options.onDeselect) {
- this.options.onDeselect([product], this);
+ this.options.onDeselect(this);
}
} else {
- // Проверка множественного выбора
- if (!this.options.multiSelect) {
- this.state.selected = {};
+ // Сначала снимаем выбор со старого товара
+ if (this.state.selected) {
+ var oldProductId = this.state.selected.id;
+ this._updateProductUI(oldProductId);
}
-
- // Проверка лимита
- if (this.options.maxSelection &&
- Object.keys(this.state.selected).length >= this.options.maxSelection) {
- return;
- }
-
- // Добавить в выбор
- this.state.selected[productId] = product;
+
+ // Выбираем новый товар
+ this.state.selected = product;
+ this._updateProductUI(productId);
if (this.options.onSelect) {
- this.options.onSelect([product], this);
+ this.options.onSelect(product, this);
}
}
// Обновить UI
- this._updateProductUI(productId);
this._updateSelectionUI();
-
- // Общий callback
- if (this.options.onSelectionChange) {
- this.options.onSelectionChange(this.getSelected(), this);
- }
};
/**
@@ -507,7 +479,7 @@
var card = this.elements.grid.querySelector('[data-product-id="' + productId + '"]');
if (!card) return;
- var isSelected = this.state.selected.hasOwnProperty(productId);
+ var isSelected = this.state.selected && this.state.selected.id === productId;
if (isSelected) {
card.classList.add('selected');
@@ -545,52 +517,16 @@
};
/**
- * Обновление UI выбора (счетчик, кнопки)
+ * Обновление UI выбора (кнопка)
*/
ProductSearchPicker.prototype._updateSelectionUI = function() {
- var count = Object.keys(this.state.selected).length;
-
- // Счетчик
- if (this.elements.selectedCount) {
- this.elements.selectedCount.textContent = count;
- }
-
// Кнопка "Добавить"
if (this.elements.addSelectedBtn) {
- this.elements.addSelectedBtn.disabled = count === 0;
+ this.elements.addSelectedBtn.disabled = !this.state.selected;
}
};
- /**
- * Выбрать все видимые товары
- */
- ProductSearchPicker.prototype._selectAll = function() {
- var self = this;
- var currentCount = Object.keys(this.state.selected).length;
- var maxToSelect = this.options.maxSelection
- ? this.options.maxSelection - currentCount
- : Infinity;
-
- var added = 0;
- this.state.products.forEach(function(product) {
- if (added >= maxToSelect) return;
-
- var productId = String(product.id).replace('product_', '');
- if (!self.state.selected.hasOwnProperty(productId)) {
- self.state.selected[productId] = product;
- self._updateProductUI(productId);
- added++;
- }
- });
-
- this._updateSelectionUI();
-
- if (this.options.onSelectionChange) {
- this.options.onSelectionChange(this.getSelected(), this);
- }
- };
-
- /**
+/**
* Обновление кнопок переключателя вида
*/
ProductSearchPicker.prototype._updateViewButtons = function() {
@@ -625,68 +561,25 @@
// ========== ПУБЛИЧНОЕ API ==========
/**
- * Получить массив выбранных товаров
+ * Получить выбранный товар (объект или null)
*/
ProductSearchPicker.prototype.getSelected = function() {
- var result = [];
- for (var key in this.state.selected) {
- if (this.state.selected.hasOwnProperty(key)) {
- result.push(this.state.selected[key]);
- }
- }
- return result;
- };
-
- /**
- * Получить массив ID выбранных товаров
- */
- ProductSearchPicker.prototype.getSelectedIds = function() {
- return Object.keys(this.state.selected);
+ return this.state.selected;
};
/**
* Сбросить выбор
*/
ProductSearchPicker.prototype.clearSelection = function() {
- var self = this;
- var ids = Object.keys(this.state.selected);
- this.state.selected = {};
-
- ids.forEach(function(productId) {
- self._updateProductUI(productId);
- });
-
- this._updateSelectionUI();
-
- if (this.options.onSelectionChange) {
- this.options.onSelectionChange([], this);
+ if (this.state.selected) {
+ var productId = this.state.selected.id;
+ this.state.selected = null;
+ this._updateProductUI(productId);
}
- };
-
- /**
- * Установить выбор программно
- */
- ProductSearchPicker.prototype.setSelection = function(productIds) {
- var self = this;
- this.clearSelection();
-
- productIds.forEach(function(id) {
- var productId = String(id).replace('product_', '');
- // Ищем товар в загруженном списке
- for (var i = 0; i < self.state.products.length; i++) {
- var p = self.state.products[i];
- if (String(p.id).replace('product_', '') === productId) {
- self.state.selected[productId] = p;
- self._updateProductUI(productId);
- break;
- }
- }
- });
-
this._updateSelectionUI();
};
- /**
+/**
* Обновить список товаров
*/
ProductSearchPicker.prototype.refresh = function() {
diff --git a/myproject/products/templates/products/components/product_search_picker.html b/myproject/products/templates/products/components/product_search_picker.html
index 5756c91..7079542 100644
--- a/myproject/products/templates/products/components/product_search_picker.html
+++ b/myproject/products/templates/products/components/product_search_picker.html
@@ -1,18 +1,15 @@
{% comment %}
-Переиспользуемый компонент поиска и выбора штучных товаров (Product).
+Переиспользуемый компонент поиска и выбора товара (Product).
+Только единичный выбор (single-select).
Параметры (Django template variables):
- container_id: уникальный ID контейнера (default: 'product-search-picker')
-- title: заголовок компонента (default: 'Выбор товаров')
+- title: заголовок компонента (default: 'Выбор товара')
- api_url: URL для AJAX поиска (default: '/products/api/search-products-variants/')
- show_filters: показывать фильтры (default: True)
- show_view_toggle: показывать переключатель вида (default: True)
-- show_select_all: показывать кнопку "Выбрать все" (default: True)
-- show_add_button: показывать кнопку "Добавить выбранные" (default: True)
-- add_button_text: текст кнопки добавления (default: 'Добавить выбранные')
+- add_button_text: текст кнопки добавления (default: 'Выбрать товар')
- initial_view: начальный вид 'grid' или 'list' (default: 'list')
-- multi_select: множественный выбор (default: True)
-- max_selection: максимальное количество выбранных товаров (default: null)
- filter_in_stock_only: показывать только товары в наличии (default: False)
- warehouse_id: ID склада для фильтрации товаров (default: None)
- categories: список категорий для фильтра (queryset или list)
@@ -22,7 +19,7 @@
Пример использования:
{% include 'products/components/product_search_picker.html' with
container_id='writeoff-products'
- title='Выберите товары для списания'
+ title='Выберите товар для списания'
show_filters=True
filter_in_stock_only=True
categories=categories
@@ -31,9 +28,9 @@
После включения инициализируйте JS: