Kgshop commited on
Commit
ece89ac
·
verified ·
1 Parent(s): 9ab6055

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1328 -274
app.py CHANGED
@@ -1,89 +1,108 @@
 
1
  import json
 
2
  import logging
3
  import threading
4
  import time
5
  from datetime import datetime
6
- from flask import Flask, request, redirect, url_for, render_template_string
7
- from huggingface_hub import HfApi, login
8
  from werkzeug.utils import secure_filename
9
 
10
  app = Flask(__name__)
 
11
 
12
- # Настройки логирования
13
- logging.basicConfig(level=logging.INFO)
 
 
14
 
15
- # Конфигурация Hugging Face
16
- HF_TOKEN_READ = "hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Ваш токен для чтения
17
- HF_TOKEN_WRITE = "hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Ваш токен для записи
18
- REPO_ID = "your_username/your_dataset" # Замените на ваш репозиторий
19
- DB_FILE = "db.json"
20
 
21
- # Авторизация в Hugging Face
22
- login(token=HF_TOKEN_READ)
23
-
24
- # Загрузка данных
25
  def load_data():
26
  try:
27
- api = HfApi()
28
- db_content = api.hf_hub_download(repo_id=REPO_ID, filename=DB_FILE, repo_type="dataset", token=HF_TOKEN_READ)
29
- with open(db_content, 'r', encoding='utf-8') as f:
30
- return json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
31
  except Exception as e:
32
- logging.error(f"Ошибка загрузки данных: {e}")
33
- return {"products": [], "categories": []}
34
 
35
- # Сохранение данных
36
  def save_data(data):
37
- with open(DB_FILE, 'w', encoding='utf-8') as f:
38
- json.dump(data, f, ensure_ascii=False, indent=4)
39
- api = HfApi()
40
- api.upload_file(
41
- path_or_fileobj=DB_FILE,
42
- path_in_repo=DB_FILE,
43
- repo_id=REPO_ID,
44
- repo_type="dataset",
45
- token=HF_TOKEN_WRITE
46
- )
47
-
48
- # Периодический бэкап
49
- def periodic_backup():
50
- while True:
51
- time.sleep(24 * 60 * 60) # Каждые 24 часа
52
  upload_db_to_hf()
 
 
 
53
 
54
  def upload_db_to_hf():
55
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
56
- backup_file = f"backups/db_{timestamp}.json"
57
- api = HfApi()
58
- api.upload_file(
59
- path_or_fileobj=DB_FILE,
60
- path_in_repo=backup_file,
61
- repo_id=REPO_ID,
62
- repo_type="dataset",
63
- token=HF_TOKEN_WRITE
64
- )
65
- logging.info(f"Бэкап создан: {backup_file}")
 
 
66
 
67
  def download_db_from_hf():
68
- api = HfApi()
69
- db_content = api.hf_hub_download(repo_id=REPO_ID, filename=DB_FILE, repo_type="dataset", token=HF_TOKEN_READ)
70
- with open(DB_FILE, 'wb') as f:
71
- with open(db_content, 'rb') as src:
72
- f.write(src.read())
73
- logging.info("База данных скачана.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  @app.route('/')
76
- def index():
77
  data = load_data()
78
  products = data['products']
 
79
 
80
- index_html = '''
81
  <!DOCTYPE html>
82
  <html lang="ru">
83
  <head>
84
  <meta charset="UTF-8">
85
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
86
- <title>Канцелярия оптом</title>
87
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
88
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
89
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
@@ -139,6 +158,13 @@ def index():
139
  body.dark-mode .theme-toggle {
140
  color: #bbb;
141
  }
 
 
 
 
 
 
 
142
  .search-container {
143
  margin: 20px 0;
144
  text-align: center;
@@ -158,10 +184,26 @@ def index():
158
  border-color: #526df2;
159
  box-shadow: 0 4px 15px rgba(82, 109, 242, 0.2);
160
  }
161
- body.dark-mode #search-input {
162
- background: #2a2a3e;
163
- color: #e0e0e0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  border-color: rgba(255, 255, 255, 0.1);
 
165
  }
166
  .products-grid {
167
  display: grid;
@@ -467,28 +509,21 @@ def index():
467
  font-size: 0.75rem;
468
  font-weight: 500;
469
  }
470
- .contact-info {
471
- text-align: center;
472
- margin: 20px 0;
473
- font-size: 1rem;
474
- color: #666;
475
- }
476
- body.dark-mode .contact-info {
477
- color: #bbb;
478
- }
479
  </style>
480
  </head>
481
  <body>
482
  <div class="container">
483
  <div class="header">
484
- <h1>Канцелярия оптом</h1>
485
  <button class="theme-toggle" onclick="toggleTheme()">
486
  <i class="fas fa-moon"></i>
487
  </button>
488
  </div>
489
- <div class="contact-info">
490
- <p>Рынок Дордой, Джунхай, 5 проход 505-506 контейнеры</p>
491
- <p>График работы: с 9:00 до 16:00, без выходных</p>
 
 
492
  </div>
493
  <div class="search-container">
494
  <input type="text" id="search-input" placeholder="Поиск товаров...">
@@ -569,8 +604,18 @@ def index():
569
  </div>
570
  </div>
571
 
 
 
 
 
 
 
 
 
 
572
  <button id="cart-button" onclick="openCartModal()">🛒</button>
573
 
 
574
  <div class="navbar">
575
  <a href="/" class="active">
576
  <i class="fas fa-home"></i>
@@ -588,6 +633,10 @@ def index():
588
  <i class="fas fa-tag"></i>
589
  Скидки
590
  </a>
 
 
 
 
591
  <a href="https://api.whatsapp.com/send?phone=996500654659">
592
  <i class="fab fa-whatsapp"></i>
593
  WhatsApp
@@ -614,19 +663,6 @@ def index():
614
  document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
615
  }
616
 
617
- function filterProducts() {
618
- const searchTerm = document.getElementById('search-input').value.toLowerCase();
619
- document.querySelectorAll('.product').forEach(product => {
620
- const name = product.getAttribute('data-name');
621
- const description = product.getAttribute('data-description');
622
- const category = product.getAttribute('data-category');
623
- const matchesSearch = name.includes(searchTerm) || description.includes(searchTerm) || category.includes(searchTerm);
624
- product.style.display = matchesSearch ? 'block' : 'none';
625
- });
626
- }
627
-
628
- document.getElementById('search-input').addEventListener('input', filterProducts);
629
-
630
  function openModal(index) {
631
  loadProductDetails(index);
632
  document.getElementById('productModal').style.display = "block";
@@ -791,7 +827,6 @@ def index():
791
  favoriteButton.classList.add('favorited');
792
  }
793
  localStorage.setItem('favorites', JSON.stringify(favorites));
794
- loadFavorites();
795
  }
796
 
797
  function loadFavorites() {
@@ -804,18 +839,43 @@ def index():
804
  });
805
  }
806
 
 
 
 
 
807
  window.onclick = function(event) {
808
  if (event.target.className === 'modal') event.target.style.display = "none";
809
  }
810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  updateCartButton();
812
  loadFavorites();
813
- filterProducts();
814
  </script>
815
  </body>
816
  </html>
817
  '''
818
- return render_template_string(index_html, products=products, repo_id=REPO_ID)
819
 
820
  @app.route('/categories')
821
  def categories_page():
@@ -828,7 +888,7 @@ def categories_page():
828
  <head>
829
  <meta charset="UTF-8">
830
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
831
- <title>Каталог</title>
832
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
833
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
834
  <style>
@@ -883,142 +943,43 @@ def categories_page():
883
  body.dark-mode .theme-toggle {
884
  color: #bbb;
885
  }
886
- .category-list {
887
  display: grid;
888
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
889
  gap: 20px;
890
- padding: 20px 0;
891
  }
892
  .category-item {
893
- background: rgba(255, 255, 255, 0.9);
894
  border: 1px solid rgba(0, 0, 0, 0.05);
895
  border-radius: 15px;
896
- padding: 15px;
897
  text-align: center;
898
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
899
- transition: transform 0.3s ease, box-shadow 0.3s ease;
900
- cursor: pointer;
901
  text-decoration: none;
902
  color: #333;
903
  }
 
 
 
 
 
904
  .category-item:hover {
905
  transform: translateY(-5px);
906
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
 
 
 
907
  }
908
- body.dark-mode .category-item {
909
  background: rgba(42, 42, 62, 0.9);
910
- border-color: rgba(255, 255, 255, 0.05);
911
- color: #e0e0e0;
912
  }
913
  .category-item h2 {
914
  font-size: 1.2rem;
915
  font-weight: 500;
916
  }
917
- #cart-button {
918
- position: fixed;
919
- bottom: 80px;
920
- right: 20px;
921
- background-color: #e63946;
922
- color: white;
923
- border: none;
924
- border-radius: 50%;
925
- width: 60px;
926
- height: 60px;
927
- font-size: 1.5rem;
928
- cursor: pointer;
929
- display: none;
930
- box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4);
931
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
932
- z-index: 1000;
933
- }
934
- #cart-button:hover {
935
- transform: scale(1.1);
936
- }
937
- .modal {
938
- display: none;
939
- position: fixed;
940
- z-index: 1001;
941
- left: 0;
942
- top: 0;
943
- width: 100%;
944
- height: 100%;
945
- background-color: rgba(0,0,0,0.5);
946
- backdrop-filter: blur(5px);
947
- }
948
- .modal-content {
949
- background: #fff;
950
- margin: 5% auto;
951
- padding: 20px;
952
- border-radius: 15px;
953
- width: 90%;
954
- max-width: 700px;
955
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
956
- animation: slideIn 0.3s ease-out;
957
- }
958
- body.dark-mode .modal-content {
959
- background: #2a2a3e;
960
- color: #e0e0e0;
961
- }
962
- @keyframes slideIn {
963
- from { transform: translateY(-50px); opacity: 0; }
964
- to { transform: translateY(0); opacity: 1; }
965
- }
966
- .close {
967
- float: right;
968
- font-size: 1.5rem;
969
- color: #666;
970
- cursor: pointer;
971
- transition: color 0.3s;
972
- }
973
- .close:hover {
974
- color: #333;
975
- }
976
- body.dark-mode .close {
977
- color: #bbb;
978
- }
979
- body.dark-mode .close:hover {
980
- color: #fff;
981
- }
982
- .cart-item {
983
- display: flex;
984
- justify-content: space-between;
985
- align-items: center;
986
- padding: 15px 0;
987
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
988
- }
989
- body.dark-mode .cart-item {
990
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
991
- }
992
- .cart-item img {
993
- width: 50px;
994
- height: 50px;
995
- object-fit: contain;
996
- border-radius: 8px;
997
- margin-right: 15px;
998
- }
999
- .quantity-input, .color-select {
1000
- width: 100%;
1001
- max-width: 150px;
1002
- padding: 8px;
1003
- border: 1px solid rgba(0, 0, 0, 0.1);
1004
- border-radius: 8px;
1005
- font-size: 1rem;
1006
- margin: 5px 0;
1007
- }
1008
- .clear-cart {
1009
- background-color: #e63946;
1010
- }
1011
- .clear-cart:hover {
1012
- background-color: #d62828;
1013
- box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4);
1014
- }
1015
- .order-button {
1016
- background-color: #2ecc71;
1017
- }
1018
- .order-button:hover {
1019
- background-color: #27ae60;
1020
- box-shadow: 0 4px 15px rgba(46, 204, 113, 0.4);
1021
- }
1022
  .navbar {
1023
  position: fixed;
1024
  bottom: 0;
@@ -1060,36 +1021,29 @@ def categories_page():
1060
  <body>
1061
  <div class="container">
1062
  <div class="header">
1063
- <h1>Каталог</h1>
1064
  <button class="theme-toggle" onclick="toggleTheme()">
1065
  <i class="fas fa-moon"></i>
1066
  </button>
1067
  </div>
1068
- <div class="category-list">
1069
  {% for category in categories %}
1070
- <a href="/category/{{ category|urlencode }}" class="category-item">
1071
  <h2>{{ category }}</h2>
1072
  </a>
1073
  {% endfor %}
1074
  </div>
1075
  </div>
1076
 
1077
- <!-- Cart Modal -->
1078
- <div id="cartModal" class="modal">
1079
  <div class="modal-content">
1080
- <span class="close" onclick="closeModal('cartModal')">×</span>
1081
- <h2>Корзина</h2>
1082
- <div id="cartContent"></div>
1083
- <div style="margin-top: 20px; text-align: right;">
1084
- <strong>Итого: <span id="cartTotal">0</span> с</strong>
1085
- <button class="product-button clear-cart" onclick="clearCart()">Очистить</button>
1086
- <button class="product-button order-button" onclick="orderViaWhatsApp()">Заказать</button>
1087
- </div>
1088
  </div>
1089
  </div>
1090
 
1091
- <button id="cart-button" onclick="openCartModal()">🛒</button>
1092
-
1093
  <div class="navbar">
1094
  <a href="/">
1095
  <i class="fas fa-home"></i>
@@ -1107,6 +1061,10 @@ def categories_page():
1107
  <i class="fas fa-tag"></i>
1108
  Скидки
1109
  </a>
 
 
 
 
1110
  <a href="https://api.whatsapp.com/send?phone=996500654659">
1111
  <i class="fab fa-whatsapp"></i>
1112
  WhatsApp
@@ -1122,9 +1080,639 @@ def categories_page():
1122
  localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');
1123
  }
