Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -150,11 +150,10 @@ def load_data():
|
|
| 150 |
if 'categories' not in data: data['categories'] = []
|
| 151 |
if 'orders' not in data: data['orders'] = {}
|
| 152 |
|
| 153 |
-
# 1. Ensure categories are in dictionary format with subcategories (Migration)
|
| 154 |
if 'categories' in data:
|
| 155 |
new_categories = []
|
| 156 |
for cat in data['categories']:
|
| 157 |
-
if isinstance(cat, str):
|
| 158 |
new_categories.append({'name': cat, 'subcategories': []})
|
| 159 |
elif isinstance(cat, dict) and 'name' in cat:
|
| 160 |
if 'subcategories' not in cat or not isinstance(cat['subcategories'], list):
|
|
@@ -166,13 +165,12 @@ def load_data():
|
|
| 166 |
else:
|
| 167 |
data['categories'] = []
|
| 168 |
|
| 169 |
-
# 2. Ensure products have 'prices' and 'subcategory' field
|
| 170 |
for product in data['products']:
|
| 171 |
if 'subcategory' not in product:
|
| 172 |
product['subcategory'] = 'Без подкатегории'
|
| 173 |
|
| 174 |
if 'prices' not in product or not isinstance(product['prices'], list):
|
| 175 |
-
if 'price' in product:
|
| 176 |
product['prices'] = [{'type': 'шт', 'value': product.pop('price')}]
|
| 177 |
else:
|
| 178 |
product['prices'] = []
|
|
@@ -205,7 +203,6 @@ def load_data():
|
|
| 205 |
if 'categories' not in data: data['categories'] = []
|
| 206 |
if 'orders' not in data: data['orders'] = {}
|
| 207 |
|
| 208 |
-
# Apply same structure fixes after download
|
| 209 |
if 'categories' in data:
|
| 210 |
new_categories = []
|
| 211 |
for cat in data['categories']:
|
|
@@ -293,13 +290,36 @@ CATALOG_TEMPLATE = '''
|
|
| 293 |
.header { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 1px solid #e0e0e0; }
|
| 294 |
.header h1 { font-size: 1.8rem; font-weight: 600; color: #e3a84f; }
|
| 295 |
.store-address { padding: 15px; text-align: center; background-color: #f9f9f9; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.03); font-size: 1rem; color: #666; }
|
| 296 |
-
.filters-container { margin: 20px 0; display: flex; flex-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
.search-container { margin: 20px 0; text-align: center; }
|
| 298 |
#search-input { width: 90%; max-width: 600px; padding: 12px 18px; font-size: 1rem; border: 1px solid #e0e0e0; border-radius: 25px; outline: none; box-shadow: 0 2px 5px rgba(0,0,0,0.03); transition: all 0.3s ease; }
|
| 299 |
#search-input:focus { border-color: #e3a84f; box-shadow: 0 0 0 3px rgba(227, 168, 79, 0.15); }
|
| 300 |
.category-filter { padding: 8px 16px; border: 1px solid #e0e0e0; border-radius: 20px; background-color: #fff; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 0.9rem; font-weight: 400; color: #c89345; white-space: nowrap;}
|
| 301 |
-
.category-filter.
|
|
|
|
| 302 |
.category-filter.active, .category-filter:hover { background-color: #e3a84f; color: white; border-color: #e3a84f; box-shadow: 0 2px 10px rgba(227, 168, 79, 0.2); }
|
|
|
|
| 303 |
.products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 20px; padding: 10px; }
|
| 304 |
@media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
|
| 305 |
@media (min-width: 900px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } }
|
|
@@ -367,13 +387,25 @@ CATALOG_TEMPLATE = '''
|
|
| 367 |
<div class="store-address">Наш адрес: {{ store_address }}</div>
|
| 368 |
|
| 369 |
<div class="filters-container">
|
| 370 |
-
<
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
<button class="category-filter subcategory-filter" data-category="{{ category_obj.name }}" data-subcategory="{{ subcategory }}">{{ subcategory }}</button>
|
| 375 |
{% endfor %}
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
</div>
|
| 378 |
|
| 379 |
<div class="search-container">
|
|
@@ -826,8 +858,35 @@ CATALOG_TEMPLATE = '''
|
|
| 826 |
grid.appendChild(p);
|
| 827 |
}
|
| 828 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 829 |
|
| 830 |
-
|
| 831 |
const searchInput = document.getElementById('search-input');
|
| 832 |
const categoryFilters = document.querySelectorAll('.category-filter');
|
| 833 |
|
|
@@ -837,9 +896,15 @@ CATALOG_TEMPLATE = '''
|
|
| 837 |
filter.addEventListener('click', function() {
|
| 838 |
categoryFilters.forEach(f => f.classList.remove('active'));
|
| 839 |
this.classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
filterProducts();
|
| 841 |
});
|
| 842 |
});
|
|
|
|
|
|
|
| 843 |
filterProducts();
|
| 844 |
}
|
| 845 |
|
|
@@ -949,9 +1014,8 @@ PRODUCT_DETAIL_TEMPLATE = '''
|
|
| 949 |
</div>
|
| 950 |
</div>
|
| 951 |
<style>
|
| 952 |
-
/* Add Swiper Modal Specific Styles if needed */
|
| 953 |
#productModal .swiper-button-next, #productModal .swiper-button-prev {
|
| 954 |
-
color: #e3a84f;
|
| 955 |
}
|
| 956 |
</style>
|
| 957 |
'''
|
|
@@ -1486,15 +1550,12 @@ ADMIN_TEMPLATE = '''
|
|
| 1486 |
const selectedCategory = categorySelect.value;
|
| 1487 |
const subcategories = categorySubcategoryMap[selectedCategory] || [];
|
| 1488 |
|
| 1489 |
-
// Determine the initial subcategory value for edit forms
|
| 1490 |
let currentSubcategory = 'Без подкатегории';
|
| 1491 |
if (categorySelect.dataset.initialSubcategory) {
|
| 1492 |
currentSubcategory = categorySelect.dataset.initialSubcategory;
|
| 1493 |
-
// Clear the data attribute after using it once, so manual changes don't get overwritten
|
| 1494 |
delete categorySelect.dataset.initialSubcategory;
|
| 1495 |
}
|
| 1496 |
|
| 1497 |
-
// If the main category changed, reset the current subcategory selection check
|
| 1498 |
if (categorySelect.value !== selectedCategory) {
|
| 1499 |
currentSubcategory = 'Без подкатегории';
|
| 1500 |
}
|
|
@@ -1517,14 +1578,11 @@ ADMIN_TEMPLATE = '''
|
|
| 1517 |
if (formContainer) {
|
| 1518 |
formContainer.style.display = formContainer.style.display === 'none' || formContainer.style.display === '' ? 'block' : 'none';
|
| 1519 |
if (formContainer.style.display === 'block') {
|
| 1520 |
-
// Re-initialize subcategory select on opening the edit form
|
| 1521 |
const index = formId.split('-').pop();
|
| 1522 |
const catSelect = document.getElementById(`edit_category_${index}`);
|
| 1523 |
if (catSelect) {
|
| 1524 |
-
|
| 1525 |
-
|
| 1526 |
-
updateSubcategorySelect(`edit_category_${index}`, `edit_subcategory_${index}`, initialSubcategory);
|
| 1527 |
-
// Re-attach listener if not already done via onchange attribute
|
| 1528 |
}
|
| 1529 |
}
|
| 1530 |
}
|
|
@@ -1597,14 +1655,12 @@ ADMIN_TEMPLATE = '''
|
|
| 1597 |
addPriceInput('add-price-inputs');
|
| 1598 |
}
|
| 1599 |
|
| 1600 |
-
// Setup for ADD form
|
| 1601 |
const addCatSelect = document.getElementById('add_category');
|
| 1602 |
if (addCatSelect) {
|
| 1603 |
updateSubcategorySelect('add_category', 'add_subcategory');
|
| 1604 |
addCatSelect.addEventListener('change', () => updateSubcategorySelect('add_category', 'add_subcategory'));
|
| 1605 |
}
|
| 1606 |
|
| 1607 |
-
// Initial setup for all EDIT forms (only run if the form is already open, otherwise done when opening via toggleEditForm)
|
| 1608 |
document.querySelectorAll('[id^="edit-form-"]').forEach(formContainer => {
|
| 1609 |
if (formContainer.style.display === 'block') {
|
| 1610 |
const index = formContainer.id.split('-').pop();
|
|
|
|
| 150 |
if 'categories' not in data: data['categories'] = []
|
| 151 |
if 'orders' not in data: data['orders'] = {}
|
| 152 |
|
|
|
|
| 153 |
if 'categories' in data:
|
| 154 |
new_categories = []
|
| 155 |
for cat in data['categories']:
|
| 156 |
+
if isinstance(cat, str):
|
| 157 |
new_categories.append({'name': cat, 'subcategories': []})
|
| 158 |
elif isinstance(cat, dict) and 'name' in cat:
|
| 159 |
if 'subcategories' not in cat or not isinstance(cat['subcategories'], list):
|
|
|
|
| 165 |
else:
|
| 166 |
data['categories'] = []
|
| 167 |
|
|
|
|
| 168 |
for product in data['products']:
|
| 169 |
if 'subcategory' not in product:
|
| 170 |
product['subcategory'] = 'Без подкатегории'
|
| 171 |
|
| 172 |
if 'prices' not in product or not isinstance(product['prices'], list):
|
| 173 |
+
if 'price' in product:
|
| 174 |
product['prices'] = [{'type': 'шт', 'value': product.pop('price')}]
|
| 175 |
else:
|
| 176 |
product['prices'] = []
|
|
|
|
| 203 |
if 'categories' not in data: data['categories'] = []
|
| 204 |
if 'orders' not in data: data['orders'] = {}
|
| 205 |
|
|
|
|
| 206 |
if 'categories' in data:
|
| 207 |
new_categories = []
|
| 208 |
for cat in data['categories']:
|
|
|
|
| 290 |
.header { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 1px solid #e0e0e0; }
|
| 291 |
.header h1 { font-size: 1.8rem; font-weight: 600; color: #e3a84f; }
|
| 292 |
.store-address { padding: 15px; text-align: center; background-color: #f9f9f9; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.03); font-size: 1rem; color: #666; }
|
| 293 |
+
.filters-container { margin: 20px 0; display: flex; flex-direction: column; gap: 10px; justify-content: center; align-items: center; }
|
| 294 |
+
#main-category-filters, #subcategory-filters {
|
| 295 |
+
width: 100%;
|
| 296 |
+
max-width: 1000px;
|
| 297 |
+
display: flex;
|
| 298 |
+
flex-wrap: wrap;
|
| 299 |
+
gap: 10px;
|
| 300 |
+
justify-content: center;
|
| 301 |
+
}
|
| 302 |
+
#main-category-filters {
|
| 303 |
+
padding-bottom: 10px;
|
| 304 |
+
border-bottom: 1px solid #e0e0e0;
|
| 305 |
+
}
|
| 306 |
+
#subcategory-filters {
|
| 307 |
+
padding-top: 10px;
|
| 308 |
+
}
|
| 309 |
+
.subcategory-group {
|
| 310 |
+
display: flex;
|
| 311 |
+
flex-wrap: wrap;
|
| 312 |
+
gap: 10px;
|
| 313 |
+
justify-content: center;
|
| 314 |
+
}
|
| 315 |
.search-container { margin: 20px 0; text-align: center; }
|
| 316 |
#search-input { width: 90%; max-width: 600px; padding: 12px 18px; font-size: 1rem; border: 1px solid #e0e0e0; border-radius: 25px; outline: none; box-shadow: 0 2px 5px rgba(0,0,0,0.03); transition: all 0.3s ease; }
|
| 317 |
#search-input:focus { border-color: #e3a84f; box-shadow: 0 0 0 3px rgba(227, 168, 79, 0.15); }
|
| 318 |
.category-filter { padding: 8px 16px; border: 1px solid #e0e0e0; border-radius: 20px; background-color: #fff; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 0.9rem; font-weight: 400; color: #c89345; white-space: nowrap;}
|
| 319 |
+
.category-filter.main-cat-btn { font-weight: 600; }
|
| 320 |
+
.category-filter.subcategory-filter { font-size: 0.8rem; padding: 6px 12px; background-color: #f5f5f5; color: #666; border-color: #f5f5f5; }
|
| 321 |
.category-filter.active, .category-filter:hover { background-color: #e3a84f; color: white; border-color: #e3a84f; box-shadow: 0 2px 10px rgba(227, 168, 79, 0.2); }
|
| 322 |
+
.category-filter.subcategory-filter.active, .category-filter.subcategory-filter:hover { background-color: #e3a84f; color: white; border-color: #e3a84f; }
|
| 323 |
.products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 20px; padding: 10px; }
|
| 324 |
@media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
|
| 325 |
@media (min-width: 900px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } }
|
|
|
|
| 387 |
<div class="store-address">Наш адрес: {{ store_address }}</div>
|
| 388 |
|
| 389 |
<div class="filters-container">
|
| 390 |
+
<div id="main-category-filters">
|
| 391 |
+
<button class="category-filter active" data-category="all" data-subcategory="all">Все категории</button>
|
| 392 |
+
{% for category_obj in categories_data %}
|
| 393 |
+
<button class="category-filter main-cat-btn" data-category="{{ category_obj.name }}" data-subcategory="all">{{ category_obj.name }}</button>
|
|
|
|
| 394 |
{% endfor %}
|
| 395 |
+
</div>
|
| 396 |
+
|
| 397 |
+
<div id="subcategory-filters" style="display: none;">
|
| 398 |
+
{% for category_obj in categories_data %}
|
| 399 |
+
{% if category_obj.subcategories %}
|
| 400 |
+
<div class="subcategory-group" data-parent-category="{{ category_obj.name }}" style="display: none;">
|
| 401 |
+
<span style="font-size: 0.9rem; color: #999; margin-right: 10px; padding: 6px 0;">Подкатегории {{ category_obj.name }}:</span>
|
| 402 |
+
{% for subcategory in category_obj.subcategories %}
|
| 403 |
+
<button class="category-filter subcategory-filter" data-category="{{ category_obj.name }}" data-subcategory="{{ subcategory }}">{{ subcategory }}</button>
|
| 404 |
+
{% endfor %}
|
| 405 |
+
</div>
|
| 406 |
+
{% endif %}
|
| 407 |
+
{% endfor %}
|
| 408 |
+
</div>
|
| 409 |
</div>
|
| 410 |
|
| 411 |
<div class="search-container">
|
|
|
|
| 858 |
grid.appendChild(p);
|
| 859 |
}
|
| 860 |
}
|
| 861 |
+
|
| 862 |
+
function updateSubcategoryVisibility(activeCategory) {
|
| 863 |
+
const subcategoryContainer = document.getElementById('subcategory-filters');
|
| 864 |
+
|
| 865 |
+
if (activeCategory === 'all') {
|
| 866 |
+
subcategoryContainer.style.display = 'none';
|
| 867 |
+
document.querySelectorAll('.subcategory-group').forEach(group => group.style.display = 'none');
|
| 868 |
+
return;
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
let found = false;
|
| 872 |
+
document.querySelectorAll('.subcategory-group').forEach(group => {
|
| 873 |
+
if (group.dataset.parentCategory === activeCategory) {
|
| 874 |
+
group.style.display = 'flex';
|
| 875 |
+
found = true;
|
| 876 |
+
} else {
|
| 877 |
+
group.style.display = 'none';
|
| 878 |
+
}
|
| 879 |
+
});
|
| 880 |
+
|
| 881 |
+
if (found) {
|
| 882 |
+
subcategoryContainer.style.display = 'flex';
|
| 883 |
+
} else {
|
| 884 |
+
subcategoryContainer.style.display = 'none';
|
| 885 |
+
}
|
| 886 |
+
}
|
| 887 |
+
|
| 888 |
|
| 889 |
+
function setupFilters() {
|
| 890 |
const searchInput = document.getElementById('search-input');
|
| 891 |
const categoryFilters = document.querySelectorAll('.category-filter');
|
| 892 |
|
|
|
|
| 896 |
filter.addEventListener('click', function() {
|
| 897 |
categoryFilters.forEach(f => f.classList.remove('active'));
|
| 898 |
this.classList.add('active');
|
| 899 |
+
|
| 900 |
+
const activeCategory = this.dataset.category;
|
| 901 |
+
updateSubcategoryVisibility(activeCategory);
|
| 902 |
+
|
| 903 |
filterProducts();
|
| 904 |
});
|
| 905 |
});
|
| 906 |
+
|
| 907 |
+
updateSubcategoryVisibility('all');
|
| 908 |
filterProducts();
|
| 909 |
}
|
| 910 |
|
|
|
|
| 1014 |
</div>
|
| 1015 |
</div>
|
| 1016 |
<style>
|
|
|
|
| 1017 |
#productModal .swiper-button-next, #productModal .swiper-button-prev {
|
| 1018 |
+
color: #e3a84f;
|
| 1019 |
}
|
| 1020 |
</style>
|
| 1021 |
'''
|
|
|
|
| 1550 |
const selectedCategory = categorySelect.value;
|
| 1551 |
const subcategories = categorySubcategoryMap[selectedCategory] || [];
|
| 1552 |
|
|
|
|
| 1553 |
let currentSubcategory = 'Без подкатегории';
|
| 1554 |
if (categorySelect.dataset.initialSubcategory) {
|
| 1555 |
currentSubcategory = categorySelect.dataset.initialSubcategory;
|
|
|
|
| 1556 |
delete categorySelect.dataset.initialSubcategory;
|
| 1557 |
}
|
| 1558 |
|
|
|
|
| 1559 |
if (categorySelect.value !== selectedCategory) {
|
| 1560 |
currentSubcategory = 'Без подкатегории';
|
| 1561 |
}
|
|
|
|
| 1578 |
if (formContainer) {
|
| 1579 |
formContainer.style.display = formContainer.style.display === 'none' || formContainer.style.display === '' ? 'block' : 'none';
|
| 1580 |
if (formContainer.style.display === 'block') {
|
|
|
|
| 1581 |
const index = formId.split('-').pop();
|
| 1582 |
const catSelect = document.getElementById(`edit_category_${index}`);
|
| 1583 |
if (catSelect) {
|
| 1584 |
+
const initialSubcategory = catSelect.getAttribute('data-initial-subcategory');
|
| 1585 |
+
updateSubcategorySelect(`edit_category_${index}`, `edit_subcategory_${index}`, initialSubcategory);
|
|
|
|
|
|
|
| 1586 |
}
|
| 1587 |
}
|
| 1588 |
}
|
|
|
|
| 1655 |
addPriceInput('add-price-inputs');
|
| 1656 |
}
|
| 1657 |
|
|
|
|
| 1658 |
const addCatSelect = document.getElementById('add_category');
|
| 1659 |
if (addCatSelect) {
|
| 1660 |
updateSubcategorySelect('add_category', 'add_subcategory');
|
| 1661 |
addCatSelect.addEventListener('change', () => updateSubcategorySelect('add_category', 'add_subcategory'));
|
| 1662 |
}
|
| 1663 |
|
|
|
|
| 1664 |
document.querySelectorAll('[id^="edit-form-"]').forEach(formContainer => {
|
| 1665 |
if (formContainer.style.display === 'block') {
|
| 1666 |
const index = formContainer.id.split('-').pop();
|