poprawki w js

This commit is contained in:
Mateusz Gruszczyński
2025-07-16 09:04:01 +02:00
parent 53394469de
commit d3e50305a7
24 changed files with 779 additions and 817 deletions

View File

@@ -10,7 +10,8 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark rounded mb-4">
<div class="container-fluid p-0">
<a class="navbar-brand" href="#">Funkcje:</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#adminNavbar" aria-controls="adminNavbar" aria-expanded="false" aria-label="Przełącz nawigację">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#adminNavbar"
aria-controls="adminNavbar" aria-expanded="false" aria-label="Przełącz nawigację">
<span class="navbar-toggler-icon"></span>
</button>
@@ -26,7 +27,8 @@
<a class="nav-link" href="/admin/products">🛍️ Produkty</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-danger" href="#" id="clearDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<a class="nav-link dropdown-toggle text-danger" href="#" id="clearDropdown" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
🗑️ Czyszczenie
</a>
<ul class="dropdown-menu">
@@ -57,7 +59,7 @@
<h5>🔥 Najczęściej kupowane produkty:</h5>
<ul class="mb-0">
{% for name, count in top_products %}
<li>{{ name }} — {{ count }}×</li>
<li>{{ name }} — {{ count }}×</li>
{% endfor %}
</ul>
</div>
@@ -74,7 +76,8 @@
<li><strong>Obecny rok:</strong> {{ '%.2f'|format(year_expense_sum) }} PLN</li>
<li><strong>Całkowite:</strong> {{ '%.2f'|format(total_expense_sum) }} PLN</li>
</ul>
<button type="button" class="btn btn-outline-primary w-100 mt-3" data-bs-toggle="modal" data-bs-target="#expensesChartModal" id="loadExpensesBtn">
<button type="button" class="btn btn-outline-primary w-100 mt-3" data-bs-toggle="modal"
data-bs-target="#expensesChartModal" id="loadExpensesBtn">
📊 Pokaż wykres wydatków
</button>
</div>
@@ -82,124 +85,125 @@
</div>
<h3 class="mt-4">📄 Wszystkie listy zakupowe</h3>
<form method="post" action="{{ url_for('delete_selected_lists') }}">
<div class="table-responsive">
<table class="table table-dark table-striped align-middle">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>ID</th>
<th>Tytuł</th>
<th>Status</th>
<th>Utworzono</th>
<th>Właściciel</th>
<th>Produkty</th>
<th>Wypełnienie</th>
<th>Komentarze</th>
<th>Paragony</th>
<th>Wydatki</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for e in enriched_lists %}
{% set l = e.list %}
<tr>
<td><input type="checkbox" name="list_ids" value="{{ l.id }}"></td>
<td>{{ l.id }}</td>
<td class="fw-bold">
<a href="{{ url_for('view_list', list_id=l.id) }}" class="text-white">{{ l.title }}</a>
</td>
<td>
{% if l.is_archived %}
<h3 class="mt-4">📄 Wszystkie listy zakupowe</h3>
<form method="post" action="{{ url_for('delete_selected_lists') }}">
<div class="table-responsive">
<table class="table table-dark table-striped align-middle">
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>ID</th>
<th>Tytuł</th>
<th>Status</th>
<th>Utworzono</th>
<th>Właściciel</th>
<th>Produkty</th>
<th>Wypełnienie</th>
<th>Komentarze</th>
<th>Paragony</th>
<th>Wydatki</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for e in enriched_lists %}
{% set l = e.list %}
<tr>
<td><input type="checkbox" name="list_ids" value="{{ l.id }}"></td>
<td>{{ l.id }}</td>
<td class="fw-bold">
<a href="{{ url_for('view_list', list_id=l.id) }}" class="text-white">{{ l.title }}</a>
</td>
<td>
{% if l.is_archived %}
<span class="badge bg-secondary">Archiwalna</span>
{% elif l.is_temporary and l.expires_at and l.expires_at < now %}
<span class="badge bg-warning text-dark">Wygasła</span>
{% else %}
<span class="badge bg-success">Aktywna</span>
{% endif %}
</td>
<td>{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }}</td>
<td>
{% if l.owner_id %}
{% elif l.is_temporary and l.expires_at and l.expires_at < now %} <span
class="badge bg-warning text-dark">Wygasła</span>
{% else %}
<span class="badge bg-success">Aktywna</span>
{% endif %}
</td>
<td>{{ l.created_at.strftime('%Y-%m-%d %H:%M') if l.created_at else '-' }}</td>
<td>
{% if l.owner_id %}
{{ l.owner_id }} / {{ l.owner.username if l.owner else 'Brak użytkownika' }}
{% else %}
{% else %}
-
{% endif %}
</td>
<td>{{ e.total_count }}</td>
<td>{{ e.purchased_count }}/{{ e.total_count }} ({{ e.percent }}%)</td>
<td>{{ e.comments_count }}</td>
<td>{{ e.receipts_count }}</td>
<td>
{% if e.total_expense > 0 %}
{% endif %}
</td>
<td>{{ e.total_count }}</td>
<td>{{ e.purchased_count }}/{{ e.total_count }} ({{ e.percent }}%)</td>
<td>{{ e.comments_count }}</td>
<td>{{ e.receipts_count }}</td>
<td>
{% if e.total_expense > 0 %}
{{ '%.2f'|format(e.total_expense) }} PLN
{% else %}
{% else %}
-
{% endif %}
</td>
<td class="d-flex flex-wrap gap-1">
<a href="{{ url_for('edit_list', list_id=l.id) }}" class="btn btn-sm btn-outline-primary">✏️ Edytuj</a>
<a href="{{ url_for('delete_list', list_id=l.id) }}" class="btn btn-sm btn-outline-danger">🗑️ Usuń</a>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</td>
<td class="d-flex flex-wrap gap-1">
<a href="{{ url_for('edit_list', list_id=l.id) }}" class="btn btn-sm btn-outline-primary">✏️ Edytuj</a>
<a href="{{ url_for('delete_list', list_id=l.id) }}" class="btn btn-sm btn-outline-danger">🗑️ Usuń</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<button type="submit" class="btn btn-danger mt-2">🗑️ Usuń zaznaczone listy</button>
</form>
</table>
</div>
<button type="submit" class="btn btn-danger mt-2">🗑️ Usuń zaznaczone listy</button>
</form>
<div class="modal fade" id="expensesChartModal" tabindex="-1" aria-labelledby="expensesChartModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content bg-dark text-white rounded">
<div class="modal-header border-0">
<div>
<h5 class="modal-title m-0" id="expensesChartModalLabel">📊 Wydatki</h5>
<small id="chartRangeLabel" class="text-muted">Widok: miesięczne</small>
</div>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<div class="modal-body pt-0">
<div class="d-flex flex-wrap gap-2 mb-3">
<button class="btn btn-outline-light btn-sm range-btn active" data-range="monthly">📅 Miesięczne</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="quarterly">📊 Kwartalne</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="halfyearly">🗓️ Półroczne</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="yearly">📆 Roczne</button>
<div class="modal fade" id="expensesChartModal" tabindex="-1" aria-labelledby="expensesChartModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content bg-dark text-white rounded">
<div class="modal-header border-0">
<div>
<h5 class="modal-title m-0" id="expensesChartModalLabel">📊 Wydatki</h5>
<small id="chartRangeLabel" class="text-muted">Widok: miesięczne</small>
</div>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<div class="modal-body pt-0">
<div class="d-flex flex-wrap gap-2 mb-3">
<button class="btn btn-outline-light btn-sm range-btn active" data-range="monthly">📅 Miesięczne</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="quarterly">📊 Kwartalne</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="halfyearly">🗓️ Półroczne</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="yearly">📆 Roczne</button>
</div>
<div class="input-group input-group-sm mb-3 w-100" style="max-width: 570px;">
<span class="input-group-text bg-secondary text-white border-secondary">Od</span>
<input type="date" class="form-control bg-dark text-white border-secondary flex-grow-1" id="startDate">
<span class="input-group-text bg-secondary text-white border-secondary">Do</span>
<input type="date" class="form-control bg-dark text-white border-secondary flex-grow-1" id="endDate">
<button class="btn btn-outline-success" id="customRangeBtn">Pokaż dane z zakresu 📅</button>
</div>
<div class="input-group input-group-sm mb-3 w-100" style="max-width: 570px;">
<span class="input-group-text bg-secondary text-white border-secondary">Od</span>
<input type="date" class="form-control bg-dark text-white border-secondary flex-grow-1" id="startDate">
<span class="input-group-text bg-secondary text-white border-secondary">Do</span>
<input type="date" class="form-control bg-dark text-white border-secondary flex-grow-1" id="endDate">
<button class="btn btn-outline-success" id="customRangeBtn">Pokaż dane z zakresu 📅</button>
</div>
<div class="bg-dark rounded p-2">
<canvas id="expensesChart" height="100"></canvas>
<div class="bg-dark rounded p-2">
<canvas id="expensesChart" height="100"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
{% block scripts %}
<script src="{{ url_for('static_bp.serve_js_lib', filename='chart.js') }}"></script>
<script>
document.getElementById('select-all').addEventListener('click', function(){
const checkboxes = document.querySelectorAll('input[name="list_ids"]');
checkboxes.forEach(cb => cb.checked = this.checked);
});
</script>
<script src="{{ url_for('static_bp.serve_js', filename='expenses.js') }}"></script>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static_bp.serve_js_lib', filename='chart.js') }}"></script>
<script>
document.getElementById('select-all').addEventListener('click', function () {
const checkboxes = document.querySelectorAll('input[name="list_ids"]');
checkboxes.forEach(cb => cb.checked = this.checked);
});
</script>
<script src="{{ url_for('static_bp.serve_js', filename='expenses.js') }}"></script>
{% endblock %}
<div class="info-bar-fixed">
Python: {{ python_version.split()[0] }} | {{ system_info }} | RAM app: {{ app_memory }}
</div>
<div class="info-bar-fixed">
Python: {{ python_version.split()[0] }} | {{ system_info }} | RAM app: {{ app_memory }}
</div>
{% endblock %}
{% endblock %}