1124
 
1125
- if (localStorage.getItem('theme') === 'dark') {
1126
- document.body.classList.add('dark-mode');
1127
- document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1128
  }
1129
 
1130
  function updateCartButton() {
@@ -1159,10 +1747,6 @@ def categories_page():
1159
  document.getElementById('cartModal').style.display = 'block';
1160
  }
1161
 
1162
- function closeModal(modalId) {
1163
- document.getElementById(modalId).style.display = "none";
1164
- }
1165
-
1166
  function orderViaWhatsApp() {
1167
  const cart = JSON.parse(localStorage.getItem('cart') || '[]');
1168
  if (cart.length === 0) {
@@ -1186,29 +1770,59 @@ def categories_page():
1186
  updateCartButton();
1187
  }
1188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1189
  window.onclick = function(event) {
1190
  if (event.target.className === 'modal') event.target.style.display = "none";
1191
  }
1192
 
1193
  updateCartButton();
 
1194
  </script>
1195
  </body>
1196
  </html>
1197
  '''
1198
- return render_template_string(categories_html, categories=categories, repo_id=REPO_ID)
1199
 
1200
- @app.route('/category/<category>')
1201
- def category_page(category):
1202
  data = load_data()
1203
- products = [p for p in data['products'] if p.get('category') == category]
 
1204
 
1205
- category_html = '''
1206
  <!DOCTYPE html>
1207
  <html lang="ru">
1208
  <head>
1209
  <meta charset="UTF-8">
1210
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1211
- <title>{{ category }}</title>
1212
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1213
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
1214
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
@@ -1579,7 +2193,6 @@ def category_page(category):
1579
  </button>
1580
  </div>
1581
  <div class="products-grid" id="products-grid">
1582
- <!-- Продукты будут добавлены через JavaScript -->
1583
  </div>
1584
  </div>
1585
 
@@ -1616,6 +2229,15 @@ def category_page(category):
1616
  </div>
1617
  </div>
1618
 
 
 
 
 
 
 
 
 
 
1619
  <button id="cart-button" onclick="openCartModal()">🛒</button>
1620
 
1621
  <div class="navbar">
@@ -1635,6 +2257,10 @@ def category_page(category):
1635
  <i class="fas fa-tag"></i>
1636
  Скидки
1637
  </a>
 
 
 
 
1638
  <a href="https://api.whatsapp.com/send?phone=996500654659">
1639
  <i class="fab fa-whatsapp"></i>
1640
  WhatsApp
@@ -1661,7 +2287,7 @@ def category_page(category):
1661
  document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
1662
  }
1663
 
