Kgshop commited on
Commit
84d66d7
·
verified ·
1 Parent(s): 6ca5846

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +273 -256
app.py CHANGED
@@ -25,7 +25,11 @@ REPO_ID = "Kgshop/shaik"
25
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
26
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
27
 
28
- STORE_ADDRESS = " Алматы , ТЦНовый Ялян , 2 этаж , сектор D , бутик 53-55 , Рынок Ялян , ряд G , 46 бутик "
 
 
 
 
29
 
30
  CURRENCY_CODE = 'KZT'
31
  CURRENCY_NAME = 'Казахстанский тенге'
@@ -221,138 +225,152 @@ CATALOG_TEMPLATE = '''
221
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
222
  <title>SHAIK парфюм оптом и в розницу - Каталог</title>
223
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
224
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
 
 
225
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
226
  <style>
227
  :root {
228
- --primary-color: #E50914;
229
- --primary-dark: #B20710;
230
- --surface-color: #141414;
231
- --background-color: #000000;
232
- --text-color: #E0E0E0;
233
- --border-color: #2a2a2a;
 
 
234
  }
235
  * { margin: 0; padding: 0; box-sizing: border-box; }
236
  body {
237
  font-family: 'Roboto', sans-serif;
238
  background: var(--background-color);
239
  color: var(--text-color);
240
- line-height: 1.5;
241
- transition: all 0.3s;
242
  }
243
-
244
  .container { max-width: 100%; margin: 0 auto; padding: 0; }
245
- .content-area { padding: 15px; }
246
 
247
- .header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background: var(--surface-color); border-bottom: 1px solid var(--border-color); box-shadow: 0 1px 3px rgba(0,0,0,0.5); }
248
- .header h1 { font-size: 1.4rem; font-weight: 500; color: var(--primary-color); }
249
- .logo-title-container img { height: 35px; width: 35px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 5px rgba(229, 9, 20, 0.5); }
 
250
 
251
- .store-address { padding: 12px 15px; text-align: center; background-color: var(--surface-color); border-radius: 8px; box-shadow: 0 1px 3px rgba(255,255,255,0.1); font-size: 0.9rem; color: #ccc; margin: 15px; }
 
 
252
 
253
- .search-container { padding: 0 15px 15px; }
254
- #search-input { width: 100%; padding: 10px 15px; font-size: 1rem; border: 1px solid var(--border-color); border-radius: 20px; outline: none; transition: all 0.3s; background-color: var(--surface-color); color: var(--text-color); }
255
- #search-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(229, 9, 20, 0.3); }
256
 
257
- .filters-container { margin: 0 15px 15px; display: flex; overflow-x: auto; gap: 8px; padding-bottom: 5px; scrollbar-width: none; }
258
  .filters-container::-webkit-scrollbar { display: none; }
259
- .category-filter { padding: 8px 14px; border: 1px solid var(--border-color); border-radius: 20px; background-color: var(--surface-color); cursor: pointer; transition: all 0.2s ease; font-size: 0.85rem; font-weight: 400; color: #ccc; white-space: nowrap; }
260
- .category-filter.active, .category-filter:hover { background-color: var(--primary-color); color: white; border-color: var(--primary-color); box-shadow: 0 2px 5px rgba(229, 9, 20, 0.3); }
261
 
262
- .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; padding: 10px 15px 120px; }
263
- @media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 15px; } }
264
 
265
- .product { background: var(--surface-color); border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.7); transition: transform 0.2s ease, box-shadow 0.2s ease; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; height: 100%; position: relative; border: 1px solid var(--border-color);}
266
- .product:active { transform: scale(0.98); }
267
 
268
  .product-image { width: 100%; aspect-ratio: 1 / 1; background-color: #000; display: flex; justify-content: center; align-items: center; padding: 10px; }
269
- .product-image img { max-width: 100%; max-height: 100%; object-fit: contain; }
270
- .product-info { padding: 10px; flex-grow: 1; display: flex; flex-direction: column; justify-content: center; }
271
- .product h2 { font-size: 0.95rem; font-weight: 500; margin: 0 0 5px 0; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-color); }
272
- .product-price { font-size: 1.1rem; color: var(--primary-color); font-weight: 700; text-align: center; margin: 5px 0; }
273
- .product-price .from-text { font-size: 0.8rem; color: #aaa; font-weight: 400; }
 
274
  .product-description { display: none; }
275
- .product-actions { padding: 0 10px 10px 10px; display: flex; flex-direction: column; gap: 6px; }
276
 
277
- .product-button { display: block; width: 100%; padding: 8px; border: none; border-radius: 8px; background-color: var(--primary-color); color: white; font-size: 0.85rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; text-align: center; text-decoration: none; }
278
- .product-button:hover { background-color: var(--primary-dark); }
279
- .product-button i { margin-right: 5px; }
280
-
281
- #cart-button { position: fixed; bottom: 15px; right: 15px; background-color: var(--primary-color); color: white; border: none; border-radius: 50%; width: 50px; height: 50px; font-size: 1.3rem; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 4px 10px rgba(229, 9, 20, 0.5); z-index: 1000; }
282
- #cart-button span { position: absolute; top: -5px; right: -5px; background-color: #007AFF; color: white; border-radius: 50%; padding: 2px 6px; font-size: 0.7rem; font-weight: bold; min-width: 20px; text-align: center; }
 
 
283
 
284
- .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; }
285
- .modal-content { background: var(--surface-color); margin: 5% auto; padding: 20px; border-radius: 12px; width: 95%; max-width: 600px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); animation: slideIn 0.3s ease-out; position: relative; border: 1px solid var(--border-color); }
286
- @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
287
 
288
- .close { position: absolute; top: 10px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
289
- .close:hover { color: var(--primary-color); }
290
 
291
- .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--primary-color); font-size: 1.5rem; display: flex; align-items: center; gap: 10px;}
292
 
293
- .cart-item { display: grid; grid-template-columns: 60px 1fr auto 20px; gap: 10px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--border-color); }
294
  .cart-item:last-child { border-bottom: none; }
295
- .cart-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 6px; background-color: #000; padding: 5px; }
296
- .cart-item-details { grid-column: 2; }
297
- .cart-item-details strong { display: block; margin-bottom: 3px; font-size: 1rem; }
298
- .cart-item-price { font-size: 0.85rem; color: #aaa; }
299
- .cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 0.95rem;}
300
- .cart-item-remove { background:none; border:none; color:#FF3B30; cursor:pointer; font-size: 1.2em; padding: 5px; line-height: 1; }
301
 
302
- .quantity-input, .variant-select { width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 8px; font-size: 1rem; margin-top: 5px; margin-bottom: 15px; box-sizing: border-box; background-color: var(--background-color); color: var(--text-color); }
303
-
304
- .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid var(--border-color); padding-top: 15px; }
305
- .cart-summary strong { font-size: 1.2rem; color: var(--primary-color); }
306
- .cart-actions { margin-top: 20px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
307
- .cart-actions .product-button { flex-grow: 1; padding: 10px; }
308
- .clear-cart { background-color: #555; }
309
- .clear-cart:hover { background-color: #333; }
310
- .formulate-order-button { background-color: var(--primary-color); }
311
- .formulate-order-button:hover { background-color: var(--primary-dark); }
312
 
313
- .notification { position: fixed; bottom: 70px; left: 50%; transform: translateX(-50%); background-color: var(--primary-color); color: white; padding: 10px 20px; border-radius: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.4); z-index: 1002; opacity: 0; transition: opacity 0.5s ease; font-size: 0.9rem;}
314
- .notification.show { opacity: 1;}
315
- .no-results-message { grid-column: 1 / -1; text-align: center; padding: 40px; font-size: 1.1rem; color: #aaa; }
316
- .top-product-indicator { position: absolute; top: 5px; right: 5px; background-color: rgba(229, 9, 20, 0.8); color: white; padding: 2px 6px; font-size: 0.7rem; border-radius: 4px; font-weight: bold; z-index: 10; }
317
 
318
- #whatsapp-fab { position: fixed; bottom: 15px; left: 15px; background-color: #25D366; color: white; border: none; border-radius: 50%; width: 50px; height: 50px; font-size: 1.8rem; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 10px rgba(37, 211, 102, 0.4); z-index: 1000; }
319
- #whatsapp-modal { display: none; position: fixed; bottom: 75px; left: 15px; background-color: var(--surface-color); border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.5); z-index: 1001; overflow: hidden; border: 1px solid var(--border-color); }
320
- #whatsapp-modal a { display: flex; align-items: center; gap: 12px; padding: 12px 18px; text-decoration: none; color: var(--text-color); transition: background-color 0.2s; }
 
321
  #whatsapp-modal a:first-child { border-bottom: 1px solid var(--border-color); }
322
- #whatsapp-modal a:hover { background-color: rgba(255,255,255,0.1); }
323
- #whatsapp-modal i { color: #25D366; font-size: 1.5rem; }
324
  </style>
325
  </head>
326
  <body>
327
  <div class="container">
328
- <div class="header">
329
- <div class="logo-title-container" style="display: flex; align-items: center; gap: 10px;">
330
- <img src="https://huggingface.co/spaces/Kgshop/mobilnymir/resolve/main/IMG_20251019_120056_121.jpg" alt="SHAIK Logo">
331
- <h1>SHAIK парфюм</h1>
332
  </div>
 
 
 
 
 
 
 
333
  </div>
334
 
335
- <div class="store-address">Наш адрес: {{ store_address }}</div>
336
 
337
  <div class="search-container">
338
- <input type="text" id="search-input" placeholder="Поиск по названию...">
339
  </div>
340
 
341
  <div class="filters-container">
342
- <button class="category-filter active" data-category="all">Все категории</button>
343
  {% for category in categories %}
344
  <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
345
  {% endfor %}
346
  </div>
347
 
348
- <div class="products-grid" id="products-grid">
349
  {% for product in products %}
350
  <div class="product"
351
  data-name="{{ product['name']|lower }}"
352
  data-description="{{ product.get('description', '')|lower }}"
353
  data-category="{{ product.get('category', 'Без категории') }}">
354
  {% if product.get('is_top', False) %}
355
- <span class="top-product-indicator"><i class="fas fa-star"></i> Топ</span>
356
  {% endif %}
357
  <div class="product-image" onclick="openModal({{ loop.index0 }})" style="cursor: pointer;">
358
  {% if product.get('photos') and product['photos']|length > 0 %}
@@ -372,11 +390,10 @@ CATALOG_TEMPLATE = '''
372
  -
