Реализована прогрессивная загрузка товаров в POS с пагинацией и infinite scroll. Первая загрузка только категорий и склада, товары подгружаются по 60 штук при клике на категорию с сортировкой по свободным остаткам (available-reserved) по убыванию. Добавлен API endpoint /pos/api/items/ с фильтрацией по категориям и пагинацией. Infinite scroll для догрузки следующих страниц. Lazy loading для изображений.

This commit is contained in:
2025-11-17 14:43:49 +03:00
parent e23bdef679
commit cfae2fb5fb
3 changed files with 250 additions and 59 deletions

View File

@@ -1,19 +1,24 @@
// POS Terminal JavaScript
const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent);
const ITEMS = JSON.parse(document.getElementById('itemsData').textContent); // Единый массив товаров и комплектов
let showcaseKits = JSON.parse(document.getElementById('showcaseKitsData').textContent); // Витринные комплекты (изменяемый)
let ITEMS = []; // Будем загружать через API
let showcaseKits = JSON.parse(document.getElementById('showcaseKitsData').textContent);
let currentCategoryId = null;
let isShowcaseView = false; // Флаг режима просмотра витринных букетов
const cart = new Map(); // "type-id" -> {id, name, price, qty, type}
let isShowcaseView = false;
const cart = new Map();
// Переменные для пагинации
let currentPage = 1;
let hasMoreItems = false;
let isLoadingItems = false;
// Переменные для режима редактирования
let isEditMode = false;
let editingKitId = null;
// Временная корзина для модального окна создания/редактирования комплекта
const tempCart = new Map(); // Изолированное состояние для модалки
const tempCart = new Map();
function formatMoney(v) {
return (Number(v)).toFixed(2);
@@ -52,11 +57,11 @@ function renderCategories() {
allCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const allCard = document.createElement('div');
allCard.className = 'card category-card' + (currentCategoryId === null && !isShowcaseView ? ' active' : '');
allCard.onclick = () => {
allCard.onclick = async () => {
currentCategoryId = null;
isShowcaseView = false;
renderCategories();
renderProducts();
await loadItems(); // Загрузка через API
};
const allBody = document.createElement('div');
allBody.className = 'card-body';
@@ -75,11 +80,11 @@ function renderCategories() {
const card = document.createElement('div');
card.className = 'card category-card' + (currentCategoryId === cat.id && !isShowcaseView ? ' active' : '');
card.onclick = () => {
card.onclick = async () => {
currentCategoryId = cat.id;
isShowcaseView = false;
renderCategories();
renderProducts();
await loadItems(); // Загрузка через API
};
const body = document.createElement('div');
@@ -105,14 +110,13 @@ function renderProducts() {
// Если выбран режим витрины - показываем витринные комплекты
if (isShowcaseView) {
filtered = showcaseKits; // Используем изменяемую переменную
filtered = showcaseKits;
} else {
// Обычный режим - показываем товары и комплекты
filtered = currentCategoryId
? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId))
: ITEMS;
// Обычный режим - ITEMS уже отфильтрованы по категории на сервере
filtered = ITEMS;
}
// Поиск - клиентская фильтрация
if (searchTerm) {
filtered = filtered.filter(item => item.name.toLowerCase().includes(searchTerm));
}
@@ -152,6 +156,7 @@ function renderProducts() {
const img = document.createElement('img');
img.src = item.image;
img.alt = item.name;
img.loading = 'lazy'; // Lazy loading
imageDiv.appendChild(img);
} else {
imageDiv.innerHTML = '<i class="bi bi-image"></i>';
@@ -241,6 +246,76 @@ function renderProducts() {
});
}
// Загрузка товаров через API
async function loadItems(append = false) {
if (isLoadingItems) return;
isLoadingItems = true;
if (!append) {
currentPage = 1;
ITEMS = [];
}
try {
const params = new URLSearchParams({
page: currentPage,
page_size: 60
});
if (currentCategoryId) {
params.append('category_id', currentCategoryId);
}
const response = await fetch(`/pos/api/items/?${params}`);
const data = await response.json();
if (data.success) {
if (append) {
ITEMS = ITEMS.concat(data.items);
} else {
ITEMS = data.items;
}
hasMoreItems = data.has_more;
if (data.has_more) {
currentPage = data.next_page;
}
renderProducts();
}
} catch (error) {
console.error('Ошибка загрузки товаров:', error);
} finally {
isLoadingItems = false;
}
}
// Infinite scroll
function setupInfiniteScroll() {
const grid = document.getElementById('productGrid');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && hasMoreItems && !isLoadingItems && !isShowcaseView) {
loadItems(true); // Догрузка
}
});
},
{
rootMargin: '200px'
}
);
// Наблюдаем за концом грида
const sentinel = document.createElement('div');
sentinel.id = 'scroll-sentinel';
sentinel.style.height = '1px';
grid.parentElement.appendChild(sentinel);
observer.observe(sentinel);
}
function addToCart(item) {
const cartKey = `${item.type}-${item.id}`; // Уникальный ключ: "product-1" или "kit-1"
@@ -852,9 +927,9 @@ document.getElementById('scheduleLater').onclick = async () => {
alert('Функционал будет подключен позже: создание заказа на доставку/самовывоз.');
};
// Search functionality
// Search functionality - клиентская фильтрация
document.getElementById('searchInput').addEventListener('input', () => {
renderProducts();
renderProducts(); // Просто перерисовываем с фильтрацией
});
// Customer selection
@@ -927,8 +1002,9 @@ function getCsrfToken() {
// Инициализация
renderCategories();
renderProducts();
renderProducts(); // Сначала пустая сетка
renderCart();
setupInfiniteScroll(); // Установка infinite scroll
// Установить фокус на строку поиска
document.getElementById('searchInput').focus();