kancelyary_project/templates/products.html

778 lines
22 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 content %}
<div class="page-header">
<h2>Каталог товаров</h2>
<div class="header-actions">
<button onclick="showAddProductModal()" class="btn-add-light">+ Добавить товар</button>
<select id="typeFilter" class="filter-select" onchange="filterProducts()">
<option value="all">Все типы</option>
{% for type in product_types %}
<option value="{{ type.id }}">{{ type.type_name }}</option>
{% endfor %}
</select>
<input type="text" id="searchInput" placeholder="Поиск по названию..." class="search-input" onkeyup="filterProducts()">
</div>
</div>
{% if products %}
<div class="products-grid" id="productsGrid">
{% for product in products %}
<div class="product-card" data-type="{{ product.product_type_id }}" data-name="{{ product.product_name.lower() }}">
<div class="product-header">
<h3>{{ product.product_name }}</h3>
<span class="product-type">{{ product.product_type_name }}</span>
</div>
<div class="product-body">
<p><strong>Артикул:</strong> {{ product.article }}</p>
<p><strong>Цена для партнеров:</strong> {{ product.min_partner_price }} ₽</p>
<p><strong>Цена с наценкой 10%:</strong> {{ "%.2f"|format(product.min_partner_price * 1.1) }} ₽</p>
</div>
<div class="product-footer">
<button onclick="editProduct({{ product.id }})" class="btn-edit-small">✏️ Редактировать</button>
<button onclick="addToCart({{ product.id }}, '{{ product.product_name }}', {{ product.min_partner_price }})"
class="btn-add-to-cart">В корзину</button>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>Товаров пока нет</p>
<button onclick="showAddProductModal()" class="btn-add-light">Добавить первый товар</button>
</div>
{% endif %}
<!-- Модальное окно добавления/редактирования товара (БЕЗ КНОПКИ УДАЛЕНИЯ) -->
<div id="productModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">Добавление товара</h3>
<span class="close" onclick="closeProductModal()">&times;</span>
</div>
<div class="modal-body">
<form id="productForm" onsubmit="saveProduct(event)">
<input type="hidden" id="productId" name="product_id">
<div class="form-group">
<label for="productType">Тип товара *</label>
<div class="type-selector">
<select id="productType" name="product_type_id" required onchange="toggleNewTypeInput()">
<option value="">Выберите тип</option>
{% for type in product_types %}
<option value="{{ type.id }}">{{ type.type_name }}</option>
{% endfor %}
<option value="new">+ Добавить новый тип</option>
</select>
</div>
</div>
<div id="newTypeContainer" class="form-group" style="display: none;">
<label for="newTypeName">Название нового типа *</label>
<input type="text" id="newTypeName" name="new_type_name" maxlength="100">
</div>
<div class="form-group">
<label for="productName">Наименование товара *</label>
<input type="text" id="productName" name="product_name" required maxlength="255">
</div>
<div class="form-group">
<label for="productArticle">Артикул *</label>
<input type="text" id="productArticle" name="article" required maxlength="100">
</div>
<div class="form-group">
<label for="productPrice">Цена для партнеров *</label>
<input type="number" id="productPrice" name="min_partner_price" step="0.01" min="0.01" required>
</div>
<div class="form-actions">
<button type="submit" class="btn-submit">Сохранить</button>
<button type="button" class="btn-reset" onclick="closeProductModal()">Отмена</button>
</div>
</form>
</div>
</div>
</div>
<!-- Модальное окно корзины для создания заказа -->
<div id="cartModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>Создание заказа</h3>
<span class="close" onclick="closeCartModal()">&times;</span>
</div>
<div class="modal-body">
<div class="cart-info">
<div class="form-group">
<label for="cartPartner">Партнер *</label>
<select id="cartPartner" class="form-control" required>
<option value="">Выберите партнера</option>
{% for partner in partners %}
<option value="{{ partner.id }}">{{ partner.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="cartDate">Дата заказа *</label>
<input type="date" id="cartDate" class="form-control" value="{{ today }}">
</div>
<div class="form-group">
<label for="cartNotes">Примечания</label>
<textarea id="cartNotes" class="form-control" rows="2"></textarea>
</div>
</div>
<h4>Товары в заказе</h4>
<table class="cart-table" id="cartTable">
<thead>
<tr>
<th>Товар</th>
<th>Количество</th>
<th>Цена</th>
<th>Сумма с наценкой</th>
<th></th>
</tr>
</thead>
<tbody id="cartItems">
<!-- Будут добавлены через JavaScript -->
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right">Итого с наценкой:</td>
<td id="cartTotal">0.00 ₽</td>
<td></td>
</tr>
</tfoot>
</table>
<div class="cart-actions">
<button type="button" onclick="createOrderFromCart()" class="btn-submit">Оформить заказ</button>
<button type="button" onclick="clearCart()" class="btn-reset">Очистить корзину</button>
</div>
</div>
</div>
</div>
<script>
let cart = [];
let editingProductId = null;
function filterProducts() {
const searchText = document.getElementById('searchInput').value.toLowerCase();
const typeFilter = document.getElementById('typeFilter').value;
const products = document.querySelectorAll('.product-card');
products.forEach(product => {
const name = product.dataset.name;
const type = product.dataset.type;
const matchesSearch = name.includes(searchText);
const matchesType = typeFilter === 'all' || type == typeFilter;
product.style.display = (matchesSearch && matchesType) ? 'block' : 'none';
});
}
function toggleNewTypeInput() {
const typeSelect = document.getElementById('productType');
const newTypeContainer = document.getElementById('newTypeContainer');
if (typeSelect.value === 'new') {
newTypeContainer.style.display = 'block';
document.getElementById('newTypeName').required = true;
} else {
newTypeContainer.style.display = 'none';
document.getElementById('newTypeName').required = false;
}
}
function showAddProductModal() {
editingProductId = null;
document.getElementById('modalTitle').textContent = 'Добавление товара';
document.getElementById('productForm').reset();
document.getElementById('productId').value = '';
document.getElementById('newTypeContainer').style.display = 'none';
document.getElementById('productModal').style.display = 'block';
}
function editProduct(productId) {
editingProductId = productId;
document.getElementById('modalTitle').textContent = 'Редактирование товара';
fetch(`/api/product/${productId}`)
.then(response => response.json())
.then(product => {
document.getElementById('productType').value = product.product_type_id;
document.getElementById('productName').value = product.product_name;
document.getElementById('productArticle').value = product.article;
document.getElementById('productPrice').value = product.min_partner_price;
document.getElementById('newTypeContainer').style.display = 'none';
document.getElementById('productModal').style.display = 'block';
})
.catch(error => {
alert('Ошибка при загрузке данных товара');
console.error(error);
});
}
function closeProductModal() {
document.getElementById('productModal').style.display = 'none';
}
function saveProduct(event) {
event.preventDefault();
const typeSelect = document.getElementById('productType');
let productTypeId = typeSelect.value;
if (productTypeId === 'new') {
const newTypeName = document.getElementById('newTypeName').value.trim();
if (!newTypeName) {
alert('Введите название нового типа товара');
return;
}
fetch('/api/product_type', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type_name: newTypeName,
description: ''
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
saveProductWithType(data.type_id);
} else {
alert('Ошибка при создании типа: ' + data.error);
}
})
.catch(error => {
alert('Ошибка при создании типа товара');
console.error(error);
});
} else {
saveProductWithType(productTypeId);
}
}
function saveProductWithType(productTypeId) {
const productData = {
product_type_id: productTypeId,
product_name: document.getElementById('productName').value,
article: document.getElementById('productArticle').value,
min_partner_price: parseFloat(document.getElementById('productPrice').value)
};
const url = editingProductId ? `/api/product/${editingProductId}` : '/api/product';
const method = editingProductId ? 'PUT' : 'POST';
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(productData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(editingProductId ? 'Товар обновлен' : 'Товар добавлен');
location.reload();
} else {
alert('Ошибка: ' + data.error);
}
})
.catch(error => {
alert('Ошибка при сохранении товара');
console.error(error);
});
}
function addToCart(productId, productName, price) {
const quantity = prompt("Введите количество:", "1");
if (quantity === null) return;
const qty = parseInt(quantity);
if (isNaN(qty) || qty <= 0) {
alert('Введите корректное положительное число');
return;
}
const existingItem = cart.find(item => item.id === productId);
if (existingItem) {
existingItem.quantity += qty;
} else {
cart.push({
id: productId,
name: productName,
price: price,
quantity: qty
});
}
updateCartDisplay();
document.getElementById('cartModal').style.display = 'block';
}
function updateCartDisplay() {
const tbody = document.getElementById('cartItems');
tbody.innerHTML = '';
let total = 0;
cart.forEach((item, index) => {
const subtotalWithMarkup = item.price * item.quantity * 1.1;
total += subtotalWithMarkup;
const row = document.createElement('tr');
row.innerHTML = `
<td>${item.name}</td>
<td>
<input type="number" min="1" value="${item.quantity}"
onchange="updateCartQuantity(${index}, this.value)" class="cart-quantity">
</td>
<td>${item.price.toFixed(2)} ₽</td>
<td>${subtotalWithMarkup.toFixed(2)} ₽</td>
<td><button onclick="removeFromCart(${index})" class="btn-remove-small">×</button></td>
`;
tbody.appendChild(row);
});
document.getElementById('cartTotal').textContent = total.toFixed(2) + ' ₽';
}
function updateCartQuantity(index, quantity) {
const qty = parseInt(quantity);
if (qty > 0) {
cart[index].quantity = qty;
updateCartDisplay();
}
}
function removeFromCart(index) {
cart.splice(index, 1);
updateCartDisplay();
if (cart.length === 0) {
closeCartModal();
}
}
function clearCart() {
if (cart.length > 0 && confirm('Очистить корзину?')) {
cart = [];
updateCartDisplay();
closeCartModal();
}
}
function closeCartModal() {
document.getElementById('cartModal').style.display = 'none';
}
function createOrderFromCart() {
const partnerId = document.getElementById('cartPartner').value;
const orderDate = document.getElementById('cartDate').value;
const notes = document.getElementById('cartNotes').value;
if (!partnerId) {
alert('Выберите партнера');
return;
}
if (!orderDate) {
alert('Выберите дату заказа');
return;
}
if (cart.length === 0) {
alert('Добавьте товары в корзину');
return;
}
const form = document.createElement('form');
form.method = 'POST';
form.action = '/add_order';
form.style.display = 'none';
const partnerInput = document.createElement('input');
partnerInput.type = 'hidden';
partnerInput.name = 'partner_id';
partnerInput.value = partnerId;
form.appendChild(partnerInput);
const dateInput = document.createElement('input');
dateInput.type = 'hidden';
dateInput.name = 'order_date';
dateInput.value = orderDate;
form.appendChild(dateInput);
const notesInput = document.createElement('input');
notesInput.type = 'hidden';
notesInput.name = 'notes';
notesInput.value = notes;
form.appendChild(notesInput);
cart.forEach(item => {
const productInput = document.createElement('input');
productInput.type = 'hidden';
productInput.name = 'product_id[]';
productInput.value = item.id;
form.appendChild(productInput);
const quantityInput = document.createElement('input');
quantityInput.type = 'hidden';
quantityInput.name = 'quantity[]';
quantityInput.value = item.quantity;
form.appendChild(quantityInput);
const priceInput = document.createElement('input');
priceInput.type = 'hidden';
priceInput.name = 'price[]';
priceInput.value = item.price;
form.appendChild(priceInput);
});
document.body.appendChild(form);
form.submit();
}
window.onclick = function(event) {
const productModal = document.getElementById('productModal');
const cartModal = document.getElementById('cartModal');
if (event.target == productModal) {
productModal.style.display = 'none';
}
if (event.target == cartModal) {
cartModal.style.display = 'none';
}
}
</script>
<style>
/* Стили для модальных окон */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: #F5F5F5;
margin: 5% auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #E0E0E0;
}
.modal-header h3 {
color: #696969;
margin: 0;
}
.close {
color: #696969;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #333;
}
.cart-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.cart-table th {
background-color: #E0E0E0;
padding: 10px;
text-align: left;
}
.cart-table td {
padding: 10px;
border-bottom: 1px solid #E0E0E0;
}
.cart-quantity {
width: 60px;
padding: 5px;
border: 1px solid #E0E0E0;
border-radius: 4px;
}
.btn-remove-small {
background-color: #ff6b6b;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 16px;
line-height: 1;
}
.btn-remove-small:hover {
background-color: #ff5252;
}
.cart-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.cart-info {
background-color: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.btn-add-to-cart {
background-color: #696969;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: 'Calibri', sans-serif;
transition: background-color 0.3s;
}
.btn-add-to-cart:hover:not(:disabled) {
background-color: #555;
}
.btn-add-to-cart:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.form-row {
display: flex;
gap: 20px;
}
.form-group.half {
flex: 1;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-top: 20px;
}
.product-card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s;
display: flex;
flex-direction: column;
}
.product-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
.product-header {
background-color: #E0E0E0;
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.product-header h3 {
color: #696969;
font-size: 1.2rem;
margin: 0;
}
.product-type {
background-color: #696969;
color: white;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.8rem;
}
.product-body {
padding: 15px;
flex: 1;
}
.product-body p {
margin-bottom: 8px;
}
.in-stock {
color: #4CAF50;
font-weight: 600;
}
.out-of-stock {
color: #ff6b6b;
font-weight: 600;
}
.product-footer {
padding: 15px;
background-color: #F5F5F5;
display: flex;
gap: 10px;
justify-content: center;
}
.btn-edit-small {
background-color: #E0E0E0;
color: #696969;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: 'Calibri', sans-serif;
transition: background-color 0.3s;
}
.btn-edit-small:hover {
background-color: #D0D0D0;
}
.filter-select, .search-input {
padding: 8px 12px;
border: 1px solid #E0E0E0;
border-radius: 4px;
font-family: 'Calibri', sans-serif;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
flex-wrap: wrap;
gap: 15px;
}
.page-header h2 {
font-size: 2rem;
color: #696969;
font-weight: 500;
}
.header-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn-add {
background-color: #E0E0E0;
color: #696969;
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
font-weight: 600;
transition: background-color 0.3s;
border: 1px solid #696969;
cursor: pointer;
}
.btn-add:hover {
background-color: #D0D0D0;
}
.btn-submit {
background-color: #696969;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: 'Calibri', sans-serif;
font-weight: 600;
}
.btn-reset {
background-color: #E0E0E0;
color: #696969;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: 'Calibri', sans-serif;
font-weight: 600;
}
.btn-delete {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-family: 'Calibri', sans-serif;
font-weight: 600;
}
.btn-delete:hover {
background-color: #c82333;
}
.empty-state {
text-align: center;
padding: 60px 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.empty-state p {
font-size: 1.2rem;
color: #666;
margin-bottom: 20px;
}
.type-selector {
width: 100%;
}
</style>
{% endblock %}