View File

@@ -12,39 +12,44 @@
<h4 class="card-title">📄 Podstawowe informacje</h4>
<form method="post" class="mt-3">
<input type="hidden" name="action" value="save">
<div class="mb-3">
<label for="title" class="form-label">Nazwa listy</label>
<input type="text" class="form-control bg-dark text-white border-secondary rounded" id="title" name="title" value="{{ list.title }}" required>
<input type="text" class="form-control bg-dark text-white border-secondary rounded" id="title" name="title"
value="{{ list.title }}" required>
</div>
<div class="mb-3">
<label for="amount" class="form-label">Całkowity wydatek (PLN)</label>
<input type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary rounded" id="amount" name="amount" value="{{ '%.2f'|format(total_expense) }}">
<input type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary rounded"
id="amount" name="amount" value="{{ '%.2f'|format(total_expense) }}">
</div>
<div class="mb-3">
<label for="owner_id" class="form-label">Właściciel</label>
<select class="form-select bg-dark text-white border-secondary" id="owner_id" name="owner_id">
{% for user in users %}
<option value="{{ user.id }}" {% if list.owner_id == user.id %}selected{% endif %}>{{ user.username }}</option>
<option value="{{ user.id }}" {% if list.owner_id==user.id %}selected{% endif %}>{{ user.username }}</option>
{% endfor %}
</select>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="archived" name="archived" {% if list.is_archived %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="archived" name="archived" {% if list.is_archived %}checked{%
endif %}>
<label class="form-check-label" for="archived">Archiwalna</label>
</div>
<div class="form-check form-switch mb-4">
<input class="form-check-input" type="checkbox" id="public" name="public" {% if list.is_public %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="public" name="public" {% if list.is_public %}checked{% endif
%}>
<label class="form-check-label" for="public">Publiczna</label>
</div>
<div class="mb-4">
<label class="form-label">Link do udostępnienia</label>
<input type="text" class="form-control bg-dark text-white border-secondary rounded" readonly value="{{ request.url_root }}share/{{ list.share_token }}">
<input type="text" class="form-control bg-dark text-white border-secondary rounded" readonly
value="{{ request.url_root }}share/{{ list.share_token }}">
</div>
<button type="submit" class="btn btn-success me-2">💾 Zapisz zmiany</button>
@@ -58,98 +63,101 @@
<form method="post" class="row g-2 mb-3">
<input type="hidden" name="action" value="add_item">
<div class="col-md-8">
<input type="text" class="form-control bg-dark text-white border-secondary rounded" name="item_name" placeholder="Nazwa produktu" required>
<input type="text" class="form-control bg-dark text-white border-secondary rounded" name="item_name"
placeholder="Nazwa produktu" required>
</div>
<div class="col-md-1">
<input type="number" class="form-control bg-dark text-white border-secondary rounded" name="quantity" min="1" value="1">
<input type="number" class="form-control bg-dark text-white border-secondary rounded" name="quantity" min="1"
value="1">
</div>
<div class="col-md-3 d-grid">
<button type="submit" class="btn btn-outline-success"> Dodaj</button>
</div>
</form>
<div class="table-responsive">
<table class="table table-dark table-bordered align-middle">
<thead>
<tr>
<th scope="col">Nazwa</th>
<th scope="col">Status</th>
<th scope="col">Oznaczenie</th>
<th scope="col">Usuń</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>
<strong>{{ item.name }}</strong>
<small class="text-muted">(x{{ item.quantity }})</small>
</td>
<td>
{% if item.purchased %}
<div class="table-responsive">
<table class="table table-dark table-bordered align-middle">
<thead>
<tr>
<th scope="col">Nazwa</th>
<th scope="col">Status</th>
<th scope="col">Oznaczenie</th>
<th scope="col">Usuń</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>
<strong>{{ item.name }}</strong>
<small class="text-muted">(x{{ item.quantity }})</small>
</td>
<td>
{% if item.purchased %}
<span class="badge bg-success">✔️ Kupiony</span>
{% elif item.not_purchased %}
{% elif item.not_purchased %}
<span class="badge bg-warning text-dark">⚠️ Nie kupione</span>
{% else %}
{% else %}
<span class="badge bg-secondary">Nieoznaczony</span>
{% endif %}
</td>
<td>
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-grid gap-1">
<input type="hidden" name="action" value="toggle_purchased">
<input type="hidden" name="item_id" value="{{ item.id }}">
{% if not item.not_purchased %}
{% endif %}
</td>
<td>
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-grid gap-1">
<input type="hidden" name="action" value="toggle_purchased">
<input type="hidden" name="item_id" value="{{ item.id }}">
{% if not item.not_purchased %}
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-grid gap-1">
<input type="hidden" name="action" value="toggle_purchased">
<input type="hidden" name="item_id" value="{{ item.id }}">
{% if item.purchased %}
<button type="submit" class="btn btn-outline-warning btn-sm">🚫 Odznacz</button>
<button type="submit" class="btn btn-outline-warning btn-sm">🚫 Odznacz</button>
{% else %}
<button type="submit" class="btn btn-outline-success btn-sm">✅ Oznacz</button>
<button type="submit" class="btn btn-outline-success btn-sm">✅ Oznacz</button>
{% endif %}
</form>
{% endif %}
</form>
{% endif %}
</form>
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-grid gap-1 mt-1">
<input type="hidden" name="action" value="mark_not_purchased">
<input type="hidden" name="item_id" value="{{ item.id }}">
<button type="submit" class="btn btn-outline-warning btn-sm">⚠️ Nie kupione</button>
</form>
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-grid gap-1 mt-1">
<input type="hidden" name="action" value="mark_not_purchased">
<input type="hidden" name="item_id" value="{{ item.id }}">
<button type="submit" class="btn btn-outline-warning btn-sm">⚠️ Nie kupione</button>
</form>
{% if item.not_purchased %}
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-grid gap-1 mt-3 border-top pt-2">
{% if item.not_purchased %}
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}"
class="d-grid gap-1 mt-3 border-top pt-2">
<input type="hidden" name="action" value="unmark_not_purchased">
<input type="hidden" name="item_id" value="{{ item.id }}">
<button type="submit" class="btn btn-outline-success btn-sm">✅ Przywróć na liste</button>
</form>
{% if item.not_purchased_reason %}
<div class="mt-2 text-warning small border-top pt-2">
<strong>Powód:</strong> {{ item.not_purchased_reason }}
</div>
<div class="mt-2 text-warning small border-top pt-2">
<strong>Powód:</strong> {{ item.not_purchased_reason }}
</div>
{% endif %}
{% endif %}
</td>
{% endif %}
</td>
<td>
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-inline">
<input type="hidden" name="action" value="delete_item">
<input type="hidden" name="item_id" value="{{ item.id }}">
<button type="submit" class="btn btn-danger btn-sm w-100">🗑️ Usuń</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="text-center text-muted">Brak produktów.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<td>
<form method="post" action="{{ url_for('edit_list', list_id=list.id) }}" class="d-inline">
<input type="hidden" name="action" value="delete_item">
<input type="hidden" name="item_id" value="{{ item.id }}">
<button type="submit" class="btn btn-danger btn-sm w-100">🗑️ Usuń</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="text-center text-muted">Brak produktów.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
@@ -166,32 +174,35 @@
<div class="row g-3">
{% for img in receipts %}
{% set file_path = upload_folder ~ '/' ~ img %}
{% set file_size = (file_path | filesizeformat) %}
{% set upload_time = (file_path | filemtime) %}
<div class="col-6 col-md-4 col-lg-3">
<div class="card bg-dark text-white h-100">
<a href="{{ url_for('uploaded_file', filename=img) }}" data-lightbox="receipts" data-title="{{ img }}" class="glightbox">
<img src="{{ url_for('uploaded_file', filename=img) }}" class="card-img-top" style="object-fit: cover; height: 200px;">
</a>
<div class="card-body text-center">
<p class="small text-truncate mb-1">{{ img }}</p>
<p class="small mb-1">Rozmiar: {{ file_size }}</p>
<p class="small mb-1">Wgrano: {{ upload_time.strftime('%Y-%m-%d %H:%M') }}</p>
<a href="{{ url_for('delete_receipt', filename=img) }}?next={{ url_for('edit_list', list_id=list.id) }}" class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
</div>
{% set file_path = upload_folder ~ '/' ~ img %}
{% set file_size = (file_path | filesizeformat) %}
{% set upload_time = (file_path | filemtime) %}
<div class="col-6 col-md-4 col-lg-3">
<div class="card bg-dark text-white h-100">
<a href="{{ url_for('uploaded_file', filename=img) }}" data-lightbox="receipts" data-title="{{ img }}"
class="glightbox">
<img src="{{ url_for('uploaded_file', filename=img) }}" class="card-img-top"
style="object-fit: cover; height: 200px;">
</a>
<div class="card-body text-center">
<p class="small text-truncate mb-1">{{ img }}</p>
<p class="small mb-1">Rozmiar: {{ file_size }}</p>
<p class="small mb-1">Wgrano: {{ upload_time.strftime('%Y-%m-%d %H:%M') }}</p>
<a href="{{ url_for('delete_receipt', filename=img) }}?next={{ url_for('edit_list', list_id=list.id) }}"
class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not receipts %}
<div class="alert alert-info text-center mt-3" role="alert">
Brak paragonów.
</div>
<div class="alert alert-info text-center mt-3" role="alert">
Brak paragonów.
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -30,18 +30,20 @@
<td class="fw-bold">{{ item.name }}</td>
<td>
{% if item.added_by %}
{{ users_dict.get(item.added_by, 'Nieznany') }}
{{ users_dict.get(item.added_by, 'Nieznany') }}
{% else %}
Gość
Gość
{% endif %}
</td>
<td>
{% set suggestion = suggestions_dict.get(item.name.lower()) %}
{% if suggestion %}
✅ Istnieje (ID: {{ suggestion.id }})
<button class="btn btn-sm btn-outline-danger ms-1 delete-suggestion-btn" data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
✅ Istnieje (ID: {{ suggestion.id }})
<button class="btn btn-sm btn-outline-danger ms-1 delete-suggestion-btn"
data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
{% else %}
<button class="btn btn-sm btn-outline-primary sync-btn" data-item-id="{{ item.id }}">🔄 Synchronizuj</button>
<button class="btn btn-sm btn-outline-primary sync-btn" data-item-id="{{ item.id }}">🔄
Synchronizuj</button>
{% endif %}
</td>
<td>
@@ -77,15 +79,16 @@
</thead>
<tbody>
{% for suggestion in suggestions_dict.values() %}
{% if suggestion.name.lower() not in item_names %}
<tr>
<td>{{ suggestion.id }}</td>
<td class="fw-bold">{{ suggestion.name }}</td>
<td>
<button class="btn btn-sm btn-outline-danger delete-suggestion-btn" data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
</td>
</tr>
{% endif %}
{% if suggestion.name.lower() not in item_names %}
<tr>
<td>{{ suggestion.id }}</td>
<td class="fw-bold">{{ suggestion.name }}</td>
<td>
<button class="btn btn-sm btn-outline-danger delete-suggestion-btn"
data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
</td>
</tr>
{% endif %}
{% endfor %}
{% if suggestions_dict|length == 0 %}
<tr>
@@ -101,4 +104,4 @@
<script src="{{ url_for('static_bp.serve_js', filename='product_suggestion.js') }}"></script>
{% endblock %}
{% endblock %}
{% endblock %}

