my-project/templates/catalog.html
2026-03-23 11:35:48 +05:00

564 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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 %}