Улучшены карточки товаров в POS: добавлены фото, артикул, цена и статус наличия

This commit is contained in:
2025-11-16 14:54:31 +03:00
parent bb51a72f4c
commit fab4c78966
4 changed files with 111 additions and 14 deletions

View File

@@ -30,7 +30,9 @@ body {
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
background: white; background: white;
height: 100%; height: 100%;
min-height: 140px; min-height: 200px;
display: flex;
flex-direction: column;
} }
.product-card:hover { .product-card:hover {
@@ -44,27 +46,73 @@ body {
} }
.product-card .card-body { .product-card .card-body {
padding: 1.25rem; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; height: 100%;
}
.product-image {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 12px 12px 0 0;
background: #f8f9fa;
display: flex;
align-items: center; align-items: center;
text-align: center; justify-content: center;
color: #adb5bd;
font-size: 3rem;
}
.product-image img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 12px 12px 0 0;
}
.product-info {
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
flex-grow: 1;
} }
.product-name { .product-name {
font-weight: 500; font-weight: 600;
font-size: 1rem; font-size: 0.9rem;
margin-bottom: 0.5rem; line-height: 1.2;
color: #495057; overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
color: #212529;
} }
.product-stock { .product-stock {
font-size: 0.9rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
font-style: italic; font-style: italic;
} }
.product-sku {
font-size: 0.75rem;
color: #adb5bd;
margin-top: auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 0.85rem;
font-weight: 600;
color: #212529;
}
/* Карточки категорий */ /* Карточки категорий */
.category-card { .category-card {
cursor: pointer; cursor: pointer;

View File

@@ -3,6 +3,11 @@
const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent); const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent);
const PRODUCTS = JSON.parse(document.getElementById('productsData').textContent); const PRODUCTS = JSON.parse(document.getElementById('productsData').textContent);
// Отладка: проверить количество загруженных товаров
console.log('Загружено категорий:', CATEGORIES.length);
console.log('Загружено товаров:', PRODUCTS.length);
console.log('Товары:', PRODUCTS);
let currentCategoryId = null; let currentCategoryId = null;
const cart = new Map(); // productId -> {id, name, price, qty} const cart = new Map(); // productId -> {id, name, price, qty}
@@ -85,16 +90,52 @@ function renderProducts() {
const body = document.createElement('div'); const body = document.createElement('div');
body.className = 'card-body'; body.className = 'card-body';
// Изображение товара
const imageDiv = document.createElement('div');
imageDiv.className = 'product-image';
if (p.image) {
const img = document.createElement('img');
img.src = p.image;
img.alt = p.name;
imageDiv.appendChild(img);
} else {
imageDiv.innerHTML = '<i class="bi bi-image"></i>';
}
// Информация о товаре
const info = document.createElement('div');
info.className = 'product-info';
const name = document.createElement('div'); const name = document.createElement('div');
name.className = 'product-name'; name.className = 'product-name';
name.textContent = p.name; name.textContent = p.name;
const stock = document.createElement('div'); const stock = document.createElement('div');
stock.className = 'product-stock'; stock.className = 'product-stock';
stock.textContent = 'В наличии'; stock.textContent = p.in_stock ? 'В наличии' : 'Под заказ';
if (!p.in_stock) {
stock.style.color = '#dc3545';
}
body.appendChild(name); const sku = document.createElement('div');
body.appendChild(stock); sku.className = 'product-sku';
const skuText = document.createElement('span');
skuText.textContent = p.sku || 'н/д';
const priceSpan = document.createElement('span');
priceSpan.className = 'product-price';
priceSpan.textContent = `${formatMoney(p.price)}`;
sku.appendChild(skuText);
sku.appendChild(priceSpan);
info.appendChild(name);
info.appendChild(stock);
info.appendChild(sku);
body.appendChild(imageDiv);
body.appendChild(info);
card.appendChild(body); card.appendChild(body);
col.appendChild(card); col.appendChild(card);
grid.appendChild(col); grid.appendChild(col);
@@ -218,3 +259,6 @@ document.getElementById('customerSelectBtn').addEventListener('click', () => {
renderCategories(); renderCategories();
renderProducts(); renderProducts();
renderCart(); renderCart();
// Установить фокус на строку поиска
document.getElementById('searchInput').focus();

View File

@@ -23,6 +23,7 @@
<div class="row g-3" id="categoryGrid"></div> <div class="row g-3" id="categoryGrid"></div>
</div> </div>
<!-- Products Grid (Блок товаров) -->
<div class="row g-3" id="productGrid"></div> <div class="row g-3" id="productGrid"></div>
</div> </div>

View File

@@ -12,14 +12,18 @@ def pos_terminal(request):
Shows categories and in-stock products for quick tap-to-add. Shows categories and in-stock products for quick tap-to-add.
""" """
categories_qs = ProductCategory.objects.filter(is_active=True) categories_qs = ProductCategory.objects.filter(is_active=True)
products_qs = Product.objects.filter(in_stock=True).prefetch_related('categories') # Показываем все товары, не только in_stock
products_qs = Product.objects.all().prefetch_related('categories', 'photos')
categories = [{'id': c.id, 'name': c.name} for c in categories_qs] categories = [{'id': c.id, 'name': c.name} for c in categories_qs]
products = [{ products = [{
'id': p.id, 'id': p.id,
'name': p.name, 'name': p.name,
'price': str(p.actual_price), 'price': str(p.actual_price),
'category_ids': [c.id for c in p.categories.all()] 'category_ids': [c.id for c in p.categories.all()],
'in_stock': p.in_stock,
'sku': p.sku or '',
'image': p.photos.first().get_thumbnail_url() if p.photos.exists() else None
} for p in products_qs] } for p in products_qs]
context = { context = {