View File

@@ -9,33 +9,37 @@
<div class="row g-3">
{% for img in image_files %}
{% set list_id = img.split('_')[1] if '_' in img else None %}
{% set file_path = upload_folder ~ '/' ~ img %}
{% set file_size = (file_path | filesizeformat) %}
{% set upload_time = (file_path | filemtime) %}
<div class="col-6 col-md-4 col-lg-3">
<div class="card bg-dark text-white h-100">
<a href="{{ url_for('uploaded_file', filename=img) }}" class="glightbox" data-gallery="receipts" data-title="{{ img }}">
<img src="{{ url_for('uploaded_file', filename=img) }}" class="card-img-top" style="object-fit: cover; height: 200px;">
</a>
<div class="card-body text-center">
<p class="small text-truncate mb-1">{{ img }}</p>
<p class="small mb-1">Rozmiar: {{ file_size }}</p>
<p class="small mb-1">Wgrano: {{ upload_time.strftime('%Y-%m-%d %H:%M') }}</p>
{% if list_id %}
<a href="{{ url_for('edit_list', list_id=list_id|int) }}" class="btn btn-sm btn-outline-light w-100 mb-2">✏️ Edytuj listę #{{ list_id }}</a>
{% endif %}
<a href="{{ url_for('delete_receipt', filename=img) }}?next={{ request.path }}" class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
</div>
{% set list_id = img.split('_')[1] if '_' in img else None %}
{% set file_path = upload_folder ~ '/' ~ img %}
{% set file_size = (file_path | filesizeformat) %}
{% set upload_time = (file_path | filemtime) %}
<div class="col-6 col-md-4 col-lg-3">
<div class="card bg-dark text-white h-100">
<a href="{{ url_for('uploaded_file', filename=img) }}" class="glightbox" data-gallery="receipts"
data-title="{{ img }}">
<img src="{{ url_for('uploaded_file', filename=img) }}" class="card-img-top"
style="object-fit: cover; height: 200px;">
</a>
<div class="card-body text-center">
<p class="small text-truncate mb-1">{{ img }}</p>
<p class="small mb-1">Rozmiar: {{ file_size }}</p>
<p class="small mb-1">Wgrano: {{ upload_time.strftime('%Y-%m-%d %H:%M') }}</p>
{% if list_id %}
<a href="{{ url_for('edit_list', list_id=list_id|int) }}" class="btn btn-sm btn-outline-light w-100 mb-2">✏️
Edytuj listę #{{ list_id }}</a>
{% endif %}
<a href="{{ url_for('delete_receipt', filename=img) }}?next={{ request.path }}"
class="btn btn-sm btn-outline-danger w-100">🗑️ Usuń</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% if not image_files %}
<div class="alert alert-info text-center mt-4" role="alert">
Nie wgrano żadnych paragonów.
</div>
<div class="alert alert-info text-center mt-4" role="alert">
Nie wgrano żadnych paragonów.
</div>
{% endif %}
{% endblock %}
{% endblock %}

