Shveiauto commited on
Commit
42fac02
·
verified ·
1 Parent(s): dded8ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -195
app.py CHANGED
@@ -1,5 +1,4 @@
1
 
2
- # --- START OF FILE app.py ---
3
 
4
  from flask import Flask, render_template_string, request, redirect, url_for, send_file, flash, jsonify
5
  import json
@@ -18,10 +17,9 @@ import uuid
18
  load_dotenv()
19
 
20
  app = Flask(__name__)
21
- app.secret_key = 'your_unique_secret_key_soola_cosmetics_67890_no_login' # Secret key still needed for flash messages
22
  DATA_FILE = 'data.json'
23
 
24
-
25
  SYNC_FILES = [DATA_FILE]
26
 
27
  REPO_ID = "Kgshop/dakokg"
@@ -34,12 +32,10 @@ CURRENCY_CODE = 'KGS'
34
  CURRENCY_NAME = 'Кыргызский сом'
35
 
36
  DOWNLOAD_RETRIES = 3
37
- DOWNLOAD_DELAY = 5 # seconds
38
 
39
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
40
 
41
- # --- Hugging Face Sync Functions ---
42
-
43
  def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY):
44
  if not HF_TOKEN_READ and not HF_TOKEN_WRITE:
45
  logging.warning("HF_TOKEN_READ/HF_TOKEN_WRITE not set. Download might fail for private repos.")
@@ -139,8 +135,6 @@ def periodic_backup():
139
  upload_db_to_hf()
140
  logging.info("Periodic backup finished.")
141
 
142
- # --- Data Loading and Saving Functions ---
143
-
144
  def load_data():
145
  default_data = {'products': [], 'categories': [], 'orders': {}}
146
  try:
@@ -207,104 +201,79 @@ def save_data(data):
207
  except Exception as e:
208
  logging.error(f"Error saving data to {DATA_FILE}: {e}", exc_info=True)
209
 
210
- # --- Templates ---
211
-
212
  CATALOG_TEMPLATE = '''
213
  <!DOCTYPE html>
214
  <html lang="ru">
215
  <head>
216
  <meta charset="UTF-8">
217
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
218
- <title>MedinaTurkey.kz - Каталог</title>
219
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
220
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
221
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
222
  <style>
223
  * { margin: 0; padding: 0; box-sizing: border-box; }
224
- body { font-family: 'Poppins', sans-serif; background: #f0f9f4; color: #2d332f; line-height: 1.6; transition: background 0.3s, color 0.3s; }
225
- body.dark-mode { background: #1a2b26; color: #c8d8d3; }
226
  .container { max-width: 1300px; margin: 0 auto; padding: 20px; }
227
- .header { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 1px solid #d1e7dd; }
228
- body.dark-mode .header { border-bottom-color: #2c4a41; }
229
- .header h1 { font-size: 1.8rem; font-weight: 600; color: #1C6758; }
230
- .theme-toggle { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #7a8d85; transition: color 0.3s ease; }
231
- .theme-toggle:hover { color: #3D8361; }
232
- body.dark-mode .theme-toggle { color: #8aa39a; }
233
- body.dark-mode .theme-toggle:hover { color: #55a683; }
234
- .store-address { padding: 15px; text-align: center; background-color: #ffffff; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); font-size: 1rem; color: #44524c; }
235
- body.dark-mode .store-address { background-color: #253f37; color: #b0c8c1; }
236
  .filters-container { margin: 20px 0; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
237
  .search-container { margin: 20px 0; text-align: center; }
238
- #search-input { width: 90%; max-width: 600px; padding: 12px 18px; font-size: 1rem; border: 1px solid #d1e7dd; border-radius: 25px; outline: none; box-shadow: 0 2px 5px rgba(0,0,0,0.05); transition: all 0.3s ease; }
239
- body.dark-mode #search-input { background-color: #253f37; border-color: #2c4a41; color: #c8d8d3; }
240
- #search-input:focus { border-color: #1C6758; box-shadow: 0 0 0 3px rgba(28, 103, 88, 0.2); }
241
- body.dark-mode #search-input:focus { border-color: #3D8361; box-shadow: 0 0 0 3px rgba(61, 131, 97, 0.3); }
242
- .category-filter { padding: 8px 16px; border: 1px solid #d1e7dd; border-radius: 20px; background-color: #fff; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 0.9rem; font-weight: 400; color: #1C6758; }
243
- body.dark-mode .category-filter { background-color: #253f37; border-color: #2c4a41; color: #97b7ae; }
244
- .category-filter.active, .category-filter:hover { background-color: #1C6758; color: white; border-color: #1C6758; box-shadow: 0 2px 10px rgba(28, 103, 88, 0.3); }
245
- body.dark-mode .category-filter.active, body.dark-mode .category-filter:hover { background-color: #3D8361; border-color: #3D8361; color: #1a2b26; box-shadow: 0 2px 10px rgba(61, 131, 97, 0.4); }
246
  .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 20px; padding: 10px; }
247
  @media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
248
  @media (min-width: 900px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } }
249
 
250
- .product { background: #fff; border-radius: 15px; padding: 0; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; height: 100%; border: 1px solid #e1f0e9;}
251
- body.dark-mode .product { background: #253f37; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); border-color: #2c4a41; }
252
- .product:hover { transform: translateY(-5px) scale(1.02); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12); }
253
- body.dark-mode .product:hover { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); }
254
- .product-image { width: 100%; aspect-ratio: 1 / 1; background-color: #fff; border-radius: 10px 10px 0 0; overflow: hidden; display: flex; justify-content: center; align-items: center; margin-bottom: 0; }
255
  .product-image img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform 0.3s ease; }
256
  .product-info { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; justify-content: center; }
257
- .product h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 8px 0; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #2d332f; }
258
- body.dark-mode .product h2 { color: #c8d8d3; }
259
- .product-price { font-size: 1.2rem; color: #1C6758; font-weight: 700; text-align: center; margin: 5px 0; }
260
- body.dark-mode .product-price { color: #55a683; }
261
- .product-description { font-size: 0.85rem; color: #7a8d85; text-align: center; margin-bottom: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
262
- body.dark-mode .product-description { color: #8aa39a; }
263
  .product-actions { padding: 0 15px 15px 15px; display: flex; flex-direction: column; gap: 8px; }
264
- .product-button { display: block; width: 100%; padding: 10px; border: none; border-radius: 8px; background-color: #1C6758; color: white; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); text-align: center; text-decoration: none; }
265
- .product-button:hover { background-color: #164B41; box-shadow: 0 4px 15px rgba(22, 75, 65, 0.4); transform: translateY(-2px); }
266
  .product-button i { margin-right: 5px; }
267
- .add-to-cart { background-color: #38a169; }
268
- .add-to-cart:hover { background-color: #2f855a; box-shadow: 0 4px 15px rgba(47, 133, 90, 0.4); }
269
- #cart-button { position: fixed; bottom: 25px; right: 25px; background-color: #1C6758; color: white; border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(28, 103, 88, 0.4); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); z-index: 1000; }
 
270
  #cart-button .fa-shopping-cart { margin-right: 0; }
271
- #cart-button span { position: absolute; top: -5px; right: -5px; background-color: #38a169; color: white; border-radius: 50%; padding: 2px 6px; font-size: 0.7rem; font-weight: bold; }
272
- .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(5px); overflow-y: auto; }
273
- .modal-content { background: #f8fcfb; margin: 5% auto; padding: 25px; border-radius: 15px; width: 90%; max-width: 700px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); animation: slideIn 0.3s ease-out; position: relative; }
274
- body.dark-mode .modal-content { background: #253f37; color: #c8d8d3; }
275
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
276
- .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
277
- .close:hover { color: #333; }
278
- body.dark-mode .close { color: #7a8d85; }
279
- body.dark-mode .close:hover { color: #b0c8c1; }
280
- .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: #1C6758; display: flex; align-items: center; gap: 10px;}
281
- body.dark-mode .modal-content h2 { color: #55a683; }
282
- .cart-item { display: grid; grid-template-columns: auto 1fr auto auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #d1e7dd; }
283
- body.dark-mode .cart-item { border-bottom-color: #2c4a41; }
284
  .cart-item:last-child { border-bottom: none; }
285
- .cart-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #fff; padding: 5px; grid-column: 1; }
286
  .cart-item-details { grid-column: 2; }
287
- .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; }
288
- .cart-item-price { font-size: 0.9rem; color: #44524c; }
289
- body.dark-mode .cart-item-price { color: #8aa39a; }
290
- .cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 1rem;}
291
  .cart-item-remove { grid-column: 4; background:none; border:none; color:#f56565; cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
292
  .cart-item-remove:hover { color: #c53030; }
293
- .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #d1e7dd; border-radius: 8px; font-size: 1rem; margin: 10px 0; box-sizing: border-box; }
294
- body.dark-mode .quantity-input, body.dark-mode .color-select { background-color: #1a2b26; border-color: #2c4a41; color: #c8d8d3; }
295
- .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #d1e7dd; padding-top: 15px; }
296
- body.dark-mode .cart-summary { border-top-color: #2c4a41; }
297
- .cart-summary strong { font-size: 1.2rem; }
298
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
299
  .cart-actions .product-button { width: auto; flex-grow: 1; }
300
- .clear-cart { background-color: #7a8d85; }
301
- .clear-cart:hover { background-color: #5e6e68; box-shadow: 0 4px 15px rgba(94, 110, 104, 0.4); }
302
- .formulate-order-button { background-color: #38a169; }
303
- .formulate-order-button:hover { background-color: #2f855a; box-shadow: 0 4px 15px rgba(47, 133, 90, 0.4); }
304
- .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: #38a169; color: white; padding: 10px 20px; border-radius: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); z-index: 1002; opacity: 0; transition: opacity 0.5s ease; font-size: 0.9rem;}
305
  .notification.show { opacity: 1;}
306
- .no-results-message { grid-column: 1 / -1; text-align: center; padding: 40px; font-size: 1.1rem; color: #5e6e68; }
307
- body.dark-mode .no-results-message { color: #8aa39a; }
308
  .top-product-indicator { position: absolute; top: 8px; right: 8px; background-color: rgba(255, 215, 0, 0.8); color: #333; padding: 2px 6px; font-size: 0.7rem; border-radius: 4px; font-weight: bold; z-index: 10; backdrop-filter: blur(2px); }
309
  .product { position: relative; }
310
  </style>
@@ -312,10 +281,7 @@ CATALOG_TEMPLATE = '''
312
  <body>