1664
- function loadFavorites() {
1665
  const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
1666
  const productsGrid = document.getElementById('products-grid');
1667
  productsGrid.innerHTML = '';
@@ -1674,43 +2300,30 @@ def category_page(category):
1674
  favorites.forEach(index => {
1675
  const product = products[index];
1676
  if (!product) return;
1677
-
1678
  const productElement = document.createElement('div');
1679
  productElement.className = 'product';
1680
  productElement.setAttribute('onclick', `openModal(${index})`);
1681
  productElement.setAttribute('data-name', product.name.toLowerCase());
1682
  productElement.setAttribute('data-description', product.description.toLowerCase());
1683
  productElement.setAttribute('data-category', product.category || 'Без категории');
1684
-
1685
- let priceHtml = '';
1686
- if (product.discount) {
1687
- const discountedPrice = (product.price * (1 - product.discount / 100)).toFixed(2);
1688
- priceHtml = `
1689
- <span style="text-decoration: line-through; color: #666;">${product.price} с</span>
1690
- <span>${discountedPrice} с</span>
1691
- <span class="discount">Скидка: ${product.discount}%</span>
1692
- `;
1693
- } else {
1694
- priceHtml = `<span>${product.price} с</span>`;
1695
- }
1696
- if (product.wholesale_price && product.min_wholesale) {
1697
- priceHtml += `<span class="wholesale">Опт: ${product.wholesale_price} с</span>`;
1698
- }
1699
-
1700
  productElement.innerHTML = `
1701
  <button class="favorite-button favorited" onclick="event.stopPropagation(); toggleFavorite(${index})">
1702
  <i class="fas fa-heart"></i>
1703
  </button>
1704
  ${product.photos && product.photos.length > 0 ? `
1705
  <div class="product-image">
1706
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${product.photos[0]}"
1707
- alt="${product.name}"
1708
- loading="lazy">
1709
  </div>` : ''}
1710
  ${product.wholesale_price && product.min_wholesale ? `<span class="wholesale-badge">Опт от ${product.min_wholesale}</span>` : ''}
1711
  ${product.discount ? `<span class="discount-badge">Скидка ${product.discount}%</span>` : ''}
1712
  <h2>${product.name}</h2>
1713
- <div class="product-price">${priceHtml}</div>
 
 
 
 
 
 
1714
  <p class="product-description">${product.description.slice(0, 50)}${product.description.length > 50 ? '...' : ''}</p>
1715
  <button class="product-button add-to-cart" onclick="event.stopPropagation(); openQuantityModal(${index})">В корзину</button>
1716
  `;
@@ -1873,13 +2486,20 @@ def category_page(category):
1873
  function toggleFavorite(index) {
1874
  let favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
1875
  const productId = index.toString();
 
1876
  if (favorites.includes(productId)) {
1877
  favorites = favorites.filter(id => id !== productId);
 
1878
  } else {
1879
  favorites.push(productId);
 
1880
  }
1881
  localStorage.setItem('favorites', JSON.stringify(favorites));
1882
- loadFavorites();
 
 
 
 
1883
  }
1884
 
1885
  window.onclick = function(event) {
@@ -1887,7 +2507,7 @@ def category_page(category):
1887
  }
1888
 
1889
  updateCartButton();
1890
- loadFavorites();
1891
  </script>
1892
  </body>
1893
  </html>
@@ -1898,6 +2518,7 @@ def category_page(category):
1898
  def discounts_page():
1899
  data = load_data()
1900
  products = [p for p in data['products'] if p.get('discount')]
 
1901
 
1902
  discounts_html = '''
1903
  <!DOCTYPE html>
@@ -2351,6 +2972,15 @@ def discounts_page():
2351
  </div>
2352
  </div>
2353
 
 
 
 
 
 
 
 
 
 
2354
  <button id="cart-button" onclick="openCartModal()">🛒</button>
2355
 
2356
  <div class="navbar">
@@ -2370,6 +3000,10 @@ def discounts_page():
2370
  <i class="fas fa-tag"></i>
2371
  Скидки
2372
  </a>
 
 
 
 
2373
  <a href="https://api.whatsapp.com/send?phone=996500654659">
2374
  <i class="fab fa-whatsapp"></i>
2375
  WhatsApp
@@ -2560,7 +3194,6 @@ def discounts_page():
2560
  favoriteButton.classList.add('favorited');
2561
  }
2562
  localStorage.setItem('favorites', JSON.stringify(favorites));
2563
- loadFavorites();
2564
  }
2565
 
2566
  function loadFavorites() {
@@ -2573,6 +3206,10 @@ def discounts_page():
2573
  });
2574
  }
2575
 
 
 
 
 
2576
  window.onclick = function(event) {
2577
  if (event.target.className === 'modal') event.target.style.display = "none";
2578
  }
@@ -2583,7 +3220,7 @@ def discounts_page():
2583
  </body>
2584
  </html>
2585
  '''
2586
- return render_template_string(discounts_html, products=products, repo_id=REPO_ID)
2587
 
2588
  @app.route('/product/<int:index>')
2589
  def product_details(index):
@@ -2594,7 +3231,7 @@ def product_details(index):
2594
  <style>
2595
  .swiper-container {
2596
  width: 100%;
2597
- max-width: 500px;
2598
  margin: 20px auto;
2599
  }
2600
  .swiper-slide {
@@ -2646,24 +3283,25 @@ def product_details(index):
2646
  .product-details p {
2647
  font-size: 0.95rem;
2648
  color: #666;
2649
- margin-bottom: 15px;
2650
  }
2651
  body.dark-mode .product-details p {
2652
  color: #bbb;
2653
  }
2654
- .colors {
2655
  margin: 10px 0;
2656
  }
2657
- .colors span {
2658
  display: inline-block;
2659
  margin-right: 10px;
2660
  padding: 5px 10px;
2661
- border: 1px solid rgba(0, 0, 0, 0.1);
2662
- border-radius: 5px;
2663
  font-size: 0.9rem;
2664
  }
2665
- body.dark-mode .colors span {
2666
- border-color: rgba(255, 255, 255, 0.1);
 
2667
  }
2668
  </style>
2669
  <div class="product-details">
@@ -2672,9 +3310,7 @@ def product_details(index):
2672
  <div class="swiper-wrapper">
2673
  {% for photo in product.get('photos', []) %}
2674
  <div class="swiper-slide">
2675
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}"
2676
- alt="{{ product['name'] }}"
2677
- loading="lazy">
2678
  </div>
2679
  {% endfor %}
2680
  </div>
@@ -2694,8 +3330,9 @@ def product_details(index):
2694
  <span class="wholesale">Опт: {{ product['wholesale_price'] }} с (от {{ product['min_wholesale'] }})</span>
2695
  {% endif %}
2696
  </div>
2697
- <p>{{ product['description'] }}</p>
2698
- {% if product.get('colors') and product['colors']|length > 0 %}
 
2699
  <div class="colors">
2700
  <strong>Цвета:</strong>
2701
  {% for color in product['colors'] %}
@@ -2707,10 +3344,427 @@ def product_details(index):
2707
  '''
2708
  return render_template_string(product_html, product=product, repo_id=REPO_ID)
2709
 
2710
- # Запуск периодического бэкапа в отдельном потоке
2711
- backup_thread = threading.Thread(target=periodic_backup, daemon=True)
2712
- backup_thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2713
 
2714
  if __name__ == '__main__':
 
 
 
 
 
 
2715
  app.run(debug=True, host='0.0.0.0', port=7860)
2716
 
 
1
+ from flask import Flask, render_template_string, request, redirect, url_for
2
  import json
3
+ import os
4
  import logging
5
  import threading
6
  import time
7
  from datetime import datetime
8
+ from huggingface_hub import HfApi, hf_hub_download
9
+ from huggingface_hub.utils import RepositoryNotFoundError
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"
17
+ HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
18
+ HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
19
 
20
+ # Настройка логирования
21
+ logging.basicConfig(level=logging.DEBUG)
 
 
 
22
 
 
 
 
 
23
  def load_data():
24
  try:
25
+ download_db_from_hf()
26
+ with open(DATA_FILE, 'r', encoding='utf-8') as file:
27
+ data = json.load(file)
28
+ logging.info("Данные успешно загружены из JSON")
29
+ if not isinstance(data, dict) or 'products' not in data or 'categories' not in data:
30
+ return {'products': [], 'categories': [] if not isinstance(data, list) else data}
31
+ return data
32
+ except FileNotFoundError:
33
+ logging.warning("Локальный файл базы данных не найден после скачивания.")
34
+ return {'products': [], 'categories': []}
35
+ except json.JSONDecodeError:
36
+ logging.error("Ошибка: Невозможно декодировать JSON файл.")
37
+ return {'products': [], 'categories': []}
38
+ except RepositoryNotFoundError:
39
+ logging.error("Репозиторий не найден. Создание локальной базы данных.")
40
+ return {'products': [], 'categories': []}
41
  except Exception as e:
42
+ logging.error(f"Произошла ошибка при загрузке данных: {e}")
43
+ return {'products': [], 'categories': []}
44
 
 
45
  def save_data(data):
46
+ try:
47
+ with open(DATA_FILE, 'w', encoding='utf-8') as file:
48
+ json.dump(data, file, ensure_ascii=False, indent=4)
49
+ logging.info("Данные успешно сохранены в JSON")
 
 
 
 
 
 
 
 
 
 
 
50
  upload_db_to_hf()
51
+ except Exception as e:
52
+ logging.error(f"Ошибка при сохранении данных: {e}")
53
+ raise
54
 
55
  def upload_db_to_hf():
56
+ try:
57
+ api = HfApi()
58
+ api.upload_file(
59
+ path_or_fileobj=DATA_FILE,
60
+ path_in_repo=DATA_FILE,
61
+ repo_id=REPO_ID,
62
+ repo_type="dataset",
63
+ token=HF_TOKEN_WRITE,
64
+ commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
65
+ )
66
+ logging.info("Резервная копия JSON базы успешно загружена на Hugging Face.")
67
+ except Exception as e:
68
+ logging.error(f"Ошибка при загрузке резервной копии: {e}")
69
 
70
  def download_db_from_hf():
71
+ try:
72
+ hf_hub_download(
73
+ repo_id=REPO_ID,
74
+ filename=DATA_FILE,
75
+ repo_type="dataset",
76
+ token=HF_TOKEN_READ,
77
+ local_dir=".",
78
+ local_dir_use_symlinks=False
79
+ )
80
+ logging.info("JSON база успешно скачана из Hugging Face.")
81
+ except RepositoryNotFoundError as e:
82
+ logging.error(f"Репозиторий не найден: {e}")
83
+ raise
84
+ except Exception as e:
85
+ logging.error(f"Ошибка при скачивании JSON базы: {e}")
86
+ raise
87
+
88
+ def periodic_backup():
89
+ while True:
90
+ upload_db_to_hf()
91
+ time.sleep(800)
92
 
93
  @app.route('/')
94
+ def catalog():
95
  data = load_data()
96
  products = data['products']
97
+ categories = data['categories']
98
 
99
+ catalog_html = '''
100
  <!DOCTYPE html>
101
  <html lang="ru">
102
  <head>
103
  <meta charset="UTF-8">
104
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
105
+ <title>Канцелярия Оптом</title>
106
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
107
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
108
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
 
158
  body.dark-mode .theme-toggle {
159
  color: #bbb;
160
  }
161
+ .filters-container {
162
+ margin: 20px 0;
163
+ display: flex;
164
+ flex-wrap: wrap;
165
+ gap: 10px;
166
+ justify-content: center;
167
+ }
168
  .search-container {
169
  margin: 20px 0;
170
  text-align: center;
 
184
  border-color: #526df2;
185
  box-shadow: 0 4px 15px rgba(82, 109, 242, 0.2);
186
  }
187
+ .category-filter {
188
+ padding: 10px 20px;
189
+ border: 1px solid rgba(0, 0, 0, 0.1);
190
+ border-radius: 25px;
191
+ background-color: #fff;
192
+ cursor: pointer;
193
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
194
+ font-size: 0.9rem;
195
+ font-weight: 500;
196
+ }
197
+ .category-filter.active, .category-filter:hover {
198
+ background-color: #526df2;
199
+ color: white;
200
+ border-color: #526df2;
201
+ box-shadow: 0 2px 10px rgba(82, 109, 242, 0.3);
202
+ }
203
+ body.dark-mode .category-filter {
204
+ background-color: #2a2a3e;
205
  border-color: rgba(255, 255, 255, 0.1);
206
+ color: #e0e0e0;
207
  }
208
  .products-grid {
209
  display: grid;
 
509
  font-size: 0.75rem;
510
  font-weight: 500;
511
  }
 
 
 
 
 
 
 
 
 
512
  </style>
513
  </head>
514
  <body>
515
  <div class="container">
516
  <div class="header">
517
+ <h1>Канцелярия Оптом</h1>
518
  <button class="theme-toggle" onclick="toggleTheme()">
519
  <i class="fas fa-moon"></i>
520
  </button>
521
  </div>
522
+ <div class="filters-container">
523
+ <button class="category-filter active" data-category="all">Все категории</button>
524
+ {% for category in categories %}
525
+ <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
526
+ {% endfor %}
527
  </div>
528
  <div class="search-container">
529
  <input type="text" id="search-input" placeholder="Поиск товаров...">
 
604
  </div>
605
  </div>
606
 
607
+ <!-- Address Modal -->
608
+ <div id="addressModal" class="modal">
609
+ <div class="modal-content">
610
+ <span class="close" onclick="closeModal('addressModal')">×</span>
611
+ <h2>Наш адрес</h2>
612
+ <p>Рынок Д��рдой, Джунхай, 5 проход 505-506 контейнеры</p>
613
+ </div>
614
+ </div>
615
+
616
  <button id="cart-button" onclick="openCartModal()">🛒</button>
617
 
618
+ <!-- Navigation Bar -->
619
  <div class="navbar">
620
  <a href="/" class="active">
621
  <i class="fas fa-home"></i>
 
633
  <i class="fas fa-tag"></i>
634
  Скидки
635
  </a>
636
+ <a href="#" onclick="openAddressModal()">
637
+ <i class="fas fa-map-marker-alt"></i>
638
+ Адрес
639
+ </a>
640
  <a href="https://api.whatsapp.com/send?phone=996500654659">
641
  <i class="fab fa-whatsapp"></i>
642
  WhatsApp
 
663
  document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
664
  }
665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  function openModal(index) {
667
  loadProductDetails(index);
668
  document.getElementById('productModal').style.display = "block";
 
827
  favoriteButton.classList.add('favorited');
828
  }
829
  localStorage.setItem('favorites', JSON.stringify(favorites));
 
830
  }
831
 
832
  function loadFavorites() {
 
839
  });
840
  }
841
 
842
+ function openAddressModal() {
843
+ document.getElementById('addressModal').style.display = 'block';
844
+ }
845
+
846
  window.onclick = function(event) {
847
  if (event.target.className === 'modal') event.target.style.display = "none";
848
  }
849
 
850
+ document.getElementById('search-input').addEventListener('input', filterProducts);
851
+ document.querySelectorAll('.category-filter').forEach(filter => {
852
+ filter.addEventListener('click', function() {
853
+ document.querySelectorAll('.category-filter').forEach(f => f.classList.remove('active'));
854
+ this.classList.add('active');
855
+ filterProducts();
856
+ });
857
+ });
858
+
859
+ function filterProducts() {
860
+ const searchTerm = document.getElementById('search-input').value.toLowerCase();
861
+ const activeCategory = document.querySelector('.category-filter.active').dataset.category;
862
+ document.querySelectorAll('.product').forEach(product => {
863
+ const name = product.getAttribute('data-name');
864
+ const description = product.getAttribute('data-description');
865
+ const category = product.getAttribute('data-category');
866
+ const matchesSearch = name.includes(searchTerm) || description.includes(searchTerm);
867
+ const matchesCategory = activeCategory === 'all' || category === activeCategory;
868
+ product.style.display = matchesSearch && matchesCategory ? 'block' : 'none';
869
+ });
870
+ }
871
+
872
  updateCartButton();
873
  loadFavorites();
 
874
  </script>
875
  </body>
876
  </html>
