564 lines
22 KiB
HTML
564 lines
22 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Каталог товаров{% endblock %}
|
||
|
||
{% block content %}
|
||
<!-- Заголовок с корзиной и кнопкой добавления -->
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h2 class="page-title">Каталог товаров</h2>
|
||
<div class="d-flex gap-2">
|
||
<a href="{{ url_for('add_product') }}" class="btn btn-primary">
|
||
<i class="fas fa-plus"></i> Добавить товар
|
||
</a>
|
||
<a href="{{ url_for('view_cart') }}" class="btn btn-primary">
|
||
<i class="fas fa-shopping-cart"></i> Корзина
|
||
{% if cart_count > 0 %}
|
||
<span class="cart-badge">{{ cart_count }}</span>
|
||
{% endif %}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Поиск и сортировка -->
|
||
<div class="filter-section mb-4">
|
||
<h4 class="filter-title">Поиск и сортировка</h4>
|
||
<form method="GET" action="{{ url_for('catalog') }}" id="filterForm">
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="search" class="form-label">Поиск по названию или артикулу:</label>
|
||
<div class="input-group">
|
||
<input type="text" class="form-control" name="search" id="search"
|
||
placeholder="Введите название или артикул..."
|
||
value="{{ request.args.get('search', '') }}">
|
||
<button class="btn btn-primary" type="submit">
|
||
<i class="fas fa-search"></i> Найти
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3 mb-3">
|
||
<label for="sort" class="form-label">Сортировать по:</label>
|
||
<select class="form-select" name="sort" id="sort">
|
||
<option value="name_asc" {% if request.args.get('sort') == 'name_asc' or not request.args.get('sort') %}selected{% endif %}>Названию (А-Я)</option>
|
||
<option value="name_desc" {% if request.args.get('sort') == 'name_desc' %}selected{% endif %}>Названию (Я-А)</option>
|
||
<option value="price_asc" {% if request.args.get('sort') == 'price_asc' %}selected{% endif %}>Цене (сначала дешевые)</option>
|
||
<option value="price_desc" {% if request.args.get('sort') == 'price_desc' %}selected{% endif %}>Цене (сначала дорогие)</option>
|
||
<option value="stock_desc" {% if request.args.get('sort') == 'stock_desc' %}selected{% endif %}>Наличию (сначала много)</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3 mb-3">
|
||
<label for="category" class="form-label">Категория:</label>
|
||
<select class="form-select" name="category" id="category">
|
||
<option value="all" {% if request.args.get('category') == 'all' or not request.args.get('category') %}selected{% endif %}>Все категории</option>
|
||
{% for category in categories %}
|
||
<option value="{{ category.id }}" {% if request.args.get('category')|int == category.id %}selected{% endif %}>
|
||
{{ category.category_name }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="row mt-2">
|
||
<div class="col-12">
|
||
<a href="{{ url_for('catalog') }}" class="btn btn-secondary">
|
||
<i class="fas fa-times"></i> Сбросить все фильтры
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Каталог товаров -->
|
||
{% if products %}
|
||
{% for product in products %}
|
||
<div class="product-card mb-3">
|
||
<div class="product-card-header">
|
||
<div class="d-flex justify-content-between align-items-start">
|
||
<div>
|
||
<h5 class="product-title">{{ product.product_name }}</h5>
|
||
<span class="product-article">Артикул: {{ product.article }}</span>
|
||
</div>
|
||
<a href="{{ url_for('edit_product', product_id=product.id) }}" class="btn-edit-icon" title="Редактировать товар">
|
||
<i class="fas fa-edit"></i>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div class="product-card-body">
|
||
<div class="row">
|
||
<div class="col-md-8">
|
||
<div class="product-info">
|
||
<span class="info-label">Категория:</span>
|
||
<span class="info-value">{{ product.category_name }}</span>
|
||
</div>
|
||
|
||
<div class="product-info">
|
||
<span class="info-label">Материал:</span>
|
||
<span class="info-value">
|
||
{% if product.material_name %}
|
||
{{ product.material_name }}
|
||
{% else %}
|
||
Не указан
|
||
{% endif %}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="product-info">
|
||
<span class="info-label">Время изготовления:</span>
|
||
<span class="info-value">{{ product.production_time_days or 1 }} дн.</span>
|
||
</div>
|
||
|
||
{% if product.description %}
|
||
<div class="product-info">
|
||
<span class="info-label">Описание:</span>
|
||
<span class="info-value">{{ product.description }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="product-info">
|
||
<span class="info-label">В наличии:</span>
|
||
<span class="info-value">
|
||
{% if product.quantity_in_stock and product.quantity_in_stock > 0 %}
|
||
<span class="stock-badge">{{ product.quantity_in_stock }} ед.</span>
|
||
{% else %}
|
||
<span class="text-danger">Нет в наличии</span>
|
||
{% endif %}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="product-price">
|
||
<span class="price-label">Цена:</span>
|
||
<span class="price-value">{{ product.min_price|int }} руб.</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Кнопки действий с выбором партнера -->
|
||
<div class="product-card-footer mt-3">
|
||
<form method="POST" action="{{ url_for('add_to_cart', product_id=product.id) }}" class="row g-2 align-items-end">
|
||
<div class="col-md-4">
|
||
<label class="form-label small mb-1">Количество:</label>
|
||
<input type="number" name="quantity" value="1" min="1"
|
||
{% if product.quantity_in_stock and product.quantity_in_stock > 0 %}max="{{ product.quantity_in_stock }}"{% endif %}
|
||
class="form-control quantity-input" style="width: 100%;"
|
||
onkeypress="return isNumberKey(event)"
|
||
oninput="this.value = this.value.replace(/[^0-9]/g, '')">
|
||
</div>
|
||
<div class="col-md-5">
|
||
<label class="form-label small mb-1">Для партнера:</label>
|
||
<select name="client_id" class="form-select" required>
|
||
<option value="">Выберите партнера</option>
|
||
{% for client in clients %}
|
||
<option value="{{ client.id }}">{{ client.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<button type="submit" class="btn btn-primary w-100"
|
||
{% if not product.quantity_in_stock or product.quantity_in_stock == 0 %}disabled{% endif %}>
|
||
<i class="fas fa-cart-plus"></i> В корзину
|
||
</button>
|
||
</div>
|
||
</form>
|
||
<div class="mt-2">
|
||
<a href="{{ url_for('product_detail', product_id=product.id) }}" class="btn btn-info btn-sm w-100">
|
||
<i class="fas fa-eye"></i> Подробнее
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<!-- Информация о результатах поиска -->
|
||
<div class="row mt-3">
|
||
<div class="col-12">
|
||
<p class="text-muted">
|
||
<i class="fas fa-info-circle"></i>
|
||
Найдено товаров: <strong>{{ products|length }}</strong>
|
||
{% if request.args.get('search') %}
|
||
по запросу "{{ request.args.get('search') }}"
|
||
{% endif %}
|
||
{% if request.args.get('category') and request.args.get('category') != 'all' %}
|
||
{% for category in categories %}
|
||
{% if category.id == request.args.get('category')|int %}
|
||
, категория: {{ category.category_name }}
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{% else %}
|
||
<div class="alert alert-info">
|
||
<i class="fas fa-info-circle"></i>
|
||
{% if request.args.get('search') or request.args.get('category') != 'all' %}
|
||
<strong>Ничего не найдено</strong> по вашему запросу.
|
||
<br><a href="{{ url_for('catalog') }}" class="alert-link">Сбросить все фильтры</a>
|
||
{% else %}
|
||
В каталоге нет товаров. <a href="{{ url_for('add_product') }}" class="alert-link">Добавьте первый товар</a>.
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- JavaScript -->
|
||
<script>
|
||
function isNumberKey(evt) {
|
||
var charCode = (evt.which) ? evt.which : evt.keyCode;
|
||
if (charCode == 8 || charCode == 46 || charCode == 9 || charCode == 13 ||
|
||
charCode == 37 || charCode == 38 || charCode == 39 || charCode == 40) {
|
||
return true;
|
||
}
|
||
if (charCode >= 48 && charCode <= 57) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
document.getElementById('sort').addEventListener('change', function() {
|
||
document.getElementById('filterForm').submit();
|
||
});
|
||
|
||
document.getElementById('category').addEventListener('change', function() {
|
||
document.getElementById('filterForm').submit();
|
||
});
|
||
|
||
let searchTimeout;
|
||
document.getElementById('search').addEventListener('input', function() {
|
||
clearTimeout(searchTimeout);
|
||
searchTimeout = setTimeout(() => {
|
||
if (this.value.length >= 2 || this.value.length === 0) {
|
||
document.getElementById('filterForm').submit();
|
||
}
|
||
}, 800);
|
||
});
|
||
|
||
document.getElementById('search').addEventListener('keypress', function(e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
document.getElementById('filterForm').submit();
|
||
}
|
||
});
|
||
|
||
document.querySelectorAll('.quantity-input').forEach(input => {
|
||
input.addEventListener('paste', function(e) {
|
||
e.preventDefault();
|
||
return false;
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<style>
|
||
.page-title {
|
||
color: var(--steel-blue);
|
||
font-size: 1.8rem;
|
||
font-weight: 600;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.d-flex.gap-2 {
|
||
gap: 10px;
|
||
}
|
||
|
||
.filter-section {
|
||
background-color: var(--alice-blue);
|
||
padding: 20px;
|
||
border-radius: 12px;
|
||
border: 1px solid var(--light-steel-blue);
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.filter-title {
|
||
color: var(--steel-blue);
|
||
margin-bottom: 20px;
|
||
font-size: 1.25rem;
|
||
font-weight: 500;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.form-label {
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
color: var(--steel-blue);
|
||
font-weight: 500;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.form-control, .form-select {
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 8px;
|
||
height: 38px;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.form-select {
|
||
padding: 0 30px 0 12px;
|
||
background-position: right 10px center;
|
||
line-height: 38px;
|
||
}
|
||
|
||
.form-control:focus, .form-select:focus {
|
||
border-color: var(--steel-blue);
|
||
box-shadow: 0 0 0 0.2rem rgba(70, 130, 180, 0.25);
|
||
outline: none;
|
||
}
|
||
|
||
.btn {
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
transition: all 0.3s ease;
|
||
font-size: 1rem;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 5px;
|
||
height: 38px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, var(--steel-blue) 0%, #3a6d99 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: linear-gradient(135deg, #3a6d99 0%, var(--steel-blue) 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.3);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:disabled {
|
||
background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%);
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: linear-gradient(135deg, #9fb6d4 0%, var(--light-steel-blue) 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 10px rgba(176, 196, 222, 0.3);
|
||
}
|
||
|
||
.btn-info {
|
||
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
font-size: 1rem;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 5px;
|
||
height: 38px;
|
||
}
|
||
|
||
.btn-info:hover {
|
||
background: linear-gradient(135deg, #138496 0%, #17a2b8 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 10px rgba(23, 162, 184, 0.3);
|
||
color: white;
|
||
}
|
||
|
||
.btn-info.btn-sm {
|
||
height: auto;
|
||
padding: 8px 12px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.cart-badge {
|
||
background-color: #dc3545;
|
||
color: white;
|
||
border-radius: 50%;
|
||
padding: 2px 6px;
|
||
font-size: 0.7rem;
|
||
margin-left: 5px;
|
||
min-width: 18px;
|
||
text-align: center;
|
||
}
|
||
|
||
.product-card {
|
||
background: linear-gradient(135deg, var(--alice-blue) 0%, #E6F2FF 100%);
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 8px rgba(70, 130, 180, 0.1);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.product-card:hover {
|
||
box-shadow: 0 8px 16px rgba(70, 130, 180, 0.2);
|
||
border-color: var(--steel-blue);
|
||
}
|
||
|
||
.product-card-header {
|
||
border-bottom: 1px solid var(--light-steel-blue);
|
||
padding-bottom: 10px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.product-title {
|
||
color: var(--steel-blue);
|
||
font-size: 1.3rem;
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.product-article {
|
||
color: #6c757d;
|
||
font-size: 0.9rem;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.btn-edit-icon {
|
||
background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);
|
||
color: #2c3e50;
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-decoration: none;
|
||
transition: all 0.2s ease;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.btn-edit-icon:hover {
|
||
background: linear-gradient(135deg, #9fb6d4 0%, var(--light-steel-blue) 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.product-info {
|
||
margin-bottom: 8px;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.info-label {
|
||
color: #6c757d;
|
||
font-weight: 500;
|
||
min-width: 100px;
|
||
display: inline-block;
|
||
}
|
||
|
||
.info-value {
|
||
color: #2c3e50;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.product-price {
|
||
margin-top: 10px;
|
||
padding: 10px;
|
||
background-color: rgba(70, 130, 180, 0.05);
|
||
border-radius: 8px;
|
||
text-align: center;
|
||
}
|
||
|
||
.price-label {
|
||
color: #6c757d;
|
||
font-weight: 500;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.price-value {
|
||
color: var(--steel-blue);
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.product-card-footer {
|
||
margin-top: 15px;
|
||
padding-top: 15px;
|
||
border-top: 1px solid var(--light-steel-blue);
|
||
}
|
||
|
||
.product-card-footer .form-control,
|
||
.product-card-footer .form-select {
|
||
height: 38px;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.product-card-footer .btn {
|
||
height: 38px;
|
||
padding: 0 15px;
|
||
}
|
||
|
||
.stock-badge {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 0.85rem;
|
||
font-weight: 500;
|
||
background: linear-gradient(135deg, var(--steel-blue) 0%, #3a6d99 100%);
|
||
color: white;
|
||
}
|
||
|
||
.badge {
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.text-danger {
|
||
color: #dc3545;
|
||
}
|
||
|
||
.text-muted {
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
margin: 0;
|
||
}
|
||
|
||
.alert {
|
||
border-radius: 12px;
|
||
border: 1px solid var(--light-steel-blue);
|
||
background: linear-gradient(135deg, var(--alice-blue) 0%, #E6F2FF 100%);
|
||
border-left: 6px solid var(--steel-blue);
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.alert-link {
|
||
color: var(--steel-blue);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.input-group .btn {
|
||
border-top-left-radius: 0;
|
||
border-bottom-left-radius: 0;
|
||
height: 38px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.col-md-4, .col-md-8 {
|
||
width: 100%;
|
||
}
|
||
|
||
.product-price {
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.btn {
|
||
width: 100%;
|
||
justify-content: center;
|
||
}
|
||
|
||
.d-flex.gap-2 {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.d-flex.gap-2 .btn {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %} |