project2463/templates/catalog.html

868 lines
31 KiB
HTML
Raw Permalink 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>
<!-- Поиск и сортировка - УЛУЧШЕННЫЙ UX -->
<div class="search-section mb-4">
<div class="search-header">
<h4 class="search-title">
<i class="fas fa-sliders-h"></i> Фильтры и поиск
</h4>
<a href="{{ url_for('catalog') }}" class="btn-clear">
<i class="fas fa-times"></i> Сбросить все
</a>
</div>
<form method="GET" action="{{ url_for('catalog') }}" id="filterForm" class="search-form">
<!-- Строка поиска -->
<div class="search-row">
<div class="search-input-wrapper">
<i class="fas fa-search search-icon"></i>
<input type="text" class="search-input" name="search" id="search"
placeholder="Поиск по названию или артикулу..."
value="{{ request.args.get('search', '') }}" autocomplete="off">
{% if request.args.get('search') %}
<a href="{{ url_for('catalog', category=request.args.get('category'), sort=request.args.get('sort'), supplier=request.args.get('supplier')) }}" class="search-clear">
<i class="fas fa-times-circle"></i>
</a>
{% endif %}
</div>
</div>
<!-- Фильтры -->
<div class="filters-row">
<div class="filter-group">
<label for="supplier" class="filter-label">
<i class="fas fa-truck"></i> Поставщик
</label>
<select class="filter-select" name="supplier" id="supplier">
<option value="all" {% if request.args.get('supplier') == 'all' or not request.args.get('supplier') %}selected{% endif %}>Все поставщики</option>
{% for supplier in suppliers %}
<option value="{{ supplier.id }}" {% if request.args.get('supplier')|int == supplier.id %}selected{% endif %}>
{{ supplier.name }}
</option>
{% endfor %}
</select>
</div>
<div class="filter-group">
<label for="category" class="filter-label">
<i class="fas fa-tag"></i> Категория
</label>
<select class="filter-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 class="filter-group">
<label for="sort" class="filter-label">
<i class="fas fa-sort-amount-down"></i> Сортировка
</label>
<select class="filter-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>
<button type="submit" class="btn-apply">
<i class="fas fa-check"></i> Применить
</button>
</div>
</form>
</div>
<!-- Активные фильтры -->
{% if request.args.get('search') or (request.args.get('category') and request.args.get('category') != 'all') or (request.args.get('supplier') and request.args.get('supplier') != 'all') %}
<div class="active-filters mb-3">
<span class="active-filters-label">Активные фильтры:</span>
<div class="filter-tags">
{% if request.args.get('search') %}
<span class="filter-tag">
Поиск: "{{ request.args.get('search') }}"
<a href="{{ url_for('catalog', category=request.args.get('category'), sort=request.args.get('sort'), supplier=request.args.get('supplier')) }}" class="remove-tag">
<i class="fas fa-times"></i>
</a>
</span>
{% endif %}
{% if request.args.get('category') and request.args.get('category') != 'all' %}
<span class="filter-tag">
{% for category in categories %}
{% if category.id == request.args.get('category')|int %}
Категория: {{ category.category_name }}
{% endif %}
{% endfor %}
<a href="{{ url_for('catalog', search=request.args.get('search'), sort=request.args.get('sort'), supplier=request.args.get('supplier')) }}" class="remove-tag">
<i class="fas fa-times"></i>
</a>
</span>
{% endif %}
{% if request.args.get('supplier') and request.args.get('supplier') != 'all' %}
<span class="filter-tag">
{% for supplier in suppliers %}
{% if supplier.id == request.args.get('supplier')|int %}
Поставщик: {{ supplier.name }}
{% endif %}
{% endfor %}
<a href="{{ url_for('catalog', search=request.args.get('search'), category=request.args.get('category'), sort=request.args.get('sort')) }}" class="remove-tag">
<i class="fas fa-times"></i>
</a>
</span>
{% endif %}
</div>
</div>
{% endif %}
<!-- КАТАЛОГ МАТЕРИАЛОВ -->
{% if products %}
<div class="row">
{% for product in products %}
<div class="col-12">
<div class="product-card">
<!-- Первая строка: Категория | Название -->
<div class="product-header">
<div class="product-name-line">
<span class="product-category">
{{ product.category_name }}
</span>
<span class="divider">|</span>
<span class="product-name">{{ product.product_name }}</span>
</div>
<div class="stock-info">
<span class="stock-amount">
{% if product.quantity_in_stock and product.quantity_in_stock > 0 %}
{{ product.quantity_in_stock }} ед.
{% else %}
Нет в наличии
{% endif %}
</span>
</div>
</div>
<!-- Информация о материале -->
<div class="product-info-row">
<span class="info-label">Артикул:</span>
<span class="info-value">{{ product.article }}</span>
</div>
<div class="product-info-row">
<span class="info-label">Материал:</span>
<span class="info-value">
{% if product.material_name %}
{{ product.material_name }}
{% else %}
Не указан
{% endif %}
</span>
</div>
<div class="product-info-row">
<span class="info-label">Поставщик:</span>
<span class="info-value">
{% if product.supplier_name %}
{{ product.supplier_name }}
{% else %}
Не указан
{% endif %}
</span>
</div>
{% if product.description %}
<div class="product-info-row">
<span class="info-label">Описание:</span>
<span class="info-value">{{ product.description }}</span>
</div>
{% endif %}
<div class="product-info-row">
<span class="info-label">Цена:</span>
<span class="info-value price-value">{{ product.min_price|int }} руб.</span>
</div>
<!-- Кнопки действий с выбором поставщика -->
<div class="product-buttons-container">
<a href="{{ url_for('edit_product', product_id=product.id) }}" class="btn-action btn-edit">
<i class="fas fa-edit"></i> Редактирование
</a>
<!-- Форма добавления в корзину с выбором поставщика -->
<form method="POST" action="{{ url_for('add_to_cart', product_id=product.id) }}" style="flex: 1;" class="add-to-cart-form">
<div class="supplier-select-wrapper" style="display: flex; gap: 5px;">
<select name="supplier_id" class="supplier-select" required style="flex: 1; padding: 8px; border: 2px solid #DEB887; border-radius: 8px; font-family: 'Verdana', sans-serif;">
<option value="" disabled selected>Выберите поставщика</option>
{% for supplier in suppliers %}
<option value="{{ supplier.id }}">{{ supplier.name }}</option>
{% endfor %}
</select>
<input type="hidden" name="quantity" value="1">
<button type="submit" class="btn-action btn-add" style="width: auto; padding: 8px 15px;"
{% 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>
</div>
</div>
{% endfor %}
</div>
<!-- Информация о результатах -->
<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 %}
{% if request.args.get('supplier') and request.args.get('supplier') != 'all' %}
{% for supplier in suppliers %}
{% if supplier.id == request.args.get('supplier')|int %}
, поставщик: {{ supplier.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' or request.args.get('supplier') != '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>
let filterTimeout;
document.getElementById('supplier').addEventListener('change', function() {
clearTimeout(filterTimeout);
filterTimeout = setTimeout(() => {
document.getElementById('filterForm').submit();
}, 300);
});
document.getElementById('category').addEventListener('change', function() {
clearTimeout(filterTimeout);
filterTimeout = setTimeout(() => {
document.getElementById('filterForm').submit();
}, 300);
});
document.getElementById('sort').addEventListener('change', function() {
clearTimeout(filterTimeout);
filterTimeout = setTimeout(() => {
document.getElementById('filterForm').submit();
}, 300);
});
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('.add-to-cart-form').forEach(form => {
form.addEventListener('submit', function(e) {
const select = this.querySelector('.supplier-select');
if (!select.value) {
e.preventDefault();
alert('Пожалуйста, выберите поставщика');
}
});
});
</script>
<style>
.page-title {
color: #A0522D;
font-size: 1.8rem;
font-weight: 600;
font-family: 'Verdana', sans-serif;
}
.d-flex.gap-2 {
gap: 10px;
}
.btn-primary {
background: #A0522D;
color: white;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-family: 'Verdana', sans-serif;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-primary:hover {
background: #8B4513;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(160, 82, 45, 0.2);
}
.cart-badge {
background-color: #8B4513;
color: white;
border-radius: 50%;
padding: 2px 6px;
font-size: 0.7rem;
margin-left: 5px;
min-width: 18px;
text-align: center;
}
/* ===== УЛУЧШЕННЫЙ ПОИСК И ФИЛЬТРЫ ===== */
.search-section {
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
padding: 20px;
margin-bottom: 25px;
border: 1px solid #DEB887;
}
.search-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.search-title {
font-size: 1.2rem;
font-weight: 600;
color: #2c3e50;
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.search-title i {
color: #A0522D;
}
.btn-clear {
background: none;
border: 1px solid #DEB887;
padding: 8px 16px;
border-radius: 30px;
color: #6c757d;
font-size: 0.9rem;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-clear:hover {
background: #FAEBD7;
border-color: #A0522D;
color: #A0522D;
}
.search-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.search-row {
width: 100%;
}
.search-input-wrapper {
position: relative;
width: 100%;
}
.search-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #adb5bd;
font-size: 1rem;
pointer-events: none;
}
.search-input {
width: 100%;
height: 48px;
padding: 0 45px 0 45px;
border: 2px solid #DEB887;
border-radius: 30px;
font-size: 1rem;
transition: all 0.2s ease;
background: white;
}
.search-input:focus {
outline: none;
border-color: #A0522D;
box-shadow: 0 0 0 4px rgba(160, 82, 45, 0.1);
}
.search-input::placeholder {
color: #adb5bd;
}
.search-clear {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
color: #adb5bd;
text-decoration: none;
display: flex;
align-items: center;
justify-content: center;
}
.search-clear:hover {
color: #A0522D;
}
.filters-row {
display: flex;
gap: 15px;
align-items: flex-end;
flex-wrap: wrap;
}
.filter-group {
flex: 1;
min-width: 180px;
}
.filter-label {
display: block;
font-size: 0.85rem;
font-weight: 600;
color: #495057;
margin-bottom: 6px;
letter-spacing: 0.3px;
}
.filter-label i {
color: #A0522D;
margin-right: 4px;
font-size: 0.8rem;
}
.filter-select {
width: 100%;
height: 42px;
padding: 0 35px 0 12px;
border: 2px solid #DEB887;
border-radius: 10px;
font-size: 0.95rem;
color: #2c3e50;
background-color: white;
cursor: pointer;
transition: all 0.2s ease;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23A0522D' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
}
.filter-select:hover {
border-color: #A0522D;
}
.filter-select:focus {
outline: none;
border-color: #A0522D;
box-shadow: 0 0 0 3px rgba(160, 82, 45, 0.1);
}
.btn-apply {
background: #A0522D;
color: white;
border: none;
padding: 0 25px;
height: 42px;
border-radius: 10px;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.btn-apply:hover {
background: #8B4513;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(160, 82, 45, 0.3);
}
/* Активные фильтры */
.active-filters {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 10px 15px;
background: #FAEBD7;
border-radius: 30px;
font-size: 0.9rem;
margin-bottom: 20px;
border: 1px solid #DEB887;
}
.active-filters-label {
color: #6c757d;
font-weight: 500;
}
.filter-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.filter-tag {
background: white;
border: 1px solid #DEB887;
padding: 6px 12px;
border-radius: 30px;
font-size: 0.9rem;
color: #2c3e50;
display: inline-flex;
align-items: center;
gap: 8px;
}
.remove-tag {
color: #adb5bd;
text-decoration: none;
display: inline-flex;
align-items: center;
font-size: 0.8rem;
}
.remove-tag:hover {
color: #dc3545;
}
/* ===== КАРТОЧКИ МАТЕРИАЛОВ ===== */
.product-card {
background: white;
border: 1px solid #DEB887;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 4px 8px rgba(160, 82, 45, 0.1);
transition: all 0.2s ease;
}
.product-card:hover {
box-shadow: 0 8px 16px rgba(160, 82, 45, 0.2);
border-color: #A0522D;
}
.product-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #DEB887;
}
.product-name-line {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.product-category {
background-color: transparent;
color: #A0522D;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
text-transform: uppercase;
font-family: 'Verdana', sans-serif;
border: 2px solid #DEB887;
}
.divider {
color: #A0522D;
font-weight: 600;
}
.product-name {
color: #8B4513;
font-size: 1.2rem;
font-weight: 600;
font-family: 'Verdana', sans-serif;
}
.stock-info {
display: flex;
align-items: center;
}
.stock-amount {
color: #A0522D;
font-weight: 600;
font-size: 0.9rem;
background-color: #FAEBD7;
padding: 5px 10px;
border-radius: 6px;
}
.product-info-row {
display: flex;
margin-bottom: 8px;
padding: 8px 0;
border-bottom: 1px dashed #DEB887;
font-family: 'Verdana', sans-serif;
}
.product-info-row:last-of-type {
border-bottom: none;
}
.info-label {
width: 100px;
color: #6c757d;
font-weight: 500;
}
.info-value {
flex: 1;
color: #2c3e50;
font-weight: 500;
}
.price-value {
color: #A0522D;
font-weight: 700;
font-size: 1.1rem;
}
.product-buttons-container {
display: flex;
gap: 10px;
margin-top: 15px;
padding-top: 15px;
border-top: 2px solid #DEB887;
}
.btn-action {
text-align: center;
padding: 10px 15px;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.2s ease;
text-decoration: none;
font-family: 'Verdana', sans-serif;
border: none;
cursor: pointer;
}
.btn-edit {
background: linear-gradient(135deg, #DEB887 0%, #C17B4A 100%);
color: #2c3e50;
}
.btn-edit:hover {
background: linear-gradient(135deg, #C17B4A 0%, #DEB887 100%);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(160, 82, 45, 0.1);
}
.btn-add {
background: linear-gradient(135deg, #A0522D 0%, #8B4513 100%);
color: white;
}
.btn-add:hover {
background: linear-gradient(135deg, #8B4513 0%, #A0522D 100%);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(160, 82, 45, 0.3);
}
.btn-add:disabled {
background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%);
cursor: not-allowed;
transform: none;
box-shadow: none;
opacity: 0.6;
}
.supplier-select {
border: 2px solid #DEB887;
border-radius: 8px;
padding: 8px;
font-family: 'Verdana', sans-serif;
background-color: white;
width: 100%;
}
.supplier-select:focus {
border-color: #A0522D;
outline: none;
}
/* ===== ДОПОЛНИТЕЛЬНЫЕ ЭЛЕМЕНТЫ ===== */
.text-muted {
font-family: 'Verdana', sans-serif;
margin: 0;
color: #6c757d;
}
.alert {
border-radius: 12px;
border: 1px solid #DEB887;
background: #FAEBD7;
border-left: 4px solid #A0522D;
font-family: 'Verdana', sans-serif;
padding: 15px 20px;
}
.alert-link {
color: #A0522D;
font-weight: 600;
text-decoration: none;
}
.alert-link:hover {
text-decoration: underline;
}
i {
color: #A0522D;
margin-right: 6px;
}
.btn-primary i, .btn-clear i, .btn-apply i, .btn-action i {
color: inherit;
}
/* ===== АДАПТИВНОСТЬ ===== */
@media (max-width: 768px) {
.filters-row {
flex-direction: column;
gap: 10px;
}
.filter-group {
width: 100%;
}
.btn-apply {
width: 100%;
justify-content: center;
}
.active-filters {
flex-direction: column;
align-items: flex-start;
border-radius: 12px;
}
.product-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.stock-info {
width: 100%;
justify-content: flex-start;
}
.product-info-row {
flex-direction: column;
}
.info-label {
width: 100%;
margin-bottom: 3px;
}
.product-buttons-container {
flex-direction: column;
}
.supplier-select-wrapper {
flex-direction: column;
}
.d-flex.gap-2 {
flex-direction: column;
}
.d-flex.gap-2 .btn {
width: 100%;
}
}
</style>
{% endblock %}