373
  {% endif %}
374
  </div>
375
- <p class="product-description">{{ product.get('description', '')[:50] }}{% if product.get('description', '')|length > 50 %}...{% endif %}</p>
376
  </div>
377
  <div class="product-actions">
378
- <button class="product-button add-to-cart" onclick="openQuantityModal({{ loop.index0 }})">
379
- <i class="fas fa-cart-plus"></i> В корзину
380
  </button>
381
  </div>
382
  </div>
@@ -384,55 +401,55 @@ CATALOG_TEMPLATE = '''
384
  {% if not products %}
385
  <p class="no-results-message">Товары пока не добавлены.</p>
386
  {% endif %}
387
- </div>
388
  </div>
389
 
390
  <div id="productModal" class="modal">
391
  <div class="modal-content">
392
- <span class="close" onclick="closeModal('productModal')" aria-label="Закрыть">×</span>
393
  <div id="modalContent">Загрузка...</div>
394
  </div>
395
  </div>
396
 
397
  <div id="quantityModal" class="modal">
398
  <div class="modal-content">
399
- <span class="close" onclick="closeModal('quantityModal')" aria-label="Закрыть">×</span>
400
- <h2>Укажите детали заказа</h2>
401
  <label for="variantSelect">Вариант:</label>
402
  <select id="variantSelect" class="variant-select"></select>
403
 
404
  <label for="quantityInput">Количество:</label>
405
  <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
406
 
407
- <button class="product-button add-to-cart" onclick="confirmAddToCart()"><i class="fas fa-check"></i> Добавить в корзину</button>
408
  </div>
409
  </div>
410
 
411
  <div id="cartModal" class="modal">
412
  <div class="modal-content">
413
- <span class="close" onclick="closeModal('cartModal')" aria-label="Закрыть">×</span>
414
  <h2><i class="fas fa-shopping-cart"></i> Ваша корзина</h2>
415
- <div id="cartContent"><p style="text-align: center; padding: 20px;">Ваша корзина пуста.</p></div>
416
  <div class="cart-summary">
417
  <strong>Итого: <span id="cartTotal">0.00</span> {{ currency_code }}</strong>
418
  </div>
419
  <div class="cart-actions">
420
  <button class="product-button clear-cart" onclick="clearCart()">
421
- <i class="fas fa-trash"></i> Очистить корзину
422
  </button>
423
  <button class="product-button formulate-order-button" onclick="formulateOrder()">
424
- <i class="fas fa-file-alt"></i> Сформировать заказ
425
  </button>
426
  </div>
427
  </div>
428
  </div>
429
 
430
- <button id="cart-button" onclick="openCartModal()" aria-label="Открыть корзину">
431
  <i class="fas fa-shopping-cart"></i>
432
  <span id="cart-count">0</span>
433
  </button>
434
 
435
- <button id="whatsapp-fab" onclick="toggleWhatsAppModal()"><i class="fab fa-whatsapp"></i></button>
436
  <div id="whatsapp-modal">
437
  <a href="https://api.whatsapp.com/send?phone=77762021169" target="_blank" rel="noopener noreferrer">
438
  <i class="fab fa-whatsapp"></i>
@@ -491,10 +508,10 @@ CATALOG_TEMPLATE = '''
491
  function initializeSwiper() {
492
  new Swiper('#productModal .swiper-container', {
493
  slidesPerView: 1, spaceBetween: 20, loop: true, grabCursor: true,
494
- pagination: { el: '.swiper-pagination', clickable: true },
495
  navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
496
  zoom: { maxRatio: 3, containerClass: 'swiper-zoom-container' },
497
- autoplay: { delay: 5000, disableOnInteraction: true },
498
  });
499
  }
500
 
@@ -586,13 +603,13 @@ CATALOG_TEMPLATE = '''
586
  let total = 0;
587
 
588
  if (cart.length === 0) {
589
- cartContent.innerHTML = '<p style="text-align: center; padding: 20px;">Ваша корзина пуста.</p>';
590
  cartTotalElement.textContent = '0.00';
591
  } else {
592
  cartContent.innerHTML = cart.map(item => {
593
  const itemTotal = item.price * item.quantity;
594
  total += itemTotal;
595
- const photoUrl = item.photo ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}` : 'https://via.placeholder.com/60x60.png?text=N/A';
596
 
597
  return `
598
  <div class="cart-item">
@@ -602,8 +619,8 @@ CATALOG_TEMPLATE = '''
602
  <p class="cart-item-price">Вариант: ${item.variantName}</p>
603
  <p class="cart-item-price">${item.price.toFixed(2)} ${currencyCode} × ${item.quantity}</p>
604
  </div>
605
- <span class="cart-item-total">${itemTotal.toFixed(2)} ${currencyCode}</span>
606
- <button class="cart-item-remove" onclick="removeFromCart('${item.id}')" title="Удалить товар">×</button>
607
  </div>`;
608
  }).join('');
609
  cartTotalElement.textContent = total.toFixed(2);
@@ -748,8 +765,8 @@ CATALOG_TEMPLATE = '''
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: var(--primary-color);">{{ product['name'] }}</h2>
752
- <div class="swiper-container" style="max-width: 450px; margin: 0 auto 20px; border-radius: 10px; overflow: hidden;">
753
  <div class="swiper-wrapper">
754
  {% if product.get('photos') and product['photos']|length > 0 %}
755
  {% for photo in product['photos'] %}
@@ -768,30 +785,33 @@ PRODUCT_DETAIL_TEMPLATE = '''
768
  {% endif %}
769
  </div>
770
  {% if product.get('photos') and product['photos']|length > 1 %}
771
- <div class="swiper-pagination" style="position: relative; bottom: 5px;"></div>
772
  <div class="swiper-button-next" style="color: var(--primary-color);"></div>
773
  <div class="swiper-button-prev" style="color: var(--primary-color);"></div>
774
  {% endif %}
775
  </div>
776
 
777
- <div style="margin-top: 20px; font-size: 1rem; line-height: 1.7; padding: 0 10px;">
778
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
779
  {% if product.get('variants') and product.variants|length > 0 %}
780
- <p style="font-size: 1.2rem; font-weight: bold; color: var(--primary-color);">
781
- <strong>Цена:</strong> от {{ "%.2f"|format(product.variants|map(attribute='price')|min) }} {{ currency_code }}
782
  </p>
783
  <p><strong>Доступные варианты:</strong></p>
784
- <ul style="list-style: none; padding-left: 10px;">
785
  {% for variant in product.variants %}
786
- <li>- {{ variant.name }}: <strong>{{ "%.2f"|format(variant.price) }} {{ currency_code }}</strong></li>
 
 
 
787
  {% endfor %}
788
  </ul>
789
  {% endif %}
790
- <p><strong>Описание:</strong><br> {{ product.get('description', 'Описание отсутствует.')|replace('\\n', '<br>')|safe }}</p>
791
  </div>
792
- <div style="padding: 10px; text-align: center;">
793
- <button class="product-button add-to-cart" onclick="closeModal('productModal'); openQuantityModal({{ products_index }})" style="margin-top: 15px;">
794
- <i class="fas fa-cart-plus"></i> Добавить в корзину
795
  </button>
796
  </div>
797
  </div>
@@ -804,44 +824,50 @@ ORDER_TEMPLATE = '''
804
  <meta charset="UTF-8">
805
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
806
  <title>Заказ №{{ order.id }} - SHAIK парфюм</title>
807
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
 
 
808
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
809
  <style>
810
  :root {
811
- --primary-color: #E50914;
812
- --primary-dark: #B20710;
813
- --surface-color: #141414;
814
- --background-color: #000000;
815
- --text-color: #E0E0E0;
816
- --border-color: #2a2a2a;
 
817
  }
818
- body { font-family: 'Roboto', sans-serif; background: var(--background-color); color: var(--text-color); line-height: 1.5; padding: 15px; }
819
- .container { max-width: 800px; margin: 15px auto; padding: 25px; background: var(--surface-color); border-radius: 12px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); border: 1px solid var(--border-color); }
820
- h1 { text-align: center; color: var(--primary-color); margin-bottom: 20px; font-size: 1.6rem; font-weight: 500; }
821
- h2 { color: var(--primary-color); margin-top: 25px; margin-bottom: 12px; font-size: 1.3rem; border-bottom: 1px solid var(--border-color); padding-bottom: 5px;}
822
- .order-meta { font-size: 0.85rem; color: #888; margin-bottom: 15px; text-align: center; }
823
- .order-item { display: grid; grid-template-columns: 60px 1fr auto; gap: 15px; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--border-color); }
 
824
  .order-item:last-child { border-bottom: none; }
825
- .order-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 6px; background-color: #000; padding: 5px; border: 1px solid #333;}
826
- .item-details strong { display: block; margin-bottom: 3px; font-size: 1rem; color: var(--text-color);}
827
- .item-details span { font-size: 0.85rem; color: #aaa; display: block;}
828
- .item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--primary-color);}
829
- .order-summary { margin-top: 25px; padding-top: 15px; border-top: 2px solid var(--primary-color); text-align: right; }
830
- .order-summary p { margin-bottom: 8px; font-size: 1rem; }
831
- .order-summary strong { font-size: 1.2rem; color: var(--primary-color); }
832
- .customer-info { margin-top: 25px; background-color: #220204; padding: 15px; border-radius: 8px; border: 1px solid var(--primary-dark);}
833
- .customer-info p { margin-bottom: 6px; font-size: 0.95rem; }
834
- .customer-info strong { color: var(--primary-color); }
835
- .actions { margin-top: 25px; text-align: center; }
836
- .button { padding: 10px 20px; border: none; border-radius: 8px; background-color: var(--primary-color); color: white; font-weight: 500; cursor: pointer; transition: background-color 0.2s ease; font-size: 1rem; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
837
- .button:hover { background-color: var(--primary-dark); }
838
- .not-found { text-align: center; color: #FF3B30; font-size: 1.2rem; padding: 40px 0;}
 
 
839
  </style>
840
  </head>
841
  <body>
842
  <div class="container">
843
  {% if order %}
844
- <h1><i class="fas fa-receipt"></i> Ваш Заказ №{{ order.id }}</h1>
845
  <p class="order-meta">Дата создания: {{ order.created_at }}</p>
846
 
847
  <h2><i class="fas fa-shopping-bag"></i> Товары в заказе</h2>
@@ -863,21 +889,19 @@ ORDER_TEMPLATE = '''
863
 
864
  <div class="order-summary">
865
  <p>Общая сумма товаров: <strong>{{ "%.2f"|format(order.total_price) }} {{ currency_code }}</strong></p>
866
- <p><strong>ИТОГО К ОПЛАТЕ: {{ "%.2f"|format(order.total_price) }} {{ currency_code }}</strong></p>
867
  </div>
868
 
869
  <div class="customer-info">
870
- <h2><i class="fas fa-info-circle"></i> Статус заказа</h2>
871
- <p>Ваш текущий статус: <strong>{{ status_map_ru.get(order.status, order.status) }}</strong></p>
872
- <p>Этот заказ был оформлен без входа в систему.</p>
873
- <p>Пожалуйста, свяжитесь с нами по WhatsApp для подтверждения и уточнения деталей.</p>
874
  </div>
875
 
876
  <div class="actions">
877
- <button class="button" onclick="sendOrderViaWhatsApp()"><i class="fab fa-whatsapp"></i> Отправить заказ</button>
878
  </div>
879
 
880
- <a href="{{ url_for('catalog') }}" class="catalog-link" style="display: block; text-align: center; margin-top: 20px; color: var(--primary-color); text-decoration: none;">← Вернуться в каталог</a>
881
 
882
  <script>
883
  function sendOrderViaWhatsApp() {
@@ -888,7 +912,7 @@ ORDER_TEMPLATE = '''
888
  let message = `Здравствуйте! Хочу подтвердить свой заказ на SHAIK парфюм:%0A%0A`;
889
  message += `*Номер заказа:* ${orderId}%0A`;
890
  message += `*Ссылка на заказ:* ${encodeURIComponent(orderUrl)}%0A%0A`;
891
- message += `Пожалуйста, свяжитесь со мной для уточнения деталей оплаты и доставки.`;
892
 
893
  const whatsappUrl = `https://api.whatsapp.com/send?phone=${whatsappNumber}&text=${message}`;
