feat(pos): глобальный поиск товаров независимо от категории

- При поиске (3+ символа) ищет по всем товарам и комплектам, игнорируя выбранную категорию
- Добавлена визуальная индикация: кнопка "Все товары" подсвечивается при активном поиске
- При очистке поиска возвращается к товарам выбранной категории
- Витринные комплекты не участвуют в глобальном поиске

Изменения:
- terminal.js: loadItems(), updateSearchIndicator(), обработчики поиска
- views.py: get_items_api() - игнорирование category_id при search_query
This commit is contained in:
2026-01-27 15:15:27 +03:00
parent c80e2a5eca
commit 7b6a86bdf2
2 changed files with 55 additions and 12 deletions

View File

@@ -772,6 +772,8 @@ function renderCategories() {
showcaseCard.onclick = async () => {
isShowcaseView = true;
currentCategoryId = null;
currentSearchQuery = ''; // Сбрасываем поиск
document.getElementById('searchInput').value = ''; // Очищаем поле поиска
await refreshShowcaseKits(); // Загружаем свежие данные
renderCategories();
renderProducts();
@@ -791,6 +793,7 @@ function renderCategories() {
allCol.className = 'col-6 col-custom-3 col-md-3 col-lg-2';
const allCard = document.createElement('div');
allCard.className = 'card category-card' + (currentCategoryId === null && !isShowcaseView ? ' active' : '');
allCard.dataset.categoryId = 'all'; // Для идентификации в updateSearchIndicator
allCard.onclick = async () => {
currentCategoryId = null;
isShowcaseView = false;
@@ -1157,7 +1160,8 @@ async function loadItems(append = false) {
page_size: 60
});
if (currentCategoryId) {
// При активном поиске игнорируем категорию - ищем по всем товарам
if (currentCategoryId && !currentSearchQuery) {
params.append('category_id', currentCategoryId);
}
@@ -1215,6 +1219,34 @@ function setupInfiniteScroll() {
observer.observe(sentinel);
}
// ===== ВИЗУАЛЬНАЯ ИНДИКАЦИЯ ГЛОБАЛЬНОГО ПОИСКА =====
/**
* Обновляет визуальную индикацию глобального поиска.
* При активном поиске подсвечивает кнопку "Все товары".
*/
function updateSearchIndicator() {
const allCard = document.querySelector('.category-card[data-category-id="all"]');
if (!allCard) return;
if (currentSearchQuery && currentSearchQuery.length >= 3) {
// Активен глобальный поиск - подсвечиваем "Все товары"
allCard.classList.add('active');
allCard.style.backgroundColor = '#e3f2fd'; // Светло-голубой фон
allCard.style.borderColor = '#2196f3'; // Синяя рамка
allCard.title = 'Идёт поиск по всем товарам';
} else {
// Поиск неактивен - возвращаем обычный стиль
allCard.style.backgroundColor = '';
allCard.style.borderColor = '';
allCard.title = '';
// Активный класс управляется renderCategories
if (currentCategoryId !== null) {
allCard.classList.remove('active');
}
}
}
async function addToCart(item) {
// ПРОВЕРКА НА НАЛИЧИЕ ЕДИНИЦ ПРОДАЖИ
// Если у товара одна единица продажи - добавляем сразу
@@ -3815,11 +3847,14 @@ searchInput.addEventListener('input', (e) => {
clearTimeout(searchDebounceTimer);
}
// Если поле пустое — очищаем экран
// Если поле пустое — очищаем поиск и возвращаемся к выбранной категории
if (query === '') {
currentSearchQuery = '';
ITEMS = []; // Очистка
renderProducts(); // Пустой экран
updateSearchIndicator(); // Обновляем индикацию
// Возвращаем товары выбранной категории
if (!isShowcaseView) {
loadItems();
}
return;
}
@@ -3829,7 +3864,7 @@ searchInput.addEventListener('input', (e) => {
return;
}
// Для витрины — мгновенная клиентская фильтрация
// Для витрины — мгновенная клиентская фильтрация (витрина не участвует в глобальном поиске)
if (isShowcaseView) {
renderProducts();
return;
@@ -3838,7 +3873,8 @@ searchInput.addEventListener('input', (e) => {
// Для обычных товаров/комплектов — серверный поиск с debounce 300мс
searchDebounceTimer = setTimeout(async () => {
currentSearchQuery = query;
await loadItems(); // Перезагрузка с серверным поиском
updateSearchIndicator(); // Обновляем визуальную индикацию
await loadItems(); // Перезагрузка с серверным поиском (по всем категориям)
}, 300);
});
@@ -3855,8 +3891,13 @@ clearSearchBtn.addEventListener('click', () => {
searchInput.value = '';
clearSearchBtn.style.display = 'none';
currentSearchQuery = '';
ITEMS = [];
renderProducts(); // Пустой экран
updateSearchIndicator(); // Обновляем индикацию
// Возвращаем товары выбранной категории
if (!isShowcaseView) {
loadItems();
} else {
renderProducts(); // Для витрины - просто перерисовываем
}
});
// Инициализация

View File

@@ -839,8 +839,9 @@ def get_items_api(request):
'sales_units' # Загружаем единицы продажи для POS
)
# Фильтруем по категории, если указана
if category_id:
# Фильтруем по категории, если указана И нет поискового запроса
# При поиске игнорируем категорию - ищем по всем товарам
if category_id and not search_query:
products_qs = products_qs.filter(categories__id=category_id)
# Фильтруем по поисковому запросу (name или sku) - разбиваем на токены
@@ -931,8 +932,9 @@ def get_items_api(request):
first_kit_photo
)
# Фильтруем комплекты по категории, если указана
if category_id:
# Фильтруем комплекты по категории, если указана И нет поискового запроса
# При поиске игнорируем категорию - ищем по всем комплектам
if category_id and not search_query:
kits_qs = kits_qs.filter(categories__id=category_id)
# Фильтруем комплекты по поисковому запросу (name или sku) - разбиваем на токены