Kgshop commited on
Commit
a245efb
·
verified ·
1 Parent(s): dbdefa5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +920 -56
app.py CHANGED
@@ -10,7 +10,7 @@ from huggingface_hub.utils import RepositoryNotFoundError
10
  from werkzeug.utils import secure_filename
11
 
12
  app = Flask(__name__)
13
- DATA_FILE = 'data_zzirix.json'
14
 
15
  # Настройки Hugging Face
16
  REPO_ID = "Kgshop/clients"
@@ -105,7 +105,7 @@ def catalog():
105
  <head>
106
  <meta charset="UTF-8">
107
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
- <title>Канцтовары оптом и в розницу </title>
109
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
110
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
111
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
@@ -121,6 +121,7 @@ def catalog():
121
  color: #2d3748;
122
  line-height: 1.6;
123
  transition: background 0.3s, color 0.3s;
 
124
  }
125
  body.dark-mode {
126
  background: linear-gradient(135deg, #1a202c, #2d3748);
@@ -164,7 +165,7 @@ def catalog():
164
  transition: color 0.3s ease;
165
  }
166
  .theme-toggle:hover {
167
- color: #3b82f6;
168
  }
169
  .filters-container {
170
  margin: 20px 0;
@@ -189,8 +190,8 @@ def catalog():
189
  transition: all 0.3s ease;
190
  }
191
  #search-input:focus {
192
- border-color: #3b82f6;
193
- box-shadow: 0 4px 15px rgba(59, 130, 246, 0.2);
194
  }
195
  .category-filter {
196
  padding: 8px 16px;
@@ -203,10 +204,10 @@ def catalog():
203
  font-weight: 400;
204
  }
205
  .category-filter.active, .category-filter:hover {
206
- background-color: #3b82f6;
207
  color: white;
208
- border-color: #3b82f6;
209
- box-shadow: 0 2px 10px rgba(59, 130, 246, 0.3);
210
  }
211
  .products-grid {
212
  display: grid;
@@ -283,7 +284,7 @@ def catalog():
283
  padding: 8px;
284
  border: none;
285
  border-radius: 8px;
286
- background-color: #3b82f6;
287
  color: white;
288
  font-size: 0.8rem;
289
  font-weight: 500;
@@ -294,8 +295,8 @@ def catalog():
294
  text-decoration: none;
295
  }
296
  .product-button:hover {
297
- background-color: #2563eb;
298
- box-shadow: 0 4px 15px rgba(37, 99, 235, 0.4);
299
  transform: translateY(-2px);
300
  }
301
  .add-to-cart {
@@ -307,7 +308,7 @@ def catalog():
307
  }
308
  #cart-button {
309
  position: fixed;
310
- bottom: 20px;
311
  right: 20px;
312
  background-color: #ef4444;
313
  color: white;
@@ -407,26 +408,851 @@ def catalog():
407
  background-color: #059669;
408
  box-shadow: 0 4px 15px rgba(5, 150, 105, 0.4);
409
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  </style>
411
  </head>
412
  <body>
413
  <div class="container">
414
  <div class="header">
415
  <img src="''' + LOGO_URL + '''" alt="Logo" class="header-logo">
416
- <h1>Каталог</h1>
417
  <button class="theme-toggle" onclick="toggleTheme()">
418
  <i class="fas fa-moon"></i>
419
  </button>
420
  </div>
421
- <div class="filters-container">
422
- <button class="category-filter active" data-category="all">Все категории</button>
423
- {% for category in categories %}
424
- <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
425
- {% endfor %}
426
- </div>
427
- <div class="search-container">
428
- <input type="text" id="search-input" placeholder="Поиск товаров...">
429
- </div>
430
  <div class="products-grid" id="products-grid">
431
  {% for product in products %}
432
  <div class="product"
@@ -441,7 +1267,14 @@ def catalog():
441
  </div>
442
  {% endif %}
443
  <h2>{{ product['name'] }}</h2>
444
- <div class="product-price">{{ product['price'] }} с</div>
 
 
 
 
 
 
 
445
  <p class="product-description">{{ product['description'][:50] }}{% if product['description']|length > 50 %}...{% endif %}</p>
446
  <button class="product-button" onclick="openModal({{ loop.index0 }})">Подробнее</button>
447
  <button class="product-button add-to-cart" onclick="openQuantityModal({{ loop.index0 }})">В корзину</button>
@@ -485,6 +1318,21 @@ def catalog():
485
 
486
  <button id="cart-button" onclick="openCartModal()">🛒</button>
487
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
489
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
490
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
@@ -571,13 +1419,19 @@ def catalog():
571
  const cartItemId = `${product.name}-${color}`;
572
  const existingItem = cart.find(item => item.id === cartItemId);
573
 
 
 