313
  <div class="container">
314
  <div class="header">
315
- <h1>MedinaTurkey.kz</h1>
316
- <button class="theme-toggle" onclick="toggleTheme()" aria-label="Переключить тему">
317
- <i class="fas fa-moon"></i>
318
- </button>
319
  </div>
320
 
321
  <div class="store-address">Наш адрес: {{ store_address }}</div>
@@ -421,24 +387,6 @@ CATALOG_TEMPLATE = '''
421
  let selectedProductIndex = null;
422
  let cart = JSON.parse(localStorage.getItem('soolaCart') || '[]');
423
 
424
- function toggleTheme() {
425
- document.body.classList.toggle('dark-mode');
426
- const icon = document.querySelector('.theme-toggle i');
427
- const isDarkMode = document.body.classList.contains('dark-mode');
428
- icon.classList.toggle('fa-moon', !isDarkMode);
429
- icon.classList.toggle('fa-sun', isDarkMode);
430
- localStorage.setItem('soolaTheme', isDarkMode ? 'dark' : 'light');
431
- }
432
-
433
- function applyInitialTheme() {
434
- const savedTheme = localStorage.getItem('soolaTheme');
435
- if (savedTheme === 'dark') {
436
- document.body.classList.add('dark-mode');
437
- const icon = document.querySelector('.theme-toggle i');
438
- if (icon) icon.classList.replace('fa-moon', 'fa-sun');
439
- }
440
- }
441
-
442
  function openModal(index) {
443
  loadProductDetails(index);
444
  const modal = document.getElementById('productModal');
@@ -462,7 +410,7 @@ CATALOG_TEMPLATE = '''
462
  function loadProductDetails(index) {
463
  const modalContent = document.getElementById('modalContent');
464
  if (!modalContent) return;
465
- modalContent.innerHTML = '<p style="text-align:center; padding: 40px;">Загрузка...</p>';
466
  fetch('/product/' + index)
467
  .then(response => {
468
  if (!response.ok) throw new Error(`Ошибка ${response.status}: ${response.statusText}`);
@@ -474,7 +422,7 @@ CATALOG_TEMPLATE = '''
474
  })
475
  .catch(error => {
476
  console.error('Ошибка загрузки деталей продукта:', error);
477
- modalContent.innerHTML = `<p style="color: red; text-align:center; padding: 40px;">Не удалось загрузить информацию о товаре. ${error.message}</p>`;
478
  });
479
  }
480
 
@@ -551,7 +499,7 @@ CATALOG_TEMPLATE = '''
551
  return;
552
  }
553
 
554
- const cartItemId = `${product.name}-${color}`; // Use name + color as ID
555
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
556
 
557
  if (existingItemIndex > -1) {
@@ -598,7 +546,7 @@ CATALOG_TEMPLATE = '''
598
  let total = 0;
599
 
600
  if (cart.length === 0) {
601
- cartContent.innerHTML = '<p style="text-align: center; padding: 20px;">Ваша корзина пуста.</p>';
602
  cartTotalElement.textContent = '0.00';
603
  } else {
604
  cartContent.innerHTML = cart.map(item => {
@@ -690,7 +638,6 @@ CATALOG_TEMPLATE = '''
690
  });
691
  }
692
 
693
-
694
  function filterProducts() {
695
  const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
696
  const activeCategoryButton = document.querySelector('.category-filter.active');
@@ -760,7 +707,6 @@ CATALOG_TEMPLATE = '''
760
  placeholder = newPlaceholder;
761
  }
762
 
763
-
764
  const notification = document.createElement('div');
765
  notification.className = 'notification';
766
  notification.textContent = message;
@@ -777,7 +723,6 @@ CATALOG_TEMPLATE = '''
777
  }
778
 