View File

@@ -14,10 +14,12 @@
<form method="post" action="{{ url_for('add_user') }}">
<div class="row g-2">
<div class="col-md-4">
<input type="text" name="username" class="form-control bg-dark text-white border-secondary rounded" placeholder="Nazwa użytkownika" required>
<input type="text" name="username" class="form-control bg-dark text-white border-secondary rounded"
placeholder="Nazwa użytkownika" required>
</div>
<div class="col-md-4">
<input type="password" name="password" class="form-control bg-dark text-white border-secondary rounded" placeholder="Hasło" required>
<input type="password" name="password" class="form-control bg-dark text-white border-secondary rounded"
placeholder="Hasło" required>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-outline-success w-100">Dodaj użytkownika</button>
@@ -43,24 +45,20 @@
<td class="fw-bold">{{ user.username }}</td>
<td>
{% if user.is_admin %}
<span class="badge bg-primary">Admin</span>
<span class="badge bg-primary">Admin</span>
{% else %}
<span class="badge bg-secondary">Użytkownik</span>
<span class="badge bg-secondary">Użytkownik</span>
{% endif %}
</td>
<td>
<button
class="btn btn-sm btn-outline-warning me-1"
data-bs-toggle="modal"
data-bs-target="#resetPasswordModal"
data-user-id="{{ user.id }}"
data-username="{{ user.username }}">
<button class="btn btn-sm btn-outline-warning me-1" data-bs-toggle="modal" data-bs-target="#resetPasswordModal"
data-user-id="{{ user.id }}" data-username="{{ user.username }}">
🔑 Ustaw hasło
</button>
{% if not user.is_admin %}
<a href="/admin/promote_user/{{ user.id }}" class="btn btn-sm btn-outline-info">⬆️ Ustaw admina</a>
<a href="/admin/promote_user/{{ user.id }}" class="btn btn-sm btn-outline-info">⬆️ Ustaw admina</a>
{% else %}
<a href="/admin/demote_user/{{ user.id }}" class="btn btn-sm btn-outline-secondary">⬇️ Usuń admina</a>
<a href="/admin/demote_user/{{ user.id }}" class="btn btn-sm btn-outline-secondary">⬇️ Usuń admina</a>
{% endif %}
<a href="/admin/delete_user/{{ user.id }}" class="btn btn-sm btn-outline-danger me-1">🗑️ Usuń</a>
</td>
@@ -70,7 +68,8 @@
</table>
<!-- Modal resetowania hasła -->
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<form method="post" id="resetPasswordForm">

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -11,84 +12,86 @@
{% endif %}
<link href="{{ url_for('static_bp.serve_css_lib', filename='bootstrap.min.css') }}" rel="stylesheet">
</head>
<body class="bg-dark text-white">
<nav class="navbar navbar-dark bg-dark mb-3">
<div class="container-fluid">
<a class="navbar-brand fw-bold fs-4 text-success" href="/">
🛒 <span class="text-warning">Lista</span> Zakupów
</a>
<nav class="navbar navbar-dark bg-dark mb-3">
<div class="container-fluid">
<a class="navbar-brand fw-bold fs-4 text-success" href="/">
🛒 <span class="text-warning">Lista</span> Zakupów
</a>
{% if has_authorized_cookie and not is_blocked %}
{% if has_authorized_cookie and not is_blocked %}
{% if current_user.is_authenticated %}
<div class="d-flex justify-content-center align-items-center text-white small flex-wrap text-center">
<span class="me-1">Zalogowany:</span>
<span class="badge bg-success">{{ current_user.username }}</span>
</div>
<div class="d-flex justify-content-center align-items-center text-white small flex-wrap text-center">
<span class="me-1">Zalogowany:</span>
<span class="badge bg-success">{{ current_user.username }}</span>
</div>
{% else %}
<div class="d-flex justify-content-center align-items-center text-white small flex-wrap text-center">
<span class="me-1">Przeglądasz jako</span>
<span class="badge bg-info">gość</span>
</div>
<div class="d-flex justify-content-center align-items-center text-white small flex-wrap text-center">
<span class="me-1">Przeglądasz jako</span>
<span class="badge bg-info">gość</span>
</div>
{% endif %}
{% endif %}
{% endif %}
{% if not is_blocked %}
<div class="d-flex align-items-center gap-2">
{% if request.endpoint and request.endpoint != 'system_auth' %}
{% if not is_blocked %}
<div class="d-flex align-items-center gap-2">
{% if request.endpoint and request.endpoint != 'system_auth' %}
{% if current_user.is_authenticated and current_user.is_admin %}
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-warning btn-sm">⚙️ Panel admina</a>
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-warning btn-sm">⚙️ Panel admina</a>
{% endif %}
{% if current_user.is_authenticated %}
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm">🚪 Wyloguj</a>
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm">🚪 Wyloguj</a>
{% else %}
<a href="{{ url_for('login') }}" class="btn btn-outline-light btn-sm">🔑 Zaloguj</a>
<a href="{{ url_for('login') }}" class="btn btn-outline-light btn-sm">🔑 Zaloguj</a>
{% endif %}
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</nav>
<div class="container px-2">
{% block content %}{% endblock %}
</div>
</nav>
<div class="container px-2">
{% block content %}{% endblock %}
</div>
<div id="toast-container" class="toast-container position-fixed bottom-0 end-0 p-3"></div>
<div id="toast-container" class="toast-container position-fixed bottom-0 end-0 p-3"></div>
<script src="{{ url_for('static_bp.serve_js_lib', filename='bootstrap.bundle.min.js') }}"></script>
{% if not is_blocked %}
<script>
document.addEventListener('DOMContentLoaded', function() {
{% with messages = get_flashed_messages(with_categories=true) %}
<script src="{{ url_for('static_bp.serve_js_lib', filename='bootstrap.bundle.min.js') }}"></script>
{% if not is_blocked %}
<script>
document.addEventListener('DOMContentLoaded', function () {
{% with messages = get_flashed_messages(with_categories = true) %}
{% for category, message in messages %}
{% set cat = 'info' if not category else ('danger' if category == 'error' else category) %}
{% if message == 'Please log in to access this page.' %}
showToast("Aby uzyskać dostęp do tej strony, musisz być zalogowany.", "danger");
{% else %}
showToast({{ message|tojson }}, "{{ cat }}");
{% endif %}
{% endfor %}
{% set cat = 'info' if not category else ('danger' if category == 'error' else category) %}
{% if message == 'Please log in to access this page.' %}
showToast("Aby uzyskać dostęp do tej strony, musisz być zalogowany.", "danger");
{% else %}
showToast({{ message| tojson }}, "{{ cat }}");
{% endif %}
{% endfor %}
{% endwith %}
});
</script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='glightbox.min.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='socket.io.min.js') }}"></script>
{% if request.endpoint != 'system_auth' %}
<script src="{{ url_for('static_bp.serve_js', filename='functions.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='live.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='sockets.js') }}"></script>
{% endif %}
<script src="{{ url_for('static_bp.serve_js', filename='toasts.js') }}"></script>
<script>
let lightbox = GLightbox({
</script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='glightbox.min.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='socket.io.min.js') }}"></script>
{% if request.endpoint != 'system_auth' %}
<script src="{{ url_for('static_bp.serve_js', filename='functions.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='live.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='sockets.js') }}"></script>
{% endif %}
<script src="{{ url_for('static_bp.serve_js', filename='toasts.js') }}"></script>
<script>
let lightbox = GLightbox({
selector: '.glightbox'
});
</script>
{% endif %}
});
</script>
{% endif %}
{% block scripts %}{% endblock %}
{% block scripts %}{% endblock %}
</body>
</html>
</html>

View File

@@ -11,4 +11,4 @@
<a href="{{ url_for('main_page') }}" class="btn btn-secondary">Anuluj</a>
</form>
{% endblock %}
{% endblock %}

View File

@@ -13,4 +13,4 @@
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -3,18 +3,18 @@
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
<h2 class="mb-2">
Lista: <strong>{{ list.title }}</strong>
{% if list.is_archived %}
<span class="badge bg-secondary ms-2">(Archiwalna)</span>
{% endif %}</h2>
<h2 class="mb-2">
Lista: <strong>{{ list.title }}</strong>
{% if list.is_archived %}
<span class="badge bg-secondary ms-2">(Archiwalna)</span>
{% endif %}
</h2>
<a href="/" class="btn btn-outline-secondary">← Powrót do list</a>
<a href="/" class="btn btn-outline-secondary">← Powrót do list</a>
</div>
<a href="{{ request.url_root }}share/{{ list.share_token }}"
class="btn btn-primary btn-sm w-100 mb-3"
{% if not list.is_public %}disabled{% endif %}>
<a href="{{ request.url_root }}share/{{ list.share_token }}" class="btn btn-primary btn-sm w-100 mb-3" {% if not
list.is_public %}disabled{% endif %}>
✅ Otwórz tryb zakupowy / odznaczania produktów
</a>
<div id="share-card" class="card bg-dark text-white mb-4">
@@ -22,26 +22,28 @@ Lista: <strong>{{ list.title }}</strong>
<div class="mb-2">
<strong id="share-header">
{% if list.is_public %}
🔗 Udostępnij link:
🔗 Udostępnij link:
{% else %}
🙈 Lista jest ukryta przed gośćmi
🙈 Lista jest ukryta przed gośćmi
{% endif %}
</strong>
<span id="share-url" class="badge bg-secondary text-wrap" style="font-size: 0.7rem; {% if not list.is_public %}display: none;{% endif %}">
<span id="share-url" class="badge bg-secondary text-wrap"
style="font-size: 0.7rem; {% if not list.is_public %}display: none;{% endif %}">
{{ request.url_root }}share/{{ list.share_token }}
</span>
</div>
<div class="d-flex flex-column flex-md-row gap-2">
<button id="copyBtn" class="btn btn-success btn-sm flex-fill"
onclick="copyLink('{{ request.url_root }}share/{{ list.share_token }}')"
{% if not list.is_public %}disabled{% endif %}>
onclick="copyLink('{{ request.url_root }}share/{{ list.share_token }}')" {% if not list.is_public %}disabled{%
endif %}>
📋 Skopiuj / Udostępnij
</button>
<button id="toggleVisibilityBtn" class="btn btn-outline-light btn-sm flex-fill" onclick="toggleVisibility({{ list.id }})">
<button id="toggleVisibilityBtn" class="btn btn-outline-light btn-sm flex-fill"
onclick="toggleVisibility({{ list.id }})">
{% if list.is_public %}
🙈 Ukryj listę
🙈 Ukryj listę
{% else %}
👁️ Udostępnij ponownie
👁️ Udostępnij ponownie
{% endif %}
</button>
</div>
@@ -57,27 +59,24 @@ Lista: <strong>{{ list.title }}</strong>
</h5>
<div class="progress progress-dark position-relative">
<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 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 id="progress-label" class="progress-label small fw-bold
{% if percent < 50 %}text-white{% else %}text-dark{% endif %}">
{{ percent|round(0) }}%
{{ percent|round(0) }}%
</span>
</div>
{% if total_expense > 0 %}
<div id="total-expense2" class="text-success fw-bold mb-3">
💸 Łącznie wydano: {{ '%.2f'|format(total_expense) }} PLN
</div>
<div id="total-expense2" class="text-success fw-bold mb-3">
💸 Łącznie wydano: {{ '%.2f'|format(total_expense) }} PLN
</div>
{% else %}
<div id="total-expense2" class="text-success fw-bold mb-3">
💸 Łącznie wydano: 0.00 PLN
</div>
<div id="total-expense2" class="text-success fw-bold mb-3">
💸 Łącznie wydano: 0.00 PLN
</div>
{% endif %}
<div class="form-check form-switch mb-3 d-flex justify-content-end">
@@ -87,63 +86,62 @@ Lista: <strong>{{ list.title }}</strong>
<ul id="items" class="list-group mb-3">
{% for item in items %}
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}">
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}"
data-is-share="{{ 'true' if is_share else 'false' }}">
<div class="d-flex align-items-center gap-3 flex-grow-1">
<div class="d-flex align-items-center gap-3 flex-grow-1">
<input id="checkbox-{{ item.id }}" class="large-checkbox" type="checkbox"
{% if item.purchased %}checked{% endif %}
{% if list.is_archived or item.not_purchased %}disabled{% endif %}>
<input id="checkbox-{{ item.id }}" class="large-checkbox" type="checkbox" {% if item.purchased %}checked{% endif
%} {% if list.is_archived or item.not_purchased %}disabled{% endif %}>
<span id="name-{{ item.id }}" class="text-white">
{{ item.name }}
{% if item.quantity and item.quantity > 1 %}
<span id="name-{{ item.id }}" class="text-white">
{{ item.name }}
{% if item.quantity and item.quantity > 1 %}
<span class="badge bg-secondary">x{{ item.quantity }}</span>
{% endif %}
</span>
{% endif %}
</span>
{% if item.note %}
{% if item.note %}
<small class="text-danger ms-4">[ <b>{{ item.note }}</b> ]</small>
{% endif %}
{% endif %}
{% if item.not_purchased_reason %}
{% if item.not_purchased_reason %}
<small class="text-dark ms-4">[ <b>Powód: {{ item.not_purchased_reason }}</b> ]</small>
{% endif %}
</div>
{% endif %}
</div>
<div class="btn-group btn-group-sm" role="group">
{% if item.not_purchased %}
<button type="button" class="btn btn-outline-success"
{% if list.is_archived %}disabled{% else %}onclick="unmarkNotPurchased({{ item.id }})"{% endif %}>
<div class="btn-group btn-group-sm" role="group">
{% if item.not_purchased %}
<button type="button" class="btn btn-outline-success" {% if list.is_archived %}disabled{% else
%}onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>
✅ Przywróć
</button>
{% else %}
<button type="button" class="btn btn-outline-light"
{% if list.is_archived %}disabled{% else %}onclick="markNotPurchasedModal(event, {{ item.id }})"{% endif %}>
{% else %}
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else
%}onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %}>
⚠️
</button>
{% endif %}
{% endif %}
{% if not is_share %}
<button type="button" class="btn btn-outline-warning"
{% if list.is_archived %}disabled{% else %}onclick="editItem({{ item.id }}, '{{ item.name }}', {{ item.quantity or 1 }})"{% endif %}>
{% if not is_share %}
<button type="button" class="btn btn-outline-warning" {% if list.is_archived %}disabled{% else
%}onclick="editItem({{ item.id }}, '{{ item.name }}', {{ item.quantity or 1 }})" {% endif %}>
✏️
</button>
<button type="button" class="btn btn-outline-danger"
{% if list.is_archived %}disabled{% else %}onclick="deleteItem({{ item.id }})"{% endif %}>
<button type="button" class="btn btn-outline-danger" {% if list.is_archived %}disabled{% else
%}onclick="deleteItem({{ item.id }})" {% endif %}>
🗑️
</button>
{% endif %}
</div>
</li>
{% endif %}
</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>
<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>
@@ -156,8 +154,10 @@ Lista: <strong>{{ list.title }}</strong>
</div>
<div class="col-12 col-md-10">
<div class="input-group w-100">
<input type="text" id="newItem" name="name" class="form-control bg-dark text-white border-secondary" placeholder="Dodaj produkt i ilość" required>
<input type="number" id="newQuantity" name="quantity" class="form-control bg-dark text-white border-secondary" placeholder="Ilość" min="1" value="1" style="max-width: 90px;">
<input type="text" id="newItem" name="name" class="form-control bg-dark text-white border-secondary"
placeholder="Dodaj produkt i ilość" required>
<input type="number" id="newQuantity" name="quantity" class="form-control bg-dark text-white border-secondary"
placeholder="Ilość" min="1" value="1" style="max-width: 90px;">
<button type="button" class="btn btn-success rounded-end" onclick="addItem({{ list.id }})"> Dodaj</button>
</div>
</div>
@@ -170,17 +170,18 @@ Lista: <strong>{{ list.title }}</strong>
<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) }}" class="glightbox" data-gallery="receipt-gallery">
<img src="{{ url_for('uploaded_file', filename=file) }}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
</div>
{% endfor %}
{% 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) }}" class="glightbox" data-gallery="receipt-gallery">
<img src="{{ url_for('uploaded_file', filename=file) }}"
class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
</div>
{% endfor %}
{% else %}
<div class="alert alert-info text-center w-100" role="alert">
Brak wgranych paragonów do tej listy.
</div>
<div class="alert alert-info text-center w-100" role="alert">
Brak wgranych paragonów do tej listy.
</div>
{% endif %}
</div>
@@ -205,14 +206,14 @@ Lista: <strong>{{ list.title }}</strong>
{% block scripts %}
<script>
window.IS_SHARE = false;
const isShare = document.getElementById('items').dataset.isShare === 'true';
window.IS_SHARE = isShare;
</script>
<script src="{{ url_for('static_bp.serve_js', filename='mass_add.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>
<script src="{{ url_for('static_bp.serve_js', filename='receipt_upload.js') }}"></script>
{% endblock %}
{% endblock %}
{% endblock %}

