Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -800,6 +800,8 @@ ADMIN_TEMPLATE = """
|
|
| 800 |
.btn { padding: 12px 20px; font-size: 1em; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: background-color 0.2s ease; }
|
| 801 |
.btn-primary { background-color: var(--admin-primary); color: #000; }
|
| 802 |
.btn-primary:hover { background-color: var(--admin-primary-dark); }
|
|
|
|
|
|
|
| 803 |
.btn-delete { background-color: var(--admin-danger); color: white; }
|
| 804 |
.btn-delete:hover { background-color: #c82333; }
|
| 805 |
.user-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: var(--padding); margin-top: var(--padding); }
|
|
@@ -809,11 +811,7 @@ ADMIN_TEMPLATE = """
|
|
| 809 |
.user-info img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; border: 3px solid var(--admin-border); background-color: #eee; }
|
| 810 |
.user-details .name { font-weight: 600; font-size: 1.2em; }
|
| 811 |
.user-details .username { color: var(--admin-secondary); font-size: 0.9em; }
|
| 812 |
-
.referral-info {
|
| 813 |
-
font-size: 0.85em; color: var(--admin-secondary); margin-top: 8px;
|
| 814 |
-
padding: 8px; background-color: #f8f9fa; border-radius: 8px;
|
| 815 |
-
border: 1px solid var(--admin-border);
|
| 816 |
-
}
|
| 817 |
.referral-info .ref-by { color: var(--admin-success); }
|
| 818 |
.referral-info .ref-count { color: var(--admin-info); font-weight: 600; }
|
| 819 |
.user-balances { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; text-align: center; margin-bottom: 1rem; margin-top: 1rem; }
|
|
@@ -838,9 +836,6 @@ ADMIN_TEMPLATE = """
|
|
| 838 |
.form-group { display: flex; flex-direction: column; }
|
| 839 |
.form-group label { margin-bottom: 0.5rem; font-weight: 500; font-size: 0.9em; }
|
| 840 |
.form-group input, .form-group textarea { padding: 10px; font-size: 1rem; border: 1px solid var(--admin-border); border-radius: 8px; width: 100%; box-sizing: border-box; }
|
| 841 |
-
.calculation-summary { background: #f0f0f0; padding: 1rem; border-radius: 8px; margin-top: 1rem; }
|
| 842 |
-
.summary-item { display: flex; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.95em; }
|
| 843 |
-
.summary-item strong { font-weight: 600; }
|
| 844 |
.history-container { margin-top: 1.5rem; }
|
| 845 |
.history-container h3 { font-size: 1.2rem; margin-bottom: 1rem; }
|
| 846 |
.history-list { list-style: none; padding: 0; max-height: 200px; overflow-y: auto; border: 1px solid var(--admin-border); border-radius: 8px; }
|
|
@@ -863,11 +858,16 @@ ADMIN_TEMPLATE = """
|
|
| 863 |
.tab-btn.active { color: var(--admin-primary-dark); border-bottom-color: var(--admin-primary); }
|
| 864 |
.tab-content { display: none; }
|
| 865 |
.tab-content.active { display: block; }
|
| 866 |
-
|
| 867 |
-
.invoice-
|
| 868 |
-
.invoice-
|
| 869 |
-
.
|
| 870 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
.invoice-section-summary { padding: 1rem; background-color: #e9ecef; border-radius: 8px; margin-top: 1rem; font-weight: 600; font-size: 1.1em; text-align: right; }
|
| 872 |
.invoice-section-summary span { color: var(--admin-success); }
|
| 873 |
.invoice-list-admin { list-style: none; padding: 0; max-height: 200px; overflow-y: auto; border: 1px solid var(--admin-border); border-radius: 8px; }
|
|
@@ -890,7 +890,7 @@ ADMIN_TEMPLATE = """
|
|
| 890 |
.item-total { flex-basis: 20%; text-align: right; font-weight: bold; }
|
| 891 |
.invoice-total-display { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6; display: flex; flex-direction: column; gap: 0.5rem; font-size: 1.1em; }
|
| 892 |
.invoice-total-display div { display: flex; justify-content: space-between; }
|
| 893 |
-
.bonus-source-selector { display: flex; gap: 1rem; margin-bottom: 1rem; align-items: center; }
|
| 894 |
.bonus-source-selector label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
|
| 895 |
</style>
|
| 896 |
</head>
|
|
@@ -1026,22 +1026,14 @@ ADMIN_TEMPLATE = """
|
|
| 1026 |
<div id="invoice-tab" class="tab-content">
|
| 1027 |
<div class="form-section">
|
| 1028 |
<h3>Добавить новую накладную</h3>
|
| 1029 |
-
<
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
| 1034 |
-
</
|
| 1035 |
-
</
|
| 1036 |
-
|
| 1037 |
-
<tfoot>
|
| 1038 |
-
<tr class="total-row">
|
| 1039 |
-
<td colspan="3" style="text-align: right;"><strong>Итоговая сумма:</strong></td>
|
| 1040 |
-
<td id="newInvoiceTotalAmount">0.00</td><td></td>
|
| 1041 |
-
</tr>
|
| 1042 |
-
</tfoot>
|
| 1043 |
-
</table>
|
| 1044 |
-
<button class="btn btn-primary" style="margin-top: 1rem;" onclick="addNewInvoiceItemRow()">Добавить товар</button>
|
| 1045 |
</div>
|
| 1046 |
<div class="form-section">
|
| 1047 |
<h3>Оплата бонусами</h3>
|
|
@@ -1454,9 +1446,10 @@ ADMIN_TEMPLATE = """
|
|
| 1454 |
const price = parseFloat(newInvoiceItems[index].unit_price) || 0;
|
| 1455 |
const itemTotal = qty * price;
|
| 1456 |
newInvoiceItems[index].item_total = itemTotal;
|
| 1457 |
-
const
|
| 1458 |
-
|
| 1459 |
-
|
|
|
|
| 1460 |
}
|
| 1461 |
updateNewInvoiceTotal();
|
| 1462 |
}
|
|
@@ -1467,12 +1460,32 @@ ADMIN_TEMPLATE = """
|
|
| 1467 |
}
|
| 1468 |
|
| 1469 |
function renderNewInvoiceItems() {
|
| 1470 |
-
const
|
| 1471 |
-
|
| 1472 |
newInvoiceItems.forEach((item, index) => {
|
| 1473 |
-
const
|
|
|
|
| 1474 |
const productName = item.product_name ? String(item.product_name).replace(/"/g, '"') : '';
|
| 1475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1476 |
});
|
| 1477 |
updateNewInvoiceTotal();
|
| 1478 |
}
|
|
@@ -2030,4 +2043,4 @@ if __name__ == '__main__':
|
|
| 2030 |
print("WARNING: HF_TOKEN_WRITE not set. Periodic backups to Hugging Face are disabled.")
|
| 2031 |
|
| 2032 |
print("--- Server Ready ---")
|
| 2033 |
-
app.run(host=HOST, port=PORT, debug=False)
|
|
|
|
| 800 |
.btn { padding: 12px 20px; font-size: 1em; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: background-color 0.2s ease; }
|
| 801 |
.btn-primary { background-color: var(--admin-primary); color: #000; }
|
| 802 |
.btn-primary:hover { background-color: var(--admin-primary-dark); }
|
| 803 |
+
.btn-secondary { background-color: var(--admin-secondary); color: white; }
|
| 804 |
+
.btn-secondary:hover { background-color: #5a6268; }
|
| 805 |
.btn-delete { background-color: var(--admin-danger); color: white; }
|
| 806 |
.btn-delete:hover { background-color: #c82333; }
|
| 807 |
.user-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: var(--padding); margin-top: var(--padding); }
|
|
|
|
| 811 |
.user-info img { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; border: 3px solid var(--admin-border); background-color: #eee; }
|
| 812 |
.user-details .name { font-weight: 600; font-size: 1.2em; }
|
| 813 |
.user-details .username { color: var(--admin-secondary); font-size: 0.9em; }
|
| 814 |
+
.referral-info { font-size: 0.85em; color: var(--admin-secondary); margin-top: 8px; padding: 8px; background-color: #f8f9fa; border-radius: 8px; border: 1px solid var(--admin-border); }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
.referral-info .ref-by { color: var(--admin-success); }
|
| 816 |
.referral-info .ref-count { color: var(--admin-info); font-weight: 600; }
|
| 817 |
.user-balances { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; text-align: center; margin-bottom: 1rem; margin-top: 1rem; }
|
|
|
|
| 836 |
.form-group { display: flex; flex-direction: column; }
|
| 837 |
.form-group label { margin-bottom: 0.5rem; font-weight: 500; font-size: 0.9em; }
|
| 838 |
.form-group input, .form-group textarea { padding: 10px; font-size: 1rem; border: 1px solid var(--admin-border); border-radius: 8px; width: 100%; box-sizing: border-box; }
|
|
|
|
|
|
|
|
|
|
| 839 |
.history-container { margin-top: 1.5rem; }
|
| 840 |
.history-container h3 { font-size: 1.2rem; margin-bottom: 1rem; }
|
| 841 |
.history-list { list-style: none; padding: 0; max-height: 200px; overflow-y: auto; border: 1px solid var(--admin-border); border-radius: 8px; }
|
|
|
|
| 858 |
.tab-btn.active { color: var(--admin-primary-dark); border-bottom-color: var(--admin-primary); }
|
| 859 |
.tab-content { display: none; }
|
| 860 |
.tab-content.active { display: block; }
|
| 861 |
+
#newInvoiceItemsList { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1rem; }
|
| 862 |
+
.invoice-item-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; padding-bottom: 10px; border-bottom: 1px solid var(--admin-border); }
|
| 863 |
+
.invoice-item-row .form-group { margin-bottom: 0; }
|
| 864 |
+
.form-group.item-name { flex: 3 1 200px; }
|
| 865 |
+
.form-group.item-qty, .form-group.item-price { flex: 1 1 80px; }
|
| 866 |
+
.item-total-group { flex: 1 1 90px; text-align: right; padding-bottom: 10px; }
|
| 867 |
+
.item-total-group .item-total-display { font-weight: 700; font-size: 1.1em; }
|
| 868 |
+
.invoice-item-row .action-btn { align-self: center; margin-bottom: 5px; background: none; border: none; color: var(--admin-danger); cursor: pointer; font-size: 1.2em; }
|
| 869 |
+
.invoice-items-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--admin-border); }
|
| 870 |
+
.invoice-items-total { font-size: 1.2em; font-weight: 700; }
|
| 871 |
.invoice-section-summary { padding: 1rem; background-color: #e9ecef; border-radius: 8px; margin-top: 1rem; font-weight: 600; font-size: 1.1em; text-align: right; }
|
| 872 |
.invoice-section-summary span { color: var(--admin-success); }
|
| 873 |
.invoice-list-admin { list-style: none; padding: 0; max-height: 200px; overflow-y: auto; border: 1px solid var(--admin-border); border-radius: 8px; }
|
|
|
|
| 890 |
.item-total { flex-basis: 20%; text-align: right; font-weight: bold; }
|
| 891 |
.invoice-total-display { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6; display: flex; flex-direction: column; gap: 0.5rem; font-size: 1.1em; }
|
| 892 |
.invoice-total-display div { display: flex; justify-content: space-between; }
|
| 893 |
+
.bonus-source-selector { display: flex; gap: 1rem; margin-bottom: 1rem; align-items: center; flex-wrap: wrap; }
|
| 894 |
.bonus-source-selector label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
|
| 895 |
</style>
|
| 896 |
</head>
|
|
|
|
| 1026 |
<div id="invoice-tab" class="tab-content">
|
| 1027 |
<div class="form-section">
|
| 1028 |
<h3>Добавить новую накладную</h3>
|
| 1029 |
+
<div id="newInvoiceItemsList"></div>
|
| 1030 |
+
<div class="invoice-items-footer">
|
| 1031 |
+
<button class="btn btn-secondary" onclick="addNewInvoiceItemRow()">Добавить товар</button>
|
| 1032 |
+
<div class="invoice-items-total">
|
| 1033 |
+
<strong>Итого:</strong>
|
| 1034 |
+
<span id="newInvoiceTotalAmount">0.00</span>
|
| 1035 |
+
</div>
|
| 1036 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1037 |
</div>
|
| 1038 |
<div class="form-section">
|
| 1039 |
<h3>Оплата бонусами</h3>
|
|
|
|
| 1446 |
const price = parseFloat(newInvoiceItems[index].unit_price) || 0;
|
| 1447 |
const itemTotal = qty * price;
|
| 1448 |
newInvoiceItems[index].item_total = itemTotal;
|
| 1449 |
+
const list = document.getElementById('newInvoiceItemsList');
|
| 1450 |
+
const itemRow = list.children[index];
|
| 1451 |
+
if (itemRow) {
|
| 1452 |
+
itemRow.querySelector('.item-total-display').textContent = itemTotal.toFixed(2);
|
| 1453 |
}
|
| 1454 |
updateNewInvoiceTotal();
|
| 1455 |
}
|
|
|
|
| 1460 |
}
|
| 1461 |
|
| 1462 |
function renderNewInvoiceItems() {
|
| 1463 |
+
const list = document.getElementById('newInvoiceItemsList');
|
| 1464 |
+
list.innerHTML = '';
|
| 1465 |
newInvoiceItems.forEach((item, index) => {
|
| 1466 |
+
const itemDiv = document.createElement('div');
|
| 1467 |
+
itemDiv.className = 'invoice-item-row';
|
| 1468 |
const productName = item.product_name ? String(item.product_name).replace(/"/g, '"') : '';
|
| 1469 |
+
itemDiv.innerHTML = `
|
| 1470 |
+
<div class="form-group item-name">
|
| 1471 |
+
<label>Товар</label>
|
| 1472 |
+
<input type="text" placeholder="Название товара" value="${productName}" oninput="updateInvoiceItem(${index}, 'product_name', this.value)">
|
| 1473 |
+
</div>
|
| 1474 |
+
<div class="form-group item-qty">
|
| 1475 |
+
<label>Кол-во</label>
|
| 1476 |
+
<input type="number" step="1" min="1" placeholder="1" value="${item.quantity || '1'}" oninput="updateInvoiceItem(${index}, 'quantity', this.value)">
|
| 1477 |
+
</div>
|
| 1478 |
+
<div class="form-group item-price">
|
| 1479 |
+
<label>Цена</label>
|
| 1480 |
+
<input type="number" step="0.01" min="0" placeholder="0.00" value="${item.unit_price || ''}" oninput="updateInvoiceItem(${index}, 'unit_price', this.value)">
|
| 1481 |
+
</div>
|
| 1482 |
+
<div class="item-total-group">
|
| 1483 |
+
<label>Сумма</label>
|
| 1484 |
+
<div class="item-total-display">${(item.item_total || 0).toFixed(2)}</div>
|
| 1485 |
+
</div>
|
| 1486 |
+
<button class="action-btn" onclick="removeInvoiceItemRow(${index})">🗑️</button>
|
| 1487 |
+
`;
|
| 1488 |
+
list.appendChild(itemDiv);
|
| 1489 |
});
|
| 1490 |
updateNewInvoiceTotal();
|
| 1491 |
}
|
|
|
|
| 2043 |
print("WARNING: HF_TOKEN_WRITE not set. Periodic backups to Hugging Face are disabled.")
|
| 2044 |
|
| 2045 |
print("--- Server Ready ---")
|
| 2046 |
+
app.run(host=HOST, port=PORT, debug=False)
|