Kgshop commited on
Commit
8acf026
·
verified ·
1 Parent(s): 37cdce7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -119
app.py CHANGED
@@ -16,7 +16,7 @@ import uuid
16
  load_dotenv()
17
 
18
  app = Flask(__name__)
19
- app.secret_key = 'your_unique_secret_key_soola_cosmetics_67890_no_login'
20
  DATA_FILE = 'data.json'
21
 
22
 
@@ -50,8 +50,8 @@ STATUS_MAPS = {
50
 
51
  translations = {
52
  'ru': {
53
- 'page_title': "s.amir.kz - одежда оптом и в розницу",
54
- 'header_title': "s.amir.kz",
55
  'our_addresses': "Наши адреса в г. Алматы",
56
  'search_placeholder': "Поиск по названию или описанию...",
57
  'all_filter': "Все",
@@ -95,7 +95,7 @@ translations = {
95
  'return_to_catalog': "Вернуться в каталог",
96
  'order_not_found_error': "Ошибка",
97
  'order_not_found_message': "Заказ с таким ID не найден.",
98
- 'whatsapp_confirm_message_1': "Здравствуйте! Хочу подтвердить свой заказ в s.amir.kz:",
99
  'whatsapp_confirm_message_2': "Номер заказа:",
100
  'whatsapp_confirm_message_3': "Ссылка на заказ:",
101
  'whatsapp_confirm_message_4': "Пожалуйста, свяжитесь со мной для уточнения деталей.",
@@ -103,8 +103,8 @@ translations = {
103
  'odezhda': "Одежда",
104
  },
105
  'kz': {
106
- 'page_title': "s.amir.kz - киімдер көтерме және бөлшек саудада",
107
- 'header_title': "s.amir.kz",
108
  'our_addresses': "Алматы қаласындағы мекенжайларымыз",
109
  'search_placeholder': "Аты немесе сипаттамасы бойынша іздеу...",
110
  'all_filter': "Барлығы",
@@ -148,7 +148,7 @@ translations = {
148
  'return_to_catalog': "Каталогқа оралу",
149
  'order_not_found_error': "Қате",
150
  'order_not_found_message': "Мұндай ID-мен тапсырыс табылмады.",
151
- 'whatsapp_confirm_message_1': "Сәлеметсіз бе! s.amir.kz-дағы тапсырысымды растағым келеді:",
152
  'whatsapp_confirm_message_2': "Тапсырыс нөмірі:",
153
  'whatsapp_confirm_message_3': "Тапсырысқа сілтеме:",
154
  'whatsapp_confirm_message_4': "Мәліметтерді нақтылау үшін менімен хабарласуыңызды сұраймын.",
@@ -200,7 +200,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN
200
  try:
201
  if file_name == DATA_FILE:
202
  with open(file_name, 'w', encoding='utf-8') as f:
203
- json.dump({'products': [], 'categories': [], 'orders': {}, 'settings': {'whatsapp_number': '+77073363943', 'store_addresses': ['Город Алматы, Алатау 1, блок 4, бутик 112']}}, f)
204
  logging.info(f"Created empty local file {file_name} because it was not found on HF.")
205
  except Exception as create_e:
206
  logging.error(f"Failed to create empty local file {file_name}: {create_e}")
@@ -264,7 +264,7 @@ def periodic_backup():
264
 
265
 
266
  def load_data():
267
- default_data = {'products': [], 'categories': [], 'orders': {}, 'settings': {'whatsapp_number': '+77073363943', 'store_addresses': ['Город Алматы, Алатау 1, блок 4, бутик 112']}}
268
  try:
269
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
270
  data = json.load(file)
@@ -275,8 +275,8 @@ def load_data():
275
  if 'products' not in data: data['products'] = []
276
  if 'categories' not in data: data['categories'] = []
277
  if 'orders' not in data: data['orders'] = {}
278
- if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': ['Город Алматы, Алатау 1, блок 4, бутик 112']}
279
- if 'store_addresses' not in data['settings']: data['settings']['store_addresses'] = ['Город Алматы, Алатау 1, блок 4, бутик 112']
280
  return data
281
  except FileNotFoundError:
282
  logging.warning(f"Local file {DATA_FILE} not found. Attempting download from HF.")
@@ -294,8 +294,8 @@ def load_data():
294
  if 'products' not in data: data['products'] = []
295
  if 'categories' not in data: data['categories'] = []
296
  if 'orders' not in data: data['orders'] = {}
297
- if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': ['Город Алматы, Алатау 1, блок 4, бутик 112']}
298
- if 'store_addresses' not in data['settings']: data['settings']['store_addresses'] = ['Город Алматы, Алатау 1, блок 4, бутик 112']
299
  return data
300
  except FileNotFoundError:
301
  logging.error(f"File {DATA_FILE} still not found even after download reported success. Using default.")
@@ -325,8 +325,8 @@ def save_data(data):
325
  if 'products' not in data: data['products'] = []
326
  if 'categories' not in data: data['categories'] = []
327
  if 'orders' not in data: data['orders'] = {}
328
- if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': []}
329
- if 'store_addresses' not in data['settings']: data['settings']['store_addresses'] = []
330
 
331
 
332
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
@@ -348,26 +348,25 @@ CATALOG_TEMPLATE = '''
348
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
349
  <link rel="preconnect" href="https://fonts.googleapis.com">
350
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
351
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
352
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
353
  <style>
354
  :root {
355
  --primary-color: #000000;
356
  --primary-dark: #333333;
357
- --surface-color: #f8f9fa;
358
- --background-color: #ffffff;
359
- --text-color: #212529;
360
- --text-color-muted: #6c757d;
361
- --border-color: #dee2e6;
362
  --accent-color: #000000;
363
  }
364
  * { margin: 0; padding: 0; box-sizing: border-box; }
365
  body {
366
- font-family: 'Roboto', sans-serif;
367
  background: var(--background-color);
368
  color: var(--text-color);
369
  line-height: 1.6;
370
- transition: background-color 0.3s;
371
  }
372
  .container { max-width: 100%; margin: 0 auto; padding: 0; }
373
  .content-area { padding: 20px; }
@@ -375,45 +374,45 @@ CATALOG_TEMPLATE = '''
375
  .header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-color); position: sticky; top: 0; z-index: 1000; }
376
  .logo-title-container { display: flex; align-items: center; gap: 15px; }
377
  .logo-title-container img { height: 50px; width: 50px; object-fit: cover; border-radius: 50%; }
378
- .header h1 { font-family: 'Montserrat', sans-serif; font-size: 1.8rem; font-weight: 700; color: var(--accent-color); }
379
 
380
- .lang-switcher { display: flex; gap: 5px; background-color: var(--surface-color); padding: 5px; border-radius: 50px; }
381
  .lang-switcher a { color: var(--text-color-muted); text-decoration: none; font-size: 0.9rem; padding: 5px 10px; border-radius: 50px; transition: all 0.3s; }
382
  .lang-switcher a.active { background-color: var(--primary-color); color: white; font-weight: bold; }
383
 
384
  .store-addresses { padding: 20px; text-align: center; background-color: var(--surface-color); border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); font-size: 0.95rem; color: var(--text-color-muted); margin: 20px; }
385
- .store-addresses h3 { font-family: 'Montserrat', sans-serif; color: var(--primary-color); font-size: 1.3rem; margin-bottom: 10px; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 8px; }
386
  .store-addresses p { margin: 5px 0; }
387
 
388
  .search-container { padding: 0 20px 20px; }
389
- #search-input { width: 100%; padding: 12px 20px; font-size: 1rem; border: 1px solid var(--border-color); border-radius: 50px; outline: none; transition: all 0.3s; background-color: #fff; color: var(--text-color); }
390
  #search-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1); }
391
 
392
- .filters-wrapper { margin: 0 20px 20px; display: flex; flex-direction: column; gap: 15px; }
393
  .filters-container { display: flex; overflow-x: auto; gap: 10px; padding-bottom: 10px; scrollbar-width: none; -ms-overflow-style: none; }
394
  .filters-container::-webkit-scrollbar { display: none; }
395
  .filter-label { font-size: 0.9rem; color: var(--text-color-muted); margin-left: 5px; }
396
- .filter-btn { padding: 8px 18px; border: 1px solid var(--border-color); border-radius: 50px; background-color: transparent; cursor: pointer; transition: all 0.3s ease; font-size: 0.9rem; font-weight: 400; color: var(--text-color); white-space: nowrap; }
397
- .filter-btn.active, .filter-btn:hover { background-color: var(--primary-color); color: #fff; border-color: var(--primary-color); font-weight: 500; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); transform: translateY(-2px); }
398
 
399
  .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; padding: 0 20px 120px; }
400
  @media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 20px; } }
401
 
402
- .product { background: #fff; border-radius: 16px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); transition: all 0.3s ease; overflow: hidden; display: flex; flex-direction: column; height: 100%; position: relative; border: 1px solid var(--border-color); }
403
- .product:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12); border-color: var(--primary-color); }
404
 