877
  '''
878
+ return render_template_string(catalog_html, products=products, categories=categories, repo_id=REPO_ID)
879
 
880
  @app.route('/categories')
881
  def categories_page():
 
888
  <head>
889
  <meta charset="UTF-8">
890
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
891
+ <title>Категории</title>
892
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
893
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
894
  <style>
 
943
  body.dark-mode .theme-toggle {
944
  color: #bbb;
945
  }
946
+ .categories-grid {
947
  display: grid;
948
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
949
  gap: 20px;
950
+ padding: 10px;
951
  }
952
  .category-item {
953
+ background: rgba(255, 255, 255, 0.8);
954
  border: 1px solid rgba(0, 0, 0, 0.05);
955
  border-radius: 15px;
956
+ padding: 20px;
957
  text-align: center;
958
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
959
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease, background 0.3s ease;
 
960
  text-decoration: none;
961
  color: #333;
962
  }
963
+ body.dark-mode .category-item {
964
+ background: rgba(42, 42, 62, 0.8);
965
+ border-color: rgba(255, 255, 255, 0.05);
966
+ color: #e0e0e0;
967
+ }
968
  .category-item:hover {
969
  transform: translateY(-5px);
970
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
971
+ background: rgba(255, 255, 255, 0.9);
972
+ background-color: #526df2;
973
+ color: white;
974
  }
975
+ body.dark-mode .category-item:hover {
976
  background: rgba(42, 42, 62, 0.9);
977
+ box-shadow: 0 8px 25px rgba(255, 255, 255, 0.1);
 
978
  }
979
  .category-item h2 {
980
  font-size: 1.2rem;
981
  font-weight: 500;
982
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  .navbar {
984
  position: fixed;
985
  bottom: 0;
 
1021
  <body>
1022
  <div class="container">
1023
  <div class="header">
1024
+ <h1>Категории</h1>
1025
  <button class="theme-toggle" onclick="toggleTheme()">
1026
  <i class="fas fa-moon"></i>
1027
  </button>
1028
  </div>
1029
+ <div class="categories-grid">
1030
  {% for category in categories %}
1031
+ <a href="/category/{{ category }}" class="category-item">
1032
  <h2>{{ category }}</h2>
1033
  </a>
1034
  {% endfor %}
1035
  </div>
1036
  </div>
1037
 
1038
+ <!-- Address Modal -->
1039
+ <div id="addressModal" class="modal">
1040
  <div class="modal-content">
1041
+ <span class="close" onclick="closeModal('addressModal')">×</span>
1042
+ <h2>Наш адрес</h2>
1043
+ <p>Рынок Дордой, Джунхай, 5 проход 505-506 контейнеры</p>
 
 
 
 
 
1044
  </div>
1045
  </div>
1046
 
 
 
1047
  <div class="navbar">
1048
  <a href="/">
1049
  <i class="fas fa-home"></i>
 
1061
  <i class="fas fa-tag"></i>
1062
  Скидки
1063
  </a>
1064
+ <a href="#" onclick="openAddressModal()">
1065
+ <i class="fas fa-map-marker-alt"></i>
1066
+ Адрес
1067
+ </a>
1068
  <a href="https://api.whatsapp.com/send?phone=996500654659">
1069
  <i class="fab fa-whatsapp"></i>
1070
  WhatsApp
 
1080
  localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');
1081
  }
1082
 
1083
+ if (localStorage.getItem('theme') === 'dark') {
1084
+ document.body.classList.add('dark-mode');
1085
+ document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
1086
+ }
1087
+
1088
+ function openAddressModal() {
1089
+ document.getElementById('addressModal').style.display = 'block';
1090
+ }
1091
+
1092
+ function closeModal(modalId) {
1093
+ document.getElementById(modalId).style.display = "none";
1094
+ }
1095
+
1096
+ window.onclick = function(event) {
1097
+ if (event.target.className === 'modal') event.target.style.display = "none";
1098
+ }
1099
+ </script>
1100
+ </body>
1101
+ </html>
1102
+ '''
1103
+ return render_template_string(categories_html, categories=categories)
1104
+
1105
+ @app.route('/category/<category>')
1106
+ def category_products(category):
1107
+ data = load_data()
1108
+ products = [p for p in data['products'] if p.get('category') == category]
1109
+ categories = data['categories']
1110
+
1111
+ category_html = '''
1112
+ <!DOCTYPE html>
1113
+ <html lang="ru">
1114
+ <head>
1115
+ <meta charset="UTF-8">
1116
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1117
+ <title>{{ category }}</title>
1118
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1119
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
1120
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1121
+ <style>
1122
+ * {
1123
+ margin: 0;
1124
+ padding: 0;
1125
+ box-sizing: border-box;
1126
+ }
1127
+ body {
1128
+ font-family: 'Roboto', sans-serif;
1129
+ background: linear-gradient(135deg, #f5f7fa, #e4e7eb);
1130
+ color: #333;
1131
+ line-height: 1.6;
1132
+ transition: background 0.3s, color 0.3s;
1133
+ padding-bottom: 60px;
1134
+ }
1135
+ body.dark-mode {
1136
+ background: linear-gradient(135deg, #1a1a2e, #2a2a3e);
1137
+ color: #e0e0e0;
1138
+ }
1139
+ .container {
1140
+ max-width: 1400px;
1141
+ margin: 0 auto;
1142
+ padding: 20px;
1143
+ }
1144
+ .header {
1145
+ display: flex;
1146
+ justify-content: space-between;
1147
+ align-items: center;
1148
+ padding: 15px 0;
1149
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
1150
+ }
1151
+ .header h1 {
1152
+ font-size: 2rem;
1153
+ font-weight: 700;
1154
+ letter-spacing: 1px;
1155
+ background: linear-gradient(90deg, #526df2, #7a8ff5);
1156
+ -webkit-background-clip: text;
1157
+ -webkit-text-fill-color: transparent;
1158
+ }
1159
+ .theme-toggle {
1160
+ background: none;
1161
+ border: none;
1162
+ font-size: 1.5rem;
1163
+ cursor: pointer;
1164
+ color: #666;
1165
+ transition: color 0.3s ease;
1166
+ }
1167
+ .theme-toggle:hover {
1168
+ color: #526df2;
1169
+ }
1170
+ body.dark-mode .theme-toggle {
1171
+ color: #bbb;
1172
+ }
1173
+ .products-grid {
1174
+ display: grid;
1175
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
1176
+ gap: 20px;
1177
+ padding: 10px;
1178
+ }
1179
+ .product {
1180
+ background: rgba(255, 255, 255, 0.8);
1181
+ border: 1px solid rgba(0, 0, 0, 0.05);
1182
+ border-radius: 15px;
1183
+ padding: 15px;
1184
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
1185
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease, background 0.3s ease;
1186
+ cursor: pointer;
1187
+ position: relative;
1188
+ overflow: hidden;
1189
+ }
1190
+ .product:hover {
1191
+ transform: translateY(-5px);
1192
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
1193
+ background: rgba(255, 255, 255, 0.9);
1194
+ }
1195
+ body.dark-mode .product {
1196
+ background: rgba(42, 42, 62, 0.8);
1197
+ border-color: rgba(255, 255, 255, 0.05);
1198
+ color: #e0e0e0;
1199
+ }
1200
+ body.dark-mode .product:hover {
1201
+ background: rgba(42, 42, 62, 0.9);
1202
+ box-shadow: 0 8px 25px rgba(255, 255, 255, 0.1);
1203
+ }
1204
+ .product-image {
1205
+ width: 100%;
1206
+ aspect-ratio: 1;
1207
+ background-color: #fff;
1208
+ border-radius: 10px;
1209
+ overflow: hidden;
1210
+ display: flex;
1211
+ justify-content: center;
1212
+ align-items: center;
1213
+ transition: transform 0.3s ease;
1214
+ }
1215
+ body.dark-mode .product-image {
1216
+ background-color: #333;
1217
+ }
1218
+ .product-image img {
1219
+ max-width: 100%;
1220
+ max-height: 100%;
1221
+ object-fit: contain;
1222
+ transition: transform 0.3s ease;
1223
+ }
1224
+ .product-image img:hover {
1225
+ transform: scale(1.05);
1226
+ }
1227
+ .product h2 {
1228
+ font-size: 1.1rem;
1229
+ font-weight: 500;
1230
+ margin: 10px 0;
1231
+ text-align: center;
1232
+ white-space: nowrap;
1233
+ overflow: hidden;
1234
+ text-overflow: ellipsis;
1235
+ }
1236
+ .product-price {
1237
+ font-size: 1.2rem;
1238
+ color: #e63946;
1239
+ font-weight: 700;
1240
+ text-align: center;
1241
+ margin: 5px 0;
1242
+ }
1243
+ .product-price .wholesale {
1244
+ font-size: 0.9rem;
1245
+ color: #526df2;
1246
+ font-weight: 500;
1247
+ display: block;
1248
+ margin-top: 5px;
1249
+ }
1250
+ .product-price .discount {
1251
+ font-size: 0.9rem;
1252
+ color: #2ecc71;
1253
+ font-weight: 500;
1254
+ display: block;
1255
+ margin-top: 5px;
1256
+ }
1257
+ .product-description {
1258
+ font-size: 0.85rem;
1259
+ color: #666;
1260
+ text-align: center;
1261
+ margin-bottom: 15px;
1262
+ overflow: hidden;
1263
+ text-overflow: ellipsis;
1264
+ white-space: nowrap;
1265
+ }
1266
+ body.dark-mode .product-description {
1267
+ color: #bbb;
1268
+ }
1269
+ .product-button {
1270
+ display: block;
1271
+ width: 100%;
1272
+ padding: 10px;
1273
+ border: none;
1274
+ border-radius: 25px;
1275
+ background-color: #526df2;
1276
+ color: white;
1277
+ font-size: 0.9rem;
1278
+ font-weight: 500;
1279
+ cursor: pointer;
1280
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1281
+ margin: 5px 0;
1282
+ text-align: center;
1283
+ text-decoration: none;
1284
+ }
1285
+ .product-button:hover {
1286
+ background-color: #3e55d1;
1287
+ box-shadow: 0 4px 15px rgba(62, 85, 209, 0.4);
1288
+ transform: translateY(-2px);
1289
+ }
1290
+ .add-to-cart {
1291
+ background-color: #2ecc71;
1292
+ }
1293
+ .add-to-cart:hover {
1294
+ background-color: #27ae60;
1295
+ box-shadow: 0 4px 15px rgba(46, 204, 113, 0.4);
1296
+ }
1297
+ .favorite-button {
1298
+ position: absolute;
1299
+ top: 10px;
1300
+ left: 10px;
1301
+ background: none;
1302
+ border: none;
1303
+ font-size: 1.5rem;
1304
+ cursor: pointer;
1305
+ color: #666;
1306
+ transition: color 0.3s ease;
1307
+ }
1308
+ .favorite-button.favorited {
1309
+ color: #e63946;
1310
+ }
1311
+ .favorite-button:hover {
1312
+ color: #e63946;
1313
+ }
1314
+ #cart-button {
1315
+ position: fixed;
1316
+ bottom: 80px;
1317
+ right: 20px;
1318
+ background-color: #e63946;
1319
+ color: white;
1320
+ border: none;
1321
+ border-radius: 50%;
1322
+ width: 60px;
1323
+ height: 60px;
1324
+ font-size: 1.5rem;
1325
+ cursor: pointer;
1326
+ display: none;
1327
+ box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4);
1328
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1329
+ z-index: 1000;
1330
+ }
1331
+ #cart-button:hover {
1332
+ transform: scale(1.1);
1333
+ }
1334
+ .modal {
1335
+ display: none;
1336
+ position: fixed;
1337
+ z-index: 1001;
1338
+ left: 0;
1339
+ top: 0;
1340
+ width: 100%;
1341
+ height: 100%;
1342
+ background-color: rgba(0,0,0,0.5);
1343
+ backdrop-filter: blur(5px);
1344
+ }
1345
+ .modal-content {
1346
+ background: #fff;
1347
+ margin: 5% auto;
1348
+ padding: 20px;
1349
+ border-radius: 15px;
1350
+ width: 90%;
1351
+ max-width: 700px;
1352
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
1353
+ animation: slideIn 0.3s ease-out;
1354
+ }
1355
+ body.dark-mode .modal-content {
1356
+ background: #2a2a3e;
1357
+ color: #e0e0e0;
1358
+ }
1359
+ @keyframes slideIn {
1360
+ from { transform: translateY(-50px); opacity: 0; }
1361
+ to { transform: translateY(0); opacity: 1; }
1362
+ }
1363
+ .close {
1364
+ float: right;
1365
+ font-size: 1.5rem;
1366
+ color: #666;
1367
+ cursor: pointer;
1368
+ transition: color 0.3s;
1369
+ }
1370
+ .close:hover {
1371
+ color: #333;
1372
+ }
1373
+ body.dark-mode .close {
1374
+ color: #bbb;
1375
+ }
1376
+ body.dark-mode .close:hover {
1377
+ color: #fff;
1378
+ }
1379
+ .cart-item {
1380
+ display: flex;
1381
+ justify-content: space-between;
1382
+ align-items: center;
1383
+ padding: 15px 0;
1384
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
1385
+ }
1386
+ body.dark-mode .cart-item {
1387
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
1388
+ }
1389
+ .cart-item img {
1390
+ width: 50px;
1391
+ height: 50px;
1392
+ object-fit: contain;
1393
+ border-radius: 8px;
1394
+ margin-right: 15px;
1395
+ }
1396
+ .quantity-input, .color-select {
1397
+ width: 100%;
1398
+ max-width: 150px;
1399
+ padding: 8px;
1400
+ border: 1px solid rgba(0, 0, 0, 0.1);
1401
+ border-radius: 8px;
1402
+ font-size: 1rem;
1403
+ margin: 5px 0;
1404
+ }
1405
+ .clear-cart {
1406
+ background-color: #e63946;
1407
+ }
1408
+ .clear-cart:hover {
1409
+ background-color: #d62828;
1410
+ box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4);
1411
+ }
1412
+ .order-button {
1413
+ background-color: #2ecc71;
1414
+ }
1415
+ .order-button:hover {
1416
+ background-color: #27ae60;
1417
+ box-shadow: 0 4px 15px rgba(46, 204, 113, 0.4);
1418
+ }
1419
+ .navbar {
1420
+ position: fixed;
1421
+ bottom: 0;
1422
+ left: 0;
1423
+ width: 100%;
1424
+ background-color: #fff;
1425
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
1426
+ display: flex;
1427
+ justify-content: space-around;
1428
+ padding: 10px 0;
1429
+ z-index: 1000;
1430
+ }
1431
+ body.dark-mode .navbar {
1432
+ background-color: #2a2a3e;
1433
+ }
1434
+ .navbar a {
1435
+ text-align: center;
1436
+ color: #666;
1437
+ text-decoration: none;
1438
+ font-size: 0.9rem;
1439
+ transition: color 0.3s ease;
1440
+ }
1441
+ .navbar a.active {
1442
+ color: #526df2;
1443
+ }
1444
+ .navbar a i {
1445
+ display: block;
1446
+ font-size: 1.5rem;
1447
+ margin-bottom: 5px;
1448
+ }
1449
+ body.dark-mode .navbar a {
1450
+ color: #bbb;
1451
+ }
1452
+ .navbar a:hover {
1453
+ color: #526df2;
1454
+ }
1455
+ .wholesale-badge {
1456
+ position: absolute;
1457
+ top: 10px;
1458
+ right: 10px;
1459
+ background-color: #526df2;
1460
+ color: white;
1461
+ padding: 5px 10px;
1462
+ border-radius: 15px;
1463
+ font-size: 0.75rem;
1464
+ font-weight: 500;
1465
+ }
1466
+ .discount-badge {
1467
+ position: absolute;
1468
+ top: 40px;
1469
+ right: 10px;
1470
+ background-color: #2ecc71;
1471
+ color: white;
1472
+ padding: 5px 10px;
1473
+ border-radius: 15px;
1474
+ font-size: 0.75rem;
1475
+ font-weight: 500;
1476
+ }
1477
+ </style>
1478
+ </head>
1479
+ <body>
1480
+ <div class="container">
1481
+ <div class="header">
1482
+ <h1>{{ category }}</h1>
1483
+ <button class="theme-toggle" onclick="toggleTheme()">
1484
+ <i class="fas fa-moon"></i>
1485
+ </button>
1486
+ </div>
1487
+ <div class="products-grid" id="products-grid">
1488
+ {% for product in products %}
1489
+ <div class="product"
1490
+ onclick="openModal({{ loop.index0 }})"
1491
+ data-name="{{ product['name']|lower }}"
1492
+ data-description="{{ product['description']|lower }}"
1493
+ data-category="{{ product.get('category', 'Без категории') }}">
1494
+ <button class="favorite-button" onclick="event.stopPropagation(); toggleFavorite({{ loop.index0 }})">
1495
+ <i class="fas fa-heart"></i>
1496
+ </button>
1497
+ {% if product.get('photos') and product['photos']|length > 0 %}
1498
+ <div class="product-image">
1499
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
1500
+ alt="{{ product['name'] }}"
1501
+ loading="lazy">
1502
+ </div>
1503
+ {% endif %}
1504
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
1505
+ <span class="wholesale-badge">Опт от {{ product['min_wholesale'] }}</span>
1506
+ {% endif %}
1507
+ {% if product.get('discount') %}
1508
+ <span class="discount-badge">Скидка {{ product['discount'] }}%</span>
1509
+ {% endif %}
1510
+ <h2>{{ product['name'] }}</h2>
1511
+ <div class="product-price">
1512
+ {% if product.get('discount') %}
1513
+ <span style="text-decoration: line-through; color: #666;">{{ product['price'] }} с</span>
1514
+ <span>{{ (product['price'] * (1 - product['discount'] / 100))|round(2) }} с</span>
1515
+ <span class="discount">Скидка: {{ product['discount'] }}%</span>
1516
+ {% else %}
1517
+ <span>{{ product['price'] }} с</span>
1518
+ {% endif %}
1519
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
1520
+ <span class="wholesale">Опт: {{ product['wholesale_price'] }} с</span>
1521
+ {% endif %}
1522
+ </div>
1523
+ <p class="product-description">{{ product['description'][:50] }}{% if product['description']|length > 50 %}...{% endif %}</p>
1524
+ <button class="product-button add-to-cart" onclick="event.stopPropagation(); openQuantityModal({{ loop.index0 }})">В корзину</button>
1525
+ </div>
1526
+ {% endfor %}
1527
+ </div>
1528
+ </div>
1529
+
1530
+ <!-- Product Modal -->
1531
+ <div id="productModal" class="modal">
1532
+ <div class="modal-content">
1533
+ <span class="close" onclick="closeModal('productModal')">×</span>
1534
+ <div id="modalContent"></div>
1535
+ </div>
1536
+ </div>
1537
+
1538
+ <!-- Quantity and Color Modal -->
1539
+ <div id="quantityModal" class="modal">
1540
+ <div class="modal-content">
1541
+ <span class="close" onclick="closeModal('quantityModal')">×</span>
1542
+ <h2>Укажите количество и цвет</h2>
1543
+ <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
1544
+ <select id="colorSelect" class="color-select"></select>
1545
+ <button class="product-button" onclick="confirmAddToCart()">Добавить</button>
1546
+ </div>
1547
+ </div>
1548
+
1549
+ <!-- Cart Modal -->
1550
+ <div id="cartModal" class="modal">
1551
+ <div class="modal-content">
1552
+ <span class="close" onclick="closeModal('cartModal')">×</span>
1553
+ <h2>Корзина</h2>
1554
+ <div id="cartContent"></div>
1555
+ <div style="margin-top: 20px; text-align: right;">
1556
+ <strong>Итого: <span id="cartTotal">0</span> с</strong>
1557
+ <button class="product-button clear-cart" onclick="clearCart()">Очистить</button>
1558
+ <button class="product-button order-button" onclick="orderViaWhatsApp()">Заказать</button>
1559
+ </div>
1560
+ </div>
1561
+ </div>
1562
+
1563
+ <!-- Address Modal -->
1564
+ <div id="addressModal" class="modal">
1565
+ <div class="modal-content">
1566
+ <span class="close" onclick="closeModal('addressModal')">×</span>
1567
+ <h2>Наш адрес</h2>
1568
+ <p>Рынок Дордой, Джунхай, 5 проход 505-506 контейнеры</p>
1569
+ </div>
1570
+ </div>
1571
+
1572
+ <button id="cart-button" onclick="openCartModal()">🛒</button>
1573
+
1574
+ <div class="navbar">
1575
+ <a href="/">
1576
+ <i class="fas fa-home"></i>
1577
+ Главная
1578
+ </a>
1579
+ <a href="/categories" class="active">
1580
+ <i class="fas fa-list"></i>
1581
+ Каталог
1582
+ </a>
1583
+ <a href="/favorites">
1584
+ <i class="fas fa-heart"></i>
1585
+ Избранное
1586
+ </a>
1587
+ <a href="/discounts">
1588
+ <i class="fas fa-tag"></i>
1589
+ Скидки
1590
+ </a>
1591
+ <a href="#" onclick="openAddressModal()">
1592
+ <i class="fas fa-map-marker-alt"></i>
1593
+ Адрес
1594
+ </a>
1595
+ <a href="https://api.whatsapp.com/send?phone=996500654659">
1596
+ <i class="fab fa-whatsapp"></i>
1597
+ WhatsApp
1598
+ </a>
1599
+ </div>
1600
+
1601
+ <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
1602
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.3/dist/umd/popper.min.js"></script>
1603
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
1604
+ <script>
1605
+ const products = {{ products|tojson }};
1606
+ let selectedProductIndex = null;
1607
+
1608
+ function toggleTheme() {
1609
+ document.body.classList.toggle('dark-mode');
1610
+ const icon = document.querySelector('.theme-toggle i');
1611
+ icon.classList.toggle('fa-moon');
1612
+ icon.classList.toggle('fa-sun');
1613
+ localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');
1614
+ }
1615
+
1616
+ if (localStorage.getItem('theme') === 'dark') {
1617
+ document.body.classList.add('dark-mode');
1618
+ document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
1619
+ }
1620
+
1621
+ function openModal(index) {
1622
+ loadProductDetails(index);
1623
+ document.getElementById('productModal').style.display = "block";
1624
+ }
1625
+
1626
+ function closeModal(modalId) {
1627
+ document.getElementById(modalId).style.display = "none";
1628
+ }
1629
+
1630
+ function loadProductDetails(index) {
1631
+ fetch('/product/' + index)
1632
+ .then(response => response.text())
1633
+ .then(data => {
1634
+ document.getElementById('modalContent').innerHTML = data;
1635
+ initializeSwiper();
1636
+ })
1637
+ .catch(error => console.error('Ошибка:', error));
1638
+ }
1639
+
1640
+ function initializeSwiper() {
1641
+ new Swiper('.swiper-container', {
1642
+ slidesPerView: 1,
1643
+ spaceBetween: 20,
1644
+ loop: true,
1645
+ grabCursor: true,
1646
+ pagination: { el: '.swiper-pagination', clickable: true },
1647
+ navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
1648
+ zoom: { maxRatio: 3 }
1649
+ });
1650
+ }
1651
+
1652
+ function openQuantityModal(index) {
1653
+ selectedProductIndex = index;
1654
+ const product = products[index];
1655
+ const colorSelect = document.getElementById('colorSelect');
1656
+ colorSelect.innerHTML = '';
1657
+ if (product.colors && product.colors.length > 0) {
1658
+ product.colors.forEach(color => {
1659
+ const option = document.createElement('option');
1660
+ option.value = color;
1661
+ option.text = color;
1662
+ colorSelect.appendChild(option);
1663
+ });
1664
+ } else {
1665
+ const option = document.createElement('option');
1666
+ option.value = 'Нет цвета';
1667
+ option.text = 'Нет цвета';
1668
+ colorSelect.appendChild(option);
1669
+ }
1670
+ document.getElementById('quantityModal').style.display = 'block';
1671
+ document.getElementById('quantityInput').value = 1;
1672
+ }
1673
+
1674
+ function confirmAddToCart() {
1675
+ if (selectedProductIndex === null) return;
1676
+ const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
1677
+ const color = document.getElementById('colorSelect').value;
1678
+ if (quantity <= 0) {
1679
+ alert("Укажите количество больше 0");
1680
+ return;
1681
+ }
1682
+ let cart = JSON.parse(localStorage.getItem('cart') || '[]');
1683
+ const product = products[selectedProductIndex];
1684
+ const cartItemId = `${product.name}-${color}`;
1685
+ const existingItem = cart.find(item => item.id === cartItemId);
1686
+
1687
+ let priceToUse = product.price;
1688
+ if (product.discount) {
1689
+ priceToUse = product.price * (1 - product.discount / 100);
1690
+ }
1691
+ if (product.min_wholesale && quantity >= product.min_wholesale) {
1692
+ priceToUse = product.wholesale_price;
1693
+ }
1694
+
1695
+ if (existingItem) {
1696
+ existingItem.quantity += quantity;
1697
+ existingItem.price = (existingItem.quantity >= product.min_wholesale) ? product.wholesale_price : (product.discount ? product.price * (1 - product.discount / 100) : product.price);
1698
+ } else {
1699
+ cart.push({
1700
+ id: cartItemId,
1701
+ name: product.name,
1702
+ price: priceToUse,
1703
+ retail_price: product.price,
1704
+ wholesale_price: product.wholesale_price,
1705
+ min_wholesale: product.min_wholesale,
1706
+ discount: product.discount,
1707
+ photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
1708
+ quantity: quantity,
1709
+ color: color
1710
+ });
1711
+ }
1712
+
1713
+ localStorage.setItem('cart', JSON.stringify(cart));
1714
+ closeModal('quantityModal');
1715
+ updateCartButton();
1716
  }
1717
 
1718
  function updateCartButton() {
 
1747
  document.getElementById('cartModal').style.display = 'block';
1748
  }
1749
 
 
 
 
 
1750
  function orderViaWhatsApp() {
1751
  const cart = JSON.parse(localStorage.getItem('cart') || '[]');
1752
  if (cart.length === 0) {
 
1770
  updateCartButton();
1771
  }
1772
 
1773
+ function toggleFavorite(index) {
1774
+ let favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
1775
+ const productId = index.toString();
1776
+ const favoriteButton = document.querySelector(`.favorite-button[onclick="event.stopPropagation(); toggleFavorite(${index})"]`);
1777
+ if (favorites.includes(productId)) {
1778
+ favorites = favorites.filter(id => id !== productId);
1779
+ favoriteButton.classList.remove('favorited');
1780
+ } else {
1781
+ favorites.push(productId);
1782
+ favoriteButton.classList.add('favorited');
1783
+ }
1784
+ localStorage.setItem('favorites', JSON.stringify(favorites));
1785
+ }
1786
+
1787
+ function loadFavorites() {
1788
+ const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
1789
+ document.querySelectorAll('.favorite-button').forEach(button => {
1790
+ const index = button.getAttribute('onclick').match(/\d+/)[0];
1791
+ if (favorites.includes(index)) {
1792
+ button.classList.add('favorited');
1793
+ }
1794
+ });
1795
+ }
1796
+
1797
+ function openAddressModal() {
1798
+ document.getElementById('addressModal').style.display = 'block';
1799
+ }
1800
+
1801
  window.onclick = function(event) {
1802
  if (event.target.className === 'modal') event.target.style.display = "none";
1803
  }
1804
 
1805
  updateCartButton();
1806
+ loadFavorites();
1807
  </script>
1808
  </body>
1809
  </html>
1810
  '''