View File

@@ -7,16 +7,16 @@
{% if list.is_archived %}
<span class="badge bg-secondary ms-2">(Archiwalna)</span>
<span class="badge bg-secondary ms-2">(Archiwalna)</span>
{% endif %}
{% if total_expense > 0 %}
<span id="total-expense1" class="badge bg-success ms-2">
💸 {{ '%.2f'|format(total_expense) }} PLN
</span>
<span id="total-expense1" class="badge bg-success ms-2">
💸 {{ '%.2f'|format(total_expense) }} PLN
</span>
{% else %}
<span id="total-expense" class="badge bg-secondary ms-2" style="display: none;">
💸 0.00 PLN
</span>
<span id="total-expense" class="badge bg-secondary ms-2" style="display: none;">
💸 0.00 PLN
</span>
{% endif %}
</h2>
@@ -28,106 +28,111 @@
<ul id="items" class="list-group mb-3">
{% for item in items %}
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}">
<li data-name="{{ item.name|lower }}" id="item-{{ item.id }}"
class="list-group-item d-flex justify-content-between align-items-center flex-wrap clickable-item
{% if item.purchased %}bg-success text-white{% elif item.not_purchased %}bg-warning text-dark{% else %}item-not-checked{% endif %}"
data-is-share="{{ 'true' if is_share else 'false' }}">
<div class="d-flex align-items-center gap-3 flex-grow-1">
<div class="d-flex align-items-center gap-3 flex-grow-1">
<input id="checkbox-{{ item.id }}" class="large-checkbox" type="checkbox"
{% if item.purchased %}checked{% endif %}
{% if list.is_archived or item.not_purchased %}disabled{% endif %}>
<input id="checkbox-{{ item.id }}" class="large-checkbox" type="checkbox" {% if item.purchased %}checked{% endif
%} {% if list.is_archived or item.not_purchased %}disabled{% endif %}>
<span id="name-{{ item.id }}" class="text-white">
{{ item.name }}
{% if item.quantity and item.quantity > 1 %}
<span class="badge bg-secondary">x{{ item.quantity }}</span>
{% endif %}
</span>
{% if item.note %}
<small class="text-danger ms-4">[ <b>{{ item.note }}</b> ]</small>
<span id="name-{{ item.id }}" class="text-white">
{{ item.name }}
{% if item.quantity and item.quantity > 1 %}
<span class="badge bg-secondary">x{{ item.quantity }}</span>
{% endif %}
{% if item.not_purchased_reason %}
<small class="text-dark ms-4">[ <b>Powód: {{ item.not_purchased_reason }}</b> ]</small>
{% endif %}
</div>
</span>
<div class="btn-group btn-group-sm" role="group">
{% if item.not_purchased %}
<button type="button" class="btn btn-outline-success"
{% if list.is_archived %}disabled{% else %}onclick="unmarkNotPurchased({{ item.id }})"{% endif %}>
✅ Przywróć
</button>
{% else %}
<button type="button" class="btn btn-outline-light"
{% if list.is_archived %}disabled{% else %}onclick="markNotPurchasedModal(event, {{ item.id }})"{% endif %}>
⚠️
</button>
<button type="button" class="btn btn-outline-light"
{% if list.is_archived %}disabled{% else %}onclick="openNoteModal(event, {{ item.id }})"{% endif %}>
📝
</button>
{% endif %}
</div>
</li>
{% if item.note %}
<small class="text-danger ms-4">[ <b>{{ item.note }}</b> ]</small>
{% endif %}
{% if item.not_purchased_reason %}
<small class="text-dark ms-4">[ <b>Powód: {{ item.not_purchased_reason }}</b> ]</small>
{% endif %}
</div>
<div class="btn-group btn-group-sm" role="group">
{% if item.not_purchased %}
<button type="button" class="btn btn-outline-success" {% if list.is_archived %}disabled{% else
%}onclick="unmarkNotPurchased({{ item.id }})" {% endif %}>
✅ Przywróć
</button>
{% else %}
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else
%}onclick="markNotPurchasedModal(event, {{ item.id }})" {% endif %}>
⚠️
</button>
<button type="button" class="btn btn-outline-light" {% if list.is_archived %}disabled{% else
%}onclick="openNoteModal(event, {{ item.id }})" {% endif %}>
📝
</button>
{% endif %}
</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>
<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>
{% if not list.is_archived %}
<div class="input-group mb-2">
<input id="newItem" class="form-control bg-dark text-white border-secondary" placeholder="Dodaj produkt i ilość">
<input id="newQuantity" type="number" class="form-control bg-dark text-white border-secondary" placeholder="Ilość" min="1" value="1" style="max-width: 90px;">
<button onclick="addItem({{ list.id }})" class="btn btn-success rounded-end"> Dodaj</button>
</div>
<div class="input-group mb-2">
<input id="newItem" class="form-control bg-dark text-white border-secondary" placeholder="Dodaj produkt i ilość">
<input id="newQuantity" type="number" class="form-control bg-dark text-white border-secondary" placeholder="Ilość"
min="1" value="1" style="max-width: 90px;">
<button onclick="addItem({{ list.id }})" class="btn btn-success rounded-end"> Dodaj</button>
</div>
{% endif %}
{% if not list.is_archived %}
<hr>
<h5>💰 Dodaj wydatek</h5>
<div class="input-group mb-2">
<input id="expenseAmount" type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary" placeholder="Kwota (PLN)">
<button onclick="submitExpense({{ list.id }})" class="btn btn-success rounded-end">💾 Zapisz</button>
</div>
<hr>
<h5>💰 Dodaj wydatek</h5>
<div class="input-group mb-2">
<input id="expenseAmount" type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary"
placeholder="Kwota (PLN)">
<button onclick="submitExpense({{ list.id }})" class="btn btn-success rounded-end">💾 Zapisz</button>
</div>
{% endif %}
<p id="total-expense2"><b>💸 Łącznie wydano:</b> {{ '%.2f'|format(total_expense) }} PLN</p>
<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 class="btn btn-outline-light mb-3" 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">
{% set receipt_pattern = 'list_' ~ list.id %}
{% set receipt_pattern = 'list_' ~ list.id %}
<h5 class="mt-4">📸 Paragony dodane do tej listy</h5>
<h5 class="mt-4">📸 Paragony dodane do tej listy</h5>
<div class="row g-3 mt-2" id="receiptGallery">
{% if receipt_files %}
<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) }}" class="glightbox" data-gallery="receipt-gallery">
<img src="{{ url_for('uploaded_file', filename=file) }}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
</div>
<div class="col-6 col-md-4 col-lg-3 text-center">
<a href="{{ url_for('uploaded_file', filename=file) }}" class="glightbox" data-gallery="receipt-gallery">
<img src="{{ url_for('uploaded_file', filename=file) }}"
class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
</div>
{% endfor %}
{% else %}
{% else %}
<div class="alert alert-info text-center w-100" role="alert">
Brak wgranych paragonów do tej listy.
</div>
{% endif %}
</div>
{% endif %}
</div>
{% if not list.is_archived %}
{% if not list.is_archived %}
<hr>
<h5>📤 Dodaj zdjęcie paragonu</h5>
<form id="receiptForm" action="{{ url_for('upload_receipt', list_id=list.id) }}" method="post" enctype="multipart/form-data" class="text-center">
<label for="receiptInput" class="btn btn-outline-light w-100 py-3 mb-2 d-flex align-items-center justify-content-center gap-2">
<form id="receiptForm" action="{{ url_for('upload_receipt', list_id=list.id) }}" method="post"
enctype="multipart/form-data" class="text-center">
<label for="receiptInput"
class="btn btn-outline-light w-100 py-3 mb-2 d-flex align-items-center justify-content-center gap-2">
<i class="bi bi-upload"></i> 📸 <span id="fileLabel">Wybierz zdjęcie paragonu</span>
</label>
<input type="file" name="receipt" accept="image/*" capture="environment" class="d-none" id="receiptInput">
@@ -136,7 +141,7 @@
<div id="progressBar" class="progress-bar bg-success fw-bold" role="progressbar" style="width: 0%;">0%</div>
</div>
</form>
{% endif %}
{% endif %}
</div>
@@ -150,7 +155,8 @@
</div>
<form id="noteForm" onsubmit="submitNote(event)">
<div class="modal-body">
<textarea id="noteText" class="form-control" rows="4" placeholder="Np. 'Nie było, zamieniłem na inny'"></textarea>
<textarea id="noteText" class="form-control" rows="4"
placeholder="Np. 'Nie było, zamieniłem na inny'"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button>
@@ -163,16 +169,17 @@
{% block scripts %}
<script>
window.IS_SHARE = true;
const isShare = document.getElementById('items').dataset.isShare === 'true';
window.IS_SHARE = isShare;
</script>
<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>
{% endblock %}
{% endblock %}
{% endblock %}