894
  window.open(whatsappUrl, '_blank');
@@ -896,9 +920,9 @@ ORDER_TEMPLATE = '''
896
  </script>
897
 
898
  {% else %}
899
- <h1 style="color: #FF3B30;"><i class="fas fa-exclamation-triangle"></i> Ошибка</h1>
900
  <p class="not-found">Заказ с таким ID не найден.</p>
901
- <a href="{{ url_for('catalog') }}" class="catalog-link" style="display: block; text-align: center; margin-top: 20px; color: var(--primary-color); text-decoration: none;">← Вернуться в каталог</a>
902
  {% endif %}
903
  </div>
904
  </body>
@@ -912,16 +936,19 @@ ADMIN_TEMPLATE = '''
912
  <meta charset="UTF-8">
913
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
914
  <title>Админ-панель - SHAIK парфюм</title>
915
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
 
 
916
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
917
  <style>
918
  :root {
919
- --primary-color: #E50914;
920
- --primary-dark: #B20710;
921
- --surface-color: #141414;
922
- --background-color: #000000;
923
- --text-color: #E0E0E0;
924
- --border-color: #2a2a2a;
 
925
  --success-bg: #113d21;
926
  --success-text: #6ee791;
927
  --error-bg: #4d0a0a;
@@ -931,63 +958,54 @@ ADMIN_TEMPLATE = '''
931
  }
932
  body { font-family: 'Roboto', sans-serif; background-color: var(--background-color); color: var(--text-color); padding: 15px; line-height: 1.5; }
933
  .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.5); border: 1px solid var(--border-color); }
934
- .header { padding-bottom: 15px; margin-bottom: 20px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;}
935
- h1, h2, h3, h4 { font-weight: 500; color: var(--primary-color); margin-bottom: 15px; }
936
- h1 { font-size: 1.6rem; }
937
- h2 { font-size: 1.4rem; margin-top: 30px; display: flex; align-items: center; gap: 8px; padding-bottom: 5px; border-bottom: 1px solid var(--border-color);}
938
- h3 { font-size: 1.1rem; color: var(--text-color); margin-top: 20px; font-weight: 600; }
939
- .section { margin-bottom: 30px; padding: 20px; background-color: #050505; border: 1px solid var(--border-color); border-radius: 8px; }
940
 
941
- label { font-weight: 500; margin-top: 10px; display: block; color: var(--text-color); font-size: 0.9rem;}
942
- 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 var(--border-color); border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; background-color: var(--background-color); color: var(--text-color); }
943
- input:focus, textarea:focus, select:focus { border-color: var(--primary-color); outline: none; box-shadow: 0 0 0 2px rgba(229, 9, 20, 0.3); }
944
- textarea { min-height: 80px; resize: vertical; }
945
  input[type="file"] { padding: 8px; background-color: #222; cursor: pointer; border: 1px solid var(--border-color); border-radius: 6px; }
946
- input[type="checkbox"] { transform: scale(1.1); margin-right: 5px; vertical-align: middle; accent-color: var(--primary-color); }
947
 
948
- button, .button { padding: 8px 15px; border: none; border-radius: 6px; background-color: var(--primary-color); color: white; font-weight: 500; cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease; margin-top: 10px; font-size: 0.9rem; display: inline-flex; align-items: center; gap: 5px; text-decoration: none; line-height: 1.2;}
949
- button:hover, .button:hover { background-color: var(--primary-dark); }
950
- .delete-button { background-color: #8B0000; }
951
  .delete-button:hover { background-color: #6e0000; }
952
 
953
  .item-list { display: grid; gap: 15px; }
954
- .item { background: var(--surface-color); padding: 15px; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.5); border: 1px solid var(--border-color); }
955
- .item p { margin: 3px 0; font-size: 0.85rem; color: #aaa; }
956
- .item strong { color: var(--text-color); }
957
- .item .description { font-size: 0.8rem; color: #888; max-height: 40px; overflow: hidden; }
958
- .item-actions { margin-top: 10px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
959
- .photo-preview img { max-width: 60px; max-height: 60px; border-radius: 4px; margin: 3px 3px 0 0; border: 1px solid var(--border-color); object-fit: cover;}
960
 
961
  .variant-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
962
  .variant-input-group input { flex-grow: 1; margin: 0; }
963
- .remove-variant-btn { background-color: #8B0000; padding: 6px 10px; font-size: 0.8rem; margin-top: 0; line-height: 1; }
964
- .add-variant-btn { background-color: #440205; color: var(--text-color); margin-top: 5px;}
965
- .add-variant-btn:hover { background-color: #6e0000; }
966
 
967
- .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem; border: 1px solid;}
968
  .message.success { background-color: var(--success-bg); color: var(--success-text); border-color: var(--success-text);}
969
  .message.error { background-color: var(--error-bg); color: var(--error-text); border-color: var(--error-text);}
970
  .message.warning { background-color: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-text); }
971
- .status-indicator { display: inline-block; padding: 2px 7px; border-radius: 10px; font-size: 0.75rem; font-weight: 500; margin-left: 5px; vertical-align: middle; }
972
- .status-indicator.in-stock { background-color: #176f44; color: #c6f6d5; }
973
- .status-indicator.out-of-stock { background-color: #c53030; color: #fed7d7; }
974
  .status-indicator.top-product { background-color: #9c4221; color: #fff3c4; }
975
-
976
  .status-indicator.new { background-color: #e65100; color: #ffe0b2; }
977
  .status-indicator.accepted { background-color: #0277bd; color: #b3e5fc; }
978
  .status-indicator.prepared { background-color: #2f855a; color: #c6f6d5; }
979
  .status-indicator.shipped { background-color: #065f46; color: #a7f3d0; }
980
 
981
- details { background-color: var(--surface-color); border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 10px; }
982
  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; }
983
  details[open] > summary { border-bottom: 1px solid var(--border-color); }
984
- .order-details-content { padding: 10px 15px; }
985
- .order-item-detail { display: flex; gap: 10px; padding: 8px 0; border-top: 1px dashed #333; font-size: 0.9rem; align-items: center; }
986
- .order-item-detail:first-child { border-top: none; }
987
- .order-item-detail img { width: 30px; height: 30px; object-fit: contain; border-radius: 3px; }
988
- .order-item-name { flex-grow: 1; }
989
- .order-item-qty-price { width: 150px; text-align: right; flex-shrink: 0; }
990
- .order-total-summary { font-size: 1rem; font-weight: 600; margin-top: 15px; padding-top: 10px; border-top: 1px solid var(--border-color); text-align: right; color: var(--primary-color); }
991
  .order-status-form { display: flex; gap: 10px; align-items: center; margin-top: 15px; padding-top: 15px; border-top: 1px dashed var(--border-color); flex-wrap: wrap; }
992
  .order-status-form select { max-width: 180px; margin-top: 0; flex-grow: 1; }
993
 
@@ -998,11 +1016,11 @@ ADMIN_TEMPLATE = '''
998
  <body>
999
  <div class="container">
1000
  <div class="header">
1001
- <div class="logo-title-container" style="display: flex; align-items: center; gap: 10px;">
1002
- <img src="https://huggingface.co/spaces/Kgshop/mobilnymir/resolve/main/IMG_20251019_120056_121.jpg" alt="SHAIK Logo" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 5px rgba(229, 9, 20, 0.7);">
1003
  <h1><i class="fas fa-tools"></i> Админ-панель</h1>
1004
  </div>
1005
- <a href="{{ url_for('catalog') }}" class="button"><i class="fas fa-store"></i> Каталог</a>
1006
  </div>
1007
 
1008
 
@@ -1020,36 +1038,18 @@ ADMIN_TEMPLATE = '''
1020
  {% if orders %}
1021
  {% for order in orders | sort(attribute='created_at', reverse=true) %}
1022
  {% set current_status = order.get('status', 'new') %}
1023
- <details style="margin-bottom: 10px;">
1024
  <summary>
1025
  <span>
1026
  Заказ №{{ order.id }}
1027
  <span class="status-indicator {{ current_status }}">{{ status_map_ru.get(current_status, current_status) }}</span>
1028
  </span>
1029
- <span style="font-size: 0.85rem; color: #888; flex-shrink: 0; margin-left: 10px;">
1030
  Дата: {{ order.created_at }} | Итого: {{ "%.2f"|format(order.total_price) }} {{ currency_code }}
1031
  </span>
1032
  </summary>
1033
  <div class="order-details-content">
1034
  <h3>Состав заказа:</h3>
1035
- <div>
1036
- {% for item in order.cart %}
1037
- <div class="order-item-detail">
1038
- <img src="{{ item.photo_url }}" alt="{{ item.name }}">
1039
- <div class="order-item-name">
1040
- <strong>{{ item.name }}</strong>
1041
- <p style="margin: 0; color: #aaa; font-size: 0.8rem;">Вариант: {{ item.variantName }}</p>
1042
- </div>
1043
- <div class="order-item-qty-price">
1044
- {{ item.quantity }} × {{ "%.2f"|format(item.price) }} = <strong>{{ "%.2f"|format(item.price * item.quantity) }} {{ currency_code }}</strong>
1045
- </div>
1046
- </div>
1047
- {% endfor %}
1048
- </div>
1049
- <div class="order-total-summary">
1050
- Общая сумма: {{ "%.2f"|format(order.total_price) }} {{ currency_code }}
1051
- </div>
1052
-
1053
  <div class="order-status-form">
1054
  <form method="POST" style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
1055
  <input type="hidden" name="action" value="update_order_status">
@@ -1060,8 +1060,8 @@ ADMIN_TEMPLATE = '''
1060
  <option value="{{ status_key }}" {% if current_status == status_key %}selected{% endif %}>{{ status_value }}</option>
1061
  {% endfor %}
1062
  </select>
1063
- <button type="submit" class="button"><i class="fas fa-check"></i> Сохранить</button>
1064
- <a href="{{ url_for('view_order', order_id=order.id) }}" target="_blank" class="button" style="background-color: #555;"><i class="fas fa-eye"></i> Просмотр</a>
1065
  </form>
1066
  </div>
1067
  </div>
@@ -1076,7 +1076,7 @@ ADMIN_TEMPLATE = '''
1076
  <div class="flex-container">
1077
  <div class="flex-item">
1078
  <div class="section">
1079
- <h2><i class="fas fa-tags"></i> Управление категориями</h2>
1080
  <details>
1081
  <summary><i class="fas fa-plus-circle"></i> Добавить новую категорию</summary>
1082
  <div style="padding: 15px;">
@@ -1095,10 +1095,10 @@ ADMIN_TEMPLATE = '''
1095
  {% for category in categories %}
1096
  <div class="item" style="display: flex; justify-content: space-between; align-items: center; padding: 10px;">
1097
  <span>{{ category }}</span>
1098
- <form method="POST" style="margin: 0;" onsubmit="return confirm('Вы уверены, что хотите удалить категорию \'{{ category }}\'?');">
1099
  <input type="hidden" name="action" value="delete_category">
1100
  <input type="hidden" name="category_name" value="{{ category }}">
1101
- <button type="submit" class="delete-button" style="padding: 4px 8px; font-size: 0.75rem; margin: 0;"><i class="fas fa-trash-alt"></i></button>
1102
  </form>
1103
  </div>
1104
  {% endfor %}
@@ -1111,15 +1111,20 @@ ADMIN_TEMPLATE = '''
1111
 
1112
  <div class="flex-item">
1113
  <div class="section">
1114
- <h2><i class="fas fa-info-circle"></i> Информация о магазине</h2>
1115
- <p>Адрес: <strong>{{ store_address }}</strong></p>
1116
- <p>Валюта: <strong>{{ currency_name }} ({{ currency_code }})</strong></p>
 
 
 
 
 
1117
  </div>
1118
  </div>
1119
  </div>
1120
 
1121
  <div class="section">
1122
- <h2><i class="fas fa-box-open"></i> Управление товарами</h2>
1123
  <details>
1124
  <summary><i class="fas fa-plus-circle"></i> Добавить новый товар</summary>
1125
  <div style="padding: 15px;">
@@ -1150,15 +1155,11 @@ ADMIN_TEMPLATE = '''
1150
  <button type="button" class="button add-variant-btn" onclick="addVariantInput('add-variants-container')"><i class="fas fa-plus"></i> Добавить вариант</button>
1151
 
1152
  <div style="margin-top: 20px;">
1153
- <input type="checkbox" id="add_in_stock" name="in_stock" checked>
1154
- <label for="add_in_stock">В наличии</label>
1155
- </div>
1156
- <div>
1157
  <input type="checkbox" id="add_is_top" name="is_top">
1158
  <label for="add_is_top">Топ товар</label>
1159
  </div>
1160
  <br>
1161
- <button type="submit" style="margin-top: 20px;"><i class="fas fa-save"></i> Добавить товар</button>
1162
  </form>
1163
  </div>
1164
  </details>
@@ -1181,11 +1182,10 @@ ADMIN_TEMPLATE = '''
1181
  <div style="flex-grow: 1;">
1182
  <h3 style="margin-top: 0; margin-bottom: 5px;">
1183
  {{ product['name'] }}
1184
- {% if product.get('in_stock', True) %}<span class="status-indicator in-stock" наличии</span>{% else %}<span class="status-indicator out-of-stock">Нет</span>{% endif %}
1185
- {% if product.get('is_top', False) %}<span class="status-indicator top-product"><i class="fas fa-star"></i> Топ</span>{% endif %}
1186
  </h3>
1187
- <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1188
- <p><strong>Варианты:</strong> {{ product.get('variants', [])|length }} шт.</p>
1189
  </div>
1190
  </div>
1191
 
@@ -1214,15 +1214,23 @@ ADMIN_TEMPLATE = '''
1214
  <option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>
1215
  {% endfor %}
1216
  </select>
1217
- <label>Заменить фотографии:</label>
1218
- <input type="file" name="photos" accept="image/*" multiple>
1219
- {% if product.get('photos') %}
1220
- <div class="photo-preview">
1221
  {% for photo in product['photos'] %}
1222
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="Фото {{ loop.index }}">
 
 
 
1223
  {% endfor %}
1224
- </div>
1225
- {% endif %}
 
 
 
 
 
1226
 
1227
  <h4>Варианты и цены *:</h4>
1228
  <div id="edit-variants-container-{{ product.id }}">
@@ -1237,15 +1245,11 @@ ADMIN_TEMPLATE = '''
1237
  <button type="button" class="button add-variant-btn" onclick="addVariantInput('edit-variants-container-{{ product.id }}')"><i class="fas fa-plus"></i> Добавить вариант</button>
1238
 
1239
  <div style="margin-top: 20px;">
1240
- <input type="checkbox" id="edit_in_stock_{{ product.id }}" name="in_stock" {% if product.get('in_stock', True) %}checked{% endif %}>
1241
- <label for="edit_in_stock_{{ product.id }}">В наличии</label>
1242
- </div>
1243
- <div>
1244
  <input type="checkbox" id="edit_is_top_{{ product.id }}" name="is_top" {% if product.get('is_top', False) %}checked{% endif %}>
1245
  <label for="edit_is_top_{{ product.id }}">Топ товар</label>
1246
  </div>
1247
  <br>
1248
- <button type="submit" style="margin-top: 20px;"><i class="fas fa-save"></i> Сохранить</button>
1249
  </form>
1250
  </div>
1251
  </div>
@@ -1298,7 +1302,7 @@ def catalog():
1298
  all_products = data.get('products', [])
1299
  categories = sorted(data.get('categories', []))
1300
 
1301
- products_in_stock = [p for p in all_products if p.get('in_stock', True) and p.get('variants')]
1302
 
1303
  for p in products_in_stock:
1304
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
@@ -1310,7 +1314,7 @@ def catalog():
1310
  products=products_sorted,
1311
  categories=categories,
1312
  repo_id=REPO_ID,
1313
- store_address=STORE_ADDRESS,
1314
  currency_code=CURRENCY_CODE
1315
  )
1316
 
@@ -1318,7 +1322,7 @@ def catalog():
1318
  def product_detail(index):
1319
  data = load_data()
1320
  all_products = data.get('products', [])
1321
- products_in_stock = [p for p in all_products if p.get('in_stock', True) and p.get('variants')]
1322
 
1323
  for p in products_in_stock:
1324
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
@@ -1478,18 +1482,17 @@ def admin():
1478
  'name': name,
1479
  'description': request.form.get('description', '').strip(),
1480
  'category': request.form.get('category'),
1481
- 'in_stock': 'in_stock' in request.form,
1482
  'is_top': 'is_top' in request.form,
1483
  'variants': variants
1484
  }
1485
 
 
1486
  photos_files = request.files.getlist('photos')
1487
  if photos_files and any(f.filename for f in photos_files):
1488
  if not HF_TOKEN_WRITE:
1489
  flash("Токен HF_TOKEN не настроен, фото не загружены.", "warning")
1490
  else:
1491
  api = HfApi()
1492
- new_photos_list = []
1493
  uploads_dir = 'uploads_temp'
1494
  os.makedirs(uploads_dir, exist_ok=True)
1495
  for photo in photos_files[:10]:
@@ -1506,16 +1509,15 @@ def admin():
1506
  repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE,
1507
  commit_message=f"Photo for {name}"
1508
  )
1509
- new_photos_list.append(photo_filename)
1510
  os.remove(temp_path)
1511
  except Exception as e:
1512
  logging.error(f"Error uploading photo {photo.filename}: {e}")
1513
  flash(f"Ошибка загрузки фото {photo.filename}.", 'error')
1514
- product_data['photos'] = new_photos_list
1515
 
1516
  if action == 'add_product':
1517
  product_data['id'] = str(uuid.uuid4())
1518
- if 'photos' not in product_data: product_data['photos'] = []
1519
  data['products'].append(product_data)
1520
  flash(f"Товар '{name}' добавлен.", 'success')
1521
 
@@ -1523,8 +1525,23 @@ def admin():
1523
  product_id = request.form.get('product_id')
1524
  product_index = next((i for i, p in enumerate(data['products']) if p.get('id') == product_id), -1)
1525
  if product_index != -1:
1526
- if 'photos' not in product_data:
1527
- product_data['photos'] = data['products'][product_index].get('photos', [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1528
  product_data['id'] = product_id
1529
  data['products'][product_index] = product_data
1530
  flash(f"Товар '{name}' обновлен.", 'success')
@@ -1567,7 +1584,7 @@ def admin():
1567
  orders=list(current_data.get('orders', {}).values()),
1568
  status_map_ru=STATUS_MAP_RU,
1569
  repo_id=REPO_ID,
1570
- store_address=STORE_ADDRESS,
1571
  currency_code=CURRENCY_CODE,
1572
  currency_name=CURRENCY_NAME
1573
  )
 
25
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
26
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
27
 
28
+ STORE_ADDRESSES = [
29
+ "ТЦ Новый Ялян: 2 этаж, сектор D, бутик 53-55",
30
+ "Рынок Ялян: ряд G, бутик 46"
31
+ ]
32
+
33
 
34
  CURRENCY_CODE = 'KZT'
35
  CURRENCY_NAME = 'Казахстанский тенге'
 
225
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
226
  <title>SHAIK парфюм оптом и в розницу - Каталог</title>
227
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
228
+ <link rel="preconnect" href="https://fonts.googleapis.com">
229
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
230
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600;700&family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
231
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
232
  <style>
233
  :root {
234
+ --primary-color: #D4AF37;
235
+ --primary-dark: #B8860B;
236
+ --surface-color: #1A1A1A;
237
+ --background-color: #121212;
238
+ --text-color: #F5F5F5;
239
+ --text-color-muted: #999;
240
+ --border-color: #333333;
241
+ --success-color: #28a745;
242
  }
243
  * { margin: 0; padding: 0; box-sizing: border-box; }
244
  body {
245
  font-family: 'Roboto', sans-serif;
246
  background: var(--background-color);
247
  color: var(--text-color);
248
+ line-height: 1.6;
249
+ transition: background-color 0.3s;
250
  }
 
251
  .container { max-width: 100%; margin: 0 auto; padding: 0; }
252
+ .content-area { padding: 20px; }
253
 
254
+ .header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: rgba(18, 18, 18, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-color); position: sticky; top: 0; z-index: 1000; }
255
+ .logo-title-container { display: flex; align-items: center; gap: 15px; }
256
+ .logo-title-container img { height: 45px; width: 45px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 10px rgba(212, 175, 55, 0.5); }
257
+ .header h1 { font-family: 'Cormorant Garamond', serif; font-size: 1.8rem; font-weight: 700; color: var(--text-color); }
258
 
259
+ .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.5); font-size: 0.95rem; color: var(--text-color-muted); margin: 20px; }
260
+ .store-addresses h3 { font-family: 'Cormorant Garamond', 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; }
261
+ .store-addresses p { margin: 5px 0; }
262
 
263
+ .search-container { padding: 0 20px 20px; }
264
+ #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); }
265
+ #search-input:focus { border-color: var(--primary-color); box-shadow: 0 0 0 4px rgba(212, 175, 55, 0.2); }
266
 
267
+ .filters-container { margin: 0 20px 20px; display: flex; overflow-x: auto; gap: 10px; padding-bottom: 10px; scrollbar-width: none; -ms-overflow-style: none; }
268
  .filters-container::-webkit-scrollbar { display: none; }
269
+ .category-filter { 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; }
270
+ .category-filter.active, .category-filter:hover { background-color: var(--primary-color); color: #000; border-color: var(--primary-color); font-weight: 500; box-shadow: 0 2px 8px rgba(212, 175, 55, 0.3); transform: translateY(-2px); }
271
 
272
+ .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; padding: 0 20px 120px; }
273
+ @media (min-width: 600px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 20px; } }
274
 
275
+ .product { background: var(--surface-color); border-radius: 16px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); transition: all 0.3s ease; overflow: hidden; display: flex; flex-direction: column; height: 100%; position: relative; border: 1px solid var(--border-color); }
276
+ .product:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(212, 175, 55, 0.15); border-color: var(--primary-color); }
277
 
278
  .product-image { width: 100%; aspect-ratio: 1 / 1; background-color: #000; display: flex; justify-content: center; align-items: center; padding: 10px; }
279
+ .product-image img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform 0.3s ease; }
280
+ .product:hover .product-image img { transform: scale(1.05); }
281
+ .product-info { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; text-align: center; }
282
+ .product h2 { font-family: 'Cormorant Garamond', serif; font-size: 1.2rem; font-weight: 600; margin: 0 0 8px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-color); }
283
+ .product-price { font-size: 1.2rem; color: var(--primary-color); font-weight: 700; margin: 5px 0; }
284
+ .product-price .from-text { font-size: 0.8rem; color: var(--text-color-muted); font-weight: 400; }
285
  .product-description { display: none; }
286
+ .product-actions { padding: 0 15px 15px; }
287
 
288
+ .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: #000; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; text-decoration: none; text-transform: uppercase; letter-spacing: 0.5px; }
289
+ .product-button:hover { background-color: var(--primary-dark); box-shadow: 0 4px 10px rgba(212, 175, 55, 0.4); }
290
+ .product-button i { margin-right: 8px; }
291
+
292
+ .fab { position: fixed; background-color: var(--primary-color); color: #000; 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(212, 175, 55, 0.4); z-index: 1000; transition: transform 0.2s ease; }
293
+ .fab:hover { transform: scale(1.1); }
294
+ #cart-button { bottom: 20px; right: 20px; display: none; }
295
+ #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; }
296
 
297
+ .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.85); backdrop-filter: blur(8px); overflow-y: auto; }
298
+ .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.7); animation: slideIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); position: relative; border: 1px solid var(--border-color); }
299
+ @keyframes slideIn { from { transform: translateY(-40px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
300
 
301
+ .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; }
302
+ .close:hover { color: var(--primary-color); transform: rotate(90deg); }
303
 
304
+ .modal-content h2 { font-family: 'Cormorant Garamond', serif; margin-top: 0; margin-bottom: 25px; color: var(--primary-color); font-size: 1.8rem; display: flex; align-items: center; gap: 12px;}
305
 
306
+ .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); }
307
  .cart-item:last-child { border-bottom: none; }
308
+ .cart-item img { width: 65px; height: 65px; object-fit: contain; border-radius: 8px; background-color: #000; padding: 5px; }
309
+ .cart-item-details strong { display: block; margin-bottom: 4px; font-size: 1.05rem; }
310
+ .cart-item-price { font-size: 0.9rem; color: var(--text-color-muted); }
311
+ .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--primary-color); }
312
+ .cart-item-remove { background:none; border:none; color:#FF453A; cursor:pointer; font-size: 1.5rem; line-height: 1; transition: color 0.2s; }
313
+ .cart-item-remove:hover { color: #ff0000; }
314
 
315
+ .quantity-input, .variant-select { width: 100%; padding: 12px; border: 1px solid var(--border-color); border-radius: 8px; font-size: 1rem; margin-top: 8px; margin-bottom: 20px; box-sizing: border-box; background-color: var(--background-color); color: var(--text-color); }
316
+
317
+ .cart-summary { margin-top: 25px; text-align: right; border-top: 1px solid var(--border-color); padding-top: 20px; }
318
+ .cart-summary strong { font-size: 1.4rem; color: var(--primary-color); }
319
+ .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 12px; }
320
+ .cart-actions .product-button { flex-grow: 1; }
321
+ .clear-cart { background: #444; color: var(--text-color); }
322
+ .clear-cart:hover { background: #555; }
 
 
323
 
324
+ .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--primary-color); color: #000; padding: 12px 25px; border-radius: 50px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); 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; }
325
+ .notification.show { opacity: 1; bottom: 90px; }
326
+ .no-results-message { grid-column: 1 / -1; text-align: center; padding: 50px; font-size: 1.2rem; color: var(--text-color-muted); }
327
+ .top-product-indicator { position: absolute; top: 10px; right: 10px; background: linear-gradient(135deg, #D4AF37, #B8860B); color: #000; padding: 3px 8px; font-size: 0.7rem; border-radius: 50px; font-weight: bold; z-index: 10; display: flex; align-items: center; gap: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.5); }
328
 
329
+ #whatsapp-fab { bottom: 20px; left: 20px; background-color: #25D366; color: white; }
330
+ #whatsapp-modal { display: none; position: fixed; bottom: 85px; left: 20px; background-color: var(--surface-color); border-radius: 12px; box-shadow: 0 5px 20px rgba(0,0,0,0.6); z-index: 1001; overflow: hidden; border: 1px solid var(--border-color); animation: fadeIn 0.3s; }
331
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
332
+ #whatsapp-modal a { display: flex; align-items: center; gap: 15px; padding: 15px 20px; text-decoration: none; color: var(--text-color); transition: background-color 0.2s; }
333
  #whatsapp-modal a:first-child { border-bottom: 1px solid var(--border-color); }
334
+ #whatsapp-modal a:hover { background-color: rgba(255,255,255,0.05); }
335
+ #whatsapp-modal i { color: #25D366; font-size: 1.6rem; }
336
  </style>
337
  </head>
338
  <body>
339
  <div class="container">
340
+ <header class="header">
341
+ <div class="logo-title-container">
342
+ <img src="https://huggingface.co/spaces/Shaik-parfume/app/resolve/main/icon.png" alt="SHAIK Logo">
343
+ <h1>SHAIK Parfum</h1>
344
  </div>
345
+ </header>
346
+
347
+ <div class="store-addresses">
348
+ <h3><i class="fas fa-map-marker-alt"></i> Наши адреса в г. Алматы</h3>
349
+ {% for address in store_addresses %}
350
+ <p>{{ address }}</p>
351
+ {% endfor %}
352
  </div>
353
 
 
354
 
355
  <div class="search-container">
356
+ <input type="text" id="search-input" placeholder="Поиск по названию или описанию...">
357
  </div>
358
 
359
  <div class="filters-container">
360
+ <button class="category-filter active" data-category="all">Все</button>
361
  {% for category in categories %}
362
  <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
363
  {% endfor %}
364
  </div>
365
 
366
+ <main class="products-grid" id="products-grid">
367
  {% for product in products %}
368
  <div class="product"
369
  data-name="{{ product['name']|lower }}"
370
  data-description="{{ product.get('description', '')|lower }}"
371
  data-category="{{ product.get('category', 'Без категории') }}">
372
  {% if product.get('is_top', False) %}
373
+ <span class="top-product-indicator"><i class="fas fa-star fa-xs"></i> Топ</span>
374
  {% endif %}
375
  <div class="product-image" onclick="openModal({{ loop.index0 }})" style="cursor: pointer;">
376
  {% if product.get('photos') and product['photos']|length > 0 %}
 
390
  -
391
  {% endif %}
392
  </div>
 
393
  </div>
394
  <div class="product-actions">
395
+ <button class="product-button" onclick="openQuantityModal({{ loop.index0 }})">
396
+ <i class="fas fa-shopping-bag"></i> В корзину
397
  </button>
398
  </div>
399
  </div>
 
401
  {% if not products %}
402
  <p class="no-results-message">Товары пока не добавлены.</p>
403
  {% endif %}
404
+ </main>
405
  </div>
406
 
407
  <div id="productModal" class="modal">
408
  <div class="modal-content">
409
+ <span class="close" onclick="closeModal('productModal')" aria-label="Закрыть">&times;</span>
410
  <div id="modalContent">Загрузка...</div>
411
  </div>
412
  </div>
413
 
414
  <div id="quantityModal" class="modal">
415
  <div class="modal-content">
416
+ <span class="close" onclick="closeModal('quantityModal')" aria-label="Закрыть">&times;</span>
417
+ <h2>Укажите детали</h2>
418
  <label for="variantSelect">Вариант:</label>
419
  <select id="variantSelect" class="variant-select"></select>
420
 
421
  <label for="quantityInput">Количество:</label>
422
  <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
423
 
424
+ <button class="product-button" onclick="confirmAddToCart()"><i class="fas fa-check"></i> Добавить в корзину</button>
425
  </div>
426
  </div>
427
 
428
  <div id="cartModal" class="modal">
429
  <div class="modal-content">
430
+ <span class="close" onclick="closeModal('cartModal')" aria-label="Закрыть">&times;</span>
431
  <h2><i class="fas fa-shopping-cart"></i> Ваша корзина</h2>
432
+ <div id="cartContent"><p style="text-align: center; padding: 20px 0;">Ваша корзина пуста.</p></div>
433
  <div class="cart-summary">
434
  <strong>Итого: <span id="cartTotal">0.00</span> {{ currency_code }}</strong>
435
  </div>
436
  <div class="cart-actions">
437
  <button class="product-button clear-cart" onclick="clearCart()">
438
+ <i class="fas fa-trash"></i> Очистить
439
  </button>
440
  <button class="product-button formulate-order-button" onclick="formulateOrder()">
441
+ <i class="fas fa-file-alt"></i> Оформить заказ
442
  </button>
443
  </div>
444
  </div>
445
  </div>
446
 
447
+ <button id="cart-button" class="fab" onclick="openCartModal()" aria-label="Открыть корзину">
448
  <i class="fas fa-shopping-cart"></i>
449
  <span id="cart-count">0</span>
450
  </button>
451
 
452
+ <button id="whatsapp-fab" class="fab" onclick="toggleWhatsAppModal()"><i class="fab fa-whatsapp"></i></button>
453
  <div id="whatsapp-modal">
454
  <a href="https://api.whatsapp.com/send?phone=77762021169" target="_blank" rel="noopener noreferrer">
455
  <i class="fab fa-whatsapp"></i>
 
508
  function initializeSwiper() {
509
  new Swiper('#productModal .swiper-container', {
510
  slidesPerView: 1, spaceBetween: 20, loop: true, grabCursor: true,
511
+ pagination: { el: '.swiper-pagination', clickable: true, dynamicBullets: true },
512
  navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
513
  zoom: { maxRatio: 3, containerClass: 'swiper-zoom-container' },
514
+ autoplay: { delay: 4000, disableOnInteraction: false },
515
  });
516
  }
517
 
 
603
  let total = 0;
604
 
605
  if (cart.length === 0) {
606
+ cartContent.innerHTML = '<p style="text-align: center; padding: 20px 0;">Ваша корзина пуста.</p>';
607
  cartTotalElement.textContent = '0.00';
608
  } else {
609
  cartContent.innerHTML = cart.map(item => {
610
  const itemTotal = item.price * item.quantity;
611
  total += itemTotal;
612
+ const photoUrl = item.photo ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}` : 'https://via.placeholder.com/65x65.png?text=N/A';
613
 
614
  return `
615
  <div class="cart-item">
 
619
  <p class="cart-item-price">Вариант: ${item.variantName}</p>
620
  <p class="cart-item-price">${item.price.toFixed(2)} ${currencyCode} × ${item.quantity}</p>
621
  </div>
622
+ <span class="cart-item-total">${itemTotal.toFixed(2)}</span>
623
+ <button class="cart-item-remove" onclick="removeFromCart('${item.id}')" title="Удалить товар">&times;</button>
624
  </div>`;
625
  }).join('');
626
  cartTotalElement.textContent = total.toFixed(2);
 
765
 
766
  PRODUCT_DETAIL_TEMPLATE = '''
767
  <div style="padding: 10px;">
768
+ <h2 style="font-family: 'Cormorant Garamond', serif; font-size: 2rem; font-weight: 700; margin-bottom: 20px; text-align: center; color: var(--primary-color);">{{ product['name'] }}</h2>
769
+ <div class="swiper-container" style="max-width: 450px; margin: 0 auto 25px; border-radius: 12px; overflow: hidden; border: 1px solid var(--border-color);">
770
  <div class="swiper-wrapper">
771
  {% if product.get('photos') and product['photos']|length > 0 %}
772
  {% for photo in product['photos'] %}
 
785
  {% endif %}
786
  </div>
787
  {% if product.get('photos') and product['photos']|length > 1 %}
788
+ <div class="swiper-pagination" style="position: relative; bottom: 5px; margin-top: 10px;"></div>
789
  <div class="swiper-button-next" style="color: var(--primary-color);"></div>
790
  <div class="swiper-button-prev" style="color: var(--primary-color);"></div>
791
  {% endif %}
792
  </div>
793
 
794
+ <div style="font-size: 1rem; line-height: 1.7; padding: 0 10px;">
795
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
796
  {% if product.get('variants') and product.variants|length > 0 %}
797
+ <p style="font-size: 1.4rem; font-weight: bold; color: var(--primary-color); margin: 15px 0;">
798
+ Цена: от {{ "%.2f"|format(product.variants|map(attribute='price')|min) }} {{ currency_code }}
799
  </p>
800
  <p><strong>Доступные варианты:</strong></p>
801
+ <ul style="list-style: none; padding-left: 0;">
802
  {% for variant in product.variants %}
803
+ <li style="padding: 5px 0; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between;">
804
+ <span>- {{ variant.name }}</span>
805
+ <strong>{{ "%.2f"|format(variant.price) }} {{ currency_code }}</strong>
806
+ </li>
807
  {% endfor %}
808
  </ul>
809
  {% endif %}
810
+ <p style="margin-top: 20px;"><strong>Описание:</strong><br> {{ product.get('description', 'Описание отсутствует.')|replace('\\n', '<br>')|safe }}</p>
811
  </div>
812
+ <div style="padding: 20px 10px 10px; text-align: center;">
813
+ <button class="product-button" onclick="closeModal('productModal'); openQuantityModal({{ products_index }})" style="margin-top: 15px; padding: 12px 25px; font-size: 1rem;">
814
+ <i class="fas fa-shopping-bag"></i> Добавить в корзину
815
  </button>
816
  </div>
817
  </div>
 
824
  <meta charset="UTF-8">
825
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
826
  <title>Заказ №{{ order.id }} - SHAIK парфюм</title>
827
+ <link rel="preconnect" href="https://fonts.googleapis.com">
828
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
829
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600;700&family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
830
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
831
  <style>
832
  :root {
833
+ --primary-color: #D4AF37;
834
+ --primary-dark: #B8860B;
835
+ --surface-color: #1A1A1A;
836
+ --background-color: #121212;
837
+ --text-color: #F5F5F5;
838
+ --text-color-muted: #999;
839
+ --border-color: #333333;
840
  }
841
+ body { font-family: 'Roboto', sans-serif; background: var(--background-color); color: var(--text-color); line-height: 1.6; padding: 15px; }
842
+ .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.6); border: 1px solid var(--border-color); }
843
+ h1, h2 { font-family: 'Cormorant Garamond', serif; color: var(--primary-color); }
844
+ h1 { text-align: center; margin-bottom: 25px; font-size: 2.2rem; font-weight: 700; }
845
+ 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; }
846
+ .order-meta { font-size: 0.9rem; color: var(--text-color-muted); margin-bottom: 20px; text-align: center; }
847
+ .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); }
848
  .order-item:last-child { border-bottom: none; }
