diff --git a/myproject/pos/static/pos/js/terminal.js b/myproject/pos/static/pos/js/terminal.js index e715123..925242c 100644 --- a/myproject/pos/static/pos/js/terminal.js +++ b/myproject/pos/static/pos/js/terminal.js @@ -12,6 +12,8 @@ const cart = new Map(); let currentPage = 1; let hasMoreItems = false; let isLoadingItems = false; +let currentSearchQuery = ''; // Текущий поисковый запрос +let searchDebounceTimer = null; // Переменные для режима редактирования let isEditMode = false; @@ -60,6 +62,8 @@ function renderCategories() { allCard.onclick = async () => { currentCategoryId = null; isShowcaseView = false; + currentSearchQuery = ''; // Сбрасываем поиск + document.getElementById('searchInput').value = ''; // Очищаем поле поиска renderCategories(); await loadItems(); // Загрузка через API }; @@ -83,6 +87,8 @@ function renderCategories() { card.onclick = async () => { currentCategoryId = cat.id; isShowcaseView = false; + currentSearchQuery = ''; // Сбрасываем поиск + document.getElementById('searchInput').value = ''; // Очищаем поле поиска renderCategories(); await loadItems(); // Загрузка через API }; @@ -104,27 +110,26 @@ function renderCategories() { function renderProducts() { const grid = document.getElementById('productGrid'); grid.innerHTML = ''; - const searchTerm = document.getElementById('searchInput').value.toLowerCase(); let filtered; // Если выбран режим витрины - показываем витринные комплекты if (isShowcaseView) { filtered = showcaseKits; + + // Для витрины — клиентская фильтрация по поиску + const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); + if (searchTerm) { + filtered = filtered.filter(item => { + const name = (item.name || '').toLowerCase(); + const sku = (item.sku || '').toLowerCase(); + return name.includes(searchTerm) || sku.includes(searchTerm); + }); + } } else { - // Обычный режим - ITEMS уже отфильтрованы по категории на сервере + // Обычный режим - ITEMS уже отфильтрованы на сервере (категория + поиск) filtered = ITEMS; } - - // Поиск — по названию или артикулу, по любой части, без регистра - if (searchTerm) { - const term = searchTerm.trim(); - filtered = filtered.filter(item => { - const name = (item.name || '').toLowerCase(); - const sku = (item.sku || '').toLowerCase(); - return name.includes(term) || sku.includes(term); - }); - } filtered.forEach(item => { const col = document.createElement('div'); @@ -272,6 +277,11 @@ async function loadItems(append = false) { params.append('category_id', currentCategoryId); } + // Добавляем поисковый запрос, если есть + if (currentSearchQuery) { + params.append('query', currentSearchQuery); + } + const response = await fetch(`/pos/api/items/?${params}`); const data = await response.json(); @@ -932,11 +942,6 @@ document.getElementById('scheduleLater').onclick = async () => { alert('Функционал будет подключен позже: создание заказа на доставку/самовывоз.'); }; -// Search functionality - клиентская фильтрация -document.getElementById('searchInput').addEventListener('input', () => { - renderProducts(); // Просто перерисовываем с фильтрацией -}); - // Customer selection document.getElementById('customerSelectBtn').addEventListener('click', () => { alert('Функция выбора клиента будет реализована позже'); @@ -1005,6 +1010,29 @@ function getCsrfToken() { return cookieValue; } +// Обработчик поиска с debounce +const searchInput = document.getElementById('searchInput'); +searchInput.addEventListener('input', (e) => { + const query = e.target.value.trim(); + + // Отменяем предыдущий таймер + if (searchDebounceTimer) { + clearTimeout(searchDebounceTimer); + } + + // Для витрины — мгновенная клиентская фильтрация + if (isShowcaseView) { + renderProducts(); + return; + } + + // Для обычных товаров/комплектов — серверный поиск с debounce 300мс + searchDebounceTimer = setTimeout(async () => { + currentSearchQuery = query; + await loadItems(); // Перезагрузка с серверным поиском + }, 300); +}); + // Инициализация renderCategories(); renderProducts(); // Сначала пустая сетка diff --git a/myproject/pos/views.py b/myproject/pos/views.py index c4f274f..545294e 100644 --- a/myproject/pos/views.py +++ b/myproject/pos/views.py @@ -266,15 +266,17 @@ def get_showcase_kits_api(request): @require_http_methods(["GET"]) def get_items_api(request): """ - API endpoint для получения товаров и комплектов с пагинацией. + API endpoint для получения товаров и комплектов с пагинацией и поиском. Параметры: - category_id: ID категории (опционально, для фильтрации) + - query: поисковый запрос по name или sku (опционально) - page: номер страницы (по умолчанию 1) - page_size: размер страницы (по умолчанию 60) Сортировка по умолчанию: по свободному остатку (available - reserved) DESC """ from products.models import ProductPhoto, ProductKitPhoto from django.core.paginator import Paginator + from django.db.models import Q # Получаем текущий склад current_warehouse = get_pos_warehouse(request) @@ -284,8 +286,9 @@ def get_items_api(request): 'error': 'Нет активного склада' }, status=400) - # Параметры пагинации + # Параметры пагинации и поиска category_id = request.GET.get('category_id') + search_query = request.GET.get('query', '').strip() page = int(request.GET.get('page', 1)) page_size = int(request.GET.get('page_size', 60)) @@ -328,6 +331,12 @@ def get_items_api(request): if category_id: products_qs = products_qs.filter(categories__id=category_id) + # Фильтруем по поисковому запросу (name или sku) + if search_query: + products_qs = products_qs.filter( + Q(name__icontains=search_query) | Q(sku__icontains=search_query) + ) + # Сериализуем товары products = [] for p in products_qs: @@ -373,6 +382,12 @@ def get_items_api(request): if category_id: kits_qs = kits_qs.filter(categories__id=category_id) + # Фильтруем комплекты по поисковому запросу (name или sku) + if search_query: + kits_qs = kits_qs.filter( + Q(name__icontains=search_query) | Q(sku__icontains=search_query) + ) + # Сериализуем комплекты kits = [] for k in kits_qs: