From 017fa4b744da4ea58d0166b4ca43d1e0e62c84ca Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sun, 18 Jan 2026 22:08:58 +0300 Subject: [PATCH] =?UTF-8?q?feat(pos):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=86=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=B0=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D0=B7=D0=B8=D0=BD=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавить модалку редактирования товара в корзине (edit_cart_item_modal.html) - Создать JS модуль cart-item-editor.js для логики редактирования - При клике на строку товара открывается модалка с возможностью изменения цены и количества - Добавить визуальную индикацию изменённой цены (оранжевый цвет и звёздочка) - Экспортировать корзину в window.cart для доступа из других модулей - Добавить авто-выделение текста при фокусе в полях ввода Co-Authored-By: Claude Opus 4.5 --- myproject/pos/static/pos/css/terminal.css | 32 +++ .../pos/static/pos/js/cart-item-editor.js | 194 ++++++++++++++++++ myproject/pos/static/pos/js/terminal.js | 25 ++- .../pos/components/edit_cart_item_modal.html | 63 ++++++ myproject/pos/templates/pos/terminal.html | 4 + 5 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 myproject/pos/static/pos/js/cart-item-editor.js create mode 100644 myproject/pos/templates/pos/components/edit_cart_item_modal.html diff --git a/myproject/pos/static/pos/css/terminal.css b/myproject/pos/static/pos/css/terminal.css index 4090ea5..9cf7691 100644 --- a/myproject/pos/static/pos/css/terminal.css +++ b/myproject/pos/static/pos/css/terminal.css @@ -872,3 +872,35 @@ body { .mobile-cart-actions .dropdown-item i { font-size: 1rem; } + +/* ============================================================ + ИНТЕРАКТИВНОСТЬ СТРОКИ КОРЗИНЫ (редактирование товара) + ============================================================ */ + +/* Интерактивность строки корзины при наведении */ +.cart-item { + transition: background-color 0.15s ease; +} + +.cart-item:hover { + background-color: #f8f9fa !important; + border-radius: 4px; + padding-left: 0.5rem !important; + padding-right: 0.5rem !important; +} + +/* Исключаем hover для витринных комплектов - они сохраняют свой фон */ +.cart-item[style*="background-color"]:hover { + background-color: #ffe6a0 !important; /* чуть светлее желтого */ +} + +/* Индикатор изменённой цены */ +.cart-item.price-overridden .item-name-price .text-muted { + color: #f59e0b !important; + font-weight: 600; +} + +.cart-item.price-overridden .item-name-price .text-muted::after { + content: ' *'; + color: #f59e0b; +} diff --git a/myproject/pos/static/pos/js/cart-item-editor.js b/myproject/pos/static/pos/js/cart-item-editor.js new file mode 100644 index 0000000..efc7352 --- /dev/null +++ b/myproject/pos/static/pos/js/cart-item-editor.js @@ -0,0 +1,194 @@ +/** + * Модуль редактирования товара в корзине POS-терминала + * Отвечает за открытие модалки и сохранение изменений + */ + +(function() { + 'use strict'; + + let editingCartKey = null; + let basePrice = 0; + + /** + * Округление цены до 2 знаков + */ + function roundPrice(value) { + if (value === null || value === undefined || isNaN(value)) return '0.00'; + return (Number(value)).toFixed(2); + } + + /** + * Открытие модалки редактирования + * @param {string} cartKey - ключ товара в корзине + */ + function openModal(cartKey) { + const item = window.cart?.get(cartKey); + if (!item) { + console.error('CartItemEditor: Item not found for key:', cartKey); + return; + } + + // Проверяем наличие модалки + const modalEl = document.getElementById('editCartItemModal'); + if (!modalEl) { + console.error('CartItemEditor: Modal element not found!'); + return; + } + + editingCartKey = cartKey; + basePrice = parseFloat(item.price) || 0; + + // Заполнение полей + document.getElementById('editModalProductName').textContent = item.name || '—'; + + // Используем formatMoney из terminal.js + const fmtMoney = typeof formatMoney === 'function' ? formatMoney : (v) => Number(v).toFixed(2); + document.getElementById('editModalBasePrice').textContent = fmtMoney(basePrice) + ' руб.'; + + document.getElementById('editModalPrice').value = roundPrice(basePrice); + document.getElementById('editModalQuantity').value = item.qty || 1; + + // Бейдж единицы измерения + const unitBadge = document.getElementById('editModalUnitBadge'); + if (item.unit_name) { + unitBadge.textContent = item.unit_name; + unitBadge.style.display = 'inline-block'; + } else { + unitBadge.style.display = 'none'; + } + + updateTotal(); + + // Показ модалки + const modal = new bootstrap.Modal(modalEl); + modal.show(); + + console.log('CartItemEditor: Modal opened for', item.name); + } + + /** + * Обновление суммы в модалке + */ + function updateTotal() { + const price = parseFloat(document.getElementById('editModalPrice').value) || 0; + const qty = parseFloat(document.getElementById('editModalQuantity').value) || 0; + + const fmtMoney = typeof formatMoney === 'function' ? formatMoney : (v) => Number(v).toFixed(2); + document.getElementById('editModalTotal').textContent = fmtMoney(price * qty) + ' руб.'; + + // Индикатор изменения цены + const warning = document.getElementById('editModalPriceWarning'); + if (Math.abs(price - basePrice) > 0.01) { + warning.style.display = 'block'; + } else { + warning.style.display = 'none'; + } + } + + /** + * Сохранение изменений + */ + function saveChanges() { + if (!editingCartKey) return; + + const newPrice = parseFloat(document.getElementById('editModalPrice').value) || 0; + const newQty = parseFloat(document.getElementById('editModalQuantity').value) || 1; + + const item = window.cart?.get(editingCartKey); + if (item) { + // Используем roundQuantity из terminal.js + const rndQty = typeof roundQuantity === 'function' ? roundQuantity : (v, d) => Math.round(v * Math.pow(10, d)) / Math.pow(10, d); + + item.price = newPrice; + item.qty = rndQty(newQty, 3); + item.price_overridden = Math.abs(newPrice - basePrice) > 0.01; + + window.cart.set(editingCartKey, item); + + // Перерисовка корзины + if (typeof renderCart === 'function') { + renderCart(); + } + + // Сохранение на сервере + if (typeof saveCartToServer === 'function') { + saveCartToServer(); + } + + console.log('CartItemEditor: Changes saved for', item.name); + } + + // Закрытие модалки + const modalEl = document.getElementById('editCartItemModal'); + const modal = bootstrap.Modal.getInstance(modalEl); + if (modal) modal.hide(); + } + + /** + * Сброс состояния модалки + */ + function reset() { + editingCartKey = null; + basePrice = 0; + } + + /** + * Инициализация модуля + */ + function init() { + const priceInput = document.getElementById('editModalPrice'); + const qtyInput = document.getElementById('editModalQuantity'); + const confirmBtn = document.getElementById('confirmEditCartItem'); + + if (!priceInput || !confirmBtn) { + console.warn('CartItemEditor: Required elements not found, deferring init...'); + // Повторная попытка через короткое время + setTimeout(init, 100); + return; + } + + console.log('CartItemEditor: Initialized successfully'); + + // Обновление суммы при изменении полей + priceInput.addEventListener('input', updateTotal); + qtyInput.addEventListener('input', updateTotal); + + // Авто-выделение всего текста при фокусе + priceInput.addEventListener('focus', function() { + this.select(); + }); + qtyInput.addEventListener('focus', function() { + this.select(); + }); + + // Кнопка сохранения + confirmBtn.addEventListener('click', saveChanges); + + // Сброс при закрытии модалки + const modalEl = document.getElementById('editCartItemModal'); + modalEl.addEventListener('hidden.bs.modal', reset); + + // Enter для сохранения + priceInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') saveChanges(); + }); + qtyInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') saveChanges(); + }); + } + + // Экспорт функций для использования из terminal.js + window.CartItemEditor = { + openModal: openModal, + init: init + }; + + console.log('CartItemEditor: Module loaded'); + + // Автоинициализация при загрузке + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/myproject/pos/static/pos/js/terminal.js b/myproject/pos/static/pos/js/terminal.js index dc940e8..21b9c83 100644 --- a/myproject/pos/static/pos/js/terminal.js +++ b/myproject/pos/static/pos/js/terminal.js @@ -19,6 +19,8 @@ let showcaseKits = JSON.parse(document.getElementById('showcaseKitsData').textCo let currentCategoryId = null; let isShowcaseView = false; const cart = new Map(); +// Экспорт корзины для использования в других модулях +window.cart = cart; // Переменные для пагинации let currentPage = 1; @@ -1270,6 +1272,13 @@ function renderCart() { cart.forEach((item, cartKey) => { const row = document.createElement('div'); row.className = 'cart-item mb-2'; + row.style.cursor = 'pointer'; + row.title = 'Нажмите для редактирования'; + + // Индикатор изменённой цены + if (item.price_overridden) { + row.classList.add('price-overridden'); + } // СПЕЦИАЛЬНАЯ СТИЛИЗАЦИЯ для витринных комплектов const isShowcaseKit = item.type === 'showcase_kit'; @@ -1416,7 +1425,21 @@ function renderCart() { row.appendChild(qtyControl); row.appendChild(itemTotal); row.appendChild(deleteBtn); - + + // Обработчик клика для редактирования товара + row.addEventListener('click', function(e) { + // Игнорируем клики на кнопки управления количеством и удаления + if (e.target.closest('button') || e.target.closest('input')) { + return; + } + console.log('Cart row clicked, cartKey:', cartKey, 'CartItemEditor:', typeof window.CartItemEditor); + if (window.CartItemEditor) { + window.CartItemEditor.openModal(cartKey); + } else { + console.error('CartItemEditor not available!'); + } + }); + list.appendChild(row); total += item.qty * item.price; diff --git a/myproject/pos/templates/pos/components/edit_cart_item_modal.html b/myproject/pos/templates/pos/components/edit_cart_item_modal.html new file mode 100644 index 0000000..87a8d23 --- /dev/null +++ b/myproject/pos/templates/pos/components/edit_cart_item_modal.html @@ -0,0 +1,63 @@ +{% load static %} + diff --git a/myproject/pos/templates/pos/terminal.html b/myproject/pos/templates/pos/terminal.html index 3acb06b..87cca74 100644 --- a/myproject/pos/templates/pos/terminal.html +++ b/myproject/pos/templates/pos/terminal.html @@ -713,6 +713,9 @@ + + +{% include 'pos/components/edit_cart_item_modal.html' %} {% endblock %} {% block extra_js %} @@ -732,4 +735,5 @@ + {% endblock %}