849
+ .order-item img { width: 60px; height: 60px; object-fit: contain; border-radius: 8px; background-color: #000; padding: 5px; border: 1px solid #444;}
850
+ .item-details strong { display: block; margin-bottom: 4px; font-size: 1.05rem; color: var(--text-color);}
851
+ .item-details span { font-size: 0.9rem; color: var(--text-color-muted); display: block;}
852
+ .item-total { font-weight: bold; text-align: right; font-size: 1.1rem; color: var(--primary-color);}
853
+ .order-summary { margin-top: 30px; padding-top: 20px; border-top: 2px solid var(--primary-color); text-align: right; }
854
+ .order-summary p { margin-bottom: 10px; font-size: 1.1rem; }
855
+ .order-summary strong { font-size: 1.5rem; color: var(--primary-color); }
856
+ .customer-info { margin-top: 30px; background-color: rgba(212, 175, 55, 0.05); padding: 20px; border-radius: 12px; border: 1px solid var(--primary-color);}
857
+ .customer-info p { margin-bottom: 8px; font-size: 1rem; }
858
+ .customer-info strong { color: var(--text-color); }
859
+ .actions { margin-top: 30px; text-align: center; }
860
+ .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); }
861
+ .button:hover { background-color: #1DA851; transform: translateY(-2px); }
862
+ .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--primary-color); text-decoration: none; transition: color 0.2s; }
863
+ .catalog-link:hover { color: var(--primary-dark); }
864
+ .not-found { text-align: center; color: #ff453a; font-size: 1.2rem; padding: 40px 0;}
865
  </style>
866
  </head>
867
  <body>
868
  <div class="container">
869
  {% if order %}
870
+ <h1><i class="fas fa-receipt"></i> Заказ №{{ order.id }}</h1>
871
  <p class="order-meta">Дата создания: {{ order.created_at }}</p>
872
 
873
  <h2><i class="fas fa-shopping-bag"></i> Товары в заказе</h2>
 
889
 
890
  <div class="order-summary">
891
  <p>Общая сумма товаров: <strong>{{ "%.2f"|format(order.total_price) }} {{ currency_code }}</strong></p>
 
892
  </div>
893
 
894
  <div class="customer-info">
895
+ <h2><i class="fas fa-info-circle"></i> Статус и подтверждение</h2>
896
+ <p>Текущий статус: <strong>{{ status_map_ru.get(order.status, order.status) }}</strong></p>
897
+ <p>Пожалуйста, свяжитесь с нами по WhatsApp для подтверждения заказа и уточнения деталей доставки и оплаты.</p>
 
898
  </div>
899
 
900
  <div class="actions">
901
+ <button class="button" onclick="sendOrderViaWhatsApp()"><i class="fab fa-whatsapp"></i> Связаться с нами</button>
902
  </div>
903
 
904
+ <a href="{{ url_for('catalog') }}" class="catalog-link">&larr; Вернуться в каталог</a>
905
 
906
  <script>
907
  function sendOrderViaWhatsApp() {
 
912
  let message = `Здравствуйте! Хочу подтвердить свой заказ на SHAIK парфюм:%0A%0A`;
913
  message += `*Номер заказа:* ${orderId}%0A`;
914
  message += `*Ссылка на заказ:* ${encodeURIComponent(orderUrl)}%0A%0A`;
915
+ message += `Пожалуйста, свяжитесь со мной для уточнения деталей.`;
916
 
917
  const whatsappUrl = `https://api.whatsapp.com/send?phone=${whatsappNumber}&text=${message}`;
918
  window.open(whatsappUrl, '_blank');
 
920
  </script>
921
 
922
  {% else %}
923
+ <h1 style="color: #ff453a;"><i class="fas fa-exclamation-triangle"></i> Ошибка</h1>
924
  <p class="not-found">Заказ с таким ID не найден.</p>
925
+ <a href="{{ url_for('catalog') }}" class="catalog-link">&larr; Вернуться в каталог</a>
926
  {% endif %}
927
  </div>
928
  </body>
 
936
  <meta charset="UTF-8">
937
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
938
  <title>Админ-панель - SHAIK парфюм</title>
939
+ <link rel="preconnect" href="https://fonts.googleapis.com">
940
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
941
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@600&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
942
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
943
  <style>
944
  :root {
945
+ --primary-color: #D4AF37;
946
+ --primary-dark: #B8860B;
947
+ --surface-color: #1A1A1A;
948
+ --background-color: #121212;
949
+ --text-color: #F5F5F5;
950
+ --text-color-muted: #999;
951
+ --border-color: #333333;
952
  --success-bg: #113d21;
953
  --success-text: #6ee791;
954
  --error-bg: #4d0a0a;
 
958
  }
959
  body { font-family: 'Roboto', sans-serif; background-color: var(--background-color); color: var(--text-color); padding: 15px; line-height: 1.5; }
960
  .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.5); border: 1px solid var(--border-color); }
