Создано приложение POS с планшетным интерфейсом терминала продаж
This commit is contained in:
170
myproject/pos/static/pos/js/terminal.js
Normal file
170
myproject/pos/static/pos/js/terminal.js
Normal file
@@ -0,0 +1,170 @@
|
||||
// POS Terminal JavaScript
|
||||
|
||||
const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent);
|
||||
const PRODUCTS = JSON.parse(document.getElementById('productsData').textContent);
|
||||
|
||||
let currentCategoryId = null;
|
||||
const cart = new Map(); // productId -> {id, name, price, qty}
|
||||
|
||||
function formatMoney(v) {
|
||||
return (Number(v)).toFixed(2);
|
||||
}
|
||||
|
||||
function renderProducts() {
|
||||
const grid = document.getElementById('productGrid');
|
||||
grid.innerHTML = '';
|
||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||
|
||||
let filtered = currentCategoryId
|
||||
? PRODUCTS.filter(p => (p.category_ids || []).includes(currentCategoryId))
|
||||
: PRODUCTS;
|
||||
|
||||
if (searchTerm) {
|
||||
filtered = filtered.filter(p => p.name.toLowerCase().includes(searchTerm));
|
||||
}
|
||||
|
||||
filtered.forEach(p => {
|
||||
const col = document.createElement('div');
|
||||
col.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card product-card';
|
||||
card.onclick = () => addToCart(p);
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = 'card-body';
|
||||
|
||||
const name = document.createElement('div');
|
||||
name.className = 'product-name';
|
||||
name.textContent = p.name;
|
||||
|
||||
const stock = document.createElement('div');
|
||||
stock.className = 'product-stock';
|
||||
stock.textContent = 'В наличии';
|
||||
|
||||
body.appendChild(name);
|
||||
body.appendChild(stock);
|
||||
card.appendChild(body);
|
||||
col.appendChild(card);
|
||||
grid.appendChild(col);
|
||||
});
|
||||
}
|
||||
|
||||
function addToCart(p) {
|
||||
if (!cart.has(p.id)) {
|
||||
cart.set(p.id, { id: p.id, name: p.name, price: Number(p.price), qty: 1 });
|
||||
} else {
|
||||
cart.get(p.id).qty += 1;
|
||||
}
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function updateQty(id, delta) {
|
||||
if (!cart.has(id)) return;
|
||||
const item = cart.get(id);
|
||||
item.qty += delta;
|
||||
if (item.qty <= 0) cart.delete(id);
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function renderCart() {
|
||||
const list = document.getElementById('cartList');
|
||||
list.innerHTML = '';
|
||||
let total = 0;
|
||||
|
||||
if (cart.size === 0) {
|
||||
list.innerHTML = '<p class="text-muted text-center py-4 small">Корзина пуста</p>';
|
||||
document.getElementById('cartTotal').textContent = '0.00';
|
||||
return;
|
||||
}
|
||||
|
||||
cart.forEach(item => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'mb-2 pb-2 border-bottom';
|
||||
|
||||
const nameRow = document.createElement('div');
|
||||
nameRow.className = 'd-flex justify-content-between align-items-start mb-1';
|
||||
nameRow.innerHTML = `
|
||||
<div class="fw-semibold small">${item.name}</div>
|
||||
<button class="btn btn-sm btn-link text-danger p-0 ms-2" onclick="event.stopPropagation(); removeFromCart(${item.id});">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
const controlsRow = document.createElement('div');
|
||||
controlsRow.className = 'd-flex justify-content-between align-items-center';
|
||||
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'btn-group btn-group-sm';
|
||||
|
||||
const minus = document.createElement('button');
|
||||
minus.className = 'btn btn-outline-secondary';
|
||||
minus.innerHTML = '<i class="bi bi-dash"></i>';
|
||||
minus.onclick = (e) => { e.stopPropagation(); updateQty(item.id, -1); };
|
||||
|
||||
const qtySpan = document.createElement('button');
|
||||
qtySpan.className = 'btn btn-outline-secondary disabled';
|
||||
qtySpan.textContent = item.qty;
|
||||
|
||||
const plus = document.createElement('button');
|
||||
plus.className = 'btn btn-outline-secondary';
|
||||
plus.innerHTML = '<i class="bi bi-plus"></i>';
|
||||
plus.onclick = (e) => { e.stopPropagation(); updateQty(item.id, +1); };
|
||||
|
||||
controls.appendChild(minus);
|
||||
controls.appendChild(qtySpan);
|
||||
controls.appendChild(plus);
|
||||
|
||||
const priceDiv = document.createElement('div');
|
||||
priceDiv.className = 'text-end small';
|
||||
priceDiv.innerHTML = `<strong>${formatMoney(item.price * item.qty)}</strong>`;
|
||||
|
||||
controlsRow.appendChild(controls);
|
||||
controlsRow.appendChild(priceDiv);
|
||||
|
||||
row.appendChild(nameRow);
|
||||
row.appendChild(controlsRow);
|
||||
list.appendChild(row);
|
||||
|
||||
total += item.qty * item.price;
|
||||
});
|
||||
|
||||
document.getElementById('cartTotal').textContent = formatMoney(total);
|
||||
}
|
||||
|
||||
function removeFromCart(id) {
|
||||
cart.delete(id);
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
cart.clear();
|
||||
renderCart();
|
||||
}
|
||||
|
||||
document.getElementById('clearCart').onclick = clearCart;
|
||||
|
||||
// Заглушки для функционала (будет реализовано позже)
|
||||
document.getElementById('checkoutNow').onclick = async () => {
|
||||
alert('Функционал будет подключен позже: создание заказа и списание со склада.');
|
||||
};
|
||||
|
||||
document.getElementById('scheduleLater').onclick = async () => {
|
||||
alert('Функционал будет подключен позже: создание заказа на доставку/самовывоз.');
|
||||
};
|
||||
|
||||
// Categories removed from this view - can be added as filter dropdown later if needed
|
||||
|
||||
// Search functionality
|
||||
document.getElementById('searchInput').addEventListener('input', () => {
|
||||
renderProducts();
|
||||
});
|
||||
|
||||
// Customer selection
|
||||
document.getElementById('customerSelectBtn').addEventListener('click', () => {
|
||||
alert('Функция выбора клиента будет реализована позже');
|
||||
});
|
||||
|
||||
// Инициализация
|
||||
renderProducts();
|
||||
renderCart();
|
||||
Reference in New Issue
Block a user