Создано приложение POS с планшетным интерфейсом терминала продаж

This commit is contained in:
2025-11-16 13:38:28 +03:00
parent a073b1aa77
commit 139ac431ee
14 changed files with 406 additions and 3 deletions

View File

@@ -0,0 +1,53 @@
/* POS Terminal Styles */
body {
background-color: #e9ecef;
}
.pos-container {
max-width: 100%;
padding: 0 1rem;
}
.product-card {
cursor: pointer;
user-select: none;
transition: all 0.2s;
border-radius: 12px;
border: 1px solid #dee2e6;
background: white;
height: 100%;
min-height: 140px;
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.product-card.selected {
background: #e7f3ff;
border-color: #0d6efd;
}
.product-card .card-body {
padding: 1.25rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.product-name {
font-weight: 500;
font-size: 1rem;
margin-bottom: 0.5rem;
color: #495057;
}
.product-stock {
font-size: 0.9rem;
color: #6c757d;
font-style: italic;
}

View 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();