779
  document.addEventListener('DOMContentLoaded', () => {
780
- applyInitialTheme();
781
  updateCartButton();
782
  setupFilters();
783
 
@@ -803,8 +748,8 @@ CATALOG_TEMPLATE = '''
803
 
804
  PRODUCT_DETAIL_TEMPLATE = '''
805
  <div style="padding: 10px;">
806
- <h2 style="font-size: 1.6rem; font-weight: 600; margin-bottom: 15px; text-align: center; color: #1C6758;">{{ product['name'] }}</h2>
807
- <div class="swiper-container" style="max-width: 450px; margin: 0 auto 20px; border-radius: 10px; overflow: hidden; background-color: #fff;">
808
  <div class="swiper-wrapper">
809
  {% if product.get('photos') and product['photos']|length > 0 %}
810
  {% for photo in product['photos'] %}
@@ -823,15 +768,15 @@ PRODUCT_DETAIL_TEMPLATE = '''
823
  {% endif %}
824
  </div>
825
  {% if product.get('photos') and product['photos']|length > 1 %}
826
- <div class="swiper-pagination" style="position: relative; bottom: 5px;"></div>
827
- <div class="swiper-button-next" style="color: #1C6758;"></div>
828
- <div class="swiper-button-prev" style="color: #1C6758;"></div>
829
  {% endif %}
830
  </div>
831
 
832
- <div style="margin-top: 20px; font-size: 1rem; line-height: 1.7;">
833
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
834
- <p style="font-size: 1.2rem; font-weight: bold; color: #1C6758;"><strong>Цена:</strong> {{ "%.2f"|format(product['price']) }} {{ currency_code }}</p>
835
  <p><strong>Описание:</strong><br> {{ product.get('description', 'Описание отсутствует.')|replace('\\n', '<br>')|safe }}</p>
836
  {% set colors = product.get('colors', []) %}
837
  {% if colors and colors|select('ne', '')|list|length > 0 %}
@@ -847,35 +792,35 @@ ORDER_TEMPLATE = '''
847
  <head>
848
  <meta charset="UTF-8">
849
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
850
- <title>Заказ №{{ order.id }} - MedinaTurkey.kz</title>
851
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
852
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
853
  <style>
854
- body { font-family: 'Poppins', sans-serif; background: #f0f9f4; color: #2d332f; line-height: 1.6; padding: 20px; }
855
- .container { max-width: 800px; margin: 20px auto; padding: 30px; background: #fff; border-radius: 15px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); border: 1px solid #d1e7dd; }
856
- h1 { text-align: center; color: #1C6758; margin-bottom: 25px; font-size: 1.8rem; font-weight: 600; }
857
- h2 { color: #164B41; margin-top: 30px; margin-bottom: 15px; font-size: 1.4rem; border-bottom: 1px solid #d1e7dd; padding-bottom: 8px;}
858
- .order-meta { font-size: 0.9rem; color: #5e6e68; margin-bottom: 20px; text-align: center; }
859
- .order-item { display: grid; grid-template-columns: 60px 1fr auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #e1f0e9; }
860
  .order-item:last-child { border-bottom: none; }
861
- .order-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #fff; padding: 5px; border: 1px solid #e1f0e9;}
862
- .item-details strong { display: block; margin-bottom: 5px; font-size: 1.05rem; color: #2d332f;}
863
- .item-details span { font-size: 0.9rem; color: #44524c; display: block;}
864
- .item-total { font-weight: bold; text-align: right; font-size: 1rem; color: #1C6758;}
865
- .order-summary { margin-top: 30px; padding-top: 20px; border-top: 2px solid #1C6758; text-align: right; }
866
  .order-summary p { margin-bottom: 10px; font-size: 1.1rem; }
867
- .order-summary strong { font-size: 1.3rem; color: #1C6758; }
868
- .customer-info { margin-top: 30px; background-color: #f8fcfb; padding: 20px; border-radius: 8px; border: 1px solid #e1f0e9;}
869
- .customer-info p { margin-bottom: 8px; font-size: 0.95rem; }
870
- .customer-info strong { color: #164B41; }
871
  .actions { margin-top: 30px; text-align: center; }
872
  .button { padding: 12px 25px; border: none; border-radius: 8px; background-color: #25D366; color: white; font-weight: 600; cursor: pointer; transition: background-color 0.3s ease, transform 0.1s ease; font-size: 1rem; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
873
  .button:hover { background-color: #128C7E; }
874
  .button:active { transform: scale(0.98); }
875
  .button i { font-size: 1.2rem; }
876
- .catalog-link { display: block; text-align: center; margin-top: 25px; color: #3D8361; text-decoration: none; font-size: 0.9rem; }
877
  .catalog-link:hover { text-decoration: underline; }
878
- .not-found { text-align: center; color: #c53030; font-size: 1.2rem; padding: 40px 0;}
879
  </style>
880
  </head>
881
  <body>
@@ -921,9 +866,9 @@ ORDER_TEMPLATE = '''
921
  function sendOrderViaWhatsApp() {
922
  const orderId = '{{ order.id }}';
923
  const orderUrl = `{{ request.url }}`;
924
- const whatsappNumber = "77479003212";
925
 
926
- let message = `Здравствуйте! Хочу подтвердить свой заказ на MedinaTurkey.kz:%0A%0A`;
927
  message += `*Номер заказа:* ${orderId}%0A`;
928
  message += `*Ссылка на заказ:* ${encodeURIComponent(orderUrl)}%0A%0A`;
929
  message += `Пожалуйста, свяжитесь со мной для уточнения деталей оплаты и доставки.`;
@@ -949,77 +894,81 @@ ADMIN_TEMPLATE = '''
949
  <head>
950
  <meta charset="UTF-8">
951
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
952
- <title>Админ-панель - MedinaTurkey.kz</title>
953
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
954
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
955
  <style>
956
- body { font-family: 'Poppins', sans-serif; background-color: #e9f5f0; color: #2d332f; padding: 20px; line-height: 1.6; }
957
- .container { max-width: 1200px; margin: 0 auto; background-color: #fff; padding: 25px; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.05); }
958
- .header { padding-bottom: 15px; margin-bottom: 25px; border-bottom: 1px solid #d1e7dd; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;}
959
- h1, h2, h3 { font-weight: 600; color: #1C6758; margin-bottom: 15px; }
960
- h1 { font-size: 1.8rem; }
 
961
  h2 { font-size: 1.5rem; margin-top: 30px; display: flex; align-items: center; gap: 8px; }
962
- h3 { font-size: 1.2rem; color: #164B41; margin-top: 20px; }
963
- .section { margin-bottom: 30px; padding: 20px; background-color: #f8fcfb; border: 1px solid #d1e7dd; border-radius: 8px; }
964
  form { margin-bottom: 20px; }
965
- label { font-weight: 500; margin-top: 10px; display: block; color: #44524c; font-size: 0.9rem;}
966
- input[type="text"], input[type="number"], input[type="password"], input[type="tel"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 5px; border: 1px solid #c4d9d1; border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; }
967
- input:focus, textarea:focus, select:focus { border-color: #1C6758; outline: none; box-shadow: 0 0 0 2px rgba(28, 103, 88, 0.1); }
968
  textarea { min-height: 80px; resize: vertical; }
969
- input[type="file"] { padding: 8px; background-color: #f0f9f4; cursor: pointer; border: 1px solid #c4d9d1;}
970
- input[type="file"]::file-selector-button { padding: 5px 10px; border-radius: 4px; background-color: #e0f0e9; border: 1px solid #c4d9d1; cursor: pointer; margin-right: 10px;}
 
971
  input[type="checkbox"] { margin-right: 5px; vertical-align: middle; }
972
- label.inline-label { display: inline-block; margin-top: 10px; font-weight: normal; }
973
- button, .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: #1C6758; color: white; font-weight: 500; cursor: pointer; transition: background-color 0.3s ease, transform 0.1s ease; margin-top: 15px; font-size: 0.95rem; display: inline-flex; align-items: center; gap: 5px; text-decoration: none; line-height: 1.2;}
974
- button:hover, .button:hover { background-color: #164B41; }
975
  button:active, .button:active { transform: scale(0.98); }
976
  button[type="submit"] { min-width: 120px; justify-content: center; }
977
- .delete-button { background-color: #f56565; }
978
- .delete-button:hover { background-color: #e53e3e; }
979
- .add-button { background-color: #38a169; }
980
- .add-button:hover { background-color: #2f855a; }
 
 
981
  .item-list { display: grid; gap: 20px; }
982
- .item { background: #fff; padding: 15px 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.07); border: 1px solid #e1f0e9; }
983
- .item p { margin: 5px 0; font-size: 0.9rem; color: #44524c; }
984
- .item strong { color: #2d332f; }
985
- .item .description { font-size: 0.85rem; color: #5e6e68; max-height: 60px; overflow: hidden; text-overflow: ellipsis; }
986
  .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
987
- .item-actions button:not(.delete-button) { background-color: #1C6758; }
988
- .item-actions button:not(.delete-button):hover { background-color: #164B41; }
989
- .edit-form-container { margin-top: 15px; padding: 20px; background: #f0f9f4; border: 1px dashed #c4d9d1; border-radius: 6px; display: none; }
990
- details { background-color: #f8fcfb; border: 1px solid #d1e7dd; border-radius: 8px; margin-bottom: 20px; }
991
- details > summary { cursor: pointer; font-weight: 600; color: #164B41; display: block; padding: 15px; border-bottom: 1px solid #d1e7dd; list-style: none; position: relative; }
992
- details > summary::after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); transition: transform 0.2s ease; color: #1C6758; }
993
  details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
994
- details[open] > summary { border-bottom: 1px solid #d1e7dd; }
995
  details .form-content { padding: 20px; }
996
  .color-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
997
  .color-input-group input { flex-grow: 1; margin: 0; }
998
- .remove-color-btn { background-color: #f56565; padding: 6px 10px; font-size: 0.8rem; margin-top: 0; line-height: 1; }
999
- .remove-color-btn:hover { background-color: #e53e3e; }
1000
- .add-color-btn { background-color: #63b3ed; }
1001
- .add-color-btn:hover { background-color: #4299e1; }
1002
- .photo-preview img { max-width: 70px; max-height: 70px; border-radius: 5px; margin: 5px 5px 0 0; border: 1px solid #d1e7dd; object-fit: cover;}
1003
  .sync-buttons { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
1004
- .download-hf-button { background-color: #7a8d85; }
1005
- .download-hf-button:hover { background-color: #5e6e68; }
1006
  .flex-container { display: flex; flex-wrap: wrap; gap: 20px; }
1007
  .flex-item { flex: 1; min-width: 350px; }
1008
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem;}
1009
- .message.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb;}
1010
- .message.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb;}
1011
- .message.warning { background-color: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
1012
  .status-indicator { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 0.8rem; font-weight: 500; margin-left: 10px; vertical-align: middle; }
1013
- .status-indicator.in-stock { background-color: #c6f6d5; color: #2f855a; }
1014
- .status-indicator.out-of-stock { background-color: #fed7d7; color: #c53030; }
1015
- .status-indicator.top-product { background-color: #feebc8; color: #9c4221; margin-left: 5px;}
1016
  </style>
1017
  </head>
1018
  <body>
1019
  <div class="container">
1020
  <div class="header">
1021
- <h1><i class="fas fa-tools"></i> Админ-панель MedinaTurkey.kz</h1>
1022
- <a href="{{ url_for('catalog') }}" class="button" style="background-color: #3D8361;"><i class="fas fa-store"></i> Перейти в каталог</a>
1023
  </div>
1024
 
1025
  {% with messages = get_flashed_messages(with_categories=true) %}
@@ -1040,7 +989,7 @@ ADMIN_TEMPLATE = '''
1040
  <button type="submit" class="button download-hf-button" title="Скачать файлы (перезапишет локальные)"><i class="fas fa-download"></i> Скачать БД</button>
1041
  </form>
1042
  </div>
1043
- <p style="font-size: 0.85rem; color: #5e6e68;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
1044
  </div>
1045
 
1046
  <div class="flex-container">
@@ -1064,7 +1013,7 @@ ADMIN_TEMPLATE = '''
1064
  <div class="item-list">
1065
  {% for category in categories %}
1066
  <div class="item" style="display: flex; justify-content: space-between; align-items: center;">
1067
- <span>{{ category }}</span>
1068
  <form method="POST" style="margin: 0;" onsubmit="return confirm('Вы уверены, что хотите удалить категорию \'{{ category }}\'? Товары этой категории будут помечены как \'Без категории\'.');">
1069
  <input type="hidden" name="action" value="delete_category">
1070
  <input type="hidden" name="category_name" value="{{ category }}">
@@ -1074,7 +1023,7 @@ ADMIN_TEMPLATE = '''
1074
  {% endfor %}
1075
  </div>
1076
  {% else %}
1077
- <p>Категорий пока нет.</p>
1078
  {% endif %}
1079
  </div>
1080
  </div>
@@ -1082,8 +1031,8 @@ ADMIN_TEMPLATE = '''
1082
  <div class="flex-item">
1083
  <div class="section">
1084
  <h2><i class="fas fa-info-circle"></i> Информация</h2>
1085
- <p>Управление пользователями отключено, так как сайт не требует входа.</p>
1086
- <p>Заказы создаются анонимно и должны быть подтверждены через WhatsApp.</p>
1087
  </div>
1088
  </div>
1089
  </div>
@@ -1149,7 +1098,7 @@ ADMIN_TEMPLATE = '''
1149
  {% endif %}
1150
  </div>
1151
  <div style="flex-grow: 1;">
1152
- <h3 style="margin-top: 0; margin-bottom: 5px; color: #2d332f;">
1153
  {{ product['name'] }}
1154
  {% if product.get('in_stock', True) %}
1155
  <span class="status-indicator in-stock">В наличии</span>
@@ -1166,7 +1115,7 @@ ADMIN_TEMPLATE = '''
1166
  {% set colors = product.get('colors', []) %}
1167
  <p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
1168
  {% if product.get('photos') and product['photos']|length > 1 %}
1169
- <p style="font-size: 0.8rem; color: #5e6e68;">(Всего фото: {{ product['photos']|length }})</p>
1170
  {% endif %}
1171
  </div>
1172
  </div>
@@ -1201,7 +1150,7 @@ ADMIN_TEMPLATE = '''
1201
  <label>Заменить фотографии (выберите новые файлы, до 10 шт.):</label>
1202
  <input type="file" name="photos" accept="image/*" multiple>
1203
  {% if product.get('photos') %}
1204
- <p style="font-size: 0.85rem; margin-top: 5px;">Текущие фото:</p>
1205
  <div class="photo-preview">
1206
  {% for photo in product['photos'] %}
1207
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="Фото {{ loop.index }}">
@@ -1245,7 +1194,7 @@ ADMIN_TEMPLATE = '''
1245
  {% endfor %}
1246
  </div>
1247
  {% else %}
1248
- <p>Товаров пока нет.</p>
1249
  {% endif %}
1250
  </div>
1251
 
@@ -1299,8 +1248,6 @@ ADMIN_TEMPLATE = '''
1299
  </html>
1300
  '''
1301
 
1302
- # --- Flask Routes ---
1303
-
1304
  @app.route('/')
1305
  def catalog():
1306
  data = load_data()
@@ -1381,7 +1328,7 @@ def create_order():
1381
  "created_at": order_timestamp,
1382
  "cart": processed_cart,
1383
  "total_price": round(total_price, 2),
1384
- "user_info": None, # Explicitly set to None as users are anonymous
1385
  "status": "new"
1386
  }
1387
 
@@ -1709,7 +1656,6 @@ def admin():
1709
  flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
1710
  return redirect(url_for('admin'))
1711
 
1712
- # --- GET request ---
1713
  current_data = load_data()
1714
  display_products = sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower())
1715
  display_categories = sorted(current_data.get('categories', []))
@@ -1739,7 +1685,7 @@ def force_download():
1739
  try:
1740
  if download_db_from_hf():
1741
  flash("Данные успешно скачаны с Hugging Face. Локальные файлы обновлены.", 'success')
1742
- load_data() # Reload data in memory after download
1743
  else:
1744
  flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
1745
  except Exception as e:
@@ -1747,9 +1693,6 @@ def force_download():
1747
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
1748
  return redirect(url_for('admin'))
1749
 
1750
-
1751
- # --- App Initialization ---
1752
-
1753
  if __name__ == '__main__':
1754
  logging.info("Application starting up. Performing initial data load/download...")
1755
  download_db_from_hf()
@@ -1767,4 +1710,4 @@ if __name__ == '__main__':
1767
  logging.info(f"Starting Flask app on host 0.0.0.0 and port {port}")
1768
  app.run(debug=False, host='0.0.0.0', port=port)
1769
 
1770
- # --- END OF FILE app.py ---
 
1
 
 
2
 
3
  from flask import Flask, render_template_string, request, redirect, url_for, send_file, flash, jsonify
4
  import json
 
17
  load_dotenv()
18
 
19
  app = Flask(__name__)
20
+ app.secret_key = 'your_unique_secret_key_soola_cosmetics_67890_no_login'
21
  DATA_FILE = 'data.json'
22
 
 
23
  SYNC_FILES = [DATA_FILE]
24
 
25
  REPO_ID = "Kgshop/dakokg"
 
32
  CURRENCY_NAME = 'Кыргызский сом'
33
 
34
  DOWNLOAD_RETRIES = 3
35
+ DOWNLOAD_DELAY = 5
36
 
37
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
38
 
 
 
39
  def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY):
40
  if not HF_TOKEN_READ and not HF_TOKEN_WRITE:
41
  logging.warning("HF_TOKEN_READ/HF_TOKEN_WRITE not set. Download might fail for private repos.")
 
135
  upload_db_to_hf()
136
  logging.info("Periodic backup finished.")
137
 
 
 
138
  def load_data():
139
  default_data = {'products': [], 'categories': [], 'orders': {}}
140
  try:
 
201
  except Exception as e:
202
  logging.error(f"Error saving data to {DATA_FILE}: {e}", exc_info=True)
203
 
 
 
204
  CATALOG_TEMPLATE = '''
205
  <!DOCTYPE html>
206
  <html lang="ru">
207
  <head>
208
  <meta charset="UTF-8">
209
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
210
+ <title>Dako_kg - Каталог</title>
211
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
212
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
213
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
214
  <style>
215
  * { margin: 0; padding: 0; box-sizing: border-box; }
216
+ body { font-family: 'Poppins', sans-serif; background: #1a1a1a; color: #eee; line-height: 1.6;}
 
217
  .container { max-width: 1300px; margin: 0 auto; padding: 20px; }
218
+ .header { display: flex; justify-content: space-between; align-items: center; padding: 15px 0; border-bottom: 1px solid #444; }
219
+ .header h1 { font-size: 1.8rem; font-weight: 600; color: #ffcc00; display: flex; align-items: center;}
220
+ .header img { height: 40px; margin-right: 10px;}
221
+ .store-address { padding: 15px; text-align: center; background-color: #2a2a2a; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); font-size: 1rem; color: #ccc; border: 1px solid #444;}
 
 
 
 
 
222
  .filters-container { margin: 20px 0; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
223
  .search-container { margin: 20px 0; text-align: center; }
224
+ #search-input { width: 90%; max-width: 600px; padding: 12px 18px; font-size: 1rem; border: 1px solid #444; border-radius: 25px; outline: none; box-shadow: 0 2px 5px rgba(0,0,0,0.2); transition: all 0.3s ease; background-color: #333; color: #eee;}
225
+ #search-input:focus { border-color: #ffcc00; box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.3); }
226
+ .category-filter { padding: 8px 16px; border: 1px solid #444; border-radius: 20px; background-color: #2a2a2a; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-size: 0.9rem; font-weight: 400; color: #ffcc00; }
227
+ .category-filter.active, .category-filter:hover { background-color: #ffcc00; color: #1a1a1a; border-color: #ffcc00; box-shadow: 0 2px 10px rgba(255, 215, 0, 0.3); }
 
 
 
 
228
  .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 20px; padding: 10px; }
229
  @media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
230
  @media (min-width: 900px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } }
231
 
232
+ .product { background: #2a2a2a; border-radius: 15px; padding: 0; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; height: 100%; border: 1px solid #444;}
233
+ .product:hover { transform: translateY(-5px) scale(1.02); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); }
234
+ .product-image { width: 100%; aspect-ratio: 1 / 1; background-color: #333; border-radius: 10px 10px 0 0; overflow: hidden; display: flex; justify-content: center; align-items: center; margin-bottom: 0; }
 
 
235
  .product-image img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform 0.3s ease; }
236
  .product-info { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; justify-content: center; }
237
+ .product h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 8px 0; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #eee; }
238
+ .product-price { font-size: 1.2rem; color: #ffd700; font-weight: 700; text-align: center; margin: 5px 0; }
239
+ .product-description { font-size: 0.85rem; color: #ccc; text-align: center; margin-bottom: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 
 
 
240
  .product-actions { padding: 0 15px 15px 15px; display: flex; flex-direction: column; gap: 8px; }
241
+ .product-button { display: block; width: 100%; padding: 10px; border: none; border-radius: 8px; background-color: #444; color: #eee; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); text-align: center; text-decoration: none; border: 1px solid #555;}
242
+ .product-button:hover { background-color: #555; box-shadow: 0 4px 15px rgba(255, 215, 0, 0.2); transform: translateY(-2px); border-color: #ffcc00;}
243
  .product-button i { margin-right: 5px; }
244
+ .add-to-cart { background-color: #ffd700; color: #1a1a1a; border-color: #ffcc00;}
245
+ .add-to-cart:hover { background-color: #ffcc00; box-shadow: 0 4px 15px rgba(255, 215, 0, 0.4); border-color: #fff;}
246
+ #cart-button { position: fixed; bottom: 25px; right: 25px; background-color: #ffd700; color: #1a1a1a; border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(255, 215, 0, 0.5); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); z-index: 1000; }
247
+ #cart-button:hover { background-color: #ffcc00; transform: scale(1.05); }
248
  #cart-button .fa-shopping-cart { margin-right: 0; }
249
+ #cart-button span { position: absolute; top: -5px; right: -5px; background-color: #c53030; color: white; border-radius: 50%; padding: 2px 6px; font-size: 0.7rem; font-weight: bold; }
250
+ .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); backdrop-filter: blur(5px); overflow-y: auto; }
251
+ .modal-content { background: #2a2a2a; margin: 5% auto; padding: 25px; border-radius: 15px; width: 90%; max-width: 700px; box-shadow: 0 10px 30px rgba(0,0,0,0.4); animation: slideIn 0.3s ease-out; position: relative; color: #eee; border: 1px solid #444;}
 
252
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
253
+ .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #ccc; cursor: pointer; transition: color 0.3s; line-height: 1; }
254
+ .close:hover { color: #ffcc00; }
255
+ .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: #ffcc00; display: flex; align-items: center; gap: 10px;}
256
+ .cart-item { display: grid; grid-template-columns: auto 1fr auto auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #444; }
 
 
 
 
257
  .cart-item:last-child { border-bottom: none; }
258
+ .cart-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #333; padding: 5px; grid-column: 1; border: 1px solid #555; }
259
  .cart-item-details { grid-column: 2; }
260
+ .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: #eee;}
261
+ .cart-item-price { font-size: 0.9rem; color: #ccc; }
262
+ .cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 1rem; color: #ffcc00;}
 
263
  .cart-item-remove { grid-column: 4; background:none; border:none; color:#f56565; cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
264
  .cart-item-remove:hover { color: #c53030; }
265
+ .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #444; border-radius: 8px; font-size: 1rem; margin: 10px 0; box-sizing: border-box; background-color: #333; color: #eee;}
266
+ .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #444; padding-top: 15px; }
267
+ .cart-summary strong { font-size: 1.2rem; color: #ffcc00;}
 
 
268
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
269
  .cart-actions .product-button { width: auto; flex-grow: 1; }
270
+ .clear-cart { background-color: #555; border-color: #666;}
271
+ .clear-cart:hover { background-color: #666; box-shadow: 0 4px 15px rgba(0,0,0,0.4); }
272
+ .formulate-order-button { background-color: #ffd700; color: #1a1a1a; border-color: #ffcc00;}
273
+ .formulate-order-button:hover { background-color: #ffcc00; box-shadow: 0 4px 15px rgba(255, 215, 0, 0.4); border-color: #fff;}
274
+ .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: #ffd700; color: #1a1a1a; padding: 10px 20px; border-radius: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.3); z-index: 1002; opacity: 0; transition: opacity 0.5s ease; font-size: 0.9rem;}
275
  .notification.show { opacity: 1;}
276
+ .no-results-message { grid-column: 1 / -1; text-align: center; padding: 40px; font-size: 1.1rem; color: #ccc; }
 
277
  .top-product-indicator { position: absolute; top: 8px; right: 8px; background-color: rgba(255, 215, 0, 0.8); color: #333; padding: 2px 6px; font-size: 0.7rem; border-radius: 4px; font-weight: bold; z-index: 10; backdrop-filter: blur(2px); }
278
  .product { position: relative; }
279
  </style>
 
281
  <body>
282
  <div class="container">
283
  <div class="header">
284
+ <h1><img src="https://cdn-avatars.huggingface.co/v1/production/uploads/67effb9055fe17a33d83fcb5/9kkn1DmDQ2jNz-0tFFZ2Q.jpeg" alt="Dako_kg Logo">Dako_kg</h1>
 
 
 
285
  </div>
286
 
287
  <div class="store-address">Наш адрес: {{ store_address }}</div>
 
387
  let selectedProductIndex = null;
388
  let cart = JSON.parse(localStorage.getItem('soolaCart') || '[]');
389
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  function openModal(index) {
391
  loadProductDetails(index);
392
  const modal = document.getElementById('productModal');
 
410
  function loadProductDetails(index) {
411
  const modalContent = document.getElementById('modalContent');
412
  if (!modalContent) return;
413
+ modalContent.innerHTML = '<p style="text-align:center; padding: 40px; color: #ccc;">Загрузка...</p>';
414
  fetch('/product/' + index)
415
  .then(response => {
416
  if (!response.ok) throw new Error(`Ошибка ${response.status}: ${response.statusText}`);
 
422
  })
423
  .catch(error => {
424
  console.error('Ошибка загрузки деталей продукта:', error);
425
+ modalContent.innerHTML = `<p style="color: #f56565; text-align:center; padding: 40px;">Не удалось загрузить информацию о товаре. ${error.message}</p>`;
426
  });
427
  }
428
 
 
499
  return;
500
  }
501
 
502
+ const cartItemId = `${product.name}-${color}`;
503
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
504
 
505
  if (existingItemIndex > -1) {
 
546
  let total = 0;
547
 
548
  if (cart.length === 0) {
549
+ cartContent.innerHTML = '<p style="text-align: center; padding: 20px; color: #ccc;">Ваша корзина пуста.</p>';
550
  cartTotalElement.textContent = '0.00';
551
  } else {
552
  cartContent.innerHTML = cart.map(item => {
 
638
  });
639
  }
640
 
 
641
  function filterProducts() {
642
  const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
643
  const activeCategoryButton = document.querySelector('.category-filter.active');
 
707
  placeholder = newPlaceholder;
708
  }
709
 
 
710
  const notification = document.createElement('div');
711
  notification.className = 'notification';
712
  notification.textContent = message;
 
723
  }
724
 
725
  document.addEventListener('DOMContentLoaded', () => {
 
726
  updateCartButton();
727
  setupFilters();
728
 
 
748
 
749
  PRODUCT_DETAIL_TEMPLATE = '''
750
  <div style="padding: 10px;">
751
+ <h2 style="font-size: 1.6rem; font-weight: 600; margin-bottom: 15px; text-align: center; color: #ffcc00;">{{ product['name'] }}</h2>
752
+ <div class="swiper-container" style="max-width: 450px; margin: 0 auto 20px; border-radius: 10px; overflow: hidden; background-color: #333; border: 1px solid #444;">
753
  <div class="swiper-wrapper">
754
  {% if product.get('photos') and product['photos']|length > 0 %}
755
  {% for photo in product['photos'] %}
 
768
  {% endif %}
769
  </div>
770
  {% if product.get('photos') and product['photos']|length > 1 %}
771
+ <div class="swiper-pagination" style="position: relative; bottom: 5px; color: #ffd700 !important;"></div>
772
+ <div class="swiper-button-next" style="color: #ffcc00;"></div>
773
+ <div class="swiper-button-prev" style="color: #ffcc00;"></div>
774
  {% endif %}
775
  </div>
776
 
777
+ <div style="margin-top: 20px; font-size: 1rem; line-height: 1.7; color: #ccc;">
778
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
779
+ <p style="font-size: 1.2rem; font-weight: bold; color: #ffd700;"><strong>Цена:</strong> {{ "%.2f"|format(product['price']) }} {{ currency_code }}</p>
780
  <p><strong>Описание:</strong><br> {{ product.get('description', 'Описание отсутствует.')|replace('\\n', '<br>')|safe }}</p>
781
  {% set colors = product.get('colors', []) %}
782
  {% if colors and colors|select('ne', '')|list|length > 0 %}
 
792
  <head>
793
  <meta charset="UTF-8">
794
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
795
+ <title>Заказ №{{ order.id }} - Dako_kg</title>
796
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
797
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
798
  <style>
799
+ body { font-family: 'Poppins', sans-serif; background: #1a1a1a; color: #eee; line-height: 1.6; padding: 20px; }
800
+ .container { max-width: 800px; margin: 20px auto; padding: 30px; background: #2a2a2a; border-radius: 15px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); border: 1px solid #444; }
801
+ h1 { text-align: center; color: #ffcc00; margin-bottom: 25px; font-size: 1.8rem; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 10px;}
802
+ h2 { color: #ffcc00; margin-top: 30px; margin-bottom: 15px; font-size: 1.4rem; border-bottom: 1px solid #444; padding-bottom: 8px; display: flex; align-items: center; gap: 8px;}
803
+ .order-meta { font-size: 0.9rem; color: #ccc; margin-bottom: 20px; text-align: center; }
804
+ .order-item { display: grid; grid-template-columns: 60px 1fr auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #333; }
805
  .order-item:last-child { border-bottom: none; }
806
+ .order-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #333; padding: 5px; border: 1px solid #555;}
807
+ .item-details strong { display: block; margin-bottom: 5px; font-size: 1.05rem; color: #eee;}
808
+ .item-details span { font-size: 0.9rem; color: #ccc; display: block;}
809
+ .item-total { font-weight: bold; text-align: right; font-size: 1rem; color: #ffd700;}
810
+ .order-summary { margin-top: 30px; padding-top: 20px; border-top: 2px solid #ffcc00; text-align: right; }
811
  .order-summary p { margin-bottom: 10px; font-size: 1.1rem; }
812
+ .order-summary strong { font-size: 1.3rem; color: #ffd700; }
813
+ .customer-info { margin-top: 30px; background-color: #333; padding: 20px; border-radius: 8px; border: 1px solid #444;}
814
+ .customer-info p { margin-bottom: 8px; font-size: 0.95rem; color: #ccc; }
815
+ .customer-info strong { color: #ffcc00; }
816
  .actions { margin-top: 30px; text-align: center; }
817
  .button { padding: 12px 25px; border: none; border-radius: 8px; background-color: #25D366; color: white; font-weight: 600; cursor: pointer; transition: background-color 0.3s ease, transform 0.1s ease; font-size: 1rem; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
818
  .button:hover { background-color: #128C7E; }
819
  .button:active { transform: scale(0.98); }
820
  .button i { font-size: 1.2rem; }
821
+ .catalog-link { display: block; text-align: center; margin-top: 25px; color: #ffd700; text-decoration: none; font-size: 0.9rem; }
822
  .catalog-link:hover { text-decoration: underline; }
823
+ .not-found { text-align: center; color: #f56565; font-size: 1.2rem; padding: 40px 0;}
824
  </style>
825
  </head>
826
  <body>
 
866
  function sendOrderViaWhatsApp() {
867
  const orderId = '{{ order.id }}';
868
  const orderUrl = `{{ request.url }}`;
869
+ const whatsappNumber = "996558757157";
870
 
871
+ let message = `Здравствуйте! Хочу подтвердить свой заказ на Dako_kg:%0A%0A`;
872
  message += `*Номер заказа:* ${orderId}%0A`;
873
  message += `*Ссылка на заказ:* ${encodeURIComponent(orderUrl)}%0A%0A`;
874
  message += `Пожалуйста, свяжитесь со мной для уточнения деталей оплаты и доставки.`;
 
894
  <head>
895
  <meta charset="UTF-8">
896
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
897
+ <title>Админ-панель - Dako_kg</title>
898
  <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
899
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
900
  <style>
901
+ body { font-family: 'Poppins', sans-serif; background-color: #1a1a1a; color: #eee; padding: 20px; line-height: 1.6; }
902
+ .container { max-width: 1200px; margin: 0 auto; background-color: #2a2a2a; padding: 25px; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.3); border: 1px solid #444;}
903
+ .header { padding-bottom: 15px; margin-bottom: 25px; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;}
904
+ h1, h2, h3 { font-weight: 600; color: #ffcc00; margin-bottom: 15px; }
905
+ h1 { font-size: 1.8rem; display: flex; align-items: center; gap: 10px;}
906
+ h1 img { height: 40px; }
907
  h2 { font-size: 1.5rem; margin-top: 30px; display: flex; align-items: center; gap: 8px; }
908
+ h3 { font-size: 1.2rem; color: #ffcc00; margin-top: 20px; }
909
+ .section { margin-bottom: 30px; padding: 20px; background-color: #333; border: 1px solid #444; border-radius: 8px; }
910
  form { margin-bottom: 20px; }
911
+ label { font-weight: 500; margin-top: 10px; display: block; color: #ccc; font-size: 0.9rem;}
912
+ input[type="text"], input[type="number"], input[type="password"], input[type="tel"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 5px; border: 1px solid #555; border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; background-color: #444; color: #eee; }
913
+ input:focus, textarea:focus, select:focus { border-color: #ffcc00; outline: none; box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.1); }
914
  textarea { min-height: 80px; resize: vertical; }
915
+ input[type="file"] { padding: 8px; background-color: #333; cursor: pointer; border: 1px solid #555; color: #eee;}
916
+ input[type="file"]::file-selector-button { padding: 5px 10px; border-radius: 4px; background-color: #555; border: 1px solid #666; cursor: pointer; margin-right: 10px; color: #eee;}
917
+ input[type="file"]::file-selector-button:hover { background-color: #666;}
918
  input[type="checkbox"] { margin-right: 5px; vertical-align: middle; }
919
+ label.inline-label { display: inline-block; margin-top: 10px; font-weight: normal; color: #eee;}
920
+ button, .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: #555; color: #eee; font-weight: 500; cursor: pointer; transition: background-color 0.3s ease, transform 0.1s ease; margin-top: 15px; font-size: 0.95rem; display: inline-flex; align-items: center; gap: 5px; text-decoration: none; line-height: 1.2;}
921
+ button:hover, .button:hover { background-color: #666; }
922
  button:active, .button:active { transform: scale(0.98); }
923
  button[type="submit"] { min-width: 120px; justify-content: center; }
924
+ .delete-button { background-color: #c53030; color: white;}
925
+ .delete-button:hover { background-color: #9b2c2c; }
926
+ .add-button { background-color: #ffd700; color: #1a1a1a;}
927
+ .add-button:hover { background-color: #ffcc00; }
928
+ .catalog-link-button { background-color: #444; color: #eee; }
929
+ .catalog-link-button:hover { background-color: #555; }
930
  .item-list { display: grid; gap: 20px; }
931
+ .item { background: #333; padding: 15px 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); border: 1px solid #444; }
932
+ .item p { margin: 5px 0; font-size: 0.9rem; color: #ccc; }
933
+ .item strong { color: #eee; }
934
+ .item .description { font-size: 0.85rem; color: #bbb; max-height: 60px; overflow: hidden; text-overflow: ellipsis; }
935
  .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
936
+ .item-actions button:not(.delete-button) { background-color: #444; color: #eee;}
937
+ .item-actions button:not(.delete-button):hover { background-color: #555; }
938
+ .edit-form-container { margin-top: 15px; padding: 20px; background: #3a3a3a; border: 1px dashed #555; border-radius: 6px; display: none; }
939
+ details { background-color: #333; border: 1px solid #444; border-radius: 8px; margin-bottom: 20px; }
940
+ details > summary { cursor: pointer; font-weight: 600; color: #ffcc00; display: block; padding: 15px; border-bottom: 1px solid #444; list-style: none; position: relative; }
941
+ details > summary::after { content: '\\f078'; font-family: 'Font Awesome 6 Free'; font-weight: 900; position: absolute; right: 20px; top: 50%; transform: translateY(-50%); transition: transform 0.2s ease; color: #ffcc00; }
942
  details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
943
+ details[open] > summary { border-bottom: 1px solid #444; }
944
  details .form-content { padding: 20px; }
945
  .color-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
946
  .color-input-group input { flex-grow: 1; margin: 0; }
947
+ .remove-color-btn { background-color: #c53030; padding: 6px 10px; font-size: 0.8rem; margin-top: 0; line-height: 1; color: white;}
948
+ .remove-color-btn:hover { background-color: #9b2c2c; }
949
+ .add-color-btn { background-color: #ffd700; color: #1a1a1a;}
950
+ .add-color-btn:hover { background-color: #ffcc00; }
951
+ .photo-preview img { max-width: 70px; max-height: 70px; border-radius: 5px; margin: 5px 5px 0 0; border: 1px solid #555; object-fit: cover;}
952
  .sync-buttons { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
953
+ .download-hf-button { background-color: #444; color: #eee; }
954
+ .download-hf-button:hover { background-color: #555; }
955
  .flex-container { display: flex; flex-wrap: wrap; gap: 20px; }
956
  .flex-item { flex: 1; min-width: 350px; }
957
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem;}
958
+ .message.success { background-color: #28a745; color: white; border: 1px solid #28a745;}
959
+ .message.error { background-color: #dc3545; color: white; border: 1px solid #dc3545;}
960
+ .message.warning { background-color: #ffc107; color: #212529; border: 1px solid #ffc107; }
961
  .status-indicator { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 0.8rem; font-weight: 500; margin-left: 10px; vertical-align: middle; }
962
+ .status-indicator.in-stock { background-color: #28a745; color: white; }
963
+ .status-indicator.out-of-stock { background-color: #dc3545; color: white; }
964
+ .status-indicator.top-product { background-color: #ffd700; color: #1a1a1a; margin-left: 5px;}
965
  </style>
966
  </head>
967
  <body>
968
  <div class="container">
969
  <div class="header">
970
+ <h1><img src="https://cdn-avatars.huggingface.co/v1/production/uploads/67effb9055fe17a33d83fcb5/9kkn1DmDQ2jNz-0tFFZ2Q.jpeg" alt="Dako_kg Logo"> Админ-панель Dako_kg</h1>
971
+ <a href="{{ url_for('catalog') }}" class="button catalog-link-button"><i class="fas fa-store"></i> Перейти в каталог</a>
972
  </div>
973
 
974
  {% with messages = get_flashed_messages(with_categories=true) %}
 
989
  <button type="submit" class="button download-hf-button" title="Скачать файлы (перезапишет локальные)"><i class="fas fa-download"></i> Скачать БД</button>
990
  </form>
991
  </div>
992
+ <p style="font-size: 0.85rem; color: #ccc;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
993
  </div>
994
 
995
  <div class="flex-container">
 
1013
  <div class="item-list">
1014
  {% for category in categories %}
1015
  <div class="item" style="display: flex; justify-content: space-between; align-items: center;">
1016
+ <span style="color: #eee;">{{ category }}</span>
1017
  <form method="POST" style="margin: 0;" onsubmit="return confirm('Вы уверены, что хотите удалить категорию \'{{ category }}\'? Товары этой категории будут помечены как \'Без категории\'.');">
1018
  <input type="hidden" name="action" value="delete_category">
1019
  <input type="hidden" name="category_name" value="{{ category }}">
 
1023
  {% endfor %}
1024
  </div>
1025
  {% else %}
1026
+ <p style="color: #ccc;">Категорий пока нет.</p>
1027
  {% endif %}
1028
  </div>
1029
  </div>
 
1031
  <div class="flex-item">
1032
  <div class="section">
1033
  <h2><i class="fas fa-info-circle"></i> Информация</h2>
1034
+ <p style="color: #ccc;">Управление пользователями отключено, так как сайт не требует входа.</p>
1035
+ <p style="color: #ccc;">Заказы создаются анонимно и должны быть подтверждены через WhatsApp.</p>
1036
  </div>
1037
  </div>
1038
  </div>
 
1098
  {% endif %}
1099
  </div>
1100
  <div style="flex-grow: 1;">
1101
+ <h3 style="margin-top: 0; margin-bottom: 5px; color: #eee;">
1102
  {{ product['name'] }}
1103
  {% if product.get('in_stock', True) %}
1104
  <span class="status-indicator in-stock">В наличии</span>
 
1115
  {% set colors = product.get('colors', []) %}
1116
  <p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
1117
  {% if product.get('photos') and product['photos']|length > 1 %}
1118
+ <p style="font-size: 0.8rem; color: #bbb;">(Всего фото: {{ product['photos']|length }})</p>
1119
  {% endif %}
1120
  </div>
1121
  </div>
 
1150
  <label>Заменить фотографии (выберите новые файлы, до 10 шт.):</label>
1151
  <input type="file" name="photos" accept="image/*" multiple>
1152
  {% if product.get('photos') %}
1153
+ <p style="font-size: 0.85rem; margin-top: 5px; color: #ccc;">Текущие фото:</p>
1154
  <div class="photo-preview">
1155
  {% for photo in product['photos'] %}
1156
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="Фото {{ loop.index }}">
 
1194
  {% endfor %}
1195
  </div>
1196
  {% else %}
1197
+ <p style="color: #ccc;">Товаров пока нет.</p>
1198
  {% endif %}
1199
  </div>
1200
 
 
1248
  </html>
1249
  '''
1250
 
 
 
1251
  @app.route('/')
1252
  def catalog():
1253
  data = load_data()
 
1328
  "created_at": order_timestamp,
1329
  "cart": processed_cart,
1330
  "total_price": round(total_price, 2),
1331
+ "user_info": None,
1332
  "status": "new"
1333
  }
1334
 
 
1656
  flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
1657
  return redirect(url_for('admin'))
1658
 
 
1659
  current_data = load_data()
1660
  display_products = sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower())
1661
  display_categories = sorted(current_data.get('categories', []))
 
1685
  try:
1686
  if download_db_from_hf():
1687
  flash("Данные успешно скачаны с Hugging Face. Локальные файлы обновлены.", 'success')
1688
+ load_data()
1689
  else:
1690
  flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
1691
  except Exception as e:
 
1693
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
1694
  return redirect(url_for('admin'))
1695
 
 
 
 
1696
  if __name__ == '__main__':
1697
  logging.info("Application starting up. Performing initial data load/download...")
1698
  download_db_from_hf()
 
1710
  logging.info(f"Starting Flask app on host 0.0.0.0 and port {port}")
1711
  app.run(debug=False, host='0.0.0.0', port=port)
1712
 
1713
+