961
+ .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;}
962
+ h1, h2, h3, h4 { font-family: 'Cormorant Garamond', serif; font-weight: 600; color: var(--primary-color); margin:0 0 15px 0;}
963
+ h1 { font-size: 2rem; }
964
+ 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);}
965
+ h3 { font-size: 1.2rem; color: var(--text-color); margin-top: 20px;}
966
+ .section { margin-bottom: 30px; padding: 20px; background-color: #151515; border: 1px solid var(--border-color); border-radius: 8px; }
967
 
968
+ label { font-weight: 500; margin-top: 12px; display: block; color: var(--text-color); font-size: 0.9rem;}
969
+ 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(--background-color); color: var(--text-color); }
970
+ input:focus, textarea:focus, select:focus { border-color: var(--primary-color); outline: none; box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.2); }
971
+ textarea { min-height: 90px; resize: vertical; }
972
  input[type="file"] { padding: 8px; background-color: #222; cursor: pointer; border: 1px solid var(--border-color); border-radius: 6px; }
973
+ input[type="checkbox"] { transform: scale(1.2); margin-right: 8px; vertical-align: middle; accent-color: var(--primary-color); }
974
 
975
+ button, .button { padding: 9px 18px; border: none; border-radius: 50px; background-color: var(--primary-color); color: #000; 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; }
976
+ button:hover, .button:hover { background-color: var(--primary-dark); transform: translateY(-1px); }
977
+ .delete-button { background-color: #8B0000; color: white; }
978
  .delete-button:hover { background-color: #6e0000; }
979
 
980
  .item-list { display: grid; gap: 15px; }
981
+ .item { background: var(--surface-color); padding: 15px; border-radius: 8px; border: 1px solid var(--border-color); }
982
+ .item-actions { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
983
+ .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;}
984
+ .photo-preview-edit { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px; }
985
+ .photo-edit-item { position: relative; }
986
+ .photo-edit-item input[type="checkbox"] { position: absolute; top: 0px; right: 0px; transform: scale(1.3); accent-color: #ff453a; cursor: pointer; }
987
 
988
  .variant-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
989
  .variant-input-group input { flex-grow: 1; margin: 0; }
990
+ .remove-variant-btn { background-color: #8B0000; color: white; padding: 6px 10px; font-size: 0.8rem; margin: 0; border-radius: 6px; }
991
+ .add-variant-btn { background-color: #1c4532; color: var(--text-color); margin-top: 8px;}
992
+ .add-variant-btn:hover { background-color: #22543d; }
993
 
994
+ .message { padding: 12px 18px; border-radius: 8px; margin-bottom: 20px; font-size: 0.95rem; border: 1px solid;}
995
  .message.success { background-color: var(--success-bg); color: var(--success-text); border-color: var(--success-text);}
996
  .message.error { background-color: var(--error-bg); color: var(--error-text); border-color: var(--error-text);}
997
  .message.warning { background-color: var(--warning-bg); color: var(--warning-text); border-color: var(--warning-text); }
998
+ .status-indicator { display: inline-block; padding: 3px 9px; border-radius: 50px; font-size: 0.75rem; font-weight: 500; margin-left: 5px; vertical-align: middle; }
 
 
999
  .status-indicator.top-product { background-color: #9c4221; color: #fff3c4; }
 
1000
  .status-indicator.new { background-color: #e65100; color: #ffe0b2; }
1001
  .status-indicator.accepted { background-color: #0277bd; color: #b3e5fc; }
1002
  .status-indicator.prepared { background-color: #2f855a; color: #c6f6d5; }
1003
  .status-indicator.shipped { background-color: #065f46; color: #a7f3d0; }
1004
 
1005
+ details { background-color: #151515; border: 1px solid var(--border-color); border-radius: 8px; margin-bottom: 10px; }
1006
  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; }
1007
  details[open] > summary { border-bottom: 1px solid var(--border-color); }
1008
+ .order-details-content { padding: 15px 20px; }
 
 
 
 
 
 
1009
  .order-status-form { display: flex; gap: 10px; align-items: center; margin-top: 15px; padding-top: 15px; border-top: 1px dashed var(--border-color); flex-wrap: wrap; }
1010
  .order-status-form select { max-width: 180px; margin-top: 0; flex-grow: 1; }
1011
 
 
1016
  <body>
1017
  <div class="container">
1018
  <div class="header">
1019
+ <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
1020
+ <img src="https://huggingface.co/spaces/Shaik-parfume/app/resolve/main/icon.png" alt="SHAIK Logo" style="height: 45px; width: 45px; border-radius: 50%;">
1021
  <h1><i class="fas fa-tools"></i> Админ-панель</h1>
1022
  </div>
1023
+ <a href="{{ url_for('catalog') }}" class="button"><i class="fas fa-store"></i> Перейти в каталог</a>
1024
  </div>
1025
 
1026
 
 
1038
  {% if orders %}
1039
  {% for order in orders | sort(attribute='created_at', reverse=true) %}
1040
  {% set current_status = order.get('status', 'new') %}
1041
+ <details>
1042
  <summary>
1043
  <span>
1044
  Заказ №{{ order.id }}
1045
  <span class="status-indicator {{ current_status }}">{{ status_map_ru.get(current_status, current_status) }}</span>
1046
  </span>
1047
+ <span style="font-size: 0.85rem; color: var(--text-color-muted); flex-shrink: 0; margin-left: 10px;">
1048
  Дата: {{ order.created_at }} | Итого: {{ "%.2f"|format(order.total_price) }} {{ currency_code }}
1049
  </span>
1050
  </summary>
1051
  <div class="order-details-content">
1052
  <h3>Состав заказа:</h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1053
  <div class="order-status-form">
1054
  <form method="POST" style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
1055
  <input type="hidden" name="action" value="update_order_status">
 
1060
  <option value="{{ status_key }}" {% if current_status == status_key %}selected{% endif %}>{{ status_value }}</option>
1061
  {% endfor %}
1062
  </select>
1063
+ <button type="submit" class="button" style="margin-top:0;"><i class="fas fa-check"></i> Сохранить</button>
1064
+ <a href="{{ url_for('view_order', order_id=order.id) }}" target="_blank" class="button" style="background-color: #444; color: white; margin-top:0;"><i class="fas fa-eye"></i> Просмотр</a>
1065
  </form>
1066
  </div>
1067
  </div>
 
1076
  <div class="flex-container">
1077
  <div class="flex-item">
1078
  <div class="section">
1079
+ <h2><i class="fas fa-tags"></i> Категории</h2>
1080
  <details>
1081
  <summary><i class="fas fa-plus-circle"></i> Добавить новую категорию</summary>
1082
  <div style="padding: 15px;">
 
1095
  {% for category in categories %}
1096
  <div class="item" style="display: flex; justify-content: space-between; align-items: center; padding: 10px;">
1097
  <span>{{ category }}</span>
1098
+ <form method="POST" style="margin: 0;" onsubmit="return confirm('Удалить категорию \'{{ category }}\'?');">
1099
  <input type="hidden" name="action" value="delete_category">
1100
  <input type="hidden" name="category_name" value="{{ category }}">
1101
+ <button type="submit" class="delete-button" style="padding: 6px 12px; font-size: 0.8rem; margin: 0;"><i class="fas fa-trash-alt"></i></button>
1102
  </form>
1103
  </div>
1104
  {% endfor %}
 
1111
 
1112
  <div class="flex-item">
1113
  <div class="section">
1114
+ <h2><i class="fas fa-info-circle"></i> Инфо о магазине</h2>
1115
+ <p><strong>Адреса в г. Алматы:</strong></p>
1116
+ <ul style="padding-left: 20px;">
1117
+ {% for address in store_addresses %}
1118
+ <li>{{ address }}</li>
1119
+ {% endfor %}
1120
+ </ul>
1121
+ <p style="margin-top: 10px;"><strong>Валюта:</strong> {{ currency_name }} ({{ currency_code }})</p>
1122
  </div>
1123
  </div>
1124
  </div>
1125
 
1126
  <div class="section">
1127
+ <h2><i class="fas fa-box-open"></i> Товары</h2>
1128
  <details>
1129
  <summary><i class="fas fa-plus-circle"></i> Добавить новый товар</summary>
1130
  <div style="padding: 15px;">
 
1155
  <button type="button" class="button add-variant-btn" onclick="addVariantInput('add-variants-container')"><i class="fas fa-plus"></i> Добавить вариант</button>
1156
 
1157
  <div style="margin-top: 20px;">
 
 
 
 
1158
  <input type="checkbox" id="add_is_top" name="is_top">
1159
  <label for="add_is_top">Топ товар</label>
1160
  </div>
1161
  <br>
1162
+ <button type="submit"><i class="fas fa-save"></i> Добавить товар</button>
1163
  </form>
1164
  </div>
1165
  </details>
 
1182
  <div style="flex-grow: 1;">
1183
  <h3 style="margin-top: 0; margin-bottom: 5px;">
1184
  {{ product['name'] }}
1185
+ {% if product.get('is_top', False) %}<span class="status-indicator top-product"><i class="fas fa-star fa-xs"></i> Топ</span>{% endif %}
 
1186
  </h3>
1187
+ <p style="font-size: 0.9rem; color: var(--text-color-muted);"><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1188
+ <p style="font-size: 0.9rem; color: var(--text-color-muted);"><strong>Варианты:</strong> {{ product.get('variants', [])|length }} шт.</p>
1189
  </div>
1190
  </div>
1191
 
 
1214
  <option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>
1215
  {% endfor %}
1216
  </select>
1217
+
1218
+ <label>Текущие фотографии (отметьте для удаления):</label>
1219
+ <div class="photo-preview-edit">
1220
+ {% if product.get('photos') %}
1221
  {% for photo in product['photos'] %}
1222
+ <div class="photo-edit-item">
1223
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}" alt="Фото {{ loop.index }}">
1224
+ <input type="checkbox" name="delete_photos" value="{{ photo }}" title="Отметить для удаления">
1225
+ </div>
1226
  {% endfor %}
1227
+ {% else %}
1228
+ <p style="font-size: 0.9rem; color: var(--text-color-muted);">Фотографий нет.</p>
1229
+ {% endif %}
1230
+ </div>
1231
+
1232
+ <label>Добавить новые фотографии:</label>
1233
+ <input type="file" name="photos" accept="image/*" multiple>
1234
 
1235
  <h4>Варианты и цены *:</h4>
1236
  <div id="edit-variants-container-{{ product.id }}">
 
1245
  <button type="button" class="button add-variant-btn" onclick="addVariantInput('edit-variants-container-{{ product.id }}')"><i class="fas fa-plus"></i> Добавить вариант</button>
1246
 
1247
  <div style="margin-top: 20px;">
 
 
 
 
1248
  <input type="checkbox" id="edit_is_top_{{ product.id }}" name="is_top" {% if product.get('is_top', False) %}checked{% endif %}>
1249
  <label for="edit_is_top_{{ product.id }}">Топ товар</label>
1250
  </div>
1251
  <br>
1252
+ <button type="submit"><i class="fas fa-save"></i> Сохранить изменения</button>
1253
  </form>
1254
  </div>
1255
  </div>
 
1302
  all_products = data.get('products', [])
1303
  categories = sorted(data.get('categories', []))
1304
 
1305
+ products_in_stock = [p for p in all_products if p.get('variants')]
1306
 
1307
  for p in products_in_stock:
1308
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
 
1314
  products=products_sorted,
1315
  categories=categories,
1316
  repo_id=REPO_ID,
1317
+ store_addresses=STORE_ADDRESSES,
1318
  currency_code=CURRENCY_CODE
1319
  )
1320
 
 
1322
  def product_detail(index):
1323
  data = load_data()
1324
  all_products = data.get('products', [])
1325
+ products_in_stock = [p for p in all_products if p.get('variants')]
1326
 
1327
  for p in products_in_stock:
1328
  p['variants'] = sorted(p.get('variants', []), key=lambda v: v.get('price', 0))
 
1482
  'name': name,
1483
  'description': request.form.get('description', '').strip(),
1484
  'category': request.form.get('category'),
 
1485
  'is_top': 'is_top' in request.form,
1486
  'variants': variants
1487
  }
1488
 
1489
+ newly_uploaded_photos = []
1490
  photos_files = request.files.getlist('photos')
1491
  if photos_files and any(f.filename for f in photos_files):
1492
  if not HF_TOKEN_WRITE:
1493
  flash("Токен HF_TOKEN не настроен, фото не загружены.", "warning")
1494
  else:
1495
  api = HfApi()
 
1496
  uploads_dir = 'uploads_temp'
1497
  os.makedirs(uploads_dir, exist_ok=True)
1498
  for photo in photos_files[:10]:
 
1509
  repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE,
1510
  commit_message=f"Photo for {name}"
1511
  )
1512
+ newly_uploaded_photos.append(photo_filename)
1513
  os.remove(temp_path)
1514
  except Exception as e:
1515
  logging.error(f"Error uploading photo {photo.filename}: {e}")
1516
  flash(f"Ошибка загрузки фото {photo.filename}.", 'error')
 
1517
 
1518
  if action == 'add_product':
1519
  product_data['id'] = str(uuid.uuid4())
1520
+ product_data['photos'] = newly_uploaded_photos
1521
  data['products'].append(product_data)
1522
  flash(f"Товар '{name}' добавлен.", 'success')
1523
 
 
1525
  product_id = request.form.get('product_id')
1526
  product_index = next((i for i, p in enumerate(data['products']) if p.get('id') == product_id), -1)
1527
  if product_index != -1:
1528
+ product_to_edit = data['products'][product_index]
1529
+ current_photos = product_to_edit.get('photos', [])
1530
+ photos_to_delete = request.form.getlist('delete_photos')
1531
+
1532
+ if photos_to_delete and HF_TOKEN_WRITE:
1533
+ try:
1534
+ HfApi().delete_files(
1535
+ repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in photos_to_delete],
1536
+ repo_type="dataset", token=HF_TOKEN_WRITE
1537
+ )
1538
+ flash(f"Удалено {len(photos_to_delete)} фото.", 'success')
1539
+ except Exception as e:
1540
+ logging.error(f"Error deleting photos from HF: {e}")
1541
+ flash("Не удалось удалить фото с сервера.", "warning")
1542
+
1543
+ updated_photos = [p for p in current_photos if p not in photos_to_delete]
1544
+ product_data['photos'] = updated_photos + newly_uploaded_photos
1545
  product_data['id'] = product_id
1546
  data['products'][product_index] = product_data
1547
  flash(f"Товар '{name}' обновлен.", 'success')
 
1584
  orders=list(current_data.get('orders', {}).values()),
1585
  status_map_ru=STATUS_MAP_RU,
1586
  repo_id=REPO_ID,
1587
+ store_addresses=STORE_ADDRESSES,
1588
  currency_code=CURRENCY_CODE,
1589
  currency_name=CURRENCY_NAME
1590
  )