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