Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -261,7 +261,7 @@ CATALOG_TEMPLATE = '''
|
|
| 261 |
.cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 1rem; color: #ffcc00;}
|
| 262 |
.cart-item-remove { grid-column: 4; background:none; border:none; color:#f56565; cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
|
| 263 |
.cart-item-remove:hover { color: #c53030; }
|
| 264 |
-
.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;}
|
| 265 |
.cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #444; padding-top: 15px; }
|
| 266 |
.cart-summary strong { font-size: 1.2rem; color: #ffcc00;}
|
| 267 |
.cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
|
|
@@ -348,11 +348,13 @@ CATALOG_TEMPLATE = '''
|
|
| 348 |
<div id="quantityModal" class="modal">
|
| 349 |
<div class="modal-content">
|
| 350 |
<span class="close" onclick="closeModal('quantityModal')" aria-label="Закрыть">×</span>
|
| 351 |
-
<h2>Укажите
|
| 352 |
<label for="quantityInput">Количество:</label>
|
| 353 |
<input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
|
| 354 |
<label for="colorSelect">Цвет/Вариант:</label>
|
| 355 |
<select id="colorSelect" class="color-select"></select>
|
|
|
|
|
|
|
| 356 |
<button class="product-button add-to-cart" onclick="confirmAddToCart()"><i class="fas fa-check"></i> Добавить в корзину</button>
|
| 357 |
</div>
|
| 358 |
</div>
|
|
@@ -477,9 +479,7 @@ CATALOG_TEMPLATE = '''
|
|
| 477 |
const colorSelect = document.getElementById('colorSelect');
|
| 478 |
const colorLabel = document.querySelector('label[for="colorSelect"]');
|
| 479 |
colorSelect.innerHTML = '';
|
| 480 |
-
|
| 481 |
const validColors = product.colors ? product.colors.filter(c => c && c.trim() !== "") : [];
|
| 482 |
-
|
| 483 |
if (validColors.length > 0) {
|
| 484 |
validColors.forEach(color => {
|
| 485 |
const option = document.createElement('option');
|
|
@@ -494,6 +494,25 @@ CATALOG_TEMPLATE = '''
|
|
| 494 |
if(colorLabel) colorLabel.style.display = 'none';
|
| 495 |
}
|
| 496 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
document.getElementById('quantityInput').value = 1;
|
| 498 |
const modal = document.getElementById('quantityModal');
|
| 499 |
if(modal) {
|
|
@@ -507,9 +526,13 @@ CATALOG_TEMPLATE = '''
|
|
| 507 |
|
| 508 |
const quantityInput = document.getElementById('quantityInput');
|
| 509 |
const quantity = parseInt(quantityInput.value);
|
|
|
|
| 510 |
const colorSelect = document.getElementById('colorSelect');
|
| 511 |
const color = colorSelect.style.display !== 'none' && colorSelect.value ? colorSelect.value : 'N/A';
|
| 512 |
|
|
|
|
|
|
|
|
|
|
| 513 |
if (isNaN(quantity) || quantity <= 0) {
|
| 514 |
alert("Пожалуйста, укажите корректное количество (больше 0).");
|
| 515 |
quantityInput.focus();
|
|
@@ -522,7 +545,7 @@ CATALOG_TEMPLATE = '''
|
|
| 522 |
return;
|
| 523 |
}
|
| 524 |
|
| 525 |
-
const cartItemId = `${product.name}-${color}`;
|
| 526 |
const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
|
| 527 |
|
| 528 |
if (existingItemIndex > -1) {
|
|
@@ -534,7 +557,8 @@ CATALOG_TEMPLATE = '''
|
|
| 534 |
price: product.price,
|
| 535 |
photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
|
| 536 |
quantity: quantity,
|
| 537 |
-
color: color
|
|
|
|
| 538 |
});
|
| 539 |
}
|
| 540 |
|
|
@@ -579,12 +603,13 @@ CATALOG_TEMPLATE = '''
|
|
| 579 |
? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}`
|
| 580 |
: 'https://via.placeholder.com/60x60.png?text=N/A';
|
| 581 |
const colorText = item.color !== 'N/A' ? ` (Цвет: ${item.color})` : '';
|
|
|
|
| 582 |
|
| 583 |
return `
|
| 584 |
<div class="cart-item">
|
| 585 |
<img src="${photoUrl}" alt="${item.name}">
|
| 586 |
<div class="cart-item-details">
|
| 587 |
-
<strong>${item.name}${colorText}</strong>
|
| 588 |
<p class="cart-item-price">${item.price.toFixed(2)} ${currencyCode} × ${item.quantity}</p>
|
| 589 |
</div>
|
| 590 |
<span class="cart-item-total">${itemTotal.toFixed(2)} ${currencyCode}</span>
|
|
@@ -722,7 +747,7 @@ CATALOG_TEMPLATE = '''
|
|
| 722 |
const newPlaceholder = document.createElement('div');
|
| 723 |
newPlaceholder.id = 'notification-placeholder';
|
| 724 |
newPlaceholder.style.position = 'fixed';
|
| 725 |
-
newPlaceholder.style.bottom = '150px';
|
| 726 |
newPlaceholder.style.left = '50%';
|
| 727 |
newPlaceholder.style.transform = 'translateX(-50%)';
|
| 728 |
newPlaceholder.style.zIndex = '1002';
|
|
@@ -805,6 +830,10 @@ PRODUCT_DETAIL_TEMPLATE = '''
|
|
| 805 |
{% if colors and colors|select('ne', '')|list|length > 0 %}
|
| 806 |
<p><strong>Доступные цвета/варианты:</strong> {{ colors|select('ne', '')|join(', ') }}</p>
|
| 807 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
</div>
|
| 809 |
</div>
|
| 810 |
'''
|
|
@@ -858,7 +887,7 @@ ORDER_TEMPLATE = '''
|
|
| 858 |
<div class="order-item">
|
| 859 |
<img src="{{ item.photo_url }}" alt="{{ item.name }}">
|
| 860 |
<div class="item-details">
|
| 861 |
-
<strong>{{ item.name }} {% if item.color != 'N/A' %}({{ item.color }}){% endif %}</strong>
|
| 862 |
<span>{{ "%.2f"|format(item.price) }} {{ currency_code }} × {{ item.quantity }}</span>
|
| 863 |
</div>
|
| 864 |
<div class="item-total">
|
|
@@ -889,9 +918,8 @@ ORDER_TEMPLATE = '''
|
|
| 889 |
function sendOrderViaWhatsApp() {
|
| 890 |
const orderId = '{{ order.id }}';
|
| 891 |
const orderUrl = `{{ request.url }}`;
|
| 892 |
-
const whatsappNumber = "996700575754";
|
| 893 |
-
|
| 894 |
-
let message = `Здравствуйте! Хочу подтвердить свой заказ на Yasin Luxury:%0A%0A`; // Updated Name
|
| 895 |
message += `*Номер заказа:* ${orderId}%0A`;
|
| 896 |
message += `*Ссылка на заказ:* ${encodeURIComponent(orderUrl)}%0A%0A`;
|
| 897 |
message += `Пожалуйста, свяжитесь со мной для уточнения деталей оплаты и доставки.`;
|
|
@@ -965,12 +993,12 @@ ADMIN_TEMPLATE = '''
|
|
| 965 |
details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
|
| 966 |
details[open] > summary { border-bottom: 1px solid #444; }
|
| 967 |
details .form-content { padding: 20px; }
|
| 968 |
-
.
|
| 969 |
-
.
|
| 970 |
-
.remove-
|
| 971 |
-
.remove-
|
| 972 |
-
.add-
|
| 973 |
-
.add-
|
| 974 |
.photo-preview img { max-width: 70px; max-height: 70px; border-radius: 5px; margin: 5px 5px 0 0; border: 1px solid #555; object-fit: cover;}
|
| 975 |
.sync-buttons { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
|
| 976 |
.download-hf-button { background-color: #444; color: #eee; }
|
|
@@ -1082,15 +1110,27 @@ ADMIN_TEMPLATE = '''
|
|
| 1082 |
</select>
|
| 1083 |
<label for="add_photos">Фотографии (до 10 шт.):</label>
|
| 1084 |
<input type="file" id="add_photos" name="photos" accept="image/*" multiple>
|
|
|
|
| 1085 |
<label>Цвета/Варианты (оставьте пустым, если нет):</label>
|
| 1086 |
<div id="add-color-inputs">
|
| 1087 |
-
<div class="
|
| 1088 |
<input type="text" name="colors" placeholder="Например: Розовый">
|
| 1089 |
-
<button type="button" class="remove-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1090 |
</div>
|
| 1091 |
</div>
|
| 1092 |
-
<button type="button" class="button add-
|
| 1093 |
<br>
|
|
|
|
| 1094 |
<div style="margin-top: 15px;">
|
| 1095 |
<input type="checkbox" id="add_in_stock" name="in_stock" checked>
|
| 1096 |
<label for="add_in_stock" class="inline-label">В наличии</label>
|
|
@@ -1137,6 +1177,8 @@ ADMIN_TEMPLATE = '''
|
|
| 1137 |
<p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>
|
| 1138 |
{% set colors = product.get('colors', []) %}
|
| 1139 |
<p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
|
|
|
|
|
|
|
| 1140 |
{% if product.get('photos') and product['photos']|length > 1 %}
|
| 1141 |
<p style="font-size: 0.8rem; color: #bbb;">(Всего фото: {{ product['photos']|length }})</p>
|
| 1142 |
{% endif %}
|
|
@@ -1186,21 +1228,44 @@ ADMIN_TEMPLATE = '''
|
|
| 1186 |
{% if current_colors and current_colors|select('ne', '')|list|length > 0 %}
|
| 1187 |
{% for color in current_colors %}
|
| 1188 |
{% if color.strip() %}
|
| 1189 |
-
<div class="
|
| 1190 |
<input type="text" name="colors" value="{{ color }}">
|
| 1191 |
-
<button type="button" class="remove-
|
| 1192 |
</div>
|
| 1193 |
{% endif %}
|
| 1194 |
{% endfor %}
|
| 1195 |
{% else %}
|
| 1196 |
-
<div class="
|
| 1197 |
<input type="text" name="colors" placeholder="Например: Цвет">
|
| 1198 |
-
<button type="button" class="remove-
|
| 1199 |
</div>
|
| 1200 |
{% endif %}
|
| 1201 |
</div>
|
| 1202 |
-
<button type="button" class="button add-
|
| 1203 |
<br>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1204 |
<div style="margin-top: 15px;">
|
| 1205 |
<input type="checkbox" id="edit_in_stock_{{ loop.index0 }}" name="in_stock" {% if product.get('in_stock', True) %}checked{% endif %}>
|
| 1206 |
<label for="edit_in_stock_{{ loop.index0 }}" class="inline-label">В наличии</label>
|
|
@@ -1231,39 +1296,39 @@ ADMIN_TEMPLATE = '''
|
|
| 1231 |
}
|
| 1232 |
}
|
| 1233 |
|
| 1234 |
-
function
|
| 1235 |
const container = document.getElementById(containerId);
|
| 1236 |
if (container) {
|
| 1237 |
const newInputGroup = document.createElement('div');
|
| 1238 |
-
newInputGroup.className = '
|
| 1239 |
newInputGroup.innerHTML = `
|
| 1240 |
-
<input type="text" name="
|
| 1241 |
-
<button type="button" class="remove-
|
| 1242 |
`;
|
| 1243 |
container.appendChild(newInputGroup);
|
| 1244 |
-
const newInput = newInputGroup.querySelector('input[name="
|
| 1245 |
if (newInput) {
|
| 1246 |
newInput.focus();
|
| 1247 |
}
|
| 1248 |
}
|
| 1249 |
}
|
| 1250 |
|
| 1251 |
-
function
|
| 1252 |
-
const group = button.closest('.
|
| 1253 |
if (group) {
|
| 1254 |
const container = group.parentNode;
|
| 1255 |
group.remove();
|
| 1256 |
if (container && container.children.length === 0) {
|
| 1257 |
const placeholderGroup = document.createElement('div');
|
| 1258 |
-
placeholderGroup.className = '
|
| 1259 |
placeholderGroup.innerHTML = `
|
| 1260 |
-
<input type="text" name="
|
| 1261 |
-
<button type="button" class="remove-
|
| 1262 |
`;
|
| 1263 |
container.appendChild(placeholderGroup);
|
| 1264 |
}
|
| 1265 |
} else {
|
| 1266 |
-
console.warn("Could not find parent .
|
| 1267 |
}
|
| 1268 |
}
|
| 1269 |
</script>
|
|
@@ -1336,6 +1401,7 @@ def create_order():
|
|
| 1336 |
"price": price,
|
| 1337 |
"quantity": quantity,
|
| 1338 |
"color": item.get('color', 'N/A'),
|
|
|
|
| 1339 |
"photo": item.get('photo'),
|
| 1340 |
"photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photo']}" if item.get('photo') else "https://via.placeholder.com/60x60.png?text=N/A"
|
| 1341 |
})
|
|
@@ -1437,6 +1503,7 @@ def admin():
|
|
| 1437 |
category = request.form.get('category')
|
| 1438 |
photos_files = request.files.getlist('photos')
|
| 1439 |
colors = [c.strip() for c in request.form.getlist('colors') if c.strip()]
|
|
|
|
| 1440 |
in_stock = 'in_stock' in request.form
|
| 1441 |
is_top = 'is_top' in request.form
|
| 1442 |
|
|
@@ -1508,7 +1575,7 @@ def admin():
|
|
| 1508 |
new_product = {
|
| 1509 |
'name': name, 'price': price, 'description': description,
|
| 1510 |
'category': category if category in categories else 'Без категории',
|
| 1511 |
-
'photos': photos_list, 'colors': colors,
|
| 1512 |
'in_stock': in_stock, 'is_top': is_top
|
| 1513 |
}
|
| 1514 |
products.append(new_product)
|
|
@@ -1541,6 +1608,7 @@ def admin():
|
|
| 1541 |
category = request.form.get('category')
|
| 1542 |
product_to_edit['category'] = category if category in categories else 'Без категории'
|
| 1543 |
product_to_edit['colors'] = [c.strip() for c in request.form.getlist('colors') if c.strip()]
|
|
|
|
| 1544 |
product_to_edit['in_stock'] = 'in_stock' in request.form
|
| 1545 |
product_to_edit['is_top'] = 'is_top' in request.form
|
| 1546 |
|
|
|
|
| 261 |
.cart-item-total { font-weight: bold; text-align: right; grid-column: 3; font-size: 1rem; color: #ffcc00;}
|
| 262 |
.cart-item-remove { grid-column: 4; background:none; border:none; color:#f56565; cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
|
| 263 |
.cart-item-remove:hover { color: #c53030; }
|
| 264 |
+
.quantity-input, .color-select, .size-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;}
|
| 265 |
.cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #444; padding-top: 15px; }
|
| 266 |
.cart-summary strong { font-size: 1.2rem; color: #ffcc00;}
|
| 267 |
.cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
|
|
|
|
| 348 |
<div id="quantityModal" class="modal">
|
| 349 |
<div class="modal-content">
|
| 350 |
<span class="close" onclick="closeModal('quantityModal')" aria-label="Закрыть">×</span>
|
| 351 |
+
<h2>Укажите детали</h2>
|
| 352 |
<label for="quantityInput">Количество:</label>
|
| 353 |
<input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
|
| 354 |
<label for="colorSelect">Цвет/Вариант:</label>
|
| 355 |
<select id="colorSelect" class="color-select"></select>
|
| 356 |
+
<label for="sizeSelect">Размер:</label>
|
| 357 |
+
<select id="sizeSelect" class="size-select"></select>
|
| 358 |
<button class="product-button add-to-cart" onclick="confirmAddToCart()"><i class="fas fa-check"></i> Добавить в корзину</button>
|
| 359 |
</div>
|
| 360 |
</div>
|
|
|
|
| 479 |
const colorSelect = document.getElementById('colorSelect');
|
| 480 |
const colorLabel = document.querySelector('label[for="colorSelect"]');
|
| 481 |
colorSelect.innerHTML = '';
|
|
|
|
| 482 |
const validColors = product.colors ? product.colors.filter(c => c && c.trim() !== "") : [];
|
|
|
|
| 483 |
if (validColors.length > 0) {
|
| 484 |
validColors.forEach(color => {
|
| 485 |
const option = document.createElement('option');
|
|
|
|
| 494 |
if(colorLabel) colorLabel.style.display = 'none';
|
| 495 |
}
|
| 496 |
|
| 497 |
+
const sizeSelect = document.getElementById('sizeSelect');
|
| 498 |
+
const sizeLabel = document.querySelector('label[for="sizeSelect"]');
|
| 499 |
+
sizeSelect.innerHTML = '';
|
| 500 |
+
const validSizes = product.sizes ? product.sizes.filter(s => s && s.trim() !== "") : [];
|
| 501 |
+
if (validSizes.length > 0) {
|
| 502 |
+
validSizes.forEach(size => {
|
| 503 |
+
const option = document.createElement('option');
|
| 504 |
+
option.value = size.trim();
|
| 505 |
+
option.text = size.trim();
|
| 506 |
+
sizeSelect.appendChild(option);
|
| 507 |
+
});
|
| 508 |
+
sizeSelect.style.display = 'block';
|
| 509 |
+
if(sizeLabel) sizeLabel.style.display = 'block';
|
| 510 |
+
} else {
|
| 511 |
+
sizeSelect.style.display = 'none';
|
| 512 |
+
if(sizeLabel) sizeLabel.style.display = 'none';
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
|
| 516 |
document.getElementById('quantityInput').value = 1;
|
| 517 |
const modal = document.getElementById('quantityModal');
|
| 518 |
if(modal) {
|
|
|
|
| 526 |
|
| 527 |
const quantityInput = document.getElementById('quantityInput');
|
| 528 |
const quantity = parseInt(quantityInput.value);
|
| 529 |
+
|
| 530 |
const colorSelect = document.getElementById('colorSelect');
|
| 531 |
const color = colorSelect.style.display !== 'none' && colorSelect.value ? colorSelect.value : 'N/A';
|
| 532 |
|
| 533 |
+
const sizeSelect = document.getElementById('sizeSelect');
|
| 534 |
+
const size = sizeSelect.style.display !== 'none' && sizeSelect.value ? sizeSelect.value : 'N/A';
|
| 535 |
+
|
| 536 |
if (isNaN(quantity) || quantity <= 0) {
|
| 537 |
alert("Пожалуйста, укажите корректное количество (больше 0).");
|
| 538 |
quantityInput.focus();
|
|
|
|
| 545 |
return;
|
| 546 |
}
|
| 547 |
|
| 548 |
+
const cartItemId = `${product.name}-${color}-${size}`;
|
| 549 |
const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
|
| 550 |
|
| 551 |
if (existingItemIndex > -1) {
|
|
|
|
| 557 |
price: product.price,
|
| 558 |
photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
|
| 559 |
quantity: quantity,
|
| 560 |
+
color: color,
|
| 561 |
+
size: size
|
| 562 |
});
|
| 563 |
}
|
| 564 |
|
|
|
|
| 603 |
? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}`
|
| 604 |
: 'https://via.placeholder.com/60x60.png?text=N/A';
|
| 605 |
const colorText = item.color !== 'N/A' ? ` (Цвет: ${item.color})` : '';
|
| 606 |
+
const sizeText = item.size && item.size !== 'N/A' ? ` (Размер: ${item.size})` : '';
|
| 607 |
|
| 608 |
return `
|
| 609 |
<div class="cart-item">
|
| 610 |
<img src="${photoUrl}" alt="${item.name}">
|
| 611 |
<div class="cart-item-details">
|
| 612 |
+
<strong>${item.name}${colorText}${sizeText}</strong>
|
| 613 |
<p class="cart-item-price">${item.price.toFixed(2)} ${currencyCode} × ${item.quantity}</p>
|
| 614 |
</div>
|
| 615 |
<span class="cart-item-total">${itemTotal.toFixed(2)} ${currencyCode}</span>
|
|
|
|
| 747 |
const newPlaceholder = document.createElement('div');
|
| 748 |
newPlaceholder.id = 'notification-placeholder';
|
| 749 |
newPlaceholder.style.position = 'fixed';
|
| 750 |
+
newPlaceholder.style.bottom = '150px';
|
| 751 |
newPlaceholder.style.left = '50%';
|
| 752 |
newPlaceholder.style.transform = 'translateX(-50%)';
|
| 753 |
newPlaceholder.style.zIndex = '1002';
|
|
|
|
| 830 |
{% if colors and colors|select('ne', '')|list|length > 0 %}
|
| 831 |
<p><strong>Доступные цвета/варианты:</strong> {{ colors|select('ne', '')|join(', ') }}</p>
|
| 832 |
{% endif %}
|
| 833 |
+
{% set sizes = product.get('sizes', []) %}
|
| 834 |
+
{% if sizes and sizes|select('ne', '')|list|length > 0 %}
|
| 835 |
+
<p><strong>Доступные размеры:</strong> {{ sizes|select('ne', '')|join(', ') }}</p>
|
| 836 |
+
{% endif %}
|
| 837 |
</div>
|
| 838 |
</div>
|
| 839 |
'''
|
|
|
|
| 887 |
<div class="order-item">
|
| 888 |
<img src="{{ item.photo_url }}" alt="{{ item.name }}">
|
| 889 |
<div class="item-details">
|
| 890 |
+
<strong>{{ item.name }} {% if item.color != 'N/A' %}({{ item.color }}){% endif %} {% if item.size and item.size != 'N/A' %}(Р: {{ item.size }}){% endif %}</strong>
|
| 891 |
<span>{{ "%.2f"|format(item.price) }} {{ currency_code }} × {{ item.quantity }}</span>
|
| 892 |
</div>
|
| 893 |
<div class="item-total">
|
|
|
|
| 918 |
function sendOrderViaWhatsApp() {
|
| 919 |
const orderId = '{{ order.id }}';
|
| 920 |
const orderUrl = `{{ request.url }}`;
|
| 921 |
+
const whatsappNumber = "996700575754";
|
| 922 |
+
let message = `Здравствуйте! Хочу подтвердить свой заказ на Yasin Luxury:%0A%0A`;
|
|
|
|
| 923 |
message += `*Номер заказа:* ${orderId}%0A`;
|
| 924 |
message += `*Ссылка на заказ:* ${encodeURIComponent(orderUrl)}%0A%0A`;
|
| 925 |
message += `Пожалуйста, свяжитесь со мной для уточнения деталей оплаты и доставки.`;
|
|
|
|
| 993 |
details[open] > summary::after { transform: translateY(-50%) rotate(180deg); }
|
| 994 |
details[open] > summary { border-bottom: 1px solid #444; }
|
| 995 |
details .form-content { padding: 20px; }
|
| 996 |
+
.attr-input-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
| 997 |
+
.attr-input-group input { flex-grow: 1; margin: 0; }
|
| 998 |
+
.remove-attr-btn { background-color: #c53030; padding: 6px 10px; font-size: 0.8rem; margin-top: 0; line-height: 1; color: white;}
|
| 999 |
+
.remove-attr-btn:hover { background-color: #9b2c2c; }
|
| 1000 |
+
.add-attr-btn { background-color: #ffd700; color: #1a1a1a;}
|
| 1001 |
+
.add-attr-btn:hover { background-color: #ffcc00; }
|
| 1002 |
.photo-preview img { max-width: 70px; max-height: 70px; border-radius: 5px; margin: 5px 5px 0 0; border: 1px solid #555; object-fit: cover;}
|
| 1003 |
.sync-buttons { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
|
| 1004 |
.download-hf-button { background-color: #444; color: #eee; }
|
|
|
|
| 1110 |
</select>
|
| 1111 |
<label for="add_photos">Фотографии (до 10 шт.):</label>
|
| 1112 |
<input type="file" id="add_photos" name="photos" accept="image/*" multiple>
|
| 1113 |
+
|
| 1114 |
<label>Цвета/Варианты (оставьте пустым, если нет):</label>
|
| 1115 |
<div id="add-color-inputs">
|
| 1116 |
+
<div class="attr-input-group">
|
| 1117 |
<input type="text" name="colors" placeholder="Например: Розовый">
|
| 1118 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, 'colors', 'Например: Розовый')"><i class="fas fa-times"></i></button>
|
| 1119 |
+
</div>
|
| 1120 |
+
</div>
|
| 1121 |
+
<button type="button" class="button add-attr-btn" style="margin-top: 5px;" onclick="addAttributeInput('add-color-inputs', 'colors', 'Например: Розовый')"><i class="fas fa-palette"></i> Добавить поле для цвета</button>
|
| 1122 |
+
<br>
|
| 1123 |
+
|
| 1124 |
+
<label>Размеры (оставьте пустым, если нет):</label>
|
| 1125 |
+
<div id="add-size-inputs">
|
| 1126 |
+
<div class="attr-input-group">
|
| 1127 |
+
<input type="text" name="sizes" placeholder="Например: S, M, L">
|
| 1128 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, 'sizes', 'Например: S, M, L')"><i class="fas fa-times"></i></button>
|
| 1129 |
</div>
|
| 1130 |
</div>
|
| 1131 |
+
<button type="button" class="button add-attr-btn" style="margin-top: 5px;" onclick="addAttributeInput('add-size-inputs', 'sizes', 'Например: S, M, L')"><i class="fas fa-ruler-combined"></i> Добавить поле для размера</button>
|
| 1132 |
<br>
|
| 1133 |
+
|
| 1134 |
<div style="margin-top: 15px;">
|
| 1135 |
<input type="checkbox" id="add_in_stock" name="in_stock" checked>
|
| 1136 |
<label for="add_in_stock" class="inline-label">В наличии</label>
|
|
|
|
| 1177 |
<p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>
|
| 1178 |
{% set colors = product.get('colors', []) %}
|
| 1179 |
<p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
|
| 1180 |
+
{% set sizes = product.get('sizes', []) %}
|
| 1181 |
+
<p><strong>Размеры:</strong> {{ sizes|select('ne', '')|join(', ') if sizes|select('ne', '')|list|length > 0 else 'Нет' }}</p>
|
| 1182 |
{% if product.get('photos') and product['photos']|length > 1 %}
|
| 1183 |
<p style="font-size: 0.8rem; color: #bbb;">(Всего фото: {{ product['photos']|length }})</p>
|
| 1184 |
{% endif %}
|
|
|
|
| 1228 |
{% if current_colors and current_colors|select('ne', '')|list|length > 0 %}
|
| 1229 |
{% for color in current_colors %}
|
| 1230 |
{% if color.strip() %}
|
| 1231 |
+
<div class="attr-input-group">
|
| 1232 |
<input type="text" name="colors" value="{{ color }}">
|
| 1233 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, 'colors', 'Например: Цвет')"><i class="fas fa-times"></i></button>
|
| 1234 |
</div>
|
| 1235 |
{% endif %}
|
| 1236 |
{% endfor %}
|
| 1237 |
{% else %}
|
| 1238 |
+
<div class="attr-input-group">
|
| 1239 |
<input type="text" name="colors" placeholder="Например: Цвет">
|
| 1240 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, 'colors', 'Например: Цвет')"><i class="fas fa-times"></i></button>
|
| 1241 |
</div>
|
| 1242 |
{% endif %}
|
| 1243 |
</div>
|
| 1244 |
+
<button type="button" class="button add-attr-btn" style="margin-top: 5px;" onclick="addAttributeInput('edit-color-inputs-{{ loop.index0 }}', 'colors', 'Например: Цвет')"><i class="fas fa-palette"></i> Добавить поле для цвета</button>
|
| 1245 |
<br>
|
| 1246 |
+
|
| 1247 |
+
<label>Размеры:</label>
|
| 1248 |
+
<div id="edit-size-inputs-{{ loop.index0 }}">
|
| 1249 |
+
{% set current_sizes = product.get('sizes', []) %}
|
| 1250 |
+
{% if current_sizes and current_sizes|select('ne', '')|list|length > 0 %}
|
| 1251 |
+
{% for size_val in current_sizes %}
|
| 1252 |
+
{% if size_val.strip() %}
|
| 1253 |
+
<div class="attr-input-group">
|
| 1254 |
+
<input type="text" name="sizes" value="{{ size_val }}">
|
| 1255 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, 'sizes', 'Например: S, M, L')"><i class="fas fa-times"></i></button>
|
| 1256 |
+
</div>
|
| 1257 |
+
{% endif %}
|
| 1258 |
+
{% endfor %}
|
| 1259 |
+
{% else %}
|
| 1260 |
+
<div class="attr-input-group">
|
| 1261 |
+
<input type="text" name="sizes" placeholder="Например: S, M, L">
|
| 1262 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, 'sizes', 'Например: S, M, L')"><i class="fas fa-times"></i></button>
|
| 1263 |
+
</div>
|
| 1264 |
+
{% endif %}
|
| 1265 |
+
</div>
|
| 1266 |
+
<button type="button" class="button add-attr-btn" style="margin-top: 5px;" onclick="addAttributeInput('edit-size-inputs-{{ loop.index0 }}', 'sizes', 'Например: S, M, L')"><i class="fas fa-ruler-combined"></i> Добавить поле для размера</button>
|
| 1267 |
+
<br>
|
| 1268 |
+
|
| 1269 |
<div style="margin-top: 15px;">
|
| 1270 |
<input type="checkbox" id="edit_in_stock_{{ loop.index0 }}" name="in_stock" {% if product.get('in_stock', True) %}checked{% endif %}>
|
| 1271 |
<label for="edit_in_stock_{{ loop.index0 }}" class="inline-label">В наличии</label>
|
|
|
|
| 1296 |
}
|
| 1297 |
}
|
| 1298 |
|
| 1299 |
+
function addAttributeInput(containerId, attributeName, placeholderText) {
|
| 1300 |
const container = document.getElementById(containerId);
|
| 1301 |
if (container) {
|
| 1302 |
const newInputGroup = document.createElement('div');
|
| 1303 |
+
newInputGroup.className = 'attr-input-group';
|
| 1304 |
newInputGroup.innerHTML = `
|
| 1305 |
+
<input type="text" name="${attributeName}" placeholder="${placeholderText || 'Новое значение'}">
|
| 1306 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, '${attributeName}', '${placeholderText}')"><i class="fas fa-times"></i></button>
|
| 1307 |
`;
|
| 1308 |
container.appendChild(newInputGroup);
|
| 1309 |
+
const newInput = newInputGroup.querySelector('input[name="' + attributeName + '"]');
|
| 1310 |
if (newInput) {
|
| 1311 |
newInput.focus();
|
| 1312 |
}
|
| 1313 |
}
|
| 1314 |
}
|
| 1315 |
|
| 1316 |
+
function removeAttributeInput(button, attributeName, placeholderText) {
|
| 1317 |
+
const group = button.closest('.attr-input-group');
|
| 1318 |
if (group) {
|
| 1319 |
const container = group.parentNode;
|
| 1320 |
group.remove();
|
| 1321 |
if (container && container.children.length === 0) {
|
| 1322 |
const placeholderGroup = document.createElement('div');
|
| 1323 |
+
placeholderGroup.className = 'attr-input-group';
|
| 1324 |
placeholderGroup.innerHTML = `
|
| 1325 |
+
<input type="text" name="${attributeName}" placeholder="${placeholderText || 'Значение'}">
|
| 1326 |
+
<button type="button" class="remove-attr-btn" onclick="removeAttributeInput(this, '${attributeName}', '${placeholderText}')"><i class="fas fa-times"></i></button>
|
| 1327 |
`;
|
| 1328 |
container.appendChild(placeholderGroup);
|
| 1329 |
}
|
| 1330 |
} else {
|
| 1331 |
+
console.warn("Could not find parent .attr-input-group for remove button");
|
| 1332 |
}
|
| 1333 |
}
|
| 1334 |
</script>
|
|
|
|
| 1401 |
"price": price,
|
| 1402 |
"quantity": quantity,
|
| 1403 |
"color": item.get('color', 'N/A'),
|
| 1404 |
+
"size": item.get('size', 'N/A'),
|
| 1405 |
"photo": item.get('photo'),
|
| 1406 |
"photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photo']}" if item.get('photo') else "https://via.placeholder.com/60x60.png?text=N/A"
|
| 1407 |
})
|
|
|
|
| 1503 |
category = request.form.get('category')
|
| 1504 |
photos_files = request.files.getlist('photos')
|
| 1505 |
colors = [c.strip() for c in request.form.getlist('colors') if c.strip()]
|
| 1506 |
+
sizes = [s.strip() for s in request.form.getlist('sizes') if s.strip()]
|
| 1507 |
in_stock = 'in_stock' in request.form
|
| 1508 |
is_top = 'is_top' in request.form
|
| 1509 |
|
|
|
|
| 1575 |
new_product = {
|
| 1576 |
'name': name, 'price': price, 'description': description,
|
| 1577 |
'category': category if category in categories else 'Без категории',
|
| 1578 |
+
'photos': photos_list, 'colors': colors, 'sizes': sizes,
|
| 1579 |
'in_stock': in_stock, 'is_top': is_top
|
| 1580 |
}
|
| 1581 |
products.append(new_product)
|
|
|
|
| 1608 |
category = request.form.get('category')
|
| 1609 |
product_to_edit['category'] = category if category in categories else 'Без категории'
|
| 1610 |
product_to_edit['colors'] = [c.strip() for c in request.form.getlist('colors') if c.strip()]
|
| 1611 |
+
product_to_edit['sizes'] = [s.strip() for s in request.form.getlist('sizes') if s.strip()]
|
| 1612 |
product_to_edit['in_stock'] = 'in_stock' in request.form
|
| 1613 |
product_to_edit['is_top'] = 'is_top' in request.form
|
| 1614 |
|