574
  if (existingItem) {
575
  existingItem.quantity += quantity;
 
576
  } else {
577
  cart.push({
578
  id: cartItemId,
579
  name: product.name,
580
- price: product.price,
 
 
 
581
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
582
  quantity: quantity,
583
  color: color
@@ -609,6 +1463,7 @@ def catalog():
609
  <div>
610
  <strong>${item.name}</strong>
611
  <p>${item.price} с × ${item.quantity} (Цвет: ${item.color})</p>
 
612
  </div>
613
  </div>
614
  <span>${itemTotal} с</span>
@@ -647,34 +1502,12 @@ def catalog():
647
  if (event.target.className === 'modal') event.target.style.display = "none";
648
  }
649
 
650
- document.getElementById('search-input').addEventListener('input', filterProducts);
651
- document.querySelectorAll('.category-filter').forEach(filter => {
652
- filter.addEventListener('click', function() {
653
- document.querySelectorAll('.category-filter').forEach(f => f.classList.remove('active'));
654
- this.classList.add('active');
655
- filterProducts();
656
- });
657
- });
658
-
659
- function filterProducts() {
660
- const searchTerm = document.getElementById('search-input').value.toLowerCase();
661
- const activeCategory = document.querySelector('.category-filter.active').dataset.category;
662
- document.querySelectorAll('.product').forEach(product => {
663
- const name = product.getAttribute('data-name');
664
- const description = product.getAttribute('data-description');
665
- const category = product.getAttribute('data-category');
666
- const matchesSearch = name.includes(searchTerm) || description.includes(searchTerm);
667
- const matchesCategory = activeCategory === 'all' || category === activeCategory;
668
- product.style.display = matchesSearch && matchesCategory ? 'block' : 'none';
669
- });
670
- }
671
-
672
  updateCartButton();
673
  </script>
674
  </body>
675
  </html>
676
  '''
677
- return render_template_string(catalog_html, products=products, categories=categories, repo_id=REPO_ID)
678
 
679
  @app.route('/product/<int:index>')
680
  def product_detail(index):
@@ -711,6 +1544,9 @@ def product_detail(index):
711
  </div>
712
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
713
  <p><strong>Цена:</strong> {{ product['price'] }} с</p>
 
 
 
714
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
715
  <p><strong>Доступные цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
716
  </div>
@@ -746,6 +1582,8 @@ def admin():
746
  elif action == 'add':
747
  name = request.form.get('name')
748
  price = request.form.get('price')
 
 
749
  description = request.form.get('description')
750
  category = request.form.get('category')
751
  photos_files = request.files.getlist('photos')
@@ -785,6 +1623,11 @@ def admin():
785
  'photos': photos_list,
786
  'colors': colors if colors else []
787
  }
 
 
 
 
 
788
  products.append(new_product)
789
  save_data(data)
790
  return redirect(url_for('admin'))
@@ -793,6 +1636,8 @@ def admin():
793
  index = int(request.form.get('index'))
794
  name = request.form.get('name')
795
  price = request.form.get('price')
 
 
796
  description = request.form.get('description')
797
  category = request.form.get('category')
798
  photos_files = request.files.getlist('photos')
@@ -823,6 +1668,12 @@ def admin():
823
 
824
  products[index]['name'] = name
825
  products[index]['price'] = float(price.replace(',', '.'))
 
 
 
 
 
 
826
  products[index]['description'] = description
827
  products[index]['category'] = category if category in categories else 'Без категории'
828
  products[index]['colors'] = colors if colors else []
@@ -899,15 +1750,15 @@ def admin():
899
  transition: all 0.3s ease;
900
  }
901
  input:focus, textarea:focus, select:focus {
902
- border-color: #3b82f6;
903
- box-shadow: 0 0 5px rgba(59, 130, 246, 0.3);
904
  outline: none;
905
  }
906
  button {
907
  padding: 12px 20px;
908
  border: none;
909
  border-radius: 8px;
910
- background-color: #3b82f6;
911
  color: white;
912
  font-weight: 500;
913
  cursor: pointer;
@@ -915,8 +1766,8 @@ def admin():
915
  margin-top: 15px;
916
  }
917
  button:hover {
918
- background-color: #2563eb;
919
- box-shadow: 0 4px 15px rgba(37, 99, 235, 0.4);
920
  transform: translateY(-2px);
921
  }
922
  .delete-button {
@@ -966,8 +1817,12 @@ def admin():
966
  <input type="hidden" name="action" value="add">
967
  <label>Название товара:</label>
968
  <input type="text" name="name" required>
969
- <label>Цена:</label>
970
  <input type="number" name="price" step="0.01" required>
 
 
 
 
971
  <label>Описание:</label>
972
  <textarea name="description" rows="4" required></textarea>
973
  <label>Категория:</label>
@@ -1025,7 +1880,10 @@ def admin():
1025
  <div class="product-item">
1026
  <h3>{{ product['name'] }}</h3>
1027
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1028
- <p><strong>Цена:</strong> {{ product['price'] }} с</p>
 
 
 
1029
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
1030
  <p><strong>Цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
1031
  {% if product.get('photos') and product['photos']|length > 0 %}
@@ -1044,8 +1902,12 @@ def admin():
1044
  <input type="hidden" name="index" value="{{ loop.index0 }}">
1045
  <label>Название:</label>
1046
  <input type="text" name="name" value="{{ product['name'] }}" required>
1047
- <label>Цена:</label>
1048
  <input type="number" name="price" step="0.01" value="{{ product['price'] }}" required>
 
 
 
 
1049
  <label>Описание:</label>
1050
  <textarea name="description" rows="4" required>{{ product['description'] }}</textarea>
1051
  <label>Категория:</label>
@@ -1109,4 +1971,6 @@ if __name__ == '__main__':
1109
  load_data()
1110
  except Exception as e:
1111
  logging.error(f"Не удалось загрузить базу данных: {e}")
1112
- app.run(debug=True, host='0.0.0.0', port=7860)
 
 
 
10
  from werkzeug.utils import secure_filename
11
 
12
  app = Flask(__name__)
13
+ DATA_FILE = 'data_kanc.json'
14
 
15
  # Настройки Hugging Face
16
  REPO_ID = "Kgshop/clients"
 
105
  <head>
106
  <meta charset="UTF-8">
107
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
108
+ <title>Канцтовары оптом и в розницу</title>
109
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
110
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
111
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
 
121
  color: #2d3748;
122
  line-height: 1.6;
123
  transition: background 0.3s, color 0.3s;
124
+ padding-bottom: 60px; /* Для навигационной панели */
125
  }
126
  body.dark-mode {
127
  background: linear-gradient(135deg, #1a202c, #2d3748);
 
165
  transition: color 0.3s ease;
166
  }
167
  .theme-toggle:hover {
168
+ color: #526df2;
169
  }
170
  .filters-container {
171
  margin: 20px 0;
 
190
  transition: all 0.3s ease;
191
  }
192
  #search-input:focus {
193
+ border-color: #526df2;
194
+ box-shadow: 0 4px 15px rgba(82, 109, 242, 0.2);
195
  }
196
  .category-filter {
197
  padding: 8px 16px;
 
204
  font-weight: 400;
205
  }
206
  .category-filter.active, .category-filter:hover {
207
+ background-color: #526df2;
208
  color: white;
209
+ border-color: #526df2;
210
+ box-shadow: 0 2px 10px rgba(82, 109, 242, 0.3);
211
  }
212
  .products-grid {
213
  display: grid;
 
284
  padding: 8px;
285
  border: none;
286
  border-radius: 8px;
287
+ background-color: #526df2;
288
  color: white;
289
  font-size: 0.8rem;
290
  font-weight: 500;
 
295
  text-decoration: none;
296
  }
297
  .product-button:hover {
298
+ background-color: #3e55d1;
299
+ box-shadow: 0 4px 15px rgba(62, 85, 209, 0.4);
300
  transform: translateY(-2px);
301
  }
302
  .add-to-cart {
 
308
  }
309
  #cart-button {
310
  position: fixed;
311
+ bottom: 80px;
312
  right: 20px;
313
  background-color: #ef4444;
314
  color: white;
 
408
  background-color: #059669;
409
  box-shadow: 0 4px 15px rgba(5, 150, 105, 0.4);
410
  }
411
+ .navbar {
412
+ position: fixed;
413
+ bottom: 0;
414
+ left: 0;
415
+ width: 100%;
416
+ background-color: #fff;
417
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
418
+ display: flex;
419
+ justify-content: space-around;
420
+ padding: 10px 0;
421
+ z-index: 1000;
422
+ }
423
+ body.dark-mode .navbar {
424
+ background-color: #2d3748;
425
+ }
426
+ .navbar a {
427
+ text-align: center;
428
+ color: #4a5568;
429
+ text-decoration: none;
430
+ font-size: 0.9rem;
431
+ transition: color 0.3s ease;
432
+ }
433
+ .navbar a.active {
434
+ color: #526df2;
435
+ }
436
+ .navbar a i {
437
+ display: block;
438
+ font-size: 1.5rem;
439
+ margin-bottom: 5px;
440
+ }
441
+ body.dark-mode .navbar a {
442
+ color: #a0aec0;
443
+ }
444
+ .navbar a:hover {
445
+ color: #526df2;
446
+ }
447
+ </style>
448
+ </head>
449
+ <body>
450
+ <div class="container">
451
+ <div class="header">
452
+ <img src="''' + LOGO_URL + '''" alt="Logo" class="header-logo">
453
+ <h1>Каталог</h1>
454
+ <button class="theme-toggle" onclick="toggleTheme()">
455
+ <i class="fas fa-moon"></i>
456
+ </button>
457
+ </div>
458
+ <div class="filters-container">
459
+ <button class="category-filter active" data-category="all">Все категории</button>
460
+ {% for category in categories %}
461
+ <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
462
+ {% endfor %}
463
+ </div>
464
+ <div class="search-container">
465
+ <input type="text" id="search-input" placeholder="Поиск товаров...">
466
+ </div>
467
+ <div class="products-grid" id="products-grid">
468
+ {% for product in products %}
469
+ <div class="product"
470
+ data-name="{{ product['name']|lower }}"
471
+ data-description="{{ product['description']|lower }}"
472
+ data-category="{{ product.get('category', 'Без категории') }}">
473
+ {% if product.get('photos') and product['photos']|length > 0 %}
474
+ <div class="product-image">
475
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
476
+ alt="{{ product['name'] }}"
477
+ loading="lazy">
478
+ </div>
479
+ {% endif %}
480
+ <h2>{{ product['name'] }}</h2>
481
+ <div class="product-price">
482
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
483
+ <span>Розница: {{ product['price'] }} с</span><br>
484
+ <span>Опт (от {{ product['min_wholesale'] }}): {{ product['wholesale_price'] }} с</span>
485
+ {% else %}
486
+ {{ product['price'] }} с
487
+ {% endif %}
488
+ </div>
489
+ <p class="product-description">{{ product['description'][:50] }}{% if product['description']|length > 50 %}...{% endif %}</p>
490
+ <button class="product-button" onclick="openModal({{ loop.index0 }})">Подробнее</button>
491
+ <button class="product-button add-to-cart" onclick="openQuantityModal({{ loop.index0 }})">В корзину</button>
492
+ </div>
493
+ {% endfor %}
494
+ </div>
495
+ </div>
496
+
497
+ <!-- Product Modal -->
498
+ <div id="productModal" class="modal">
499
+ <div class="modal-content">
500
+ <span class="close" onclick="closeModal('productModal')">×</span>
501
+ <div id="modalContent"></div>
502
+ </div>
503
+ </div>
504
+
505
+ <!-- Quantity and Color Modal -->
506
+ <div id="quantityModal" class="modal">
507
+ <div class="modal-content">
508
+ <span class="close" onclick="closeModal('quantityModal')">×</span>
509
+ <h2>Укажите количество и цвет</h2>
510
+ <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
511
+ <select id="colorSelect" class="color-select"></select>
512
+ <button class="product-button" onclick="confirmAddToCart()">Добавить</button>
513
+ </div>
514
+ </div>
515
+
516
+ <!-- Cart Modal -->
517
+ <div id="cartModal" class="modal">
518
+ <div class="modal-content">
519
+ <span class="close" onclick="closeModal('cartModal')">×</span>
520
+ <h2>Корзина</h2>
521
+ <div id="cartContent"></div>
522
+ <div style="margin-top: 20px; text-align: right;">
523
+ <strong>Итого: <span id="cartTotal">0</span> с</strong>
524
+ <button class="product-button clear-cart" onclick="clearCart()">Очистить</button>
525
+ <button class="product-button order-button" onclick="orderViaWhatsApp()">Заказать</button>
526
+ </div>
527
+ </div>
528
+ </div>
529
+
530
+ <button id="cart-button" onclick="openCartModal()">🛒</button>
531
+
532
+ <!-- Navigation Bar -->
533
+ <div class="navbar">
534
+ <a href="/" class="active">
535
+ <i class="fas fa-home"></i>
536
+ Главная
537
+ </a>
538
+ <a href="/categories">
539
+ <i class="fas fa-list"></i>
540
+ Каталог
541
+ </a>
542
+ <a href="https://api.whatsapp.com/send?phone=996500654659">
543
+ <i class="fab fa-whatsapp"></i>
544
+ WhatsApp
545
+ </a>
546
+ </div>
547
+
548
+ <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
549
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
550
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
551
+ <script>
552
+ const products = {{ products|tojson }};
553
+ let selectedProductIndex = null;
554
+
555
+ function toggleTheme() {
556
+ document.body.classList.toggle('dark-mode');
557
+ const icon = document.querySelector('.theme-toggle i');
558
+ icon.classList.toggle('fa-moon');
559
+ icon.classList.toggle('fa-sun');
560
+ localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');
561
+ }
562
+
563
+ if (localStorage.getItem('theme') === 'dark') {
564
+ document.body.classList.add('dark-mode');
565
+ document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
566
+ }
567
+
568
+ function openModal(index) {
569
+ loadProductDetails(index);
570
+ document.getElementById('productModal').style.display = "block";
571
+ }
572
+
573
+ function
574
+
575
+ closeModal(modalId) {
576
+ document.getElementById(modalId).style.display = "none";
577
+ }
578
+
579
+ function loadProductDetails(index) {
580
+ fetch('/product/' + index)
581
+ .then(response => response.text())
582
+ .then(data => {
583
+ document.getElementById('modalContent').innerHTML = data;
584
+ initializeSwiper();
585
+ })
586
+ .catch(error => console.error('Ошибка:', error));
587
+ }
588
+
589
+ function initializeSwiper() {
590
+ new Swiper('.swiper-container', {
591
+ slidesPerView: 1,
592
+ spaceBetween: 20,
593
+ loop: true,
594
+ grabCursor: true,
595
+ pagination: { el: '.swiper-pagination', clickable: true },
596
+ navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
597
+ zoom: { maxRatio: 3 }
598
+ });
599
+ }
600
+
601
+ function openQuantityModal(index) {
602
+ selectedProductIndex = index;
603
+ const product = products[index];
604
+ const colorSelect = document.getElementById('colorSelect');
605
+ colorSelect.innerHTML = '';
606
+ if (product.colors && product.colors.length > 0) {
607
+ product.colors.forEach(color => {
608
+ const option = document.createElement('option');
609
+ option.value = color;
610
+ option.text = color;
611
+ colorSelect.appendChild(option);
612
+ });
613
+ } else {
614
+ const option = document.createElement('option');
615
+ option.value = 'Нет цвета';
616
+ option.text = 'Нет цвета';
617
+ colorSelect.appendChild(option);
618
+ }
619
+ document.getElementById('quantityModal').style.display = 'block';
620
+ document.getElementById('quantityInput').value = 1;
621
+ }
622
+
623
+ function confirmAddToCart() {
624
+ if (selectedProductIndex === null) return;
625
+ const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
626
+ const color = document.getElementById('colorSelect').value;
627
+ if (quantity <= 0) {
628
+ alert("Укажите количество больше 0");
629
+ return;
630
+ }
631
+ let cart = JSON.parse(localStorage.getItem('cart') || '[]');
632
+ const product = products[selectedProductIndex];
633
+ const cartItemId = `${product.name}-${color}`;
634
+ const existingItem = cart.find(item => item.id === cartItemId);
635
+
636
+ const priceToUse = (product.min_wholesale && quantity >= product.min_wholesale) ? product.wholesale_price : product.price;
637
+
638
+ if (existingItem) {
639
+ existingItem.quantity += quantity;
640
+ existingItem.price = (existingItem.quantity >= product.min_wholesale) ? product.wholesale_price : product.price;
641
+ } else {
642
+ cart.push({
643
+ id: cartItemId,
644
+ name: product.name,
645
+ price: priceToUse,
646
+ retail_price: product.price,
647
+ wholesale_price: product.wholesale_price,
648
+ min_wholesale: product.min_wholesale,
649
+ photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
650
+ quantity: quantity,
651
+ color: color
652
+ });
653
+ }
654
+
655
+ localStorage.setItem('cart', JSON.stringify(cart));
656
+ closeModal('quantityModal');
657
+ updateCartButton();
658
+ }
659
+
660
+ function updateCartButton() {
661
+ const cart = JSON.parse(localStorage.getItem('cart') || '[]');
662
+ document.getElementById('cart-button').style.display = cart.length > 0 ? 'block' : 'none';
663
+ }
664
+
665
+ function openCartModal() {
666
+ const cart = JSON.parse(localStorage.getItem('cart') || '[]');
667
+ const cartContent = document.getElementById('cartContent');
668
+ let total = 0;
669
+
670
+ cartContent.innerHTML = cart.length === 0 ? '<p>Корзина пуста</p>' : cart.map(item => {
671
+ const itemTotal = item.price * item.quantity;
672
+ total += itemTotal;
673
+ return `
674
+ <div class="cart-item">
675
+ <div style="display: flex; align-items: center;">
676
+ ${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${item.photo}" alt="${item.name}">` : ''}
677
+ <div>
678
+ <strong>${item.name}</strong>
679
+ <p>${item.price} с × ${item.quantity} (Цвет: ${item.color})</p>
680
+ <p>${item.quantity >= item.min_wholesale ? 'Оптовая цена' : 'Розничная цена'}</p>
681
+ </div>
682
+ </div>
683
+ <span>${itemTotal} с</span>
684
+ </div>
685
+ `;
686
+ }).join('');
687
+
688
+ document.getElementById('cartTotal').textContent = total;
689
+ document.getElementById('cartModal').style.display = 'block';
690
+ }
691
+
692
+ function orderViaWhatsApp() {
693
+ const cart = JSON.parse(localStorage.getItem('cart') || '[]');
694
+ if (cart.length === 0) {
695
+ alert("Корзина пуста!");
696
+ return;
697
+ }
698
+ let total = 0;
699
+ let orderText = "Заказ:%0A";
700
+ cart.forEach((item, index) => {
701
+ const itemTotal = item.price * item.quantity;
702
+ total += itemTotal;
703
+ orderText += `${index + 1}. ${item.name} - ${item.price} с × ${item.quantity} (Цвет: ${item.color})%0A`;
704
+ });
705
+ orderText += `Итого: ${total} с`;
706
+ window.open(`https://api.whatsapp.com/send?phone=996500654659&text=${orderText}`, '_blank');
707
+ }
708
+
709
+ function clearCart() {
710
+ localStorage.removeItem('cart');
711
+ closeModal('cartModal');
712
+ updateCartButton();
713
+ }
714
+
715
+ window.onclick = function(event) {
716
+ if (event.target.className === 'modal') event.target.style.display = "none";
717
+ }
718
+
719
+ document.getElementById('search-input').addEventListener('input', filterProducts);
720
+ document.querySelectorAll('.category-filter').forEach(filter => {
721
+ filter.addEventListener('click', function() {
722
+ document.querySelectorAll('.category-filter').forEach(f => f.classList.remove('active'));
723
+ this.classList.add('active');
724
+ filterProducts();
725
+ });
726
+ });
727
+
728
+ function filterProducts() {
729
+ const searchTerm = document.getElementById('search-input').value.toLowerCase();
730
+ const activeCategory = document.querySelector('.category-filter.active').dataset.category;
731
+ document.querySelectorAll('.product').forEach(product => {
732
+ const name = product.getAttribute('data-name');
733
+ const description = product.getAttribute('data-description');
734
+ const category = product.getAttribute('data-category');
735
+ const matchesSearch = name.includes(searchTerm) || description.includes(searchTerm);
736
+ const matchesCategory = activeCategory === 'all' || category === activeCategory;
737
+ product.style.display = matchesSearch && matchesCategory ? 'block' : 'none';
738
+ });
739
+ }
740
+
741
+ updateCartButton();
742
+ </script>
743
+ </body>
744
+ </html>
745
+ '''
746
+ return render_template_string(catalog_html, products=products, categories=categories, repo_id=REPO_ID)
747
+
748
+ @app.route('/categories')
749
+ def categories_page():
750
+ data = load_data()
751
+ categories = data['categories']
752
+
753
+ categories_html = '''
754
+ <!DOCTYPE html>
755
+ <html lang="ru">
756
+ <head>
757
+ <meta charset="UTF-8">
758
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
759
+ <title>Категории</title>
760
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
761
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
762
+ <style>
763
+ * {
764
+ margin: 0;
765
+ padding: 0;
766
+ box-sizing: border-box;
767
+ }
768
+ body {
769
+ font-family: 'Poppins', sans-serif;
770
+ background: linear-gradient(135deg, #f0f2f5, #e9ecef);
771
+ color: #2d3748;
772
+ line-height: 1.6;
773
+ transition: background 0.3s, color 0.3s;
774
+ padding-bottom: 60px;
775
+ }
776
+ body.dark-mode {
777
+ background: linear-gradient(135deg, #1a202c, #2d3748);
778
+ color: #e2e8f0;
779
+ }
780
+ .container {
781
+ max-width: 1300px;
782
+ margin: 0 auto;
783
+ padding: 20px;
784
+ }
785
+ .header {
786
+ display: flex;
787
+ justify-content: space-between;
788
+ align-items: center;
789
+ padding: 15px 0;
790
+ border-bottom: 1px solid #e2e8f0;
791
+ }
792
+ .header-logo {
793
+ width: 60px;
794
+ height: 60px;
795
+ border-radius: 50%;
796
+ object-fit: cover;
797
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
798
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
799
+ }
800
+ .header-logo:hover {
801
+ transform: scale(1.1);
802
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
803
+ }
804
+ .header h1 {
805
+ font-size: 1.5rem;
806
+ font-weight: 600;
807
+ margin-left: 15px;
808
+ }
809
+ .theme-toggle {
810
+ background: none;
811
+ border: none;
812
+ font-size: 1.5rem;
813
+ cursor: pointer;
814
+ color: #4a5568;
815
+ transition: color 0.3s ease;
816
+ }
817
+ .theme-toggle:hover {
818
+ color: #526df2;
819
+ }
820
+ .categories-grid {
821
+ display: grid;
822
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
823
+ gap: 15px;
824
+ padding: 10px;
825
+ }
826
+ .category-item {
827
+ background: #fff;
828
+ border-radius: 15px;
829
+ padding: 15px;
830
+ text-align: center;
831
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
832
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
833
+ text-decoration: none;
834
+ color: #2d3748;
835
+ }
836
+ body.dark-mode .category-item {
837
+ background: #2d3748;
838
+ color: #e2e8f0;
839
+ }
840
+ .category-item:hover {
841
+ transform: translateY(-5px) scale(1.02);
842
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
843
+ background-color: #526df2;
844
+ color: white;
845
+ }
846
+ .navbar {
847
+ position: fixed;
848
+ bottom: 0;
849
+ left: 0;
850
+ width: 100%;
851
+ background-color: #fff;
852
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
853
+ display: flex;
854
+ justify-content: space-around;
855
+ padding: 10px 0;
856
+ z-index: 1000;
857
+ }
858
+ body.dark-mode .navbar {
859
+ background-color: #2d3748;
860
+ }
861
+ .navbar a {
862
+ text-align: center;
863
+ color: #4a5568;
864
+ text-decoration: none;
865
+ font-size: 0.9rem;
866
+ transition: color 0.3s ease;
867
+ }
868
+ .navbar a.active {
869
+ color: #526df2;
870
+ }
871
+ .navbar a i {
872
+ display: block;
873
+ font-size: 1.5rem;
874
+ margin-bottom: 5px;
875
+ }
876
+ body.dark-mode .navbar a {
877
+ color: #a0aec0;
878
+ }
879
+ .navbar a:hover {
880
+ color: #526df2;
881
+ }
882
+ </style>
883
+ </head>
884
+ <body>
885
+ <div class="container">
886
+ <div class="header">
887
+ <img src="''' + LOGO_URL + '''" alt="Logo" class="header-logo">
888
+ <h1>Категории</h1>
889
+ <button class="theme-toggle" onclick="toggleTheme()">
890
+ <i class="fas fa-moon"></i>
891
+ </button>
892
+ </div>
893
+ <div class="categories-grid">
894
+ {% for category in categories %}
895
+ <a href="/category/{{ category }}" class="category-item">
896
+ <h2>{{ category }}</h2>
897
+ </a>
898
+ {% endfor %}
899
+ </div>
900
+ </div>
901
+
902
+ <div class="navbar">
903
+ <a href="/">
904
+ <i class="fas fa-home"></i>
905
+ Главная
906
+ </a>
907
+ <a href="/categories" class="active">
908
+ <i class="fas fa-list"></i>
909
+ Каталог
910
+ </a>
911
+ <a href="https://api.whatsapp.com/send?phone=996500654659">
912
+ <i class="fab fa-whatsapp"></i>
913
+ WhatsApp
914
+ </a>
915
+ </div>
916
+
917
+ <script>
918
+ function toggleTheme() {
919
+ document.body.classList.toggle('dark-mode');
920
+ const icon = document.querySelector('.theme-toggle i');
921
+ icon.classList.toggle('fa-moon');
922
+ icon.classList.toggle('fa-sun');
923
+ localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');
924
+ }
925
+
926
+ if (localStorage.getItem('theme') === 'dark') {
927
+ document.body.classList.add('dark-mode');
928
+ document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
929
+ }
930
+ </script>
931
+ </body>
932
+ </html>
933
+ '''
934
+ return render_template_string(categories_html, categories=categories)
935
+
936
+ @app.route('/category/<category>')
937
+ def category_products(category):
938
+ data = load_data()
939
+ products = [p for p in data['products'] if p.get('category') == category]
940
+ categories = data['categories']
941
+
942
+ category_html = '''
943
+ <!DOCTYPE html>
944
+ <html lang="ru">
945
+ <head>
946
+ <meta charset="UTF-8">
947
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
948
+ <title>{{ category }}</title>
949
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
950
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
951
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
952
+ <style>
953
+ * {
954
+ margin: 0;
955
+ padding: 0;
956
+ box-sizing: border-box;
957
+ }
958
+ body {
959
+ font-family: 'Poppins', sans-serif;
960
+ background: linear-gradient(135deg, #f0f2f5, #e9ecef);
961
+ color: #2d3748;
962
+ line-height: 1.6;
963
+ transition: background 0.3s, color 0.3s;
964
+ padding-bottom: 60px;
965
+ }
966
+ body.dark-mode {
967
+ background: linear-gradient(135deg, #1a202c, #2d3748);
968
+ color: #e2e8f0;
969
+ }
970
+ .container {
971
+ max-width: 1300px;
972
+ margin: 0 auto;
973
+ padding: 20px;
974
+ }
975
+ .header {
976
+ display: flex;
977
+ justify-content: space-between;
978
+ align-items: center;
979
+ padding: 15px 0;
980
+ border-bottom: 1px solid #e2e8f0;
981
+ }
982
+ .header-logo {
983
+ width: 60px;
984
+ height: 60px;
985
+ border-radius: 50%;
986
+ object-fit: cover;
987
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
988
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
989
+ }
990
+ .header-logo:hover {
991
+ transform: scale(1.1);
992
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
993
+ }
994
+ .header h1 {
995
+ font-size: 1.5rem;
996
+ font-weight: 600;
997
+ margin-left: 15px;
998
+ }
999
+ .theme-toggle {
1000
+ background: none;
1001
+ border: none;
1002
+ font-size: 1.5rem;
1003
+ cursor: pointer;
1004
+ color: #4a5568;
1005
+ transition: color 0.3s ease;
1006
+ }
1007
+ .theme-toggle:hover {
1008
+ color: #526df2;
1009
+ }
1010
+ .products-grid {
1011
+ display: grid;
1012
+ grid-template-columns: repeat(2, minmax(200px, 1fr));
1013
+ gap: 15px;
1014
+ padding: 10px;
1015
+ }
1016
+ .product {
1017
+ background: #fff;
1018
+ border-radius: 15px;
1019
+ padding: 15px;
1020
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
1021
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
1022
+ overflow: hidden;
1023
+ }
1024
+ body.dark-mode .product {
1025
+ background: #2d3748;
1026
+ color: #fff;
1027
+ }
1028
+ .product:hover {
1029
+ transform: translateY(-5px) scale(1.02);
1030
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
1031
+ }
1032
+ .product-image {
1033
+ width: 100%;
1034
+ aspect-ratio: 1;
1035
+ background-color: #fff;
1036
+ border-radius: 10px;
1037
+ overflow: hidden;
1038
+ display: flex;
1039
+ justify-content: center;
1040
+ align-items: center;
1041
+ }
1042
+ .product-image img {
1043
+ max-width: 100%;
1044
+ max-height: 100%;
1045
+ object-fit: contain;
1046
+ transition: transform 0.3s ease;
1047
+ }
1048
+ .product-image img:hover {
1049
+ transform: scale(1.1);
1050
+ }
1051
+ .product h2 {
1052
+ font-size: 1rem;
1053
+ font-weight: 600;
1054
+ margin: 10px 0;
1055
+ text-align: center;
1056
+ white-space: nowrap;
1057
+ overflow: hidden;
1058
+ text-overflow: ellipsis;
1059
+ }
1060
+ .product-price {
1061
+ font-size: 1.1rem;
1062
+ color: #ef4444;
1063
+ font-weight: 700;
1064
+ text-align: center;
1065
+ margin: 5px 0;
1066
+ }
1067
+ .product-description {
1068
+ font-size: 0.8rem;
1069
+ color: #718096;
1070
+ text-align: center;
1071
+ margin-bottom: 15px;
1072
+ overflow: hidden;
1073
+ text-overflow: ellipsis;
1074
+ white-space: nowrap;
1075
+ }
1076
+ body.dark-mode .product-description {
1077
+ color: #a0aec0;
1078
+ }
1079
+ .product-button {
1080
+ display: block;
1081
+ width: 100%;
1082
+ padding: 8px;
1083
+ border: none;
1084
+ border-radius: 8px;
1085
+ background-color: #526df2;
1086
+ color: white;
1087
+ font-size: 0.8rem;
1088
+ font-weight: 500;
1089
+ cursor: pointer;
1090
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1091
+ margin: 5px 0;
1092
+ text-align: center;
1093
+ text-decoration: none;
1094
+ }
1095
+ .product-button:hover {
1096
+ background-color: #3e55d1;
1097
+ box-shadow: 0 4px 15px rgba(62, 85, 209, 0.4);
1098
+ transform: translateY(-2px);
1099
+ }
1100
+ .add-to-cart {
1101
+ background-color: #10b981;
1102
+ }
1103
+ .add-to-cart:hover {
1104
+ background-color: #059669;
1105
+ box-shadow: 0 4px 15px rgba(5, 150, 105, 0.4);
1106
+ }
1107
+ #cart-button {
1108
+ position: fixed;
1109
+ bottom: 80px;
1110
+ right: 20px;
1111
+ background-color: #ef4444;
1112
+ color: white;
1113
+ border: none;
1114
+ border-radius: 50%;
1115
+ width: 50px;
1116
+ height: 50px;
1117
+ font-size: 1.2rem;
1118
+ cursor: pointer;
1119
+ display: none;
1120
+ box-shadow: 0 4px 15px rgba(239, 68, 68, 0.4);
1121
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1122
+ z-index: 1000;
1123
+ }
1124
+ .modal {
1125
+ display: none;
1126
+ position: fixed;
1127
+ z-index: 1001;
1128
+ left: 0;
1129
+ top: 0;
1130
+ width: 100%;
1131
+ height: 100%;
1132
+ background-color: rgba(0,0,0,0.5);
1133
+ backdrop-filter: blur(5px);
1134
+ }
1135
+ .modal-content {
1136
+ background: #fff;
1137
+ margin: 5% auto;
1138
+ padding: 20px;
1139
+ border-radius: 15px;
1140
+ width: 90%;
1141
+ max-width: 700px;
1142
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
1143
+ animation: slideIn 0.3s ease-out;
1144
+ }
1145
+ body.dark-mode .modal-content {
1146
+ background: #2d3748;
1147
+ color: #e2e8f0;
1148
+ }
1149
+ @keyframes slideIn {
1150
+ from { transform: translateY(-50px); opacity: 0; }
1151
+ to { transform: translateY(0); opacity: 1; }
1152
+ }
1153
+ .close {
1154
+ float: right;
1155
+ font-size: 1.5rem;
1156
+ color: #718096;
1157
+ cursor: pointer;
1158
+ transition: color 0.3s;
1159
+ }
1160
+ .close:hover {
1161
+ color: #2d3748;
1162
+ }
1163
+ body.dark-mode .close {
1164
+ color: #a0aec0;
1165
+ }
1166
+ body.dark-mode .close:hover {
1167
+ color: #fff;
1168
+ }
1169
+ .cart-item {
1170
+ display: flex;
1171
+ justify-content: space-between;
1172
+ align-items: center;
1173
+ padding: 15px 0;
1174
+ border-bottom: 1px solid #e2e8f0;
1175
+ }
1176
+ body.dark-mode .cart-item {
1177
+ border-bottom: 1px solid #4a5568;
1178
+ }
1179
+ .cart-item img {
1180
+ width: 50px;
1181
+ height: 50px;
1182
+ object-fit: contain;
1183
+ border-radius: 8px;
1184
+ margin-right: 15px;
1185
+ }
1186
+ .quantity-input, .color-select {
1187
+ width: 100%;
1188
+ max-width: 150px;
1189
+ padding: 8px;
1190
+ border: 1px solid #e2e8f0;
1191
+ border-radius: 8px;
1192
+ font-size: 1rem;
1193
+ margin: 5px 0;
1194
+ }
1195
+ .clear-cart {
1196
+ background-color: #ef4444;
1197
+ }
1198
+ .clear-cart:hover {
1199
+ background-color: #dc2626;
1200
+ box-shadow: 0 4px 15px rgba(220, 38, 38, 0.4);
1201
+ }
1202
+ .order-button {
1203
+ background-color: #10b981;
1204
+ }
1205
+ .order-button:hover {
1206
+ background-color: #059669;
1207
+ box-shadow: 0 4px 15px rgba(5, 150, 105, 0.4);
1208
+ }
1209
+ .navbar {
1210
+ position: fixed;
1211
+ bottom: 0;
1212
+ left: 0;
1213
+ width: 100%;
1214
+ background-color: #fff;
1215
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
1216
+ display: flex;
1217
+ justify-content: space-around;
1218
+ padding: 10px 0;
1219
+ z-index: 1000;
1220
+ }
1221
+ body.dark-mode .navbar {
1222
+ background-color: #2d3748;
1223
+ }
1224
+ .navbar a {
1225
+ text-align: center;
1226
+ color: #4a5568;
1227
+ text-decoration: none;
1228
+ font-size: 0.9rem;
1229
+ transition: color 0.3s ease;
1230
+ }
1231
+ .navbar a.active {
1232
+ color: #526df2;
1233
+ }
1234
+ .navbar a i {
1235
+ display: block;
1236
+ font-size: 1.5rem;
1237
+ margin-bottom: 5px;
1238
+ }
1239
+ body.dark-mode .navbar a {
1240
+ color: #a0aec0;
1241
+ }
1242
+ .navbar a:hover {
1243
+ color: #526df2;
1244
+ }
1245
  </style>
1246
  </head>
1247
  <body>
1248
  <div class="container">
1249
  <div class="header">
1250
  <img src="''' + LOGO_URL + '''" alt="Logo" class="header-logo">
1251
+ <h1>{{ category }}</h1>
1252
  <button class="theme-toggle" onclick="toggleTheme()">
1253
  <i class="fas fa-moon"></i>
1254
  </button>
1255
  </div>
 
 
 
 
 
 
 
 
 
1256
  <div class="products-grid" id="products-grid">
1257
  {% for product in products %}
1258
  <div class="product"
 
1267
  </div>
1268
  {% endif %}
1269
  <h2>{{ product['name'] }}</h2>
1270
+ <div class="product-price">
1271
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
1272
+ <span>Розница: {{ product['price'] }} с</span><br>
1273
+ <span>Опт (от {{ product['min_wholesale'] }}): {{ product['wholesale_price'] }} с</span>
1274
+ {% else %}
1275
+ {{ product['price'] }} с
1276
+ {% endif %}
1277
+ </div>
1278
  <p class="product-description">{{ product['description'][:50] }}{% if product['description']|length > 50 %}...{% endif %}</p>
1279
  <button class="product-button" onclick="openModal({{ loop.index0 }})">Подробнее</button>
1280
  <button class="product-button add-to-cart" onclick="openQuantityModal({{ loop.index0 }})">В корзину</button>
 
1318
 
1319
  <button id="cart-button" onclick="openCartModal()">🛒</button>
1320
 
1321
+ <div class="navbar">
1322
+ <a href="/">
1323
+ <i class="fas fa-home"></i>
1324
+ Главная
1325
+ </a>
1326
+ <a href="/categories" class="active">
1327
+ <i class="fas fa-list"></i>
1328
+ Каталог
1329
+ </a>
1330
+ <a href="https://api.whatsapp.com/send?phone=996500654659">
1331
+ <i class="fab fa-whatsapp"></i>
1332
+ WhatsApp
1333
+ </a>
1334
+ </div>
1335
+
1336
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
1337
  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
1338
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
 
1419
  const cartItemId = `${product.name}-${color}`;
1420
  const existingItem = cart.find(item => item.id === cartItemId);
1421
 
1422
+ const priceToUse = (product.min_wholesale && quantity >= product.min_wholesale) ? product.wholesale_price : product.price;
1423
+
1424
  if (existingItem) {
1425
  existingItem.quantity += quantity;
1426
+ existingItem.price = (existingItem.quantity >= product.min_wholesale) ? product.wholesale_price : product.price;
1427
  } else {
1428
  cart.push({
1429
  id: cartItemId,
1430
  name: product.name,
1431
+ price: priceToUse,
1432
+ retail_price: product.price,
1433
+ wholesale_price: product.wholesale_price,
1434
+ min_wholesale: product.min_wholesale,
1435
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
1436
  quantity: quantity,
1437
  color: color
 
1463
  <div>
1464
  <strong>${item.name}</strong>
1465
  <p>${item.price} с × ${item.quantity} (Цвет: ${item.color})</p>
1466
+ <p>${item.quantity >= item.min_wholesale ? 'Оптовая цена' : 'Розничная цена'}</p>
1467
  </div>
1468
  </div>
1469
  <span>${itemTotal} с</span>
 
1502
  if (event.target.className === 'modal') event.target.style.display = "none";
1503
  }
1504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1505
  updateCartButton();
1506
  </script>
1507
  </body>
1508
  </html>
1509
  '''
1510
+ return render_template_string(category_html, products=products, category=category, repo_id=REPO_ID)
1511
 
1512
  @app.route('/product/<int:index>')
1513
  def product_detail(index):
 
1544
  </div>
1545
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1546
  <p><strong>Цена:</strong> {{ product['price'] }} с</p>
1547
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
1548
+ <p><strong>Оптовая цена (от {{ product['min_wholesale'] }}):</strong> {{ product['wholesale_price'] }} с</p>
1549
+ {% endif %}
1550
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
1551
  <p><strong>Доступные цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
1552
  </div>
 
1582
  elif action == 'add':
1583
  name = request.form.get('name')
1584
  price = request.form.get('price')
1585
+ wholesale_price = request.form.get('wholesale_price')
1586
+ min_wholesale = request.form.get('min_wholesale')
1587
  description = request.form.get('description')
1588
  category = request.form.get('category')
1589
  photos_files = request.files.getlist('photos')
 
1623
  'photos': photos_list,
1624
  'colors': colors if colors else []
1625
  }
1626
+
1627
+ if wholesale_price and min_wholesale:
1628
+ new_product['wholesale_price'] = float(wholesale_price.replace(',', '.'))
1629
+ new_product['min_wholesale'] = int(min_wholesale)
1630
+
1631
  products.append(new_product)
1632
  save_data(data)
1633
  return redirect(url_for('admin'))
 
1636
  index = int(request.form.get('index'))
1637
  name = request.form.get('name')
1638
  price = request.form.get('price')
1639
+ wholesale_price = request.form.get('wholesale_price')
1640
+ min_wholesale = request.form.get('min_wholesale')
1641
  description = request.form.get('description')
1642
  category = request.form.get('category')
1643
  photos_files = request.files.getlist('photos')
 
1668
 
1669
  products[index]['name'] = name
1670
  products[index]['price'] = float(price.replace(',', '.'))
1671
+ if wholesale_price and min_wholesale:
1672
+ products[index]['wholesale_price'] = float(wholesale_price.replace(',', '.'))
1673
+ products[index]['min_wholesale'] = int(min_wholesale)
1674
+ else:
1675
+ products[index].pop('wholesale_price', None)
1676
+ products[index].pop('min_wholesale', None)
1677
  products[index]['description'] = description
1678
  products[index]['category'] = category if category in categories else 'Без категории'
1679
  products[index]['colors'] = colors if colors else []
 
1750
  transition: all 0.3s ease;
1751
  }
