duzo poprawek ux i logicznych
This commit is contained in:
2
app.py
2
app.py
@@ -1147,6 +1147,7 @@ def view_list(list_id):
|
||||
total_count = len(items)
|
||||
purchased_count = len([i for i in items if i.purchased])
|
||||
percent = (purchased_count / total_count * 100) if total_count > 0 else 0
|
||||
is_owner = current_user.id == shopping_list.owner_id
|
||||
|
||||
return render_template(
|
||||
"list.html",
|
||||
@@ -1159,6 +1160,7 @@ def view_list(list_id):
|
||||
expenses=expenses,
|
||||
total_expense=total_expense,
|
||||
is_share=False,
|
||||
is_owner=is_owner
|
||||
)
|
||||
|
||||
|
||||
|
@@ -281,6 +281,9 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
|
||||
: 'item-not-checked'
|
||||
}`;
|
||||
|
||||
const isOwner = window.IS_OWNER === true || window.IS_OWNER === 'true';
|
||||
const allowEdit = !isShare || showEditOnly || isOwner;
|
||||
|
||||
let quantityBadge = '';
|
||||
if (item.quantity && item.quantity > 1) {
|
||||
quantityBadge = `<span class="badge bg-secondary">x${item.quantity}</span>`;
|
||||
@@ -309,8 +312,8 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
|
||||
|
||||
let rightButtons = '';
|
||||
|
||||
// ✏️ i 🗑️ — tylko jeśli nie jesteśmy w trybie /share lub jesteśmy w 15s (tymczasowo)
|
||||
if (!isShare || showEditOnly) {
|
||||
// ✏️ i 🗑️ — tylko jeśli nie jesteśmy w trybie /share lub jesteśmy w 15s (tymczasowo) lub jesteśmy właścicielem
|
||||
if (allowEdit) {
|
||||
rightButtons += `
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="editItem(${item.id}, '${item.name.replace(/'/g, "\\'")}', ${item.quantity || 1})">
|
||||
@@ -332,7 +335,8 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
|
||||
}
|
||||
|
||||
// ⚠️ tylko jeśli NIE jest oznaczony jako niekupiony i nie jesteśmy w 15s
|
||||
if (!item.not_purchased && !showEditOnly) {
|
||||
if (!item.not_purchased && (isOwner || (isShare && !showEditOnly))) {
|
||||
|
||||
rightButtons += `
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="markNotPurchasedModal(event, ${item.id})">
|
||||
@@ -341,7 +345,8 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
|
||||
}
|
||||
|
||||
// 📝 tylko jeśli jesteśmy w /share i nie jesteśmy w 15s
|
||||
if (isShare && !showEditOnly) {
|
||||
if (isShare && !showEditOnly && !isOwner) {
|
||||
|
||||
rightButtons += `
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
onclick="openNoteModal(event, ${item.id})">
|
||||
@@ -349,7 +354,6 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
|
||||
</button>`;
|
||||
}
|
||||
|
||||
|
||||
li.innerHTML = `${left}<div class="btn-group btn-group-sm" role="group">${rightButtons}</div>`;
|
||||
|
||||
if (item.added_by && item.owner_id && item.added_by_id && item.added_by_id !== item.owner_id) {
|
||||
|
@@ -205,43 +205,39 @@ function setupList(listId, username) {
|
||||
});
|
||||
|
||||
socket.on('note_updated', data => {
|
||||
const itemEl = document.getElementById(`item-${data.item_id}`);
|
||||
if (itemEl) {
|
||||
let noteEl = itemEl.querySelector('small');
|
||||
if (noteEl) {
|
||||
//noteEl.innerHTML = `[ Notatka: <b>${data.note}</b> ]`;
|
||||
noteEl.innerHTML = `[ <b>${data.note}</b> ]`;
|
||||
} else {
|
||||
const newNote = document.createElement('small');
|
||||
newNote.className = 'text-danger ms-4';
|
||||
//newNote.innerHTML = `[ Notatka: <b>${data.note}</b> ]`;
|
||||
newNote.innerHTML = `[ <b>${data.note}</b> ]`;
|
||||
const idx = window.currentItems.findIndex(i => i.id === data.item_id);
|
||||
if (idx !== -1) {
|
||||
window.currentItems[idx].note = data.note;
|
||||
|
||||
const flexColumn = itemEl.querySelector('.d-flex.flex-column');
|
||||
if (flexColumn) {
|
||||
flexColumn.appendChild(newNote);
|
||||
} else {
|
||||
itemEl.appendChild(newNote);
|
||||
}
|
||||
const newItem = renderItem(window.currentItems[idx], true);
|
||||
const oldItem = document.getElementById(`item-${data.item_id}`);
|
||||
if (oldItem && newItem) {
|
||||
oldItem.replaceWith(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
showToast('Notatka dodana/zaktualizowana', 'success');
|
||||
});
|
||||
|
||||
socket.on('item_edited', data => {
|
||||
const nameSpan = document.getElementById(`name-${data.item_id}`);
|
||||
if (nameSpan) {
|
||||
let quantityBadge = '';
|
||||
if (data.new_quantity && data.new_quantity > 1) {
|
||||
quantityBadge = ` <span class="badge bg-secondary">x${data.new_quantity}</span>`;
|
||||
}
|
||||
nameSpan.innerHTML = `${data.new_name}${quantityBadge}`;
|
||||
}
|
||||
showToast(`Zaktualizowano produkt: ${data.new_name} (x${data.new_quantity})`, 'success');
|
||||
});
|
||||
|
||||
updateProgressBar();
|
||||
toggleEmptyPlaceholder();
|
||||
socket.on('item_edited', data => {
|
||||
const idx = window.currentItems.findIndex(i => i.id === data.item_id);
|
||||
if (idx !== -1) {
|
||||
window.currentItems[idx].name = data.new_name;
|
||||
window.currentItems[idx].quantity = data.new_quantity;
|
||||
|
||||
const newItem = renderItem(window.currentItems[idx], true);
|
||||
const oldItem = document.getElementById(`item-${data.item_id}`);
|
||||
if (oldItem && newItem) {
|
||||
oldItem.replaceWith(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
showToast(`Zaktualizowano produkt: ${data.new_name} (x${data.new_quantity})`, 'success');
|
||||
|
||||
updateProgressBar();
|
||||
toggleEmptyPlaceholder();
|
||||
});
|
||||
|
||||
// --- WAŻNE: zapisz dane do reconnect ---
|
||||
window.LIST_ID = listId;
|
||||
|
@@ -16,3 +16,24 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
localStorage.setItem("receiptSectionOpen", "false");
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const btn = document.getElementById("toggleReceiptBtn");
|
||||
const target = document.querySelector(btn.getAttribute("data-bs-target"));
|
||||
|
||||
function updateUI() {
|
||||
const isShown = target.classList.contains("show");
|
||||
btn.innerHTML = isShown
|
||||
? "📄 Ukryj sekcję paragonów"
|
||||
: "📄 Pokaż sekcję paragonów";
|
||||
|
||||
btn.classList.toggle("active", isShown);
|
||||
btn.classList.toggle("btn-outline-light", !isShown);
|
||||
btn.classList.toggle("btn-secondary", isShown);
|
||||
}
|
||||
|
||||
target.addEventListener("shown.bs.collapse", updateUI);
|
||||
target.addEventListener("hidden.bs.collapse", updateUI);
|
||||
|
||||
updateUI();
|
||||
});
|
@@ -65,15 +65,17 @@
|
||||
</div>
|
||||
|
||||
{% if orphan_files and request.path.endswith('/all') %}
|
||||
|
||||
<hr class="my-4">
|
||||
<h4 class="mt-3 mb-2 text-warning">Znalezione nieprzypisane pliki ({{ orphan_files_count }})</h4>
|
||||
<div class="row g-3">
|
||||
{% for f in orphan_files %}
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<div class="card bg-dark border-warning text-warning h-100">
|
||||
<img src="{{ url_for('uploaded_file', filename=f) }}" class="card-img-top"
|
||||
style="object-fit: cover; height: 200px;">
|
||||
<a href="{{ url_for('uploaded_file', filename=f) }}" class="glightbox" data-gallery="receipts"
|
||||
data-title="{{ r.filename }}">
|
||||
<img src="{{ url_for('uploaded_file', filename=f) }}" class="card-img-top"
|
||||
style="object-fit: cover; height: 200px;">
|
||||
</a>
|
||||
<div class="card-body text-center">
|
||||
<p class="small mb-1 fw-bold">{{ f }}</p>
|
||||
<div class="alert alert-warning small py-1 mb-2">Brak powiązania z listą!</div>
|
||||
@@ -88,8 +90,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<div class="modal fade" id="cropModal" tabindex="-1" aria-labelledby="cropModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
|
@@ -215,6 +215,8 @@
|
||||
const isShare = document.getElementById('items').dataset.isShare === 'true';
|
||||
window.IS_SHARE = isShare;
|
||||
window.LIST_ID = {{ list.id }};
|
||||
window.IS_OWNER = {{ 'true' if is_owner else 'false' }};
|
||||
|
||||
</script>
|
||||
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='mass_add.js') }}"></script>
|
||||
|
@@ -100,14 +100,13 @@
|
||||
{% endif %}
|
||||
<p id="total-expense2"><b>💸 Łącznie wydano:</b> {{ '%.2f'|format(total_expense) }} PLN</p>
|
||||
|
||||
<button class="btn btn-outline-light mb-3" type="button" data-bs-toggle="collapse" data-bs-target="#receiptSection"
|
||||
aria-expanded="false" aria-controls="receiptSection">
|
||||
<button id="toggleReceiptBtn" class="btn btn-outline-light mb-3 w-100 w-md-auto d-block mx-auto" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#receiptSection" aria-expanded="false" aria-controls="receiptSection">
|
||||
📄 Pokaż sekcję paragonów
|
||||
</button>
|
||||
<div class="collapse" id="receiptSection">
|
||||
<div class="collapse px-2 px-md-4" id="receiptSection">
|
||||
{% set receipt_pattern = 'list_' ~ list.id %}
|
||||
|
||||
<hr>
|
||||
<div class="mt-3 p-3 border border-secondary rounded bg-dark text-white {% if not receipt_files %}d-none{% endif %}"
|
||||
id="receiptAnalysisBlock">
|
||||
<h5>🧠 Analiza paragonów (OCR)</h5>
|
||||
|
Reference in New Issue
Block a user