1811
+ return render_template_string(category_html, products=products, category=category, repo_id=REPO_ID)
1812
 
1813
+ @app.route('/favorites')
1814
+ def favorites_page():
1815
  data = load_data()
1816
+ products = data['products']
1817
+ favorites = request.args.get('favorites', '[]')
1818
 
1819
+ favorites_html = '''
1820
  <!DOCTYPE html>
1821
  <html lang="ru">
1822
  <head>
1823
  <meta charset="UTF-8">
1824
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1825
+ <title>Избранное</title>
1826
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1827
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
1828
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
 
2193
  </button>
2194
  </div>
2195
  <div class="products-grid" id="products-grid">
 
2196
  </div>
2197
  </div>
2198
 
 
2229
  </div>
2230
  </div>
2231
 
2232
+ <!-- Address Modal -->
2233
+ <div id="addressModal" class="modal">
2234
+ <div class="modal-content">
2235
+ <span class="close" onclick="closeModal('addressModal')">×</span>
2236
+ <h2>Наш адрес</h2>
2237
+ <p>Рынок Дордой, Джунхай, 5 проход 505-506 контейнеры</p>
2238
+ </div>
2239
+ </div>
2240
+
2241
  <button id="cart-button" onclick="openCartModal()">🛒</button>
2242
 
2243
  <div class="navbar">
 
2257
  <i class="fas fa-tag"></i>
2258
  Скидки
2259
  </a>
2260
+ <a href="#" onclick="openAddressModal()">
2261
+ <i class="fas fa-map-marker-alt"></i>
2262
+ Адрес
2263
+ </a>
2264
  <a href="https://api.whatsapp.com/send?phone=996500654659">
2265
  <i class="fab fa-whatsapp"></i>
2266
  WhatsApp
 
2287
  document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun');
2288
  }
2289
 
2290
+ function loadFavoritesPage() {
2291
  const favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
2292
  const productsGrid = document.getElementById('products-grid');
2293
  productsGrid.innerHTML = '';
 
2300
  favorites.forEach(index => {
2301
  const product = products[index];
2302
  if (!product) return;
 
2303
  const productElement = document.createElement('div');
2304
  productElement.className = 'product';
2305
  productElement.setAttribute('onclick', `openModal(${index})`);
2306
  productElement.setAttribute('data-name', product.name.toLowerCase());
2307
  productElement.setAttribute('data-description', product.description.toLowerCase());
2308
  productElement.setAttribute('data-category', product.category || 'Без категории');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2309
  productElement.innerHTML = `
2310
  <button class="favorite-button favorited" onclick="event.stopPropagation(); toggleFavorite(${index})">
2311
  <i class="fas fa-heart"></i>
2312
  </button>
2313
  ${product.photos && product.photos.length > 0 ? `
2314
  <div class="product-image">
2315
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${product.photos[0]}" alt="${product.name}" loading="lazy">
 
 
2316
  </div>` : ''}
2317
  ${product.wholesale_price && product.min_wholesale ? `<span class="wholesale-badge">Опт от ${product.min_wholesale}</span>` : ''}
2318
  ${product.discount ? `<span class="discount-badge">Скидка ${product.discount}%</span>` : ''}
2319
  <h2>${product.name}</h2>
2320
+ <div class="product-price">
2321
+ ${product.discount ? `
2322
+ <span style="text-decoration: line-through; color: #666;">${product.price} с</span>
2323
+ <span>${(product.price * (1 - product.discount / 100)).toFixed(2)} с</span>
2324
+ <span class="discount">Скидка: ${product.discount}%</span>` : `<span>${product.price} с</span>`}
2325
+ ${product.wholesale_price && product.min_wholesale ? `<span class="wholesale">Опт: ${product.wholesale_price} с</span>` : ''}
2326
+ </div>
2327
  <p class="product-description">${product.description.slice(0, 50)}${product.description.length > 50 ? '...' : ''}</p>
2328
  <button class="product-button add-to-cart" onclick="event.stopPropagation(); openQuantityModal(${index})">В корзину</button>
2329
  `;
 
2486
  function toggleFavorite(index) {
2487
  let favorites = JSON.parse(localStorage.getItem('favorites') || '[]');
2488
  const productId = index.toString();
2489
+ const favoriteButton = document.querySelector(`.favorite-button[onclick="event.stopPropagation(); toggleFavorite(${index})"]`);
2490
  if (favorites.includes(productId)) {
2491
  favorites = favorites.filter(id => id !== productId);
2492
+ favoriteButton.classList.remove('favorited');
2493
  } else {
2494
  favorites.push(productId);
2495
+ favoriteButton.classList.add('favorited');
2496
  }
2497
  localStorage.setItem('favorites', JSON.stringify(favorites));
2498
+ loadFavoritesPage();
2499
+ }
2500
+
2501
+ function openAddressModal() {
2502
+ document.getElementById('addressModal').style.display = 'block';
2503
  }
2504
 
2505
  window.onclick = function(event) {
 
2507
  }
2508
 
2509
  updateCartButton();
2510
+ loadFavoritesPage();
2511
  </script>
2512
  </body>
2513
  </html>
 
2518
  def discounts_page():
2519
  data = load_data()
2520
  products = [p for p in data['products'] if p.get('discount')]
2521
+ categories = data['categories']
2522
 
2523
  discounts_html = '''
