diff --git a/myproject/pos/static/pos/css/terminal.css b/myproject/pos/static/pos/css/terminal.css index 35d4cc4..7c3f675 100644 --- a/myproject/pos/static/pos/css/terminal.css +++ b/myproject/pos/static/pos/css/terminal.css @@ -576,3 +576,272 @@ body { } /* На мобильных - такой же вид с текстом */ + +/* ============================================================ + МОБИЛЬНАЯ ПЛАВАЮЩАЯ КОРЗИНА + ============================================================ */ + +/* Фиксированный бар внизу экрана */ +.mobile-cart-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: white; + border-top: 2px solid #dee2e6; + padding: 0.75rem; + display: none; /* скрываем на десктопе */ + justify-content: space-between; + align-items: center; + gap: 0.75rem; + z-index: 1000; + box-shadow: 0 -2px 10px rgba(0,0,0,0.1); +} + +/* Сводка по корзине (кликабельная) */ +.mobile-cart-summary { + flex: 1; + display: flex; + flex-direction: column; + cursor: pointer; + padding: 0.25rem; + border-radius: 8px; + transition: background-color 0.2s; +} + +.mobile-cart-summary:hover { + background-color: #f8f9fa; +} + +.mobile-cart-summary:active { + background-color: #e9ecef; +} + +/* Количество товаров */ +.mobile-cart-count { + font-size: 0.8rem; + color: #6c757d; + font-weight: 500; +} + +/* Итоговая сумма */ +.mobile-cart-total { + font-size: 1.35rem; + font-weight: 700; + color: #198754; + line-height: 1.2; +} + +/* Кнопки действий в мобильном баре */ +.mobile-cart-actions { + display: flex; + gap: 0.5rem; +} + +.mobile-cart-actions .btn { + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + white-space: nowrap; +} + +.mobile-cart-actions .btn span { + display: inline; +} + +/* Overlay корзины (фон затемнения) */ +.mobile-cart-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 1100; + display: none; +} + +.mobile-cart-overlay.active { + display: flex; + align-items: flex-end; +} + +/* Контент корзины в overlay */ +.mobile-cart-content { + background: white; + width: 100%; + max-height: 75vh; + display: flex; + flex-direction: column; + animation: slideUp 0.3s ease-out; + border-radius: 16px 16px 0 0; +} + +@keyframes slideUp { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} + +/* Заголовок мобильной корзины */ +.mobile-cart-header { + padding: 1rem; + border-bottom: 1px solid #dee2e6; + display: flex; + justify-content: space-between; + align-items: center; + background: #f8f9fa; + border-radius: 16px 16px 0 0; +} + +.mobile-cart-header h6 { + font-weight: 600; +} + +/* Тело корзины с прокруткой */ +.mobile-cart-body { + padding: 1rem; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +/* Копия стилей корзины для мобильного view */ +.mobile-cart-body .cart-item { + padding: 0.5rem 0; +} + +.mobile-cart-body .item-name-price .fw-semibold { + font-size: 0.95rem; +} + +/* Показываем мобильный бар только на маленьких экранах */ +@media (max-width: 767.98px) { + .mobile-cart-bar { + display: flex; + } + + /* Скрываем десктопную правую панель на мобильных */ + .right-panel-fixed { + display: none !important; + } + + /* Добавляем отступы чтобы контент не перекрывался барами */ + .pos-container { + padding-bottom: 80px; + padding-top: 70px; /* место для фиксированного поиска */ + } + + /* Фиксируем основной контейнер для работы с позиционированием */ + .pos-main-container { + overflow: visible; /* меняем для работы fixed внутри */ + } + + /* Создаём фиксированный бар поиска на мобильных */ + .pos-container > .row { + position: relative; + } + + /* Левая колонка с товарами */ + .pos-container > .row > .col-12:first-child { + position: relative; + } + + /* Блок поиска выносим в фиксированный бар */ + .pos-container > .row > .col-12:first-child > div:first-child, + .pos-container > .row > .col-12:first-child > .mb-3:first-child { + position: fixed; + top: 56px; /* сразу под navbar */ + left: 0; + right: 0; + z-index: 900; + background: white; + padding: 0.75rem; + margin: 0; + border-bottom: 1px solid #dee2e6; + box-shadow: 0 2px 4px rgba(0,0,0,0.08); + } + + /* Категории тоже фиксируем под поиском */ + .pos-container > .row > .col-12:first-child > div:nth-child(2) { + position: fixed; + top: 115px; /* поиск (56+~55) + категории */ + left: 0; + right: 0; + z-index: 899; + background: white; + padding: 0.5rem 0.75rem; + margin: 0; + border-bottom: 1px solid #e9ecef; + } + + /* Сворачивание категорий на мобильных */ + .categories-wrapper { + margin-bottom: 0.5rem !important; + } + + /* Кнопка переключения категорий */ + .categories-toggle { + cursor: pointer; + color: #495057; + font-size: 0.9rem; + } + + .categories-toggle:hover { + color: #212529; + } + + /* Иконка chevron с поворотом */ + .categories-toggle .bi-chevron-down { + transition: transform 0.2s ease; + } + + .categories-toggle.collapsed .bi-chevron-down { + transform: rotate(-90deg); + } + + /* Содержимое категорий */ + .categories-content { + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; + transition: max-height 0.3s ease, opacity 0.3s ease; + -webkit-overflow-scrolling: touch; + } + + .categories-content.collapsed { + max-height: 0; + opacity: 0; + overflow: hidden; + } + + /* Меньшие карточки категорий на мобильных */ + .category-card { + min-height: 32px !important; + padding: 0.25rem 0.5rem !important; + } + + .category-card .card-body { + padding: 0.25rem !important; + } + + .category-name { + font-size: 0.75rem !important; + } + + /* Уменьшаем gap между категориями */ + #categoryGrid { + gap: 0.5rem !important; + } + + /* Категории в 3 колонки на мобильных */ + #categoryGrid > div { + flex: 0 0 calc(33.333% - 0.33rem); + max-width: calc(33.333% - 0.33rem); + } + + /* Прокручиваемая область товаров смещаем вниз */ + .products-scrollable { + margin-top: 90px; /* учитываем поиск и категории */ + } +} diff --git a/myproject/pos/static/pos/js/terminal.js b/myproject/pos/static/pos/js/terminal.js index f46771f..23f47e7 100644 --- a/myproject/pos/static/pos/js/terminal.js +++ b/myproject/pos/static/pos/js/terminal.js @@ -787,10 +787,12 @@ function renderProducts() { // Для витрины — клиентская фильтрация по поиску const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); if (searchTerm) { + const tokens = searchTerm.split(/\s+/).filter(t => t.length > 0); filtered = filtered.filter(item => { const name = (item.name || '').toLowerCase(); const sku = (item.sku || '').toLowerCase(); - return name.includes(searchTerm) || sku.includes(searchTerm); + // Каждый токен должен совпадать хотя бы с одним словом в name или sku + return tokens.every(token => name.includes(token) || sku.includes(token)); }); } } else { @@ -1261,6 +1263,7 @@ function renderCart() { list.innerHTML = '
Корзина пуста
'; document.getElementById('cartTotal').textContent = '0.00'; updateShowcaseButtonState(); // Обновляем состояние кнопки + updateMobileCartBar(); // Обновляем мобильный бар даже когда корзина пуста return; } @@ -1420,9 +1423,59 @@ function renderCart() { }); document.getElementById('cartTotal').textContent = formatMoney(total); - + // Обновляем состояние кнопки "НА ВИТРИНУ" updateShowcaseButtonState(); + + // Обновляем мобильный бар корзины + updateMobileCartBar(); +} + +/** + * Склонение слов в зависимости от числа + * @param {number} number - число + * @param {string} one - форма для 1 (товар) + * @param {string} two - форма для 2-4 (товара) + * @param {string} five - форма для 5+ (товаров) + */ +function getNoun(number, one, two, five) { + const n = Math.abs(number); + const n10 = n % 10; + const n100 = n % 100; + + if (n100 >= 11 && n100 <= 19) { + return five; + } + if (n10 === 1) { + return one; + } + if (n10 >= 2 && n10 <= 4) { + return two; + } + return five; +} + +/** + * Обновляет мобильный бар корзины + */ +function updateMobileCartBar() { + const countEl = document.querySelector('.mobile-cart-count'); + const totalEl = document.querySelector('.mobile-cart-total'); + + if (!countEl || !totalEl) return; + + let count = 0; + let total = 0; + + cart.forEach((item) => { + count += item.qty; + total += item.qty * item.price; + }); + + // Округляем количество до целого для отображения + const displayCount = Math.round(count); + countEl.textContent = `${displayCount} ${getNoun(displayCount, 'товар', 'товара', 'товаров')}`; + totalEl.textContent = formatMoney(total); } async function removeFromCart(cartKey) { @@ -3260,6 +3313,85 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('confirmAddUnitToCart').addEventListener('click', () => { addToCartFromModal(); }); + + // ===== МОБИЛЬНАЯ КОРЗИНА ===== + + // Тап на бар — открываем корзину + const mobileCartSummary = document.getElementById('mobileCartSummary'); + if (mobileCartSummary) { + mobileCartSummary.addEventListener('click', () => { + const overlay = document.getElementById('mobileCartOverlay'); + const body = document.getElementById('mobileCartBody'); + + // Копируем содержимое корзины + if (body && overlay) { + const cartList = document.getElementById('cartList'); + body.innerHTML = cartList ? cartList.innerHTML : 'Корзина пуста
'; + overlay.classList.add('active'); + } + }); + } + + // Кнопка закрытия мобильной корзины + const mobileCartClose = document.getElementById('mobileCartClose'); + if (mobileCartClose) { + mobileCartClose.addEventListener('click', () => { + const overlay = document.getElementById('mobileCartOverlay'); + if (overlay) { + overlay.classList.remove('active'); + } + }); + } + + // Закрытие по клику на фон + const mobileCartOverlay = document.getElementById('mobileCartOverlay'); + if (mobileCartOverlay) { + mobileCartOverlay.addEventListener('click', (e) => { + if (e.target.id === 'mobileCartOverlay') { + e.target.classList.remove('active'); + } + }); + } + + // Мобильная кнопка "Продать" + const mobileCheckoutBtn = document.getElementById('mobileCheckoutBtn'); + if (mobileCheckoutBtn) { + mobileCheckoutBtn.addEventListener('click', () => { + const checkoutBtn = document.getElementById('checkoutNow'); + if (checkoutBtn) { + checkoutBtn.click(); + } + }); + } + + // Мобильная кнопка "Очистить" + const mobileClearCartBtn = document.getElementById('mobileClearCartBtn'); + if (mobileClearCartBtn) { + mobileClearCartBtn.addEventListener('click', () => { + const clearBtn = document.getElementById('clearCart'); + if (clearBtn) { + clearBtn.click(); + } + }); + } + + // ===== СВОРАЧИВАНИЕ КАТЕГОРИЙ НА МОБИЛЬНЫХ ===== + + const categoriesToggle = document.getElementById('categoriesToggle'); + const categoriesContent = document.getElementById('categoriesContent'); + + if (categoriesToggle && categoriesContent) { + categoriesToggle.addEventListener('click', () => { + categoriesToggle.classList.toggle('collapsed'); + categoriesContent.classList.toggle('collapsed'); + }); + + // Автоматически сворачиваем категории на мобильных при загрузке + if (window.innerWidth <= 767) { + categoriesToggle.classList.add('collapsed'); + categoriesContent.classList.add('collapsed'); + } + } }); // Смена склада diff --git a/myproject/pos/templates/pos/terminal.html b/myproject/pos/templates/pos/terminal.html index 2bcc338..e453c13 100644 --- a/myproject/pos/templates/pos/terminal.html +++ b/myproject/pos/templates/pos/terminal.html @@ -28,8 +28,14 @@ -