405
  .product-image { width: 100%; aspect-ratio: 1 / 1; background-color: #fff; display: flex; justify-content: center; align-items: center; padding: 10px; }
406
  .product-image img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform 0.3s ease; }
407
  .product:hover .product-image img { transform: scale(1.05); }
408
  .product-info { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; text-align: center; }
409
- .product h2 { font-family: 'Montserrat', sans-serif; font-size: 1.1rem; font-weight: 600; margin: 0 0 8px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-color); }
410
  .product-price { font-size: 1.2rem; color: var(--primary-color); font-weight: 700; margin: 5px 0; }
411
  .product-price .from-text { font-size: 0.8rem; color: var(--text-color-muted); font-weight: 400; }
412
  .product-description { display: none; }
413
  .product-actions { padding: 0 15px 15px; }
414
 
415
  .product-button { display: inline-flex; align-items: center; justify-content: center; width: 100%; padding: 10px; border: none; border-radius: 50px; background-color: var(--primary-color); color: #fff; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; text-decoration: none; text-transform: uppercase; letter-spacing: 0.5px; }
416
- .product-button:hover { background-color: var(--primary-dark); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); }
417
  .product-button i { margin-right: 8px; }
418
 
419
  .fab { position: fixed; background-color: var(--primary-color); color: #fff; border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); z-index: 1000; transition: transform 0.2s ease; }
@@ -421,8 +420,8 @@ CATALOG_TEMPLATE = '''
421
  #cart-button { bottom: 20px; right: 20px; display: none; }
422
  #cart-button span { position: absolute; top: -2px; right: -2px; background-color: #dc3545; color: white; border-radius: 50%; padding: 3px 7px; font-size: 0.75rem; font-weight: bold; min-width: 22px; text-align: center; }
423
 
424
- .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(8px); overflow-y: auto; }
425
- .modal-content { background: var(--background-color); margin: 5% auto; padding: 25px; border-radius: 16px; width: 95%; max-width: 600px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); animation: slideIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); position: relative; border: 1px solid var(--border-color); }
426
  @keyframes slideIn { from { transform: translateY(-40px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
427
 
428
  .close { position: absolute; top: 15px; right: 20px; font-size: 2rem; color: var(--text-color-muted); cursor: pointer; transition: color 0.3s, transform 0.3s; line-height: 1; }
@@ -432,7 +431,7 @@ CATALOG_TEMPLATE = '''
432
 
433
  .cart-item { display: grid; grid-template-columns: 65px 1fr auto 25px; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid var(--border-color); }
434
  .cart-item:last-child { border-bottom: none; }
435
- .cart-item img { width: 65px; height: 65px; object-fit: contain; border-radius: 8px; background-color: #fff; padding: 5px; border: 1px solid var(--border-color); }
436
  .cart-item-details strong { display: block; margin-bottom: 4px; font-size: 1.05rem; }
437
  .cart-item-price { font-size: 0.9rem; color: var(--text-color-muted); }
438
  .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--primary-color); }
@@ -445,10 +444,10 @@ CATALOG_TEMPLATE = '''
445
  .cart-summary strong { font-size: 1.4rem; color: var(--primary-color); }
446
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 12px; }
447
  .cart-actions .product-button { flex-grow: 1; }
448
- .clear-cart { background: #6c757d; color: #fff; }
449
- .clear-cart:hover { background: #5a6268; }
450
 
451
- .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--primary-color); color: #fff; padding: 12px 25px; border-radius: 50px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); z-index: 1002; opacity: 0; transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); font-size: 0.95rem; font-weight: 500; }
452
  .notification.show { opacity: 1; bottom: 90px; }
453
  .no-results-message { grid-column: 1 / -1; text-align: center; padding: 50px; font-size: 1.2rem; color: var(--text-color-muted); }
454
 
@@ -459,7 +458,7 @@ CATALOG_TEMPLATE = '''
459
  <div class="container">
460
  <header class="header">
461
  <div class="logo-title-container">
462
- <img src="https://huggingface.co/spaces/esmira-tkani/admin/resolve/main/Screenshot_20260208-120337.png" alt="s.amir.kz Logo">
463
  <h1>{{ _['header_title'] }}</h1>
464
  </div>
465
  <div class="lang-switcher">
@@ -499,7 +498,7 @@ CATALOG_TEMPLATE = '''
499
  data-name="{{ product['name']|lower }}"
500
  data-description="{{ product.get('description', '')|lower }}"
501
  data-category="{{ product.get('category', _['no_category']) }}"
502
- data-type="{{ product.get('product_type', 'odezhda') }}">
503
  <div class="product-image" onclick="openModal({{ loop.index0 }})" style="cursor: pointer;">
504
  {% if product.get('photos') and product['photos']|length > 0 %}
505
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
@@ -597,7 +596,7 @@ CATALOG_TEMPLATE = '''
597
  const repoId = '{{ repo_id }}';
598
  const currencyCode = '{{ currency_code }}';
599
  let selectedProductIndex = null;
600
- let cart = JSON.parse(localStorage.getItem('esmiracart') || '[]');
601
  const langCode = '{{ lang_code }}';
602
  const translations = {{ _|tojson }};
603
 
@@ -665,8 +664,8 @@ CATALOG_TEMPLATE = '''
665
  const quantityLabel = document.getElementById('quantity-label');
666
 
667
  setupSelect('variantSelect', product.variants, v => `${v.name} - ${v.price.toFixed(2)} ${currencyCode}`);
668
-
669
  setupSelect('sizeSelect', product.sizes, s => s.name);
 
670
  quantityInput.type = "number";
671
  quantityInput.step = "1";
672
  quantityInput.min = "1";
@@ -681,7 +680,7 @@ CATALOG_TEMPLATE = '''
681
  if (selectedProductIndex === null) return;
682
 
683
  const product = products[selectedProductIndex];
684
- const quantity = parseFloat(document.getElementById('quantityInput').value);
685
  const variantIndex = document.getElementById('variantSelect').value;
686
  const sizeIndex = document.getElementById('sizeSelect').value;
687
 
@@ -718,13 +717,12 @@ CATALOG_TEMPLATE = '''
718
  price: selectedVariant.price,
719
  variantName: selectedVariant.name,
720
  sizeName: selectedSize ? selectedSize.name : null,
721
- productType: product.product_type,
722
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
723
  quantity: quantity
724
  });
725
  }
726
 
727
- localStorage.setItem('esmiracart', JSON.stringify(cart));
728
  closeModal('quantityModal');
729
  updateCartButton();
730
  showNotification(`${product.name} ${translations['add_to_cart_notification']}`);
@@ -782,7 +780,7 @@ CATALOG_TEMPLATE = '''
782
 
783
  function removeFromCart(itemId) {
784
  cart = cart.filter(item => item.id !== itemId);
785
- localStorage.setItem('esmiracart', JSON.stringify(cart));
786
  openCartModal();
787
  updateCartButton();
788
  }
@@ -790,7 +788,7 @@ CATALOG_TEMPLATE = '''
790
  function clearCart() {
791
  if (confirm(translations['clear_cart_confirm'])) {
792
  cart = [];
793
- localStorage.removeItem('esmiracart');
794
  openCartModal();
795
  updateCartButton();
796
  }
@@ -812,7 +810,7 @@ CATALOG_TEMPLATE = '''
812
  .then(response => response.json())