2524
  <!DOCTYPE html>
 
2972
  </div>
2973
  </div>
2974
 
2975
+ <!-- Address Modal -->
2976
+ <div id="addressModal" class="modal">
2977
+ <div class="modal-content">
2978
+ <span class="close" onclick="closeModal('addressModal')">×</span>
2979
+ <h2>Наш адрес</h2>
2980
+ <p>Рынок Дордой, Джунхай, 5 проход 505-506 контейнеры</p>
2981
+ </div>
2982
+ </div>
2983
+
2984
  <button id="cart-button" onclick="openCartModal()">🛒</button>
2985
 
2986
  <div class="navbar">
 
3000
  <i class="fas fa-tag"></i>
3001
  Скидки
3002
  </a>
3003
+ <a href="#" onclick="openAddressModal()">
3004
+ <i class="fas fa-map-marker-alt"></i>
3005
+ Адрес
3006
+ </a>
3007
  <a href="https://api.whatsapp.com/send?phone=996500654659">
3008
  <i class="fab fa-whatsapp"></i>
3009
  WhatsApp
 
3194
  favoriteButton.classList.add('favorited');
3195
  }
3196
  localStorage.setItem('favorites', JSON.stringify(favorites));
 
3197
  }
3198
 
3199
  function loadFavorites() {
 
3206
  });
