Добавлены кнопки создания вариативных товаров и групп вариантов на страницы /products/all/ и /products/catalog/. Улучшен табличный режим каталога с фиксированной сеткой колонок, двухстрочными названиями и выравниванием по всей ширине
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
font-size: 0.65rem;
|
||||
padding: 0.15rem 0.35rem;
|
||||
}
|
||||
/* Режим списка */
|
||||
/* Режим списка - табличная структура с фиксированными колонками */
|
||||
.catalog-list.row {
|
||||
gap: 1px !important;
|
||||
}
|
||||
@@ -79,19 +79,22 @@
|
||||
padding: 0 !important;
|
||||
}
|
||||
.catalog-list .catalog-item .card {
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr 150px 120px 120px;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
min-height: 50px;
|
||||
}
|
||||
.catalog-list .catalog-item .card .position-relative {
|
||||
flex-shrink: 0;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
align-self: start;
|
||||
}
|
||||
.catalog-list .catalog-item .card .position-relative img,
|
||||
.catalog-list .catalog-item .card .position-relative > div {
|
||||
height: 100% !important;
|
||||
min-height: 36px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border-radius: 0;
|
||||
object-fit: cover;
|
||||
@@ -100,20 +103,40 @@
|
||||
display: none;
|
||||
}
|
||||
.catalog-list .catalog-item .card-body {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem !important;
|
||||
gap: 0.75rem;
|
||||
display: contents;
|
||||
}
|
||||
.catalog-list .catalog-item .card-body > div:first-child {
|
||||
flex-grow: 1;
|
||||
/* Название товара - ограничиваем 2 строками */
|
||||
.catalog-list .catalog-item .product-name {
|
||||
padding: 0.4rem 0.5rem;
|
||||
line-height: 1.3;
|
||||
max-height: 3.4em;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
font-size: 0.85rem;
|
||||
align-self: center;
|
||||
}
|
||||
.catalog-list .catalog-item .card-body > div:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
white-space: nowrap;
|
||||
/* Остатки */
|
||||
.catalog-list .catalog-item .product-stock {
|
||||
padding: 0.4rem 0.25rem;
|
||||
text-align: left;
|
||||
font-size: 0.8rem;
|
||||
align-self: center;
|
||||
}
|
||||
/* Цена */
|
||||
.catalog-list .catalog-item .product-price {
|
||||
padding: 0.4rem 0.25rem;
|
||||
text-align: left;
|
||||
font-size: 0.85rem;
|
||||
align-self: center;
|
||||
}
|
||||
/* Артикул */
|
||||
.catalog-list .catalog-item .product-sku {
|
||||
padding: 0.4rem 0.5rem 0.4rem 0.25rem;
|
||||
text-align: left;
|
||||
font-size: 0.8rem;
|
||||
align-self: center;
|
||||
}
|
||||
.catalog-list .catalog-item .card-body .mt-1 {
|
||||
margin-top: 0 !important;
|
||||
@@ -239,6 +262,20 @@
|
||||
|
||||
<!-- Сетка товаров справа -->
|
||||
<div class="col-lg-8 col-md-7">
|
||||
<!-- Кнопки действий -->
|
||||
{% if action_buttons %}
|
||||
<div class="mb-3">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
{% for button in action_buttons %}
|
||||
<a href="{{ button.url }}" class="btn {{ button.class|default:'btn-primary' }} btn-sm me-2 mb-2">
|
||||
{% if button.icon %}<i class="bi bi-{{ button.icon }}"></i>{% endif %}
|
||||
{{ button.text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white py-2 d-flex justify-content-between align-items-center">
|
||||
<strong><i class="bi bi-grid-3x3-gap text-primary"></i> Товары и комплекты</strong>
|
||||
@@ -260,6 +297,7 @@
|
||||
{% for item in items %}
|
||||
<div class="col-12 catalog-item" data-type="{{ item.item_type }}" data-category-ids="{% for cat in item.cached_categories %}{{ cat.pk }}{% if not forloop.last %},{% endif %}{% endfor %}">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<!-- Фото -->
|
||||
<div class="position-relative">
|
||||
{% if item.cached_main_photo %}
|
||||
<img src="{{ item.cached_main_photo.get_thumbnail_url }}" class="card-img-top" alt="{{ item.name }}" style="height: 120px; object-fit: cover;">
|
||||
@@ -272,8 +310,10 @@
|
||||
{% if item.item_type == 'kit' %}К{% else %}Т{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-2">
|
||||
<div class="small text-truncate fw-medium">
|
||||
<!-- Название товара (2 строки max) -->
|
||||
<div class="product-name fw-medium">
|
||||
{% if item.item_type == 'kit' %}
|
||||
<a href="{% url 'products:productkit-detail' item.pk %}" class="text-decoration-none">{{ item.name }}</a>
|
||||
{% else %}
|
||||
@@ -281,72 +321,59 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if item.item_type == 'product' %}
|
||||
{# Информация об остатках для товаров #}
|
||||
<div class="mt-1">
|
||||
<small class="stock-info {% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<strong>{{ item.total_free|floatformat:"-3" }}</strong> свободно
|
||||
<span class="text-muted">/ {{ item.total_available|floatformat:"-3" }} всего</span>
|
||||
</small>
|
||||
</div>
|
||||
{% elif item.item_type == 'kit' %}
|
||||
{# Информация об остатках для комплектов #}
|
||||
<div class="mt-1">
|
||||
<small class="stock-info {% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<strong>{{ item.total_free|floatformat:"0" }}</strong> компл. доступно
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-1">
|
||||
<!-- Остатки -->
|
||||
<div class="product-stock">
|
||||
{% if item.item_type == 'product' %}
|
||||
<div class="price-edit-container d-flex align-items-center gap-1 flex-wrap">
|
||||
<small class="stock-info {% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<strong>{{ item.total_free|floatformat:"-3" }}</strong>
|
||||
<span class="text-muted">/ {{ item.total_available|floatformat:"-3" }}</span>
|
||||
</small>
|
||||
{% elif item.item_type == 'kit' %}
|
||||
<small class="stock-info {% if item.total_free > 0 %}text-success{% else %}text-danger{% endif %}">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<strong>{{ item.total_free|floatformat:"0" }}</strong> компл.
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Цена -->
|
||||
<div class="product-price">
|
||||
{% if item.item_type == 'product' %}
|
||||
<div class="price-edit-container">
|
||||
{% if item.sale_price %}
|
||||
{# Скидочная цена #}
|
||||
<span class="editable-price sale-price fw-bold text-success small"
|
||||
data-product-id="{{ item.pk }}"
|
||||
data-field="sale_price"
|
||||
data-current-value="{{ item.sale_price }}"
|
||||
title="Скидочная цена (клик для редактирования)">
|
||||
{{ item.sale_price|floatformat:2 }} руб.
|
||||
title="Скидочная цена">
|
||||
{{ item.sale_price|floatformat:2 }}
|
||||
</span>
|
||||
{# Обычная цена зачеркнутая #}
|
||||
<br>
|
||||
<span class="editable-price regular-price text-muted text-decoration-line-through small"
|
||||
data-product-id="{{ item.pk }}"
|
||||
data-field="price"
|
||||
data-current-value="{{ item.price }}"
|
||||
title="Обычная цена (клик для редактирования)">
|
||||
{{ item.price|floatformat:2 }} руб.
|
||||
title="Обычная цена">
|
||||
{{ item.price|floatformat:2 }}
|
||||
</span>
|
||||
{# Кнопка удаления скидки #}
|
||||
<i class="bi bi-x-circle text-danger remove-sale-price"
|
||||
data-product-id="{{ item.pk }}"
|
||||
title="Убрать скидку"
|
||||
style="cursor: pointer; font-size: 0.85rem;"></i>
|
||||
{% else %}
|
||||
{# Только обычная цена #}
|
||||
<span class="editable-price fw-bold text-primary small"
|
||||
data-product-id="{{ item.pk }}"
|
||||
data-field="price"
|
||||
data-current-value="{{ item.price }}"
|
||||
title="Цена (клик для редактирования)">
|
||||
title="Цена">
|
||||
{{ item.price|floatformat:2 }} руб.
|
||||
</span>
|
||||
{# Кнопка добавления скидки #}
|
||||
<button class="btn btn-outline-secondary btn-sm add-sale-price py-0 px-1"
|
||||
data-product-id="{{ item.pk }}"
|
||||
title="Добавить скидочную цену"
|
||||
style="font-size: 0.7rem; line-height: 1.2;">
|
||||
+ скидка
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{# ProductKit - не редактируется #}
|
||||
<span class="fw-bold text-primary small">{{ item.actual_price|floatformat:2 }} руб.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Артикул -->
|
||||
<div class="product-sku">
|
||||
<small class="text-muted">{{ item.sku }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.views.generic import ListView
|
||||
from django.db.models import Prefetch, Sum, Value, DecimalField, Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.core.paginator import Paginator
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from ..models import Product, ProductKit, ProductCategory, ProductPhoto, ProductKitPhoto, KitItem
|
||||
|
||||
@@ -108,4 +109,41 @@ class CatalogView(LoginRequiredMixin, ListView):
|
||||
category_tree = self.build_category_tree(categories, parent=None)
|
||||
context['category_tree'] = category_tree
|
||||
|
||||
# Кнопки действий
|
||||
action_buttons = []
|
||||
|
||||
if self.request.user.has_perm('products.add_product'):
|
||||
action_buttons.append({
|
||||
'url': reverse_lazy('products:product-create'),
|
||||
'text': 'Создать товар',
|
||||
'class': 'btn-primary',
|
||||
'icon': 'plus-circle'
|
||||
})
|
||||
|
||||
if self.request.user.has_perm('products.add_productkit'):
|
||||
action_buttons.append({
|
||||
'url': reverse_lazy('products:productkit-create'),
|
||||
'text': 'Создать комплект',
|
||||
'class': 'btn-outline-primary',
|
||||
'icon': 'box-seam'
|
||||
})
|
||||
|
||||
if self.request.user.has_perm('products.add_configurableproduct'):
|
||||
action_buttons.append({
|
||||
'url': reverse_lazy('products:configurableproduct-create'),
|
||||
'text': 'Создать вариативный товар',
|
||||
'class': 'btn-outline-success',
|
||||
'icon': 'grid-3x3-gap'
|
||||
})
|
||||
|
||||
if self.request.user.has_perm('products.add_productvariantgroup'):
|
||||
action_buttons.append({
|
||||
'url': reverse_lazy('products:variantgroup-create'),
|
||||
'text': 'Создать группу вариантов',
|
||||
'class': 'btn-outline-info',
|
||||
'icon': 'collection'
|
||||
})
|
||||
|
||||
context['action_buttons'] = action_buttons
|
||||
|
||||
return context
|
||||
|
||||
@@ -402,6 +402,22 @@ class CombinedProductListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Lis
|
||||
'icon': 'box-seam'
|
||||
})
|
||||
|
||||
if self.request.user.has_perm('products.add_configurableproduct'):
|
||||
action_buttons.append({
|
||||
'url': reverse_lazy('products:configurableproduct-create'),
|
||||
'text': 'Создать вариативный товар',
|
||||
'class': 'btn-outline-success',
|
||||
'icon': 'grid-3x3-gap'
|
||||
})
|
||||
|
||||
if self.request.user.has_perm('products.add_productvariantgroup'):
|
||||
action_buttons.append({
|
||||
'url': reverse_lazy('products:variantgroup-create'),
|
||||
'text': 'Создать группу вариантов',
|
||||
'class': 'btn-outline-info',
|
||||
'icon': 'collection'
|
||||
})
|
||||
|
||||
context['action_buttons'] = action_buttons
|
||||
|
||||
return context
|
||||
|
||||
Reference in New Issue
Block a user