Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -146,6 +146,15 @@ def load_data():
|
|
| 146 |
except Exception:
|
| 147 |
data = default_data
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
for product in data['products']:
|
| 150 |
if 'product_id' not in product:
|
| 151 |
product['product_id'] = uuid4().hex
|
|
@@ -283,11 +292,10 @@ CATALOG_TEMPLATE = '''
|
|
| 283 |
.gallery-dot.active { background: var(--primary); }
|
| 284 |
|
| 285 |
.floating-socials { position: fixed; bottom: max(100px, calc(100px + env(safe-area-inset-bottom))); right: 15px; display: flex; flex-direction: column; gap: 12px; z-index: 90; }
|
| 286 |
-
.social-btn { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #000; font-size: 1.6rem; text-decoration: none; box-shadow: 0 4px 12px rgba(0,0,0,0.5); transition: transform 0.2s; }
|
| 287 |
.social-btn:active { transform: scale(0.9); }
|
| 288 |
-
.btn-float-wa { background: var(--primary); }
|
| 289 |
.btn-float-ig { background: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285AEB 90%); color: #fff; }
|
| 290 |
-
.btn-float-
|
| 291 |
|
| 292 |
@media (min-width: 768px) {
|
| 293 |
.categories-container { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
|
@@ -329,9 +337,8 @@ CATALOG_TEMPLATE = '''
|
|
| 329 |
<div class="products-container" id="productsContainer"></div>
|
| 330 |
|
| 331 |
<div class="floating-socials">
|
| 332 |
-
<a href="https://
|
| 333 |
-
<
|
| 334 |
-
<a href="#" class="social-btn btn-float-tg" target="_blank"><i class="fab fa-telegram-plane"></i></a>
|
| 335 |
</div>
|
| 336 |
|
| 337 |
<div class="cart-bar" id="cartBar">
|
|
@@ -364,6 +371,19 @@ CATALOG_TEMPLATE = '''
|
|
| 364 |
</div>
|
| 365 |
</div>
|
| 366 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
<div class="gallery-modal" id="galleryModal">
|
| 368 |
<button class="gallery-close" onclick="closeGallery()"><i class="fas fa-times"></i></button>
|
| 369 |
<div class="gallery-img-container" id="gallerySwipeArea">
|
|
@@ -424,17 +444,17 @@ CATALOG_TEMPLATE = '''
|
|
| 424 |
container.innerHTML = '';
|
| 425 |
|
| 426 |
categoriesList.forEach(cat => {
|
| 427 |
-
const catProducts = products.filter(p => p.category === cat);
|
| 428 |
const count = catProducts.length;
|
| 429 |
|
| 430 |
const div = document.createElement('div');
|
| 431 |
div.className = 'category-item';
|
| 432 |
-
div.onclick = () => showProducts(cat);
|
| 433 |
div.innerHTML = `
|
| 434 |
<div style="background: #1a1a1a; width: 50px; height: 50px; border-radius: 12px; border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; margin-bottom: 5px;">
|
| 435 |
-
<i class="
|
| 436 |
</div>
|
| 437 |
-
<span class="name">${cat}</span>
|
| 438 |
<span class="count">${count} шт</span>
|
| 439 |
`;
|
| 440 |
container.appendChild(div);
|
|
@@ -618,6 +638,18 @@ CATALOG_TEMPLATE = '''
|
|
| 618 |
setTimeout(() => modal.style.display = 'none', 300);
|
| 619 |
}
|
| 620 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
function submitOrder() {
|
| 622 |
const cartArray = Object.values(cart);
|
| 623 |
if(cartArray.length === 0) return;
|
|
@@ -1227,6 +1259,7 @@ ADMIN_TEMPLATE = '''
|
|
| 1227 |
<form method="POST" class="add-cat-form">
|
| 1228 |
<input type="hidden" name="action" value="add_category">
|
| 1229 |
<input type="text" name="category_name" placeholder="Название новой категории" required autocomplete="off">
|
|
|
|
| 1230 |
<button type="submit" class="btn btn-dark"><i class="fas fa-plus"></i> Добавить</button>
|
| 1231 |
</form>
|
| 1232 |
</div>
|
|
@@ -1241,16 +1274,26 @@ ADMIN_TEMPLATE = '''
|
|
| 1241 |
<div class="category-header" onclick="toggleCategory('cat-{{ loop.index }}')">
|
| 1242 |
<div style="display: flex; align-items: center; gap: 10px;">
|
| 1243 |
<i class="fas fa-chevron-down" id="icon-cat-{{ loop.index }}" style="color: var(--text-muted);"></i>
|
| 1244 |
-
<span class="cat-title-text"><i class="
|
| 1245 |
</div>
|
| 1246 |
<form method="POST" style="margin:0;" onclick="event.stopPropagation();" onsubmit="return confirm('Удалить категорию и все ее блюда?');">
|
| 1247 |
<input type="hidden" name="action" value="delete_category">
|
| 1248 |
-
<input type="hidden" name="category_name" value="{{ category }}">
|
| 1249 |
<button type="submit" class="btn btn-danger"><i class="fas fa-trash-alt"></i></button>
|
| 1250 |
</form>
|
| 1251 |
</div>
|
| 1252 |
<div class="category-content" id="cat-{{ loop.index }}">
|
| 1253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1254 |
<div class="toggle-add-product" onclick="toggleAddProduct('add-prod-{{ loop.index }}')">
|
| 1255 |
<i class="fas fa-plus"></i> Добавить блюдо
|
| 1256 |
</div>
|
|
@@ -1258,8 +1301,8 @@ ADMIN_TEMPLATE = '''
|
|
| 1258 |
<div class="add-product-wrapper" id="add-prod-{{ loop.index }}">
|
| 1259 |
<form class="add-product-form" method="POST" enctype="multipart/form-data" onsubmit="showLoading(this)">
|
| 1260 |
<input type="hidden" name="action" value="add_product">
|
| 1261 |
-
<input type="hidden" name="category" value="{{ category }}">
|
| 1262 |
-
<div style="font-weight: 600; font-size: 0.9rem; color: var(--primary);">Новое блюдо в категории "{{ category }}"</div>
|
| 1263 |
<div class="form-row">
|
| 1264 |
<input type="text" name="name" placeholder="Название блюда" required autocomplete="off" style="flex:2;">
|
| 1265 |
<input type="number" name="price" placeholder="Цена" required step="0.01" style="flex:1;">
|
|
@@ -1274,7 +1317,7 @@ ADMIN_TEMPLATE = '''
|
|
| 1274 |
</div>
|
| 1275 |
|
| 1276 |
{% for product in products %}
|
| 1277 |
-
{% if product.category == category %}
|
| 1278 |
<div class="product-item">
|
| 1279 |
<div class="product-info">
|
| 1280 |
{% if product.photos and product.photos|length > 0 %}
|
|
@@ -1302,7 +1345,7 @@ ADMIN_TEMPLATE = '''
|
|
| 1302 |
<form class="add-product-form" method="POST" enctype="multipart/form-data" onsubmit="showLoading(this)" style="padding: 0;">
|
| 1303 |
<input type="hidden" name="action" value="edit_product">
|
| 1304 |
<input type="hidden" name="product_id" value="{{ product.product_id }}">
|
| 1305 |
-
<input type="hidden" name="category" value="{{ category }}">
|
| 1306 |
<div style="font-weight: 600; font-size: 0.9rem; color: var(--primary);">Редактирование блюда</div>
|
| 1307 |
<div class="form-row">
|
| 1308 |
<input type="text" name="name" value="{{ product.name }}" required autocomplete="off" style="flex:2;">
|
|
@@ -1587,19 +1630,35 @@ def admin():
|
|
| 1587 |
|
| 1588 |
elif action == 'add_category':
|
| 1589 |
cat_name = request.form.get('category_name', '').strip()
|
| 1590 |
-
|
| 1591 |
-
|
|
|
|
| 1592 |
data['categories'] = categories
|
| 1593 |
save_data(data)
|
| 1594 |
|
| 1595 |
-
elif action == '
|
| 1596 |
-
|
| 1597 |
-
|
| 1598 |
-
|
| 1599 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1600 |
data['categories'] = categories
|
|
|
|
| 1601 |
save_data(data)
|
| 1602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
elif action == 'add_product':
|
| 1604 |
name = request.form.get('name', '').strip()
|
| 1605 |
price = float(request.form.get('price', 0))
|
|
|
|
| 146 |
except Exception:
|
| 147 |
data = default_data
|
| 148 |
|
| 149 |
+
migrated_cats = []
|
| 150 |
+
for c in data.get('categories', []):
|
| 151 |
+
if isinstance(c, str):
|
| 152 |
+
migrated_cats.append({'name': c, 'icon': 'fas fa-utensils'})
|
| 153 |
+
else:
|
| 154 |
+
if 'icon' not in c: c['icon'] = 'fas fa-utensils'
|
| 155 |
+
migrated_cats.append(c)
|
| 156 |
+
data['categories'] = migrated_cats
|
| 157 |
+
|
| 158 |
for product in data['products']:
|
| 159 |
if 'product_id' not in product:
|
| 160 |
product['product_id'] = uuid4().hex
|
|
|
|
| 292 |
.gallery-dot.active { background: var(--primary); }
|
| 293 |
|
| 294 |
.floating-socials { position: fixed; bottom: max(100px, calc(100px + env(safe-area-inset-bottom))); right: 15px; display: flex; flex-direction: column; gap: 12px; z-index: 90; }
|
| 295 |
+
.social-btn { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #000; font-size: 1.6rem; text-decoration: none; box-shadow: 0 4px 12px rgba(0,0,0,0.5); transition: transform 0.2s; border: none; cursor: pointer; }
|
| 296 |
.social-btn:active { transform: scale(0.9); }
|
|
|
|
| 297 |
.btn-float-ig { background: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285AEB 90%); color: #fff; }
|
| 298 |
+
.btn-float-map { background: #ff4757; color: #fff; }
|
| 299 |
|
| 300 |
@media (min-width: 768px) {
|
| 301 |
.categories-container { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
|
|
|
| 337 |
<div class="products-container" id="productsContainer"></div>
|
| 338 |
|
| 339 |
<div class="floating-socials">
|
| 340 |
+
<a href="https://instagram.com/hongkong_milyanfan" class="social-btn btn-float-ig" target="_blank"><i class="fab fa-instagram"></i></a>
|
| 341 |
+
<button class="social-btn btn-float-map" onclick="openAddressModal()"><i class="fas fa-map-marker-alt"></i></button>
|
|
|
|
| 342 |
</div>
|
| 343 |
|
| 344 |
<div class="cart-bar" id="cartBar">
|
|
|
|
| 371 |
</div>
|
| 372 |
</div>
|
| 373 |
|
| 374 |
+
<div class="modal-overlay" id="addressModal" onclick="if(event.target === this) closeAddressModal()">
|
| 375 |
+
<div class="modal-content" style="max-height: 30vh; align-items: center; justify-content: center; text-align: center;">
|
| 376 |
+
<div class="modal-header" style="width: 100%; margin-bottom: 10px;">
|
| 377 |
+
<h2 style="margin: 0 auto;">Наш адрес</h2>
|
| 378 |
+
<button class="modal-close" style="position: absolute; right: 20px;" onclick="closeAddressModal()"><i class="fas fa-times"></i></button>
|
| 379 |
+
</div>
|
| 380 |
+
<div style="font-size: 1.2rem; font-weight: 600; padding: 20px; color: var(--text);">
|
| 381 |
+
<i class="fas fa-map-pin" style="color: var(--primary); margin-right: 10px; font-size: 1.5rem;"></i>
|
| 382 |
+
село Милянфан<br>ул. Фрунзе 99
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
|
| 387 |
<div class="gallery-modal" id="galleryModal">
|
| 388 |
<button class="gallery-close" onclick="closeGallery()"><i class="fas fa-times"></i></button>
|
| 389 |
<div class="gallery-img-container" id="gallerySwipeArea">
|
|
|
|
| 444 |
container.innerHTML = '';
|
| 445 |
|
| 446 |
categoriesList.forEach(cat => {
|
| 447 |
+
const catProducts = products.filter(p => p.category === cat.name);
|
| 448 |
const count = catProducts.length;
|
| 449 |
|
| 450 |
const div = document.createElement('div');
|
| 451 |
div.className = 'category-item';
|
| 452 |
+
div.onclick = () => showProducts(cat.name);
|
| 453 |
div.innerHTML = `
|
| 454 |
<div style="background: #1a1a1a; width: 50px; height: 50px; border-radius: 12px; border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; margin-bottom: 5px;">
|
| 455 |
+
<i class="${cat.icon}" style="font-size: 1.5rem; color: var(--primary);"></i>
|
| 456 |
</div>
|
| 457 |
+
<span class="name">${cat.name}</span>
|
| 458 |
<span class="count">${count} шт</span>
|
| 459 |
`;
|
| 460 |
container.appendChild(div);
|
|
|
|
| 638 |
setTimeout(() => modal.style.display = 'none', 300);
|
| 639 |
}
|
| 640 |
|
| 641 |
+
function openAddressModal() {
|
| 642 |
+
const modal = document.getElementById('addressModal');
|
| 643 |
+
modal.style.display = 'flex';
|
| 644 |
+
setTimeout(() => modal.classList.add('active'), 10);
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
function closeAddressModal() {
|
| 648 |
+
const modal = document.getElementById('addressModal');
|
| 649 |
+
modal.classList.remove('active');
|
| 650 |
+
setTimeout(() => modal.style.display = 'none', 300);
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
function submitOrder() {
|
| 654 |
const cartArray = Object.values(cart);
|
| 655 |
if(cartArray.length === 0) return;
|
|
|
|
| 1259 |
<form method="POST" class="add-cat-form">
|
| 1260 |
<input type="hidden" name="action" value="add_category">
|
| 1261 |
<input type="text" name="category_name" placeholder="Название новой категории" required autocomplete="off">
|
| 1262 |
+
<input type="text" name="category_icon" placeholder="Класс иконки (напр. fas fa-pizza-slice)" value="fas fa-utensils" required autocomplete="off">
|
| 1263 |
<button type="submit" class="btn btn-dark"><i class="fas fa-plus"></i> Добавить</button>
|
| 1264 |
</form>
|
| 1265 |
</div>
|
|
|
|
| 1274 |
<div class="category-header" onclick="toggleCategory('cat-{{ loop.index }}')">
|
| 1275 |
<div style="display: flex; align-items: center; gap: 10px;">
|
| 1276 |
<i class="fas fa-chevron-down" id="icon-cat-{{ loop.index }}" style="color: var(--text-muted);"></i>
|
| 1277 |
+
<span class="cat-title-text"><i class="{{ category.icon }}" style="color:var(--info); margin-right:5px;"></i> {{ category.name }}</span>
|
| 1278 |
</div>
|
| 1279 |
<form method="POST" style="margin:0;" onclick="event.stopPropagation();" onsubmit="return confirm('Удалить категорию и все ее блюда?');">
|
| 1280 |
<input type="hidden" name="action" value="delete_category">
|
| 1281 |
+
<input type="hidden" name="category_name" value="{{ category.name }}">
|
| 1282 |
<button type="submit" class="btn btn-danger"><i class="fas fa-trash-alt"></i></button>
|
| 1283 |
</form>
|
| 1284 |
</div>
|
| 1285 |
<div class="category-content" id="cat-{{ loop.index }}">
|
| 1286 |
|
| 1287 |
+
<div style="padding: 15px; border-bottom: 1px solid var(--border);">
|
| 1288 |
+
<form method="POST" style="display: flex; gap: 10px; flex-wrap: wrap;">
|
| 1289 |
+
<input type="hidden" name="action" value="edit_category">
|
| 1290 |
+
<input type="hidden" name="old_name" value="{{ category.name }}">
|
| 1291 |
+
<input type="text" name="new_name" value="{{ category.name }}" required style="flex: 1; min-width: 150px;">
|
| 1292 |
+
<input type="text" name="new_icon" value="{{ category.icon }}" required style="flex: 1; min-width: 150px;">
|
| 1293 |
+
<button type="submit" class="btn btn-primary" style="white-space: nowrap;"><i class="fas fa-save"></i> Обновить категорию</button>
|
| 1294 |
+
</form>
|
| 1295 |
+
</div>
|
| 1296 |
+
|
| 1297 |
<div class="toggle-add-product" onclick="toggleAddProduct('add-prod-{{ loop.index }}')">
|
| 1298 |
<i class="fas fa-plus"></i> Добавить блюдо
|
| 1299 |
</div>
|
|
|
|
| 1301 |
<div class="add-product-wrapper" id="add-prod-{{ loop.index }}">
|
| 1302 |
<form class="add-product-form" method="POST" enctype="multipart/form-data" onsubmit="showLoading(this)">
|
| 1303 |
<input type="hidden" name="action" value="add_product">
|
| 1304 |
+
<input type="hidden" name="category" value="{{ category.name }}">
|
| 1305 |
+
<div style="font-weight: 600; font-size: 0.9rem; color: var(--primary);">Новое блюдо в категории "{{ category.name }}"</div>
|
| 1306 |
<div class="form-row">
|
| 1307 |
<input type="text" name="name" placeholder="Название блюда" required autocomplete="off" style="flex:2;">
|
| 1308 |
<input type="number" name="price" placeholder="Цена" required step="0.01" style="flex:1;">
|
|
|
|
| 1317 |
</div>
|
| 1318 |
|
| 1319 |
{% for product in products %}
|
| 1320 |
+
{% if product.category == category.name %}
|
| 1321 |
<div class="product-item">
|
| 1322 |
<div class="product-info">
|
| 1323 |
{% if product.photos and product.photos|length > 0 %}
|
|
|
|
| 1345 |
<form class="add-product-form" method="POST" enctype="multipart/form-data" onsubmit="showLoading(this)" style="padding: 0;">
|
| 1346 |
<input type="hidden" name="action" value="edit_product">
|
| 1347 |
<input type="hidden" name="product_id" value="{{ product.product_id }}">
|
| 1348 |
+
<input type="hidden" name="category" value="{{ category.name }}">
|
| 1349 |
<div style="font-weight: 600; font-size: 0.9rem; color: var(--primary);">Редактирование блюда</div>
|
| 1350 |
<div class="form-row">
|
| 1351 |
<input type="text" name="name" value="{{ product.name }}" required autocomplete="off" style="flex:2;">
|
|
|
|
| 1630 |
|
| 1631 |
elif action == 'add_category':
|
| 1632 |
cat_name = request.form.get('category_name', '').strip()
|
| 1633 |
+
cat_icon = request.form.get('category_icon', 'fas fa-utensils').strip()
|
| 1634 |
+
if cat_name and not any(c.get('name') == cat_name for c in categories):
|
| 1635 |
+
categories.append({'name': cat_name, 'icon': cat_icon})
|
| 1636 |
data['categories'] = categories
|
| 1637 |
save_data(data)
|
| 1638 |
|
| 1639 |
+
elif action == 'edit_category':
|
| 1640 |
+
old_name = request.form.get('old_name')
|
| 1641 |
+
new_name = request.form.get('new_name', '').strip()
|
| 1642 |
+
new_icon = request.form.get('new_icon', 'fas fa-utensils').strip()
|
| 1643 |
+
if new_name:
|
| 1644 |
+
for c in categories:
|
| 1645 |
+
if c.get('name') == old_name:
|
| 1646 |
+
c['name'] = new_name
|
| 1647 |
+
c['icon'] = new_icon
|
| 1648 |
+
break
|
| 1649 |
+
for p in products:
|
| 1650 |
+
if p.get('category') == old_name:
|
| 1651 |
+
p['category'] = new_name
|
| 1652 |
data['categories'] = categories
|
| 1653 |
+
data['products'] = products
|
| 1654 |
save_data(data)
|
| 1655 |
|
| 1656 |
+
elif action == 'delete_category':
|
| 1657 |
+
cat_name = request.form.get('category_name')
|
| 1658 |
+
data['categories'] = [c for c in categories if c.get('name') != cat_name]
|
| 1659 |
+
data['products'] = [p for p in products if p.get('category') != cat_name]
|
| 1660 |
+
save_data(data)
|
| 1661 |
+
|
| 1662 |
elif action == 'add_product':
|
| 1663 |
name = request.form.get('name', '').strip()
|
| 1664 |
price = float(request.form.get('price', 0))
|