3207
  }
3208
 
3209
+ function openAddressModal() {
3210
+ document.getElementById('addressModal').style.display = 'block';
3211
+ }
3212
+
3213
  window.onclick = function(event) {
3214
  if (event.target.className === 'modal') event.target.style.display = "none";
3215
  }
 
3220
  </body>
3221
  </html>
3222
  '''
3223
+ return render_template_string(discounts_html, products=products, categories=categories, repo_id=REPO_ID)
3224
 
3225
  @app.route('/product/<int:index>')
3226
  def product_details(index):
 
3231
  <style>
3232
  .swiper-container {
3233
  width: 100%;
3234
+ max-width: 400px;
3235
  margin: 20px auto;
3236
  }
3237
  .swiper-slide {
 
3283
  .product-details p {
3284
  font-size: 0.95rem;
3285
  color: #666;
3286
+ margin-bottom: 10px;
3287
  }
3288
  body.dark-mode .product-details p {
3289
  color: #bbb;
3290
  }
3291
+ .product-details .colors {
3292
  margin: 10px 0;
3293
  }
3294
+ .product-details .colors span {
3295
  display: inline-block;
3296
  margin-right: 10px;
3297
  padding: 5px 10px;
3298
+ border-radius: 15px;
3299
+ background: #f0f0f0;
3300
  font-size: 0.9rem;
3301
  }
3302
+ body.dark-mode .product-details .colors span {
3303
+ background: #444;
3304
+ color: #e0e0e0;
3305
  }
3306
  </style>
3307
  <div class="product-details">
 
3310
  <div class="swiper-wrapper">
3311
  {% for photo in product.get('photos', []) %}
3312
  <div class="swiper-slide">
3313
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="{{ product['name'] }}">
 
 
3314
  </div>
3315
  {% endfor %}
3316
  </div>
 
3330
  <span class="wholesale">Опт: {{ product['wholesale_price'] }} с (от {{ product['min_wholesale'] }})</span>
3331
  {% endif %}
3332
  </div>
3333
+ <p><strong>Описание:</strong> {{ product['description'] }}</p>
3334
+ <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
3335
+ {% if product.get('colors') %}
3336
  <div class="colors">
3337
  <strong>Цвета:</strong>
3338
  {% for color in product['colors'] %}
 
3344
  '''
3345
  return render_template_string(product_html, product=product, repo_id=REPO_ID)
3346
 