813
  .then(data => {
814
  if (data.order_id) {
815
- localStorage.removeItem('esmiracart');
816
  cart = [];
817
  updateCartButton();
818
  closeModal('cartModal');
@@ -865,8 +863,7 @@ CATALOG_TEMPLATE = '''
865
  document.getElementById('search-input').addEventListener('input', filterProducts);
866
  document.querySelectorAll('.filter-btn').forEach(filter => {
867
  filter.addEventListener('click', function() {
868
- const filterGroupClass = this.classList.contains('category-filter') ? '.category-filter.active' : '.type-filter.active';
869
- document.querySelector(filterGroupClass).classList.remove('active');
870
  this.classList.add('active');
871
  filterProducts();
872
  });
@@ -939,7 +936,7 @@ PRODUCT_DETAIL_TEMPLATE = '''
939
  </div>
940
 
941
  <div style="font-size: 1rem; line-height: 1.7; padding: 0 10px;">
942
- <p><strong>{{ _['product_type'] }}:</strong> {{ _[product.get('product_type', 'odezhda')] }}</p>
943
  <p><strong>{{ _['category'] }}:</strong> {{ product.get('category', _['no_category']) }}</p>
944
 
945
  {% if product.get('variants') and product.variants|length > 0 %}
@@ -957,7 +954,7 @@ PRODUCT_DETAIL_TEMPLATE = '''
957
  </ul>
958
  {% endif %}
959
 
960
- {% if product.product_type == 'odezhda' and product.get('sizes') and product.sizes|length > 0 %}
961
  <p style="margin-top: 15px;"><strong>{{ _['available_sizes'] }}</strong></p>
962
  <ul style="list-style: none; padding-left: 0;">
963
  {% for size in product.sizes %}
@@ -984,41 +981,41 @@ ORDER_TEMPLATE = '''
984
  <head>
985
  <meta charset="UTF-8">
986
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
987
- <title>{{ _['order_page_title'] }}{{ order.id }} - s.amir.kz</title>
988
  <link rel="preconnect" href="https://fonts.googleapis.com">
989
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
990
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Montserrat:wght@600;700&display=swap" rel="stylesheet">
991
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
992
  <style>
993
  :root {
994
  --primary-color: #000000;
995
  --primary-dark: #333333;
996
- --surface-color: #ffffff;
997
- --background-color: #f8f9fa;
998
- --text-color: #212529;
999
- --text-color-muted: #6c757d;
1000
- --border-color: #dee2e6;
1001
  }
1002
- body { font-family: 'Roboto', sans-serif; background: var(--background-color); color: var(--text-color); line-height: 1.6; padding: 15px; }
1003
  .container { max-width: 800px; margin: 20px auto; padding: 30px; background: var(--surface-color); border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); border: 1px solid var(--border-color); }
1004
- h1, h2 { font-family: 'Montserrat', sans-serif; color: var(--primary-color); }
1005
  h1 { text-align: center; margin-bottom: 25px; font-size: 2.2rem; font-weight: 700; }
1006
  h2 { margin-top: 30px; margin-bottom: 15px; font-size: 1.6rem; border-bottom: 1px solid var(--border-color); padding-bottom: 8px; display: flex; align-items: center; gap: 10px; }
1007
  .order-meta { font-size: 0.9rem; color: var(--text-color-muted); margin-bottom: 20px; text-align: center; }
1008
  .order-item { display: grid; grid-template-columns: 60px 1fr auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid var(--border-color); }
1009
  .order-item:last-child { border-bottom: none; }
1010
- .order-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #fff; padding: 5px; border: 1px solid var(--border-color);}
1011
  .item-details strong { display: block; margin-bottom: 4px; font-size: 1.05rem; color: var(--text-color);}
1012
  .item-details span { font-size: 0.9rem; color: var(--text-color-muted); display: block;}
1013
  .item-total { font-weight: bold; text-align: right; font-size: 1.1rem; color: var(--primary-color);}
1014
  .order-summary { margin-top: 30px; padding-top: 20px; border-top: 2px solid var(--primary-color); text-align: right; }
1015
  .order-summary p { margin-bottom: 10px; font-size: 1.1rem; }
1016
  .order-summary strong { font-size: 1.5rem; color: var(--primary-color); }
1017
- .customer-info { margin-top: 30px; background-color: #f8f9fa; padding: 20px; border-radius: 12px; border: 1px solid var(--border-color);}
1018
  .customer-info p { margin-bottom: 8px; font-size: 1rem; }
1019
  .customer-info strong { color: var(--text-color); }
1020
  .actions { margin-top: 30px; text-align: center; }
1021
- .button { padding: 12px 25px; border: none; border-radius: 50px; background-color: #25D366; color: white; font-weight: 500; cursor: pointer; transition: all 0.3s ease; font-size: 1.1rem; display: inline-flex; align-items: center; gap: 10px; text-decoration: none; box-shadow: 0 4px 10px rgba(37, 211, 102, 0.3); }
1022
  .button:hover { background-color: #1DA851; transform: translateY(-2px); }
1023
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--primary-color); text-decoration: none; transition: color 0.2s; }
1024
  .catalog-link:hover { color: var(--primary-dark); }
@@ -1099,50 +1096,50 @@ ADMIN_TEMPLATE = '''
1099
  <head>
1100
  <meta charset="UTF-8">
1101
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1102
- <title>Админ-панель - s.amir.kz</title>
1103
  <link rel="preconnect" href="https://fonts.googleapis.com">
1104
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1105
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Montserrat:wght@600&display=swap" rel="stylesheet">
1106
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1107
  <style>
1108
  :root {
1109
  --primary-color: #000000;
1110
  --primary-dark: #333333;
1111
- --surface-color: #ffffff;
1112
- --background-color: #f4f7f6;
1113
- --text-color: #212529;
1114
- --text-color-muted: #6c757d;
1115
- --border-color: #dee2e6;
1116
- --success-bg: #d4edda;
1117
- --success-text: #155724;
1118
- --error-bg: #f8d7da;
1119
- --error-text: #721c24;
1120
- --warning-bg: #fff3cd;
1121
- --warning-text: #856404;
1122
  }
1123
- body { font-family: 'Roboto', sans-serif; background-color: var(--background-color); color: var(--text-color); padding: 15px; line-height: 1.5; }
1124
- .container { max-width: 1200px; margin: 0 auto; background-color: var(--surface-color); padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); border: 1px solid var(--border-color); }
1125
  .header { padding-bottom: 20px; margin-bottom: 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;}
1126
- h1, h2, h3, h4 { font-family: 'Montserrat', sans-serif; font-weight: 600; color: var(--primary-color); margin:0 0 15px 0;}
1127
  h1 { font-size: 2rem; }
1128
  h2 { font-size: 1.6rem; margin-top: 30px; display: flex; align-items: center; gap: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--border-color);}
1129
  h3 { font-size: 1.2rem; color: var(--text-color); margin-top: 20px;}
1130
- .section { margin-bottom: 30px; padding: 20px; background-color: #fff; border: 1px solid var(--border-color); border-radius: 8px; }
1131
 
1132
  label { font-weight: 500; margin-top: 12px; display: block; color: var(--text-color); font-size: 0.9rem;}
1133
- input[type="text"], input[type="number"], input[type="password"], input[type="tel"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 6px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; background-color: #fff; color: var(--text-color); }
1134
  input:focus, textarea:focus, select:focus { border-color: var(--primary-color); outline: none; box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1); }
1135
  textarea { min-height: 90px; resize: vertical; }
1136
- input[type="file"] { padding: 8px; background-color: #f8f9fa; cursor: pointer; border: 1px solid var(--border-color); border-radius: 6px; }
1137
  input[type="checkbox"] { transform: scale(1.2); margin-right: 8px; vertical-align: middle; accent-color: var(--primary-color); }
1138
 
1139
  button, .button { padding: 9px 18px; border: none; border-radius: 50px; background-color: var(--primary-color); color: #fff; font-weight: 500; cursor: pointer; transition: all 0.2s ease; margin-top: 15px; font-size: 0.9rem; display: inline-flex; align-items: center; gap: 6px; text-decoration: none; line-height: 1.2; text-transform: uppercase; letter-spacing: 0.5px; }
1140
  button:hover, .button:hover { background-color: var(--primary-dark); transform: translateY(-1px); }
1141
- .delete-button { background-color: #dc3545; color: white; }
1142
- .delete-button:hover { background-color: #c82333; }
1143
 
1144
  .item-list { display: grid; gap: 15px; }
1145
- .item { background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid var(--border-color); }
1146
  .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
1147
  .photo-preview img, .photo-edit-item img { width: 60px; height: 60px; border-radius: 6px; margin: 5px 5px 0 0; border: 1px solid var(--border-color); object-fit: cover; background-color: #fff;}
1148
  .photo-preview-edit { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px; }
@@ -1151,21 +1148,21 @@ ADMIN_TEMPLATE = '''
1151
 
1152
  .input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
1153
  .input-group input { flex-grow: 1; margin: 0; }
1154
- .remove-btn { background-color: #dc3545; color: white; padding: 6px 10px; font-size: 0.8rem; margin: 0; border-radius: 6px; }
1155
- .add-btn { background-color: #28a745; color: #fff; margin-top: 8px;}
1156
- .add-btn:hover { background-color: #218838; }
1157
 
1158
  .message { padding: 12px 18px; border-radius: 8px; margin-bottom: 20px; font-size: 0.95rem; border: 1px solid;}
1159
  .message.success { background-color: var(--success-bg); color: var(--success-text); border-color: var(--success-text);}
1160
  .message.error { background-color: var(--error-bg); color: var(--error-text); border-color: var(--error-text);}
1161
  .message.warning { background-color: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-text); }
1162
- .status-indicator { display: inline-block; padding: 3px 9px; border-radius: 50px; font-size: 0.75rem; font-weight: 500; margin-left: 5px; vertical-align: middle; color: #fff;}
1163
- .status-indicator.new { background-color: #fd7e14; }
1164
- .status-indicator.accepted { background-color: #007bff; }
1165
- .status-indicator.prepared { background-color: #28a745; }
1166
- .status-indicator.shipped { background-color: #17a2b8; }
1167
 
1168
- details { background-color: #fff; border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 10px; }
1169
  details > summary { cursor: pointer; font-weight: 500; color: var(--text-color); display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; list-style: none; position: relative; }
1170
  details[open] > summary { border-bottom: 1px solid var(--border-color); }
1171
  .order-details-content { padding: 15px 20px; }
@@ -1180,7 +1177,7 @@ ADMIN_TEMPLATE = '''
1180
  <div class="container">
1181
  <div class="header">
1182
  <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
1183
- <img src="https://huggingface.co/spaces/esmira-tkani/admin/resolve/main/Screenshot_20260208-120337.png" alt="s.amir.kz Logo" style="height: 50px; width: 50px; border-radius: 50%;">
1184
  <h1><i class="fas fa-tools"></i> Админ-панель</h1>
1185
  </div>
1186
  <a href="{{ url_for('catalog', lang_code='ru') }}" class="button"><i class="fas fa-store"></i> Перейти в каталог</a>
@@ -1223,7 +1220,7 @@ ADMIN_TEMPLATE = '''
1223
  {% endfor %}
1224
  </select>
1225
  <button type="submit" class="button" style="margin-top:0;"><i class="fas fa-check"></i> Сохранить</button>
1226
- <a href="{{ url_for('view_order', lang_code='ru', order_id=order.id) }}" target="_blank" class="button" style="background-color: #6c757d; color: white; margin-top:0;"><i class="fas fa-eye"></i> Просмотр</a>
1227
  </form>
1228
  </div>
1229
  </div>
@@ -1241,7 +1238,7 @@ ADMIN_TEMPLATE = '''
1241
  <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
1242
  <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.get('whatsapp_number', '') }}" required>
1243
 
1244
- <label for="store_addresses">Адреса магазинов (каждый с новой строки):</label>
1245
  <textarea id="store_addresses" name="store_addresses" rows="4">{{ settings.get('store_addresses', [])|join('\n') }}</textarea>
1246
 
1247
  <button type="submit"><i class="fas fa-save"></i> Сохранить настройки</button>
@@ -1288,6 +1285,7 @@ ADMIN_TEMPLATE = '''
1288
  <div style="padding: 15px;">
1289
  <form method="POST" enctype="multipart/form-data" id="add-product-form">
1290
  <input type="hidden" name="action" value="add_product">
 
1291
  <label for="add_name">Название товара *:</label>
1292
  <input type="text" id="add_name" name="name" required>
1293
  <label for="add_description">Описание:</label>
@@ -1348,7 +1346,7 @@ ADMIN_TEMPLATE = '''
1348
  </div>
1349
  <div style="flex-grow: 1;">
1350
  <h3 style="margin-top: 0; margin-bottom: 5px;">{{ product['name'] }}</h3>
1351
- <p style="font-size: 0.9rem; color: var(--text-color-muted);"><strong>Тип:</strong> {{ _['odezhda'] }}</p>
1352
  <p style="font-size: 0.9rem; color: var(--text-color-muted);"><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1353
  </div>
1354
  </div>
@@ -1367,11 +1365,12 @@ ADMIN_TEMPLATE = '''
1367
  <form method="POST" enctype="multipart/form-data">
1368
  <input type="hidden" name="action" value="edit_product">
1369
  <input type="hidden" name="product_id" value="{{ product.id }}">
 
1370
  <label>Название *:</label>
1371
  <input type="text" name="name" value="{{ product['name'] }}" required>
1372
  <label>Описание:</label>
1373
  <textarea name="description" rows="4">{{ product.get('description', '') }}</textarea>
1374
-
1375
  <label>Категория:</label>
1376
  <select name="category">
1377
  <option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>
@@ -1409,7 +1408,7 @@ ADMIN_TEMPLATE = '''
1409
  </div>
1410
  <button type="button" class="button add-btn" onclick="addInputGroup('edit-variants-container-{{ product.id }}', 'variant')"><i class="fas fa-plus"></i> Добавить вариант</button>
1411
 
1412
- <div id="edit-{{ product.id }}-sizes-section" style="margin-top:20px;">
1413
  <h4>Размеры *:</h4>
1414
  <div id="edit-sizes-container-{{ product.id }}">
1415
  {% for size in product.get('sizes', []) %}
@@ -1495,10 +1494,10 @@ def catalog(lang_code):
1495
  categories = sorted(data.get('categories', []))
1496
  settings = data.get('settings', {})
1497
  whatsapp_number = settings.get('whatsapp_number', '+77073363943').replace(' ', '').replace('+', '')
1498
- store_addresses = settings.get('store_addresses', [])
1499
 
1500
 
1501
- products_in_stock = [p for p in all_products if p.get('variants')]
1502
 
1503
  for p in products_in_stock:
1504
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
@@ -1521,7 +1520,7 @@ def catalog(lang_code):
1521
  def product_detail(lang_code, index):
1522
  data = load_data()
1523
  all_products = data.get('products', [])
1524
- products_in_stock = [p for p in all_products if p.get('variants')]
1525
 
1526
  for p in products_in_stock:
1527
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
@@ -1565,7 +1564,7 @@ def create_order():
1565
 
1566
  try:
1567
  price = float(item['price'])
1568
- quantity = float(item['quantity'])
1569
  if price < 0 or quantity <= 0: raise ValueError("Invalid price/qty")
1570
 
1571
  total_price += price * quantity
@@ -1617,7 +1616,7 @@ def view_order(lang_code, order_id):
1617
  def admin():
1618
  data = load_data()
1619
  if 'orders' not in data: data['orders'] = {}
1620
- if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': []}
1621
 
1622
 
1623
  needs_save = False
@@ -1625,6 +1624,9 @@ def admin():
1625
  if 'id' not in p:
1626
  p['id'] = str(uuid.uuid4())
1627
  needs_save = True
 
 
 
1628
  if needs_save:
1629
  save_data(data)
1630
 
@@ -1633,18 +1635,14 @@ def admin():
1633
  try:
1634
  if action == 'update_settings':
1635
  new_whatsapp = request.form.get('whatsapp_number', '').strip()
 
1636
  if new_whatsapp:
1637
  data['settings']['whatsapp_number'] = new_whatsapp
 
 
 
1638
  else:
1639
  flash('Номер WhatsApp не может быть пустым.', 'error')
1640
-
1641
- addresses_text = request.form.get('store_addresses', '')
1642
- address_list = [addr.strip() for addr in addresses_text.splitlines() if addr.strip()]
1643
- data['settings']['store_addresses'] = address_list
1644
-
1645
- save_data(data)
1646
- flash('Настройки успешно обновлены.', 'success')
1647
-
1648
 
1649
  elif action == 'update_order_status':
1650
  order_id = request.form.get('order_id')
@@ -1700,6 +1698,7 @@ def admin():
1700
  flash("Неверный формат цены в вариантах.", 'error')
1701
  return redirect(url_for('admin'))
1702
 
 
1703
  size_names = [s.strip() for s in request.form.getlist('size_names') if s.strip()]
1704
  if not size_names:
1705
  flash("Для одежды необходимо указать хотя бы один размер.", 'error')
@@ -1711,7 +1710,7 @@ def admin():
1711
  'name': name,
1712
  'description': request.form.get('description', '').strip(),
1713
  'category': request.form.get('category'),
1714
- 'product_type': 'odezhda',
1715
  'variants': variants,
1716
  'sizes': sizes
1717
  }
@@ -1809,7 +1808,7 @@ def admin():
1809
  current_data = load_data()
1810
  return render_template_string(
1811
  ADMIN_TEMPLATE,
1812
- products=sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower()),
1813
  categories=sorted(current_data.get('categories', [])),
1814
  orders=list(current_data.get('orders', {}).values()),
1815
  settings=current_data.get('settings', {}),
@@ -1827,4 +1826,3 @@ if __name__ == '__main__':
1827
  threading.Thread(target=periodic_backup, daemon=True).start()
1828
  port = int(os.environ.get('PORT', 7860))
1829
  app.run(debug=False, host='0.0.0.0', port=port)
1830
-
 
16
  load_dotenv()
17
 
18
  app = Flask(__name__)
19
+ app.secret_key = 'your_unique_secret_key_samir_kz_12345'
20
  DATA_FILE = 'data.json'
21
 
22
 
 
50
 
51
  translations = {
52
  'ru': {
53
+ 'page_title': "S.AMIR.KZ - одежда оптом и в розницу",
54
+ 'header_title': "S.AMIR.KZ",
55
  'our_addresses': "Наши адреса в г. Алматы",
56
  'search_placeholder': "Поиск по названию или описанию...",
57
  'all_filter': "Все",
 
95
  'return_to_catalog': "Вернуться в каталог",
96
  'order_not_found_error': "Ошибка",
97
  'order_not_found_message': "Заказ с таким ID не найден.",
98
+ 'whatsapp_confirm_message_1': "Здравствуйте! Хочу подтвердить свой заказ в S.AMIR.KZ:",
99
  'whatsapp_confirm_message_2': "Номер заказа:",
100
  'whatsapp_confirm_message_3': "Ссылка на заказ:",
101
  'whatsapp_confirm_message_4': "Пожалуйста, свяжитесь со мной для уточнения деталей.",
 
103
  'odezhda': "Одежда",
104
  },
105
  'kz': {
106
+ 'page_title': "S.AMIR.KZ - киімдер көтерме және бөлшек саудада",
107
+ 'header_title': "S.AMIR.KZ",
108
  'our_addresses': "Алматы қаласындағы мекенжайларымыз",
109
  'search_placeholder': "Аты немесе сипаттамасы бойынша іздеу...",
110
  'all_filter': "Барлығы",
 
148
  'return_to_catalog': "Каталогқа оралу",
149
  'order_not_found_error': "Қате",
150
  'order_not_found_message': "Мұндай ID-мен тапсырыс табылмады.",
151
+ 'whatsapp_confirm_message_1': "Сәлеметсіз бе! S.AMIR.KZ-дағы тапсырысымды растағым келеді:",
152
  'whatsapp_confirm_message_2': "Тапсырыс нөмірі:",
153
  'whatsapp_confirm_message_3': "Тапсырысқа сілтеме:",
154
  'whatsapp_confirm_message_4': "Мәліметтерді нақтылау үшін менімен хабарласуыңызды сұраймын.",
 
200
  try:
201
  if file_name == DATA_FILE:
202
  with open(file_name, 'w', encoding='utf-8') as f:
203
+ json.dump({'products': [], 'categories': [], 'orders': {}, 'settings': {'whatsapp_number': '+77073363943', 'store_addresses': ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]}}, f)
204
  logging.info(f"Created empty local file {file_name} because it was not found on HF.")
205
  except Exception as create_e:
206
  logging.error(f"Failed to create empty local file {file_name}: {create_e}")
 
264
 
265
 
266
  def load_data():
267
+ default_data = {'products': [], 'categories': [], 'orders': {}, 'settings': {'whatsapp_number': '+77073363943', 'store_addresses': ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]}}
268
  try:
269
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
270
  data = json.load(file)
 
275
  if 'products' not in data: data['products'] = []
276
  if 'categories' not in data: data['categories'] = []
277
  if 'orders' not in data: data['orders'] = {}
278
+ if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]}
279
+ if 'store_addresses' not in data['settings']: data['settings']['store_addresses'] = ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]
280
  return data
281
  except FileNotFoundError:
282
  logging.warning(f"Local file {DATA_FILE} not found. Attempting download from HF.")
 
294
  if 'products' not in data: data['products'] = []
295
  if 'categories' not in data: data['categories'] = []
296
  if 'orders' not in data: data['orders'] = {}
297
+ if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]}
298
+ if 'store_addresses' not in data['settings']: data['settings']['store_addresses'] = ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]
299
  return data
300
  except FileNotFoundError:
301
  logging.error(f"File {DATA_FILE} still not found even after download reported success. Using default.")
 
325
  if 'products' not in data: data['products'] = []
326
  if 'categories' not in data: data['categories'] = []
327
  if 'orders' not in data: data['orders'] = {}
328
+ if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]}
329
+ if 'store_addresses' not in data['settings']: data['settings']['store_addresses'] = ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]
330
 
331
 
332
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
 
348
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
349
  <link rel="preconnect" href="https://fonts.googleapis.com">
350
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
351
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
352
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
353
  <style>
354
  :root {
355
  --primary-color: #000000;
356
  --primary-dark: #333333;
357
+ --surface-color: #FFFFFF;
358
+ --background-color: #F5F5F5;
359
+ --text-color: #000000;
360
+ --text-color-muted: #757575;
361
+ --border-color: #E0E0E0;
362
  --accent-color: #000000;
363
  }
364
  * { margin: 0; padding: 0; box-sizing: border-box; }
365
  body {
366
+ font-family: 'Montserrat', sans-serif;
367
  background: var(--background-color);
368
  color: var(--text-color);
369
  line-height: 1.6;
 
370
  }
371
  .container { max-width: 100%; margin: 0 auto; padding: 0; }
372
  .content-area { padding: 20px; }
 
374
  .header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-color); position: sticky; top: 0; z-index: 1000; }
375
  .logo-title-container { display: flex; align-items: center; gap: 15px; }
376
  .logo-title-container img { height: 50px; width: 50px; object-fit: cover; border-radius: 50%; }
377
+ .header h1 { font-family: 'Montserrat', sans-serif; font-size: 1.8rem; font-weight: 700; color: var(--accent-color); text-transform: uppercase; }
378
 
379
+ .lang-switcher { display: flex; gap: 5px; background-color: var(--background-color); padding: 5px; border-radius: 50px; }
380
  .lang-switcher a { color: var(--text-color-muted); text-decoration: none; font-size: 0.9rem; padding: 5px 10px; border-radius: 50px; transition: all 0.3s; }
381
  .lang-switcher a.active { background-color: var(--primary-color); color: white; font-weight: bold; }
382
 
383
  .store-addresses { padding: 20px; text-align: center; background-color: var(--surface-color); border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); font-size: 0.95rem; color: var(--text-color-muted); margin: 20px; }
384
+ .store-addresses h3 { font-family: 'Montserrat', sans-serif; color: var(--primary-color); font-size: 1.3rem; margin-bottom: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 8px; }
385
  .store-addresses p { margin: 5px 0; }
386
 
387
  .search-container { padding: 0 20px 20px; }
388
+ #search-input { width: 100%; padding: 12px 20px; font-size: 1rem; border: 1px solid var(--border-color); border-radius: 50px; outline: none; transition: all 0.3s; background-color: var(--surface-color); color: var(--text-color); }
389
  #search-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.1); }
390
 
391
+ .filters-wrapper { margin: 0 20px 20px; }
392
  .filters-container { display: flex; overflow-x: auto; gap: 10px; padding-bottom: 10px; scrollbar-width: none; -ms-overflow-style: none; }
393
  .filters-container::-webkit-scrollbar { display: none; }
394
  .filter-label { font-size: 0.9rem; color: var(--text-color-muted); margin-left: 5px; }
395
+ .filter-btn { padding: 8px 18px; border: 1px solid var(--border-color); border-radius: 50px; background-color: transparent; cursor: pointer; transition: all 0.3s ease; font-size: 0.9rem; font-weight: 400; color: var(--text-color-muted); white-space: nowrap; }
396
+ .filter-btn.active, .filter-btn:hover { background-color: var(--primary-color); color: #fff; border-color: var(--primary-color); font-weight: 500; }
397
 
398
  .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; padding: 0 20px 120px; }
399
  @media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 20px; } }
400
 
401
+ .product { background: var(--surface-color); border-radius: 16px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; overflow: hidden; display: flex; flex-direction: column; height: 100%; position: relative; border: 1px solid var(--border-color); }
402
+ .product:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); border-color: var(--primary-color); }
403
 
404
  .product-image { width: 100%; aspect-ratio: 1 / 1; background-color: #fff; display: flex; justify-content: center; align-items: center; padding: 10px; }
405
  .product-image img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform 0.3s ease; }
406
  .product:hover .product-image img { transform: scale(1.05); }
407
  .product-info { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; text-align: center; }
408
+ .product h2 { font-family: 'Montserrat', sans-serif; font-size: 1.2rem; font-weight: 700; margin: 0 0 8px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--accent-color); }
409
  .product-price { font-size: 1.2rem; color: var(--primary-color); font-weight: 700; margin: 5px 0; }
410
  .product-price .from-text { font-size: 0.8rem; color: var(--text-color-muted); font-weight: 400; }
411
  .product-description { display: none; }
412
  .product-actions { padding: 0 15px 15px; }
413
 
414
  .product-button { display: inline-flex; align-items: center; justify-content: center; width: 100%; padding: 10px; border: none; border-radius: 50px; background-color: var(--primary-color); color: #fff; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; text-decoration: none; text-transform: uppercase; letter-spacing: 0.5px; }
415
+ .product-button:hover { background-color: var(--primary-dark); }
416
  .product-button i { margin-right: 8px; }
417
 
418
  .fab { position: fixed; background-color: var(--primary-color); color: #fff; border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); z-index: 1000; transition: transform 0.2s ease; }
 
420
  #cart-button { bottom: 20px; right: 20px; display: none; }
421
  #cart-button span { position: absolute; top: -2px; right: -2px; background-color: #dc3545; color: white; border-radius: 50%; padding: 3px 7px; font-size: 0.75rem; font-weight: bold; min-width: 22px; text-align: center; }
422
 
423
+ .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); backdrop-filter: blur(8px); overflow-y: auto; }
424
+ .modal-content { background: var(--surface-color); margin: 5% auto; padding: 25px; border-radius: 16px; width: 95%; max-width: 600px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); animation: slideIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); position: relative; border: 1px solid var(--border-color); }
425
  @keyframes slideIn { from { transform: translateY(-40px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
426
 
427
  .close { position: absolute; top: 15px; right: 20px; font-size: 2rem; color: var(--text-color-muted); cursor: pointer; transition: color 0.3s, transform 0.3s; line-height: 1; }
 
431
 
432
  .cart-item { display: grid; grid-template-columns: 65px 1fr auto 25px; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid var(--border-color); }
433
  .cart-item:last-child { border-bottom: none; }
434
+ .cart-item img { width: 65px; height: 65px; object-fit: contain; border-radius: 8px; background-color: #fff; padding: 5px; }
435
  .cart-item-details strong { display: block; margin-bottom: 4px; font-size: 1.05rem; }
436
  .cart-item-price { font-size: 0.9rem; color: var(--text-color-muted); }
437
  .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--primary-color); }
 
444
  .cart-summary strong { font-size: 1.4rem; color: var(--primary-color); }
445
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 12px; }
446
  .cart-actions .product-button { flex-grow: 1; }
447
+ .clear-cart { background: #757575; color: var(--surface-color); }
448
+ .clear-cart:hover { background: #616161; }
449
 
450
+ .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--primary-color); color: #fff; padding: 12px 25px; border-radius: 50px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); z-index: 1002; opacity: 0; transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); font-size: 0.95rem; font-weight: 500; }
451
  .notification.show { opacity: 1; bottom: 90px; }
452
  .no-results-message { grid-column: 1 / -1; text-align: center; padding: 50px; font-size: 1.2rem; color: var(--text-color-muted); }
453
 
 
458
  <div class="container">
459
  <header class="header">
460
  <div class="logo-title-container">
461
+ <img src="https://huggingface.co/spaces/esmira-tkani/admin/resolve/main/Screenshot_20260208-120337.png" alt="S.AMIR.KZ Logo">
462
  <h1>{{ _['header_title'] }}</h1>
463
  </div>
464
  <div class="lang-switcher">
 
498
  data-name="{{ product['name']|lower }}"
499
  data-description="{{ product.get('description', '')|lower }}"
500
  data-category="{{ product.get('category', _['no_category']) }}"
501
+ data-type="odezhda">
502
  <div class="product-image" onclick="openModal({{ loop.index0 }})" style="cursor: pointer;">
503
  {% if product.get('photos') and product['photos']|length > 0 %}
504
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
 
596
  const repoId = '{{ repo_id }}';
597
  const currencyCode = '{{ currency_code }}';
598
  let selectedProductIndex = null;
599
+ let cart = JSON.parse(localStorage.getItem('samir_cart') || '[]');
600
  const langCode = '{{ lang_code }}';
601
  const translations = {{ _|tojson }};
602
 
 
664
  const quantityLabel = document.getElementById('quantity-label');
665
 
666
  setupSelect('variantSelect', product.variants, v => `${v.name} - ${v.price.toFixed(2)} ${currencyCode}`);
 
667
  setupSelect('sizeSelect', product.sizes, s => s.name);
668
+
669
  quantityInput.type = "number";
670
  quantityInput.step = "1";
671
  quantityInput.min = "1";
 
680
  if (selectedProductIndex === null) return;
681
 
682
  const product = products[selectedProductIndex];
683
+ const quantity = parseInt(document.getElementById('quantityInput').value);
684
  const variantIndex = document.getElementById('variantSelect').value;
685
  const sizeIndex = document.getElementById('sizeSelect').value;
686
 
 
717
  price: selectedVariant.price,
718
  variantName: selectedVariant.name,
719
  sizeName: selectedSize ? selectedSize.name : null,
 
720
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
721
  quantity: quantity
722
  });
723
  }
724
 
725
+ localStorage.setItem('samir_cart', JSON.stringify(cart));
726
  closeModal('quantityModal');
727
  updateCartButton();
728
  showNotification(`${product.name} ${translations['add_to_cart_notification']}`);
 
780
 
781
  function removeFromCart(itemId) {
782
  cart = cart.filter(item => item.id !== itemId);
783
+ localStorage.setItem('samir_cart', JSON.stringify(cart));
784
  openCartModal();
785
  updateCartButton();
786
  }
 
788
  function clearCart() {
789
  if (confirm(translations['clear_cart_confirm'])) {
790
  cart = [];
791
+ localStorage.removeItem('samir_cart');
792
  openCartModal();
793
  updateCartButton();
794
  }
 
810
  .then(response => response.json())
811
  .then(data => {
812
  if (data.order_id) {
813
+ localStorage.removeItem('samir_cart');
814
  cart = [];
815
  updateCartButton();
816
  closeModal('cartModal');
 
863
  document.getElementById('search-input').addEventListener('input', filterProducts);
864
  document.querySelectorAll('.filter-btn').forEach(filter => {
865
  filter.addEventListener('click', function() {
866
+ document.querySelector('.category-filter.active').classList.remove('active');
 
867
  this.classList.add('active');
868
  filterProducts();
869
  });
 
936
  </div>
937
 
938
  <div style="font-size: 1rem; line-height: 1.7; padding: 0 10px;">
939
+ <p><strong>{{ _['product_type'] }}:</strong> {{ _['odezhda'] }}</p>
940
  <p><strong>{{ _['category'] }}:</strong> {{ product.get('category', _['no_category']) }}</p>
941
 
942
  {% if product.get('variants') and product.variants|length > 0 %}
 
954
  </ul>
955
  {% endif %}
956
 
957
+ {% if product.get('sizes') and product.sizes|length > 0 %}
958
  <p style="margin-top: 15px;"><strong>{{ _['available_sizes'] }}</strong></p>
959
  <ul style="list-style: none; padding-left: 0;">
960
  {% for size in product.sizes %}
 
981
  <head>
982
  <meta charset="UTF-8">
983
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
984
+ <title>{{ _['order_page_title'] }}{{ order.id }} - S.AMIR.KZ</title>
985
  <link rel="preconnect" href="https://fonts.googleapis.com">
986
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
987
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
988
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
989
  <style>
990
  :root {
991
  --primary-color: #000000;
992
  --primary-dark: #333333;
993
+ --surface-color: #FFFFFF;
994
+ --background-color: #F5F5F5;
995
+ --text-color: #000000;
996
+ --text-color-muted: #757575;
997
+ --border-color: #E0E0E0;
998
  }
999
+ body { font-family: 'Montserrat', sans-serif; background: var(--background-color); color: var(--text-color); line-height: 1.6; padding: 15px; }
1000
  .container { max-width: 800px; margin: 20px auto; padding: 30px; background: var(--surface-color); border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); border: 1px solid var(--border-color); }
1001
+ h1, h2 { font-family: 'Montserrat', sans-serif; color: var(--primary-color); text-transform: uppercase; }
1002
  h1 { text-align: center; margin-bottom: 25px; font-size: 2.2rem; font-weight: 700; }
1003
  h2 { margin-top: 30px; margin-bottom: 15px; font-size: 1.6rem; border-bottom: 1px solid var(--border-color); padding-bottom: 8px; display: flex; align-items: center; gap: 10px; }
1004
  .order-meta { font-size: 0.9rem; color: var(--text-color-muted); margin-bottom: 20px; text-align: center; }
1005
  .order-item { display: grid; grid-template-columns: 60px 1fr auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid var(--border-color); }
1006
  .order-item:last-child { border-bottom: none; }
1007
+ .order-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #fff; padding: 5px; border: 1px solid #ddd;}
1008
  .item-details strong { display: block; margin-bottom: 4px; font-size: 1.05rem; color: var(--text-color);}
1009
  .item-details span { font-size: 0.9rem; color: var(--text-color-muted); display: block;}
1010
  .item-total { font-weight: bold; text-align: right; font-size: 1.1rem; color: var(--primary-color);}
1011
  .order-summary { margin-top: 30px; padding-top: 20px; border-top: 2px solid var(--primary-color); text-align: right; }
1012
  .order-summary p { margin-bottom: 10px; font-size: 1.1rem; }
1013
  .order-summary strong { font-size: 1.5rem; color: var(--primary-color); }
1014
+ .customer-info { margin-top: 30px; background-color: #fafafa; padding: 20px; border-radius: 12px; border: 1px solid var(--border-color);}
1015
  .customer-info p { margin-bottom: 8px; font-size: 1rem; }
1016
  .customer-info strong { color: var(--text-color); }
1017
  .actions { margin-top: 30px; text-align: center; }
1018
+ .button { padding: 12px 25px; border: none; border-radius: 50px; background-color: #25D366; color: white; font-weight: 500; cursor: pointer; transition: all 0.3s ease; font-size: 1.1rem; display: inline-flex; align-items: center; gap: 10px; text-decoration: none; box-shadow: 0 4px 10px rgba(37, 211, 102, 0.2); }
1019
  .button:hover { background-color: #1DA851; transform: translateY(-2px); }
1020
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--primary-color); text-decoration: none; transition: color 0.2s; }
1021
  .catalog-link:hover { color: var(--primary-dark); }
 
1096
  <head>
1097
  <meta charset="UTF-8">
1098
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1099
+ <title>Админ-панель - S.AMIR.KZ</title>
1100
  <link rel="preconnect" href="https://fonts.googleapis.com">
1101
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1102
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
1103
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1104
  <style>
1105
  :root {
1106
  --primary-color: #000000;
1107
  --primary-dark: #333333;
1108
+ --surface-color: #FFFFFF;
1109
+ --background-color: #F5F5F5;
1110
+ --text-color: #000000;
1111
+ --text-color-muted: #757575;
1112
+ --border-color: #E0E0E0;
1113
+ --success-bg: #E8F5E9;
1114
+ --success-text: #2E7D32;
1115
+ --error-bg: #FFEBEE;
1116
+ --error-text: #C62828;
1117
+ --warning-bg: #FFF8E1;
1118
+ --warning-text: #FF8F00;
1119
  }
1120
+ body { font-family: 'Montserrat', sans-serif; background-color: var(--background-color); color: var(--text-color); padding: 15px; line-height: 1.5; }
1121
+ .container { max-width: 1200px; margin: 0 auto; background-color: var(--surface-color); padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); border: 1px solid var(--border-color); }
1122
  .header { padding-bottom: 20px; margin-bottom: 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;}
1123
+ h1, h2, h3, h4 { font-family: 'Montserrat', sans-serif; font-weight: 700; color: var(--primary-color); margin:0 0 15px 0; text-transform: uppercase;}
1124
  h1 { font-size: 2rem; }
1125
  h2 { font-size: 1.6rem; margin-top: 30px; display: flex; align-items: center; gap: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--border-color);}
1126
  h3 { font-size: 1.2rem; color: var(--text-color); margin-top: 20px;}
1127
+ .section { margin-bottom: 30px; padding: 20px; background-color: #FAFAFA; border: 1px solid var(--border-color); border-radius: 8px; }
1128
 
1129
  label { font-weight: 500; margin-top: 12px; display: block; color: var(--text-color); font-size: 0.9rem;}
1130
+ input[type="text"], input[type="number"], input[type="password"], input[type="tel"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 6px; border: 1px solid var(--border-color); border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; background-color: var(--surface-color); color: var(--text-color); }
1131
  input:focus, textarea:focus, select:focus { border-color: var(--primary-color); outline: none; box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1); }
1132
  textarea { min-height: 90px; resize: vertical; }
1133
+ input[type="file"] { padding: 8px; background-color: #eee; cursor: pointer; border: 1px solid var(--border-color); border-radius: 6px; }
1134
  input[type="checkbox"] { transform: scale(1.2); margin-right: 8px; vertical-align: middle; accent-color: var(--primary-color); }
1135
 
1136
  button, .button { padding: 9px 18px; border: none; border-radius: 50px; background-color: var(--primary-color); color: #fff; font-weight: 500; cursor: pointer; transition: all 0.2s ease; margin-top: 15px; font-size: 0.9rem; display: inline-flex; align-items: center; gap: 6px; text-decoration: none; line-height: 1.2; text-transform: uppercase; letter-spacing: 0.5px; }
1137
  button:hover, .button:hover { background-color: var(--primary-dark); transform: translateY(-1px); }
1138
+ .delete-button { background-color: #C62828; color: white; }
1139
+ .delete-button:hover { background-color: #B71C1C; }
1140
 
1141
  .item-list { display: grid; gap: 15px; }
1142
+ .item { background: var(--surface-color); padding: 15px; border-radius: 8px; border: 1px solid var(--border-color); }
1143
  .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
1144
  .photo-preview img, .photo-edit-item img { width: 60px; height: 60px; border-radius: 6px; margin: 5px 5px 0 0; border: 1px solid var(--border-color); object-fit: cover; background-color: #fff;}
1145
  .photo-preview-edit { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px; }
 
1148
 
1149
  .input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
1150
  .input-group input { flex-grow: 1; margin: 0; }
1151
+ .remove-btn { background-color: #C62828; color: white; padding: 6px 10px; font-size: 0.8rem; margin: 0; border-radius: 6px; }
1152
+ .add-btn { background-color: #2E7D32; color: #fff; margin-top: 8px;}
1153
+ .add-btn:hover { background-color: #1B5E20; }
1154
 
1155
  .message { padding: 12px 18px; border-radius: 8px; margin-bottom: 20px; font-size: 0.95rem; border: 1px solid;}
1156
  .message.success { background-color: var(--success-bg); color: var(--success-text); border-color: var(--success-text);}
1157
  .message.error { background-color: var(--error-bg); color: var(--error-text); border-color: var(--error-text);}
1158
  .message.warning { background-color: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-text); }
1159
+ .status-indicator { display: inline-block; padding: 3px 9px; border-radius: 50px; font-size: 0.75rem; font-weight: 500; margin-left: 5px; vertical-align: middle; color: white; }
1160
+ .status-indicator.new { background-color: #FF8F00; }
1161
+ .status-indicator.accepted { background-color: #1976D2; }
1162
+ .status-indicator.prepared { background-color: #388E3C; }
1163
+ .status-indicator.shipped { background-color: #5E35B1; }
1164
 
1165
+ details { background-color: #FAFAFA; border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 10px; }
1166
  details > summary { cursor: pointer; font-weight: 500; color: var(--text-color); display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; list-style: none; position: relative; }
1167
  details[open] > summary { border-bottom: 1px solid var(--border-color); }
1168
  .order-details-content { padding: 15px 20px; }
 
1177
  <div class="container">
1178
  <div class="header">
1179
  <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
1180
+ <img src="https://huggingface.co/spaces/esmira-tkani/admin/resolve/main/Screenshot_20260208-120337.png" alt="S.AMIR.KZ Logo" style="height: 50px; width: 50px; border-radius: 50%;">
1181
  <h1><i class="fas fa-tools"></i> Админ-панель</h1>
1182
  </div>
1183
  <a href="{{ url_for('catalog', lang_code='ru') }}" class="button"><i class="fas fa-store"></i> Перейти в каталог</a>
 
1220
  {% endfor %}
1221
  </select>
1222
  <button type="submit" class="button" style="margin-top:0;"><i class="fas fa-check"></i> Сохранить</button>
1223
+ <a href="{{ url_for('view_order', lang_code='ru', order_id=order.id) }}" target="_blank" class="button" style="background-color: #757575; color: white; margin-top:0;"><i class="fas fa-eye"></i> Просмотр</a>
1224
  </form>
1225
  </div>
1226
  </div>
 
1238
  <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
1239
  <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.get('whatsapp_number', '') }}" required>
1240
 
1241
+ <label for="store_addresses">Адреса магазинов (каждый адрес с новой строки):</label>
1242
  <textarea id="store_addresses" name="store_addresses" rows="4">{{ settings.get('store_addresses', [])|join('\n') }}</textarea>
1243
 
1244
  <button type="submit"><i class="fas fa-save"></i> Сохранить настройки</button>
 
1285
  <div style="padding: 15px;">
1286
  <form method="POST" enctype="multipart/form-data" id="add-product-form">
1287
  <input type="hidden" name="action" value="add_product">
1288
+ <input type="hidden" name="product_type" value="odezhda">
1289
  <label for="add_name">Название товара *:</label>
1290
  <input type="text" id="add_name" name="name" required>
1291
  <label for="add_description">Описание:</label>
 
1346
  </div>
1347
  <div style="flex-grow: 1;">
1348
  <h3 style="margin-top: 0; margin-bottom: 5px;">{{ product['name'] }}</h3>
1349
+ <p style="font-size: 0.9rem; color: var(--text-color-muted);"><strong>Тип:</strong> Одежда</p>
1350
  <p style="font-size: 0.9rem; color: var(--text-color-muted);"><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1351
  </div>
1352
  </div>
 
1365
  <form method="POST" enctype="multipart/form-data">
1366
  <input type="hidden" name="action" value="edit_product">
1367
  <input type="hidden" name="product_id" value="{{ product.id }}">
1368
+ <input type="hidden" name="product_type" value="odezhda">
1369
  <label>Название *:</label>
1370
  <input type="text" name="name" value="{{ product['name'] }}" required>
1371
  <label>Описание:</label>
1372
  <textarea name="description" rows="4">{{ product.get('description', '') }}</textarea>
1373
+
1374
  <label>Категория:</label>
1375
  <select name="category">
1376
  <option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>
 
1408
  </div>
1409
  <button type="button" class="button add-btn" onclick="addInputGroup('edit-variants-container-{{ product.id }}', 'variant')"><i class="fas fa-plus"></i> Добавить вариант</button>
1410
 
1411
+ <div style="margin-top:20px;">
1412
  <h4>Размеры *:</h4>
1413
  <div id="edit-sizes-container-{{ product.id }}">
1414
  {% for size in product.get('sizes', []) %}
 
1494
  categories = sorted(data.get('categories', []))
1495
  settings = data.get('settings', {})
1496
  whatsapp_number = settings.get('whatsapp_number', '+77073363943').replace(' ', '').replace('+', '')
1497
+ store_addresses = settings.get('store_addresses', ["Город Алматы , Алатау 1 , блок 4 , бутик 112"])
1498
 
1499
 
1500
+ products_in_stock = [p for p in all_products if p.get('variants') and p.get('product_type') == 'odezhda']
1501
 
1502
  for p in products_in_stock:
1503
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
 
1520
  def product_detail(lang_code, index):
1521
  data = load_data()
1522
  all_products = data.get('products', [])
1523
+ products_in_stock = [p for p in all_products if p.get('variants') and p.get('product_type') == 'odezhda']
1524
 
1525
  for p in products_in_stock:
1526
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
 
1564
 
1565
  try:
1566
  price = float(item['price'])
1567
+ quantity = int(item['quantity'])
1568
  if price < 0 or quantity <= 0: raise ValueError("Invalid price/qty")
1569
 
1570
  total_price += price * quantity
 
1616
  def admin():
1617
  data = load_data()
1618
  if 'orders' not in data: data['orders'] = {}
1619
+ if 'settings' not in data: data['settings'] = {'whatsapp_number': '+77073363943', 'store_addresses': ["Город Алматы , Алатау 1 , блок 4 , бутик 112"]}
1620
 
1621
 
1622
  needs_save = False
 
1624
  if 'id' not in p:
1625
  p['id'] = str(uuid.uuid4())
1626
  needs_save = True
1627
+ if 'product_type' not in p:
1628
+ p['product_type'] = 'odezhda'
1629
+ needs_save = True
1630
  if needs_save:
1631
  save_data(data)
1632
 
 
1635
  try:
1636
  if action == 'update_settings':
1637
  new_whatsapp = request.form.get('whatsapp_number', '').strip()
1638
+ addresses_str = request.form.get('store_addresses', '').strip()
1639
  if new_whatsapp:
1640
  data['settings']['whatsapp_number'] = new_whatsapp
1641
+ data['settings']['store_addresses'] = [addr.strip() for addr in addresses_str.split('\n') if addr.strip()]
1642
+ save_data(data)
1643
+ flash('Настройки успешно обновлены.', 'success')
1644
  else:
1645
  flash('Номер WhatsApp не может быть пустым.', 'error')
 
 
 
 
 
 
 
 
1646
 
1647
  elif action == 'update_order_status':
1648
  order_id = request.form.get('order_id')
 
1698
  flash("Неверный формат цены в вариантах.", 'error')
1699
  return redirect(url_for('admin'))
1700
 
1701
+ product_type = 'odezhda'
1702
  size_names = [s.strip() for s in request.form.getlist('size_names') if s.strip()]
1703
  if not size_names:
1704
  flash("Для одежды необходимо указать хотя бы один размер.", 'error')
 
1710
  'name': name,
1711
  'description': request.form.get('description', '').strip(),
1712
  'category': request.form.get('category'),
1713
+ 'product_type': product_type,
1714
  'variants': variants,
1715
  'sizes': sizes
1716
  }
 
1808
  current_data = load_data()
1809
  return render_template_string(
1810
  ADMIN_TEMPLATE,
1811
+ products=sorted([p for p in current_data.get('products', []) if p.get('product_type') == 'odezhda'], key=lambda p: p.get('name', '').lower()),
1812
  categories=sorted(current_data.get('categories', [])),
1813
  orders=list(current_data.get('orders', {}).values()),
1814
  settings=current_data.get('settings', {}),
 
1826
  threading.Thread(target=periodic_backup, daemon=True).start()
1827
  port = int(os.environ.get('PORT', 7860))
1828
  app.run(debug=False, host='0.0.0.0', port=port)