zmiany ux i nowe funckje
This commit is contained in:
79
app.py
79
app.py
@@ -35,20 +35,6 @@ UPLOAD_FOLDER = app.config.get('UPLOAD_FOLDER', 'uploads')
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
AUTHORIZED_COOKIE_VALUE = app.config.get('AUTHORIZED_COOKIE_VALUE', '80d31cdfe63539c9')
|
||||
|
||||
PROTECTED_JS_FILES = {
|
||||
"live.js",
|
||||
"notes.js",
|
||||
"sockets.js",
|
||||
"product_suggestion.js",
|
||||
"expenses.js",
|
||||
"toggle_button.js",
|
||||
"user_management.js",
|
||||
"mass_add.js",
|
||||
"functions.js",
|
||||
"clickable_row.js",
|
||||
"receipt_section.js"
|
||||
}
|
||||
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
|
||||
failed_login_attempts = defaultdict(deque)
|
||||
@@ -273,7 +259,7 @@ def require_system_password():
|
||||
|
||||
if request.endpoint == 'static_bp.serve_js':
|
||||
requested_file = request.view_args.get("filename", "")
|
||||
if requested_file in PROTECTED_JS_FILES:
|
||||
if requested_file.endswith(".js"):
|
||||
return redirect(url_for('system_auth', next=request.url))
|
||||
else:
|
||||
return
|
||||
@@ -596,7 +582,7 @@ def all_products():
|
||||
|
||||
return {'allproducts': all_names}
|
||||
|
||||
@app.route('/upload_receipt/<int:list_id>', methods=['POST'])
|
||||
""" @app.route('/upload_receipt/<int:list_id>', methods=['POST'])
|
||||
def upload_receipt(list_id):
|
||||
if 'receipt' not in request.files:
|
||||
flash('Brak pliku', 'danger')
|
||||
@@ -618,6 +604,41 @@ def upload_receipt(list_id):
|
||||
return redirect(request.referrer)
|
||||
|
||||
flash('Niedozwolony format pliku', 'danger')
|
||||
return redirect(request.referrer) """
|
||||
|
||||
@app.route('/upload_receipt/<int:list_id>', methods=['POST'])
|
||||
def upload_receipt(list_id):
|
||||
if 'receipt' not in request.files:
|
||||
if request.is_json or request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'success': False, 'message': 'Brak pliku'}), 400
|
||||
flash('Brak pliku', 'danger')
|
||||
return redirect(request.referrer)
|
||||
|
||||
file = request.files['receipt']
|
||||
|
||||
if file.filename == '':
|
||||
if request.is_json or request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'success': False, 'message': 'Nie wybrano pliku'}), 400
|
||||
flash('Nie wybrano pliku', 'danger')
|
||||
return redirect(request.referrer)
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
full_filename = f"list_{list_id}_{filename}"
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], full_filename)
|
||||
|
||||
save_resized_image(file, file_path)
|
||||
|
||||
if request.is_json or request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
url = url_for('uploaded_file', filename=full_filename)
|
||||
return jsonify({'success': True, 'url': url})
|
||||
|
||||
flash('Wgrano paragon', 'success')
|
||||
return redirect(request.referrer)
|
||||
|
||||
if request.is_json or request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'success': False, 'message': 'Niedozwolony format pliku'}), 400
|
||||
flash('Niedozwolony format pliku', 'danger')
|
||||
return redirect(request.referrer)
|
||||
|
||||
@app.route('/uploads/<filename>')
|
||||
@@ -1096,10 +1117,19 @@ def demote_user(user_id):
|
||||
def handle_delete_item(data):
|
||||
item = Item.query.get(data['item_id'])
|
||||
if item:
|
||||
list_id = item.list_id
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
emit('item_deleted', {'item_id': item.id}, to=str(item.list_id))
|
||||
|
||||
purchased_count, total_count, percent = get_progress(list_id)
|
||||
|
||||
emit('progress_updated', {
|
||||
'purchased_count': purchased_count,
|
||||
'total_count': total_count,
|
||||
'percent': percent
|
||||
}, to=str(list_id))
|
||||
|
||||
@socketio.on('edit_item')
|
||||
def handle_edit_item(data):
|
||||
item = Item.query.get(data['item_id'])
|
||||
@@ -1188,6 +1218,14 @@ def handle_add_item(data):
|
||||
'added_by': current_user.username if current_user.is_authenticated else 'Gość'
|
||||
}, to=str(list_id), include_self=True)
|
||||
|
||||
purchased_count, total_count, percent = get_progress(list_id)
|
||||
|
||||
emit('progress_updated', {
|
||||
'purchased_count': purchased_count,
|
||||
'total_count': total_count,
|
||||
'percent': percent
|
||||
}, to=str(list_id))
|
||||
|
||||
@socketio.on('check_item')
|
||||
def handle_check_item(data):
|
||||
item = Item.query.get(data['item_id'])
|
||||
@@ -1265,6 +1303,15 @@ def handle_add_expense(data):
|
||||
'total': total
|
||||
}, to=str(list_id))
|
||||
|
||||
@socketio.on('receipt_uploaded')
|
||||
def handle_receipt_uploaded(data):
|
||||
list_id = data['list_id']
|
||||
url = data['url']
|
||||
|
||||
emit('receipt_added', {
|
||||
'url': url
|
||||
}, to=str(list_id), include_self=False)
|
||||
|
||||
@app.cli.command('create_db')
|
||||
def create_db():
|
||||
db.create_all()
|
||||
|
@@ -32,6 +32,16 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* rodzic już ma position-relative */
|
||||
.progress-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none; /* klikalne przyciski obok paska nie ucierpią */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress-thin {
|
||||
height: 12px;
|
||||
}
|
||||
@@ -250,3 +260,8 @@ input.form-control {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#empty-placeholder {
|
||||
font-style: italic;
|
||||
pointer-events: none;
|
||||
}
|
@@ -281,6 +281,7 @@ function updateListSmoothly(newItems) {
|
||||
itemsContainer.appendChild(fragment);
|
||||
|
||||
updateProgressBar();
|
||||
toggleEmptyPlaceholder();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
@@ -1,5 +1,25 @@
|
||||
const socket = io();
|
||||
|
||||
/*──────────────── placeholder pustej listy ────────────────*/
|
||||
function toggleEmptyPlaceholder() {
|
||||
const list = document.getElementById('items');
|
||||
if (!list) return;
|
||||
|
||||
// prawdziwe <li> to te z data‑name lub id="item‑…"
|
||||
const hasRealItems = list.querySelector('li[data-name], li[id^="item-"]') !== null;
|
||||
const placeholder = document.getElementById('empty-placeholder');
|
||||
|
||||
if (!hasRealItems && !placeholder) {
|
||||
const li = document.createElement('li');
|
||||
li.id = 'empty-placeholder';
|
||||
li.className = 'list-group-item bg-dark text-secondary text-center w-100';
|
||||
li.textContent = 'Brak produktów w tej liście.';
|
||||
list.appendChild(li);
|
||||
} else if (hasRealItems && placeholder) {
|
||||
placeholder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function setupList(listId, username) {
|
||||
socket.emit('join_list', { room: listId, username: username });
|
||||
|
||||
@@ -135,6 +155,7 @@ function setupList(listId, username) {
|
||||
|
||||
document.getElementById('items').appendChild(li);
|
||||
updateProgressBar();
|
||||
toggleEmptyPlaceholder();
|
||||
});
|
||||
|
||||
socket.on('item_deleted', data => {
|
||||
@@ -144,6 +165,7 @@ function setupList(listId, username) {
|
||||
}
|
||||
showToast('Usunięto produkt');
|
||||
updateProgressBar();
|
||||
toggleEmptyPlaceholder();
|
||||
});
|
||||
|
||||
socket.on('progress_updated', function(data) {
|
||||
@@ -197,10 +219,10 @@ function setupList(listId, username) {
|
||||
});
|
||||
|
||||
updateProgressBar();
|
||||
toggleEmptyPlaceholder();
|
||||
|
||||
// --- WAŻNE: zapisz dane do reconnect ---
|
||||
window.LIST_ID = listId;
|
||||
window.usernameForReconnect = username;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -3,7 +3,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const productList = document.getElementById('mass-add-list');
|
||||
|
||||
modal.addEventListener('show.bs.modal', async function () {
|
||||
// 🔥 Za każdym razem od nowa budujemy zbiór produktów już na liście
|
||||
let addedProducts = new Set();
|
||||
document.querySelectorAll('#items li').forEach(li => {
|
||||
if (li.dataset.name) {
|
||||
@@ -91,16 +90,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
productList.appendChild(li);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} catch (err) {
|
||||
productList.innerHTML = '<li class="list-group-item text-danger bg-dark">Błąd ładowania danych</li>';
|
||||
}
|
||||
});
|
||||
|
||||
// 🔥 Aktualizacja na żywo po dodaniu
|
||||
socket.on('item_added', data => {
|
||||
document.querySelectorAll('#mass-add-list li').forEach(li => {
|
||||
const itemName = li.firstChild.textContent.trim();
|
||||
|
85
static/js/receipt_upload.js
Normal file
85
static/js/receipt_upload.js
Normal file
@@ -0,0 +1,85 @@
|
||||
let receiptToastShown = false;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("receiptForm");
|
||||
const input = document.getElementById("receiptInput");
|
||||
const gallery = document.getElementById("receiptGallery");
|
||||
const progressContainer = document.getElementById("progressContainer");
|
||||
const progressBar = document.getElementById("progressBar");
|
||||
|
||||
if (!form || !input || !gallery) return;
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const file = input.files[0];
|
||||
if (!file) {
|
||||
showToast("Nie wybrano pliku!", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("receipt", file);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", form.action, true);
|
||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable) {
|
||||
const percent = Math.round((e.loaded / e.total) * 100);
|
||||
progressBar.style.width = percent + "%";
|
||||
progressBar.textContent = percent + "%";
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onloadstart = function () {
|
||||
progressContainer.style.display = "block";
|
||||
progressBar.style.width = "0%";
|
||||
progressBar.textContent = "0%";
|
||||
};
|
||||
|
||||
xhr.onloadend = function () {
|
||||
progressContainer.style.display = "none";
|
||||
progressBar.style.width = "0%";
|
||||
progressBar.textContent = "";
|
||||
input.value = "";
|
||||
};
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
const res = JSON.parse(xhr.responseText);
|
||||
if (res.success && res.url) {
|
||||
|
||||
fetch(window.location.href)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
const newGallery = doc.getElementById("receiptGallery");
|
||||
if (newGallery) {
|
||||
gallery.innerHTML = newGallery.innerHTML;
|
||||
|
||||
if (!receiptToastShown) {
|
||||
showToast("Wgrano paragon", "success");
|
||||
receiptToastShown = true;
|
||||
}
|
||||
socket.emit("receipt_uploaded", {
|
||||
list_id: LIST_ID,
|
||||
url: res.url
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showToast(res.message || "Błąd podczas wgrywania.", "danger");
|
||||
}
|
||||
} else {
|
||||
showToast("Błąd serwera. Spróbuj ponownie.", "danger");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
});
|
@@ -1,3 +1,5 @@
|
||||
let didReceiveFirstFullList = false;
|
||||
|
||||
// --- Automatyczny reconnect po powrocie do karty/przywróceniu internetu ---
|
||||
function reconnectIfNeeded() {
|
||||
if (!socket.connected) {
|
||||
@@ -75,14 +77,45 @@ socket.on('user_list', function(data) {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('full_list', function(data) {
|
||||
const itemsContainer = document.getElementById('items');
|
||||
const oldItems = Array.from(itemsContainer.querySelectorAll('li'));
|
||||
socket.on('receipt_added', function (data) {
|
||||
const gallery = document.getElementById("receiptGallery");
|
||||
if (!gallery) return;
|
||||
|
||||
if (isListDifferent(oldItems, data.items)) {
|
||||
updateListSmoothly(data.items);
|
||||
showToast('Lista została zaktualizowana', 'info');
|
||||
} else {
|
||||
updateListSmoothly(data.items);
|
||||
// Usuń placeholder, jeśli istnieje
|
||||
const alert = gallery.querySelector(".alert");
|
||||
if (alert) {
|
||||
alert.remove();
|
||||
}
|
||||
|
||||
// Sprawdź, czy już istnieje obraz z tym URL
|
||||
const existing = Array.from(gallery.querySelectorAll("img")).find(img => img.src === data.url);
|
||||
if (!existing) {
|
||||
const col = document.createElement("div");
|
||||
col.className = "col-6 col-md-4 col-lg-3 text-center";
|
||||
col.innerHTML = `
|
||||
<a href="${data.url}" data-lightbox="receipt" data-title="Paragon">
|
||||
<img src="${data.url}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
|
||||
</a>
|
||||
`;
|
||||
gallery.appendChild(col);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
socket.on('full_list', function (data) {
|
||||
const itemsContainer = document.getElementById('items');
|
||||
|
||||
const oldItems = Array.from(
|
||||
itemsContainer.querySelectorAll('li[data-name], li[id^="item-"]')
|
||||
);
|
||||
|
||||
const isDifferent = isListDifferent(oldItems, data.items);
|
||||
|
||||
updateListSmoothly(data.items);
|
||||
toggleEmptyPlaceholder();
|
||||
|
||||
if (didReceiveFirstFullList && isDifferent) {
|
||||
showToast('Lista została zaktualizowana', 'info');
|
||||
}
|
||||
didReceiveFirstFullList = true;
|
||||
});
|
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
|
||||
{% if not image_files %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<div class="alert alert-info text-center" role="alert">
|
||||
Nie wgrano paragonów.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@@ -53,12 +53,19 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
📊 Postęp listy — {{ purchased_count }}/{{ total_count }} kupionych ({{ percent|round(0) }}%)
|
||||
</h5>
|
||||
|
||||
<div class="progress progress-dark">
|
||||
<div id="progress-bar" class="progress-bar bg-warning text-dark fw-bold" role="progressbar"
|
||||
style="width: {{ percent }}%;" aria-valuenow="{{ percent }}"
|
||||
aria-valuemin="0" aria-valuemax="100">
|
||||
{{ percent|round(0) }}%
|
||||
<div class="progress progress-dark position-relative">
|
||||
{# właściwy pasek postępu #}
|
||||
<div id="progress-bar"
|
||||
class="progress-bar bg-warning text-dark"
|
||||
role="progressbar"
|
||||
style="width: {{ percent }}%;"
|
||||
aria-valuenow="{{ percent }}" aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
|
||||
<span class="progress-label small fw-bold
|
||||
{% if percent < 50 %}text-white{% else %}text-dark{% endif %}">
|
||||
{{ percent|round(0) }}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if total_expense > 0 %}
|
||||
@@ -66,7 +73,7 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
💸 Łącznie wydano: {{ '%.2f'|format(total_expense) }} PLN
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="total-expense2" class="text-success fw-bold mb-3" style="display: none;">
|
||||
<div id="total-expense2" class="text-success fw-bold mb-3">
|
||||
💸 Łącznie wydano: 0.00 PLN
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -97,6 +104,11 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li id="empty-placeholder"
|
||||
class="list-group-item bg-dark text-secondary text-center w-100">
|
||||
Brak produktów w tej liście.
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -118,10 +130,11 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
{% endif %}
|
||||
|
||||
{% set receipt_pattern = 'list_' ~ list.id %}
|
||||
{% if receipt_files %}
|
||||
<hr>
|
||||
<h5 class="mt-4">📸 Paragony dodane do tej listy</h5>
|
||||
<div class="row g-3 mt-2">
|
||||
<hr>
|
||||
<h5 class="mt-4">📸 Paragony dodane do tej listy</h5>
|
||||
|
||||
<div class="row g-3 mt-2" id="receiptGallery">
|
||||
{% if receipt_files %}
|
||||
{% for file in receipt_files %}
|
||||
<div class="col-6 col-md-4 col-lg-3 text-center">
|
||||
<a href="{{ url_for('uploaded_file', filename=file) }}" data-lightbox="receipt" data-title="Paragon">
|
||||
@@ -129,11 +142,12 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<hr>
|
||||
<p><span class="badge bg-secondary">Brak wgranych paragonów do tej listy.</span></p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="alert alert-info text-center w-100" role="alert">
|
||||
Brak wgranych paragonów do tej listy.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="massAddModal" tabindex="-1" aria-labelledby="massAddModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
@@ -158,6 +172,7 @@ Lista: <strong>{{ list.title }}</strong>
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_upload.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -37,7 +37,12 @@
|
||||
{% if list.is_archived %}disabled{% else %}onclick="openNoteModal(event, {{ item.id }})"{% endif %}>
|
||||
📝
|
||||
</button>
|
||||
</li>
|
||||
</li>
|
||||
{% else %}
|
||||
<li id="empty-placeholder"
|
||||
class="list-group-item bg-dark text-secondary text-center w-100">
|
||||
Brak produktów w tej liście.
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -64,9 +69,11 @@
|
||||
</button>
|
||||
<div class="collapse" id="receiptSection">
|
||||
{% set receipt_pattern = 'list_' ~ list.id %}
|
||||
{% if receipt_files %}
|
||||
<h5 class="mt-4">📸 Paragony dodane do tej listy</h5>
|
||||
<div class="row g-3 mt-2">
|
||||
|
||||
<h5 class="mt-4">📸 Paragony dodane do tej listy</h5>
|
||||
|
||||
<div class="row g-3 mt-2" id="receiptGallery">
|
||||
{% if receipt_files %}
|
||||
{% for file in receipt_files %}
|
||||
<div class="col-6 col-md-4 col-lg-3 text-center">
|
||||
<a href="{{ url_for('uploaded_file', filename=file) }}" data-lightbox="receipt" data-title="Paragon">
|
||||
@@ -74,21 +81,27 @@
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p><span class="badge bg-secondary">Brak wgranych paragonów do tej listy.</span></p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="alert alert-info text-center w-100" role="alert">
|
||||
Brak wgranych paragonów do tej listy.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not list.is_archived %}
|
||||
<hr>
|
||||
<h5>📤 Dodaj zdjęcie paragonu</h5>
|
||||
<form action="{{ url_for('upload_receipt', list_id=list.id) }}" method="post" enctype="multipart/form-data">
|
||||
<form id="receiptForm" action="{{ url_for('upload_receipt', list_id=list.id) }}" method="post" enctype="multipart/form-data">
|
||||
<div class="input-group mb-2">
|
||||
<input type="file" name="receipt" accept="image/*" capture="environment" class="form-control bg-dark text-white border-secondary custom-file-input" id="receiptInput">
|
||||
<input type="file" name="receipt" accept="image/*" capture="environment" class="form-control bg-dark text-white border-secondary" id="receiptInput">
|
||||
<button type="submit" class="btn btn-success rounded-end">➕ Wgraj</button>
|
||||
</div>
|
||||
<div id="progressContainer" class="progress mb-2" style="height: 20px; display: none;">
|
||||
<div id="progressBar" class="progress-bar bg-success fw-bold" role="progressbar" style="width: 0%;">0%</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Modal notatki -->
|
||||
@@ -116,6 +129,7 @@
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='notes.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='clickable_row.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_section.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_upload.js') }}"></script>
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
</script>
|
||||
|
@@ -65,13 +65,19 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress progress-dark progress-thin mt-2">
|
||||
<div class="progress-bar bg-warning text-dark fw-bold small" role="progressbar" style="width: {{ percent }}%;" aria-valuenow="{{ percent }}" aria-valuemin="0" aria-valuemax="100">
|
||||
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
|
||||
{% if l.total_expense > 0 %}
|
||||
— 💸 {{ '%.2f'|format(l.total_expense) }} PLN
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="progress progress-dark progress-thin mt-2 position-relative">
|
||||
<div class="progress-bar bg-warning text-dark"
|
||||
role="progressbar"
|
||||
style="width: {{ percent }}%;"
|
||||
aria-valuenow="{{ percent }}" aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
<span class="progress-label small fw-bold
|
||||
{% if percent < 50 %}text-white{% else %}text-dark{% endif %}">
|
||||
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
|
||||
{% if l.total_expense > 0 %}
|
||||
— 💸 {{ '%.2f'|format(l.total_expense) }} PLN
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
@@ -93,19 +99,25 @@
|
||||
<span class="fw-bold">{{ l.title }} (Autor: {{ l.owner.username }})</span>
|
||||
<a href="/guest-list/{{ l.id }}" class="btn btn-sm btn-outline-light">📄 Otwórz</a>
|
||||
</div>
|
||||
<div class="progress progress-dark progress-thin mt-2">
|
||||
<div class="progress-bar bg-warning text-dark fw-bold small" role="progressbar" style="width: {{ percent }}%" aria-valuenow="{{ percent }}" aria-valuemin="0" aria-valuemax="100">
|
||||
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
|
||||
{% if l.total_expense > 0 %}
|
||||
— 💸 {{ '%.2f'|format(l.total_expense) }} PLN
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<div class="progress progress-dark progress-thin mt-2 position-relative">
|
||||
<div class="progress-bar bg-warning text-dark"
|
||||
role="progressbar"
|
||||
style="width: {{ percent }}%;"
|
||||
aria-valuenow="{{ percent }}" aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
<span class="progress-label small fw-bold
|
||||
{% if percent < 50 %}text-white{% else %}text-dark{% endif %}">
|
||||
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0) }}%)
|
||||
{% if l.total_expense > 0 %}
|
||||
— 💸 {{ '%.2f'|format(l.total_expense) }} PLN
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p><span class="badge bg-secondary">Brak dostępnych list publicznych do wyświetlenia.</span></p>
|
||||
<p><span class="badge rounded-pill bg-secondary opacity-75">Brak dostępnych list publicznych do wyświetlenia</span></p>
|
||||
{% endif %}
|
||||
|
||||
<div class="modal fade" id="archivedModal" tabindex="-1" aria-labelledby="archivedModalLabel" aria-hidden="true">
|
||||
@@ -126,7 +138,9 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p><span class="badge bg-secondary">Nie masz żadnych zarchiwizowanych list.</span></p>
|
||||
<div class="alert alert-info text-center" role="alert">
|
||||
Nie masz żadnych zarchiwizowanych list.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
Reference in New Issue
Block a user