3347
+ @app.route('/admin', methods=['GET', 'POST'])
3348
+ def admin():
3349
+ data = load_data()
3350
+ products = data['products']
3351
+ categories = data['categories']
3352
+
3353
+ if request.method == 'POST':
3354
+ action = request.form.get('action')
3355
+
3356
+ if action == 'add':
3357
+ photos = request.files.getlist('photos')
3358
+ photo_filenames = []
3359
+ for photo in photos:
3360
+ if photo and photo.filename:
3361
+ filename = secure_filename(photo.filename)
3362
+ api = HfApi()
3363
+ api.upload_file(
3364
+ path_or_fileobj=photo,
3365
+ path_in_repo=f"photos/{filename}",
3366
+ repo_id=REPO_ID,
3367
+ repo_type="dataset",
3368
+ token=HF_TOKEN_WRITE
3369
+ )
3370
+ photo_filenames.append(filename)
3371
+
3372
+ colors = request.form.getlist('colors')
3373
+ colors = [color.strip() for color in colors if color.strip()]
3374
+
3375
+ new_product = {
3376
+ 'name': request.form['name'],
3377
+ 'price': float(request.form['price']),
3378
+ 'wholesale_price': float(request.form['wholesale_price']) if request.form['wholesale_price'] else None,
3379
+ 'min_wholesale': int(request.form['min_wholesale']) if request.form['min_wholesale'] else None,
3380
+ 'description': request.form['description'],
3381
+ 'category': request.form['category'],
3382
+ 'colors': colors,
3383
+ 'photos': photo_filenames,
3384
+ 'discount': float(request.form['discount']) if request.form['discount'] else None
3385
+ }
3386
+ products.append(new_product)
3387
+
3388
+ elif action == 'edit':
3389
+ index = int(request.form['index'])
3390
+ photos = request.files.getlist('photos')
3391
+ photo_filenames = products[index].get('photos', [])
3392
+ for photo in photos:
3393
+ if photo and photo.filename:
3394
+ filename = secure_filename(photo.filename)
3395
+ api = HfApi()
3396
+ api.upload_file(
3397
+ path_or_fileobj=photo,
3398
+ path_in_repo=f"photos/{filename}",
3399
+ repo_id=REPO_ID,
3400
+ repo_type="dataset",
3401
+ token=HF_TOKEN_WRITE
3402
+ )
3403
+ photo_filenames.append(filename)
3404
+
3405
+ colors = request.form.getlist('colors')
3406
+ colors = [color.strip() for color in colors if color.strip()]
3407
+
3408
+ products[index] = {
3409
+ 'name': request.form['name'],
3410
+ 'price': float(request.form['price']),
3411
+ 'wholesale_price': float(request.form['wholesale_price']) if request.form['wholesale_price'] else None,
3412
+ 'min_wholesale': int(request.form['min_wholesale']) if request.form['min_wholesale'] else None,
3413
+ 'description': request.form['description'],
3414
+ 'category': request.form['category'],
3415
+ 'colors': colors,
3416
+ 'photos': photo_filenames,
3417
+ 'discount': float(request.form['discount']) if request.form['discount'] else None
3418
+ }
3419
+
3420
+ elif action == 'delete':
3421
+ index = int(request.form['index'])
3422
+ products.pop(index)
3423
+
3424
+ elif action == 'add_category':
3425
+ category = request.form['category_name'].strip()
3426
+ if category and category not in categories:
3427
+ categories.append(category)
3428
+
3429
+ elif action == 'delete_category':
3430
+ index = int(request.form['category_index'])
3431
+ category_to_delete = categories[index]
3432
+ categories.pop(index)
3433
+ for product in products:
3434
+ if product.get('category') == category_to_delete:
3435
+ product['category'] = 'Без категории'
3436
+
3437
+ save_data({'products': products, 'categories': categories})
3438
+ return redirect(url_for('admin'))
3439
+
3440
+ admin_html = '''
3441
+ <!DOCTYPE html>
3442
+ <html lang="ru">
3443
+ <head>
3444
+ <meta charset="UTF-8">
3445
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3446
+ <title>Админ-панель</title>
3447
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
3448
+ <style>
3449
+ * {
3450
+ margin: 0;
3451
+ padding: 0;
3452
+ box-sizing: border-box;
3453
+ }
3454
+ body {
3455
+ font-family: 'Roboto', sans-serif;
3456
+ background: linear-gradient(135deg, #f5f7fa, #e4e7eb);
3457
+ color: #333;
3458
+ line-height: 1.6;
3459
+ padding: 20px;
3460
+ }
3461
+ .container {
3462
+ max-width: 1400px;
3463
+ margin: 0 auto;
3464
+ }
3465
+ h1 {
3466
+ font-size: 2rem;
3467
+ font-weight: 700;
3468
+ margin-bottom: 20px;
3469
+ text-align: center;
3470
+ background: linear-gradient(90deg, #526df2, #7a8ff5);
3471
+ -webkit-background-clip: text;
3472
+ -webkit-text-fill-color: transparent;
3473
+ }
3474
+ .form-container, .category-list, .product-list {
3475
+ background: rgba(255, 255, 255, 0.9);
3476
+ border-radius: 15px;
3477
+ padding: 20px;
3478
+ margin-bottom: 20px;
3479
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
3480
+ }
3481
+ .form-container label, .edit-form label {
3482
+ display: block;
3483
+ margin: 10px 0 5px;
3484
+ font-weight: 500;
3485
+ }
3486
+ .form-container input, .form-container textarea, .form-container select,
3487
+ .edit-form input, .edit-form textarea, .edit-form select {
3488
+ width: 100%;
3489
+ padding: 10px;
3490
+ margin-bottom: 10px;
3491
+ border: 1px solid rgba(0, 0, 0, 0.1);
3492
+ border-radius: 8px;
3493
+ font-size: 1rem;
3494
+ }
3495
+ .form-container textarea, .edit-form textarea {
3496
+ resize: vertical;
3497
+ }
3498
+ .form-container button, .edit-form button {
3499
+ padding: 10px 20px;
3500
+ border: none;
3501
+ border-radius: 25px;
3502
+ background-color: #526df2;
3503
+ color: white;
3504
+ font-size: 1rem;
3505
+ font-weight: 500;
3506
+ cursor: pointer;
3507
+ transition: all 0.3s ease;
3508
+ margin: 5px 0;
3509
+ }
3510
+ .form-container button:hover, .edit-form button:hover {
3511
+ background-color: #3e55d1;
3512
+ box-shadow: 0 4px 15px rgba(62, 85, 209, 0.4);
3513
+ }
3514
+ .add-color-btn {
3515
+ background-color: #2ecc71;
3516
+ }
3517
+ .add-color-btn:hover {
3518
+ background-color: #27ae60;
3519
+ box-shadow: 0 4px 15px rgba(46, 204, 113, 0.4);
3520
+ }
3521
+ .color-input-group {
3522
+ display: flex;
3523
+ align-items: center;
3524
+ margin-bottom: 10px;
3525
+ }
3526
+ .color-input-group input {
3527
+ flex: 1;
3528
+ }
3529
+ .category-list, .product-list {
3530
+ display: grid;
3531
+ gap: 20px;
3532
+ }
3533
+ .category-item, .product-item {
3534
+ background: rgba(255, 255, 255, 0.95);
3535
+ border: 1px solid rgba(0, 0, 0, 0.05);
3536
+ border-radius: 10px;
3537
+ padding: 15px;
3538
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
3539
+ }
3540
+ .category-item h2, .product-item h2 {
3541
+ font-size: 1.2rem;
3542
+ font-weight: 500;
3543
+ margin-bottom: 10px;
3544
+ }
3545
+ .category-item form, .product-item form {
3546
+ display: inline-block;
3547
+ }
3548
+ .delete-button {
3549
+ background-color: #e63946;
3550
+ }
3551
+ .delete-button:hover {
3552
+ background-color: #d62828;
3553
+ box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4);
3554
+ }
3555
+ details {
3556
+ margin: 10px 0;
3557
+ }
3558
+ summary {
3559
+ cursor: pointer;
3560
+ font-weight: 500;
3561
+ color: #526df2;
3562
+ margin-bottom: 10px;
3563
+ }
3564
+ .edit-form {
3565
+ margin-top: 10px;
3566
+ }
3567
+ .search-container {
3568
+ margin: 20px 0;
3569
+ text-align: center;
3570
+ }
3571
+ #search-input {
3572
+ width: 90%;
3573
+ max-width: 600px;
3574
+ padding: 12px 18px;
3575
+ font-size: 1rem;
3576
+ border: 1px solid rgba(0, 0, 0, 0.1);
3577
+ border-radius: 25px;
3578
+ outline: none;
3579
+ box-shadow: 0 2px 5px rgba(0,0,0,0.05);
3580
+ transition: all 0.3s ease;
3581
+ }
3582
+ #search-input:focus {
3583
+ border-color: #526df2;
3584
+ box-shadow: 0 4px 15px rgba(82, 109, 242, 0.2);
3585
+ }
3586
+ </style>
3587
+ </head>
3588
+ <body>
3589
+ <div class="container">
3590
+ <h1>Админ-панель</h1>
3591
+
3592
+ <div class="form-container">
3593
+ <h1>Добавить товар</h1>
3594
+ <form method="POST" enctype="multipart/form-data">
3595
+ <input type="hidden" name="action" value="add">
3596
+ <label>Название:</label>
3597
+ <input type="text" name="name" required>
3598
+ <label>Цена (розница):</label>
3599
+ <input type="number" name="price" step="0.01" required>
3600
+ <label>Цена (опт):</label>
3601
+ <input type="number" name="wholesale_price" step="0.01">
3602
+ <label>Минимальное количество для опта:</label>
3603
+ <input type="number" name="min_wholesale" min="1">
3604
+ <label>Скидка (%):</label>
3605
+ <input type="number" name="discount" min="0" max="100" step="0.1">
3606
+ <label>Описание:</label>
3607
+ <textarea name="description" rows="4" required></textarea>
3608
+ <label>Категория:</label>
3609
+ <select name="category">
3610
+ <option value="Без категории">Без категории</option>
3611
+ {% for category in categories %}
3612
+ <option value="{{ category }}">{{ category }}</option>
3613
+ {% endfor %}
3614
+ </select>
3615
+ <label>Фотографии (до 10):</label>
3616
+ <input type="file" name="photos" accept="image/*" multiple>
3617
+ <label>Цвета:</label>
3618
+ <div id="color-inputs">
3619
+ <div class="color-input-group">
3620
+ <input type="text" name="colors" placeholder="Например: Красный">
3621
+ </div>
3622
+ </div>
3623
+ <button type="button" class="add-color-btn" onclick="addColorInput()">Добавить цвет</button>
3624
+ <button type="submit">Добавить товар</button>
3625
+ </form>
3626
+ </div>
3627
+
3628
+ <div class="form-container">
3629
+ <h1>Добавить категорию</h1>
3630
+ <form method="POST">
3631
+ <input type="hidden" name="action" value="add_category">
3632
+ <label>Название категории:</label>
3633
+ <input type="text" name="category_name" required>
3634
+ <button type="submit">Добавить категорию</button>
3635
+ </form>
3636
+ </div>
3637
+
3638
+ <div class="category-list">
3639
+ {% for category in categories %}
3640
+ <div class="category-item">
3641
+ <h2>{{ category }}</h2>
3642
+ <form method="POST">
3643
+ <input type="hidden" name="action" value="delete_category">
3644
+ <input type="hidden" name="category_index" value="{{ loop.index0 }}">
3645
+ <button type="submit" class="delete-button">Удалить категорию</button>
3646
+ </form>
3647
+ </div>
3648
+ {% endfor %}
3649
+ </div>
3650
+
3651
+ <h1>Список товаров</h1>
3652
+ <div class="search-container">
3653
+ <input type="text" id="search-input" placeholder="Поиск товаров...">
3654
+ </div>
3655
+ <div class="product-list" id="product-list">
3656
+ {% for product in products %}
3657
+ <div class="product-item"
3658
+ data-name="{{ product['name']|lower }}"
3659
+ data-description="{{ product['description']|lower }}"
3660
+ data-category="{{ product.get('category', 'Без категории') }}">
3661
+ <h2>{{ product['name'] }}</h2>
3662
+ <p>Цена (розница): {{ product['price'] }} с</p>
3663
+ {% if product.get('wholesale_price') and product.get('min_wholesale') %}
3664
+ <p>Цена (опт): {{ product['wholesale_price'] }} с (от {{ product['min_wholesale'] }})</p>
3665
+ {% endif %}
3666
+ {% if product.get('discount') %}
3667
+ <p>Скидка: {{ product['discount'] }}%</p>
3668
+ {% endif %}
3669
+ <p>Описание: {{ product['description'] }}</p>
3670
+ <p>Категория: {{ product.get('category', 'Без категории') }}</p>
3671
+ <p>Цвета: {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
3672
+ {% if product.get('photos') %}
3673
+ <p>Фотографии:</p>
3674
+ {% for photo in product['photos'] %}
3675
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="{{ product['name'] }}" style="max-width: 100px; margin: 5px;">
3676
+ {% endfor %}
3677
+ {% endif %}
3678
+ <details>
3679
+ <summary>Редактировать</summary>
3680
+ <form method="POST" enctype="multipart/form-data" class="edit-form">
3681
+ <input type="hidden" name="action" value="edit">
3682
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
3683
+ <label>Название:</label>
3684
+ <input type="text" name="name" value="{{ product['name'] }}" required>
3685
+ <label>Цена (розница):</label>
3686
+ <input type="number" name="price" step="0.01" value="{{ product['price'] }}" required>
3687
+ <label>Цена (опт):</label>
3688
+ <input type="number" name="wholesale_price" step="0.01" value="{{ product.get('wholesale_price', '') }}">
3689
+ <label>Минимальное количество для опта:</label>
3690
+ <input type="number" name="min_wholesale" min="1" value="{{ product.get('min_wholesale', '') }}">
3691
+ <label>Скидка (%):</label>
3692
+ <input type="number" name="discount" min="0" max="100" step="0.1" value="{{ product.get('discount', '') }}">
3693
+ <label>Описание:</label>
3694
+ <textarea name="description" rows="4" required>{{ product['description'] }}</textarea>
3695
+ <label>Категория:</label>
3696
+ <select name="category">
3697
+ <option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>
3698
+ {% for category in categories %}
3699
+ <option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>
3700
+ {% endfor %}
3701
+ </select>
3702
+ <label>Фотографии (до 10):</label>
3703
+ <input type="file" name="photos" accept="image/*" multiple>
3704
+ <label>Цвета:</label>
3705
+ <div id="edit-color-inputs-{{ loop.index0 }}">
3706
+ {% for color in product.get('colors', []) %}
3707
+ <div class="color-input-group">
3708
+ <input type="text" name="colors" value="{{ color }}">
3709
+ </div>
3710
+ {% endfor %}
3711
+ </div>
3712
+ <button type="button" class="add-color-btn" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')">Добавить цвет</button>
3713
+ <button type="submit">Сохранить</button>
3714
+ </form>
3715
+ </details>
3716
+ <form method="POST">
3717
+ <input type="hidden" name="action" value="delete">
3718
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
3719
+ <button type="submit" class="delete-button">Удалить</button>
3720
+ </form>
3721
+ </div>
3722
+ {% endfor %}
3723
+ </div>
3724
+ </div>
3725
+ <script>
3726
+ function addColorInput(containerId = 'color-inputs') {
3727
+ const container = document.getElementById(containerId);
3728
+ const newInput = document.createElement('div');
3729
+ newInput.className = 'color-input-group';
3730
+ newInput.innerHTML = '<input type="text" name="colors" placeholder="Например: Красный">';
3731
+ container.appendChild(newInput);
3732
+ }
3733
+
3734
+ document.getElementById('search-input').addEventListener('input', filterProducts);
3735
+
3736
+ function filterProducts() {
3737
+ const searchTerm = document.getElementById('search-input').value.toLowerCase();
3738
+ document.querySelectorAll('.product-item').forEach(product => {
3739
+ const name = product.getAttribute('data-name');
3740
+ const description = product.getAttribute('data-description');
3741
+ const category = product.getAttribute('data-category');
3742
+ const matchesSearch = name.includes(searchTerm) || description.includes(searchTerm) || category.includes(searchTerm);
3743
+ product.style.display = matchesSearch ? 'block' : 'none';
3744
+ });
3745
+ }
3746
+ </script>
3747
+ </body>
3748
+ </html>
3749
+ '''
3750
+ return render_template_string(admin_html, products=products, categories=categories, repo_id=REPO_ID)
3751
+
3752
+ @app.route('/backup', methods=['POST'])
3753
+ def backup():
3754
+ upload_db_to_hf()
3755
+ return "Резервная копия создана.", 200
3756
+
3757
+ @app.route('/download', methods=['GET'])
3758
+ def download():
3759
+ download_db_from_hf()
3760
+ return "База данных скачана.", 200
3761
 
3762
  if __name__ == '__main__':
3763
+ backup_thread = threading.Thread(target=periodic_backup, daemon=True)
3764
+ backup_thread.start()
3765
+ try:
3766
+ load_data()
3767
+ except Exception as e:
3768
+ logging.error(f"Не удалось загрузить базу данных: {e}")
3769
  app.run(debug=True, host='0.0.0.0', port=7860)
3770