Kgshop commited on
Commit
1cbdce9
·
verified ·
1 Parent(s): 43b58b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -26
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): # Old flat structure migration
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: # Convert old 'price' to new 'prices' structure
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-wrap: wrap; gap: 10px; justify-content: center; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.subcategory-filter { font-size: 0.8rem; padding: 6px 12px; }
 
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
- <button class="category-filter active" data-category="all" data-subcategory="all">Все категории</button>
371
- {% for category_obj in categories_data %}
372
- <button class="category-filter" data-category="{{ category_obj.name }}" data-subcategory="all">{{ category_obj.name }}</button>
373
- {% for subcategory in category_obj.subcategories %}
374
- <button class="category-filter subcategory-filter" data-category="{{ category_obj.name }}" data-subcategory="{{ subcategory }}">{{ subcategory }}</button>
375
  {% endfor %}
376
- {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  </div>
378
 
379
  <div class="search-container">
@@ -826,8 +858,35 @@ CATALOG_TEMPLATE = '''
826
  grid.appendChild(p);
827
  }
828
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
 
830
- function setupFilters() {
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; /* Ensure modal navigation buttons match new color */
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
- // Use the data attribute to restore the saved subcategory
1525
- const initialSubcategory = catSelect.getAttribute('data-initial-subcategory');
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();