View File

@@ -9,10 +9,12 @@
<div class="card-body">
<form method="post">
<div class="mb-3">
<input type="text" name="username" placeholder="Login" class="form-control bg-dark text-white border-secondary rounded" required>
<input type="text" name="username" placeholder="Login"
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
<div class="mb-3">
<input type="password" name="password" placeholder="Hasło" class="form-control bg-dark text-white border-secondary rounded" required>
<input type="password" name="password" placeholder="Hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
<button type="submit" class="btn btn-success w-100">🔑 Zaloguj</button>
</form>

View File

@@ -3,9 +3,9 @@
{% block content %}
{% if not current_user.is_authenticated %}
<div class="alert alert-info text-center" role="alert">
Jesteś w trybie gościa. Możesz tylko przeglądać listy udostępnione publicznie.
</div>
<div class="alert alert-info text-center" role="alert">
Jesteś w trybie gościa. Możesz tylko przeglądać listy udostępnione publicznie.
</div>
{% endif %}
{% if current_user.is_authenticated %}
@@ -17,15 +17,10 @@
<div class="card-body">
<form action="/create" method="post">
<div class="input-group mb-3">
<input type="text" name="title" id="title" placeholder="Wprowadź nazwę nowej listy" required class="form-control bg-dark text-white border-secondary">
<button
type="button"
class="btn btn-outline-secondary rounded-end"
id="tempToggle"
data-active="0"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Po zaznaczeniu lista będzie ważna tylko 7 dni">
<input type="text" name="title" id="title" placeholder="Wprowadź nazwę nowej listy" required
class="form-control bg-dark text-white border-secondary">
<button type="button" class="btn btn-outline-secondary rounded-end" id="tempToggle" data-active="0"
data-bs-toggle="tooltip" data-bs-placement="top" title="Po zaznaczeniu lista będzie ważna tylko 7 dni">
Tymczasowa
</button>
<input type="hidden" name="temporary" id="temporaryHidden" value="0">
@@ -37,87 +32,86 @@
{% endif %}
{% if current_user.is_authenticated %}
<h3 class="mt-4 d-flex justify-content-between align-items-center flex-wrap">
Twoje listy
<button type="button" class="btn btn-sm btn-outline-light ms-2" data-bs-toggle="modal" data-bs-target="#archivedModal">
📁 Zarchiwizowane
</button>
</h3>
{% if user_lists %}
<ul class="list-group mb-4">
{% for l in user_lists %}
{% set purchased_count = l.purchased_count %}
{% set total_count = l.total_count %}
{% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
<li class="list-group-item bg-dark text-white">
<div class="d-flex justify-content-between align-items-center flex-wrap w-100">
<span class="fw-bold">{{ l.title }} (Autor: Ty)</span>
<h3 class="mt-4 d-flex justify-content-between align-items-center flex-wrap">
Twoje listy
<button type="button" class="btn btn-sm btn-outline-light ms-2" data-bs-toggle="modal"
data-bs-target="#archivedModal">
📁 Zarchiwizowane
</button>
</h3>
{% if user_lists %}
<ul class="list-group mb-4">
{% for l in user_lists %}
{% set purchased_count = l.purchased_count %}
{% set total_count = l.total_count %}
{% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
<li class="list-group-item bg-dark text-white">
<div class="d-flex justify-content-between align-items-center flex-wrap w-100">
<span class="fw-bold">{{ l.title }} (Autor: Ty)</span>
<div class="d-flex flex-wrap mt-2 mt-md-0">
<a href="/list/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">📄 Otwórz</a>
<a href="/copy/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">📋 Kopiuj</a>
<a href="/edit_my_list/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">✏️ Edytuj</a>
<a href="/toggle_archive_list/{{ l.id }}?archive=true" class="btn btn-sm btn-outline-light me-1 mb-1">🗄️ Archiwizuj</a>
{% if l.is_public %}
<a href="/toggle_visibility/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">🙈 Ukryj</a>
{% else %}
<a href="/toggle_visibility/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">👁️ Odkryj</a>
{% endif %}
</div>
</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
<div class="d-flex flex-wrap mt-2 mt-md-0">
<a href="/list/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">📄 Otwórz</a>
<a href="/copy/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">📋 Kopiuj</a>
<a href="/edit_my_list/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">✏️ Edytuj</a>
<a href="/toggle_archive_list/{{ l.id }}?archive=true" class="btn btn-sm btn-outline-light me-1 mb-1">🗄️
Archiwizuj</a>
{% if l.is_public %}
<a href="/toggle_visibility/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">🙈 Ukryj</a>
{% else %}
<a href="/toggle_visibility/{{ l.id }}" class="btn btn-sm btn-outline-light me-1 mb-1">👁️ Odkryj</a>
{% endif %}
</div>
</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 %}
</ul>
{% else %}
<p><span class="badge rounded-pill bg-secondary opacity-75">Nie masz jeszcze żadnych list. Utwórz pierwszą, korzystając z formularza powyżej!</span></p>
{% 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 rounded-pill bg-secondary opacity-75">Nie masz jeszcze żadnych list. Utwórz pierwszą, korzystając
z formularza powyżej!</span></p>
{% endif %}
{% endif %}
<h3 class="mt-4">Publiczne listy innych użytkowników</h3>
{% if public_lists %}
<ul class="list-group">
{% for l in public_lists %}
{% set purchased_count = l.purchased_count %}
{% set total_count = l.total_count %}
{% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
<li class="list-group-item bg-dark text-white">
<div class="d-flex justify-content-between align-items-center flex-wrap w-100">
<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 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
<ul class="list-group">
{% for l in public_lists %}
{% set purchased_count = l.purchased_count %}
{% set total_count = l.total_count %}
{% set percent = (purchased_count / total_count * 100) if total_count > 0 else 0 %}
<li class="list-group-item bg-dark text-white">
<div class="d-flex justify-content-between align-items-center flex-wrap w-100">
<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 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>
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 rounded-pill bg-secondary opacity-75">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">
@@ -129,18 +123,19 @@
</div>
<div class="modal-body">
{% if archived_lists %}
<ul class="list-group">
{% for l in archived_lists %}
<li class="list-group-item bg-dark text-white d-flex justify-content-between align-items-center flex-wrap">
<span>{{ l.title }}</span>
<a href="/toggle_archive_list/{{ l.id }}?archive=false" class="btn btn-sm btn-outline-success">♻️ Przywróć</a>
</li>
{% endfor %}
</ul>
<ul class="list-group">
{% for l in archived_lists %}
<li class="list-group-item bg-dark text-white d-flex justify-content-between align-items-center flex-wrap">
<span>{{ l.title }}</span>
<a href="/toggle_archive_list/{{ l.id }}?archive=false" class="btn btn-sm btn-outline-success">♻️
Przywróć</a>
</li>
{% endfor %}
</ul>
{% else %}
<div class="alert alert-info text-center" role="alert">
Nie masz żadnych zarchiwizowanych list.
</div>
<div class="alert alert-info text-center" role="alert">
Nie masz żadnych zarchiwizowanych list.
</div>
{% endif %}
</div>
<div class="modal-footer">
@@ -154,4 +149,4 @@
<script src="{{ url_for('static_bp.serve_js', filename='toggle_button.js') }}"></script>
{% endblock %}
{% endblock %}
{% endblock %}

View File

@@ -10,7 +10,8 @@
<div class="card-body">
<form method="post">
<div class="mb-3">
<input type="password" name="password" placeholder="Hasło" class="form-control bg-dark text-white border-secondary rounded" required>
<input type="password" name="password" placeholder="Hasło"
class="form-control bg-dark text-white border-secondary rounded" required>
</div>
<button type="submit" class="btn btn-success w-100">🔓 Wejdź</button>
</form>
@@ -19,9 +20,9 @@
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('input[name="password"]').focus();
});
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('input[name="password"]').focus();
});
</script>
{% endblock %}