1752
  input:focus, textarea:focus, select:focus {
1753
+ border-color: #526df2;
1754
+ box-shadow: 0 0 5px rgba(82, 109, 242, 0.3);
1755
  outline: none;
1756
  }
1757
  button {
1758
  padding: 12px 20px;
1759
  border: none;
1760
  border-radius: 8px;
1761
+ background-color: #526df2;
1762
  color: white;
1763
  font-weight: 500;
1764
  cursor: pointer;
 
1766
  margin-top: 15px;
1767
  }
1768
  button:hover {
1769
+ background-color: #3e55d1;
1770
+ box-shadow: 0 4px 15px rgba(62, 85, 209, 0.4);
1771
  transform: translateY(-2px);
1772
  }
1773
  .delete-button {
 
1817
  <input type="hidden" name="action" value="add">
1818
  <label>Название товара:</label>
1819
  <input type="text" name="name" required>
1820
+ <label>Цена (розница):</label>
1821
  <input type="number" name="price" step="0.01" required>
1822
+ <label>Цена (опт):</label>
1823
+ <input type="number" name="wholesale_price" step="0.01">
1824
+ <label>Минимальное количество для опта:</label>
1825
+ <input type="number" name="min_wholesale" min="1">
1826
  <label>Описание:</label>
1827
  <textarea name="description" rows="4" required></textarea>
1828
  <label>Категория:</label>
 
1880
  <div class="product-item">
1881
  <h3>{{ product['name'] }}</h3>
1882
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1883
+ <p><strong>Цена (розница):</strong> {{ product['price'] }} с</p>
1884
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
1885
+ <p><strong>Цена (опт, от {{ product['min_wholesale'] }}):</strong> {{ product['wholesale_price'] }} с</p>
1886
+ {% endif %}
1887
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
1888
  <p><strong>Цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
1889
  {% if product.get('photos') and product['photos']|length > 0 %}
 
1902
  <input type="hidden" name="index" value="{{ loop.index0 }}">
1903
  <label>Название:</label>
1904
  <input type="text" name="name" value="{{ product['name'] }}" required>
1905
+ <label>Цена (розница):</label>
1906
  <input type="number" name="price" step="0.01" value="{{ product['price'] }}" required>
1907
+ <label>Цена (опт):</label>
1908
+ <input type="number" name="wholesale_price" step="0.01" value="{{ product.get('wholesale_price', '') }}">
1909
+ <label>Минимальное количество для опта:</label>
1910
+ <input type="number" name="min_wholesale" min="1" value="{{ product.get('min_wholesale', '') }}">
1911
  <label>Описание:</label>
1912
  <textarea name="description" rows="4" required>{{ product['description'] }}</textarea>
1913
  <label>Категория:</label>
 
1971
  load_data()
1972
  except Exception as e:
1973
  logging.error(f"Не удалось загрузить базу данных: {e}")
1974
+ app.run(debug=True, host='0.0.0.0', port=7860)
1975
+
1976
+