poprawki w panelu, kategorie na wykresach i inne

This commit is contained in:
Mateusz Gruszczyński
2025-07-31 10:37:44 +02:00
parent 29ccd252b8
commit 0a44753eb2
6 changed files with 621 additions and 292 deletions

View File

@@ -7,70 +7,117 @@
<a href="/" class="btn btn-outline-secondary">← Powrót do strony głównej</a>
</div>
<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ę">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="adminNavbar">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('list_users') }}">👥 Zarządzanie użytkownikami</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_receipts', id='all') }}">📸 Wszystkie paragony</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('list_products') }}">🛍️ Produkty i sugestie</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin_mass_edit_categories') }}">🗂 Masowa edycja kategorii</a>
</li>
</ul>
<div class="card bg-dark text-white mb-4">
<div class="card-body p-2">
<div class="d-flex flex-wrap gap-2">
<a href="{{ url_for('list_users') }}" class="btn btn-outline-light btn-sm">👥 Użytkownicy</a>
<a href="{{ url_for('admin_receipts', id='all') }}" class="btn btn-outline-light btn-sm">📸 Paragony</a>
<a href="{{ url_for('list_products') }}" class="btn btn-outline-light btn-sm">🛍️ Produkty</a>
<a href="{{ url_for('admin_mass_edit_categories') }}" class="btn btn-outline-light btn-sm">🗂 Kategorie</a>
</div>
</div>
</nav>
</div>
<div class="row g-3 mb-4">
<!-- Statystyki liczbowe -->
<div class="col-md-4">
<div class="card bg-dark text-white h-100">
<div class="card-body">
<p><strong>👤 Liczba użytkowników:</strong> {{ user_count }}</p>
<p><strong>📝 Liczba list zakupowych:</strong> {{ list_count }}</p>
<p><strong>🛒 Liczba produktów:</strong> {{ item_count }}</p>
<p><strong>✅ Zakupionych produktów:</strong> {{ purchased_items_count }}</p>
<h5 class="mb-3">📊 Statystyki ogólne</h5>
<table class="table table-dark table-sm mb-0">
<tbody>
<tr>
<td>👤 Użytkownicy</td>
<td class="text-end fw-bold">{{ user_count }}</td>
</tr>
<tr>
<td>📝 Listy zakupowe</td>
<td class="text-end fw-bold">{{ list_count }}</td>
</tr>
<tr>
<td>🛒 Produkty</td>
<td class="text-end fw-bold">{{ item_count }}</td>
</tr>
<tr>
<td>✅ Zakupione</td>
<td class="text-end fw-bold">{{ purchased_items_count }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% if top_products %}
<!-- Najczęściej kupowane -->
<div class="col-md-4">
<div class="card bg-dark text-white h-100">
<div class="card-body">
<h5>🔥 Najczęściej kupowane produkty:</h5>
<ul class="mb-0">
{% for name, count in top_products %}
<li>{{ name }} — {{ count }}×</li>
{% endfor %}
</ul>
<h5 class="mb-3">🔥 Najczęściej kupowane produkty</h5>
{% if top_products %}
{% set max_count = top_products[0][1] %}
{% for name, count in top_products %}
<div class="mb-2">
<div class="d-flex justify-content-between">
<span>{{ name }}</span>
<span class="text-muted">{{ count }}×</span>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ (count / max_count) * 100 }}%"
aria-valuenow="{{ count }}" aria-valuemin="0" aria-valuemax="{{ max_count }}">
</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-muted mb-0">Brak danych</p>
{% endif %}
</div>
</div>
</div>
{% endif %}
<!-- Podsumowanie wydatków -->
<div class="col-md-4">
<div class="card bg-dark text-white h-100">
<div class="card-body">
<h5>💸 Podsumowanie wydatków:</h5>
<ul class="mb-3">
<li><strong>Obecny miesiąc:</strong> {{ '%.2f'|format(month_expense_sum) }} PLN</li>
<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>
<table class="table table-dark table-sm mb-3">
<thead>
<tr>
<th>Typ listy</th>
<th>Miesiąc</th>
<th>Rok</th>
<th>Całkowite</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wszystkie</td>
<td>{{ '%.2f'|format(expense_summary.all.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.all.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.all.total) }} PLN</td>
</tr>
<tr>
<td>Aktywne</td>
<td>{{ '%.2f'|format(expense_summary.active.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.active.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.active.total) }} PLN</td>
</tr>
<tr>
<td>Archiwalne</td>
<td>{{ '%.2f'|format(expense_summary.archived.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.archived.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.archived.total) }} PLN</td>
</tr>
<tr>
<td>Wygasłe</td>
<td>{{ '%.2f'|format(expense_summary.expired.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.expired.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.expired.total) }} PLN</td>
</tr>
</tbody>
</table>
<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
@@ -78,134 +125,152 @@
</div>
</div>
</div>
</div>
<div class="card bg-dark text-white mb-5">
<div class="card-body">
<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 sortable">
<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 e.expired %}
<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 %}
-
{% 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 %}
-
{% 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>
<div class="card bg-dark text-white mb-5">
<div class="card-body">
<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 sortable">
<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 align-middle">
<a href="{{ url_for('view_list', list_id=l.id) }}" class="text-white">{{ l.title }}</a>
{% if l.categories %}
<span class="ms-1 text-info" data-bs-toggle="tooltip"
title="{{ l.categories | map(attribute='name') | join(', ') }}">
🏷
</span>
{% endif %}
</td>
</table>
</div>
<button type="submit" class="btn btn-danger mt-2">🗑️ Usuń zaznaczone listy</button>
</form>
</div>
<td>
{% if l.is_archived %}
<span class="badge bg-secondary">Archiwalna</span>
{% elif e.expired %}
<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 %}
👤 {{ l.owner.username }} ({{ l.owner.id }})
{% else %}
-
{% endif %}
</td>
<td>{{ e.total_count }}</td>
<td>
<div class="progress" style="height: 14px;">
<div class="progress-bar
{% if e.percent >= 80 %}bg-success
{% elif e.percent >= 40 %}bg-warning
{% else %}bg-danger{% endif %}" role="progressbar" style="width: {{ e.percent }}%">
{{ e.purchased_count }}/{{ e.total_count }}
</div>
</div>
</td>
<td><span class="badge bg-primary">{{ e.comments_count }}</span></td>
<td><span class="badge bg-secondary">{{ e.receipts_count }}</span></td>
<td class="fw-bold
{% if e.total_expense >= 500 %}text-danger
{% elif e.total_expense > 0 %}text-success{% endif %}">
{% if e.total_expense > 0 %}
{{ '%.2f'|format(e.total_expense) }} PLN
{% 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"
onclick="return confirm('Na pewno usunąć tę listę?')">🗑️ Usuń</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<button type="submit" class="btn btn-danger mt-2">🗑️ Usuń zaznaczone listy</button>
</form>
</div>
</div>
<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 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="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>
<div class="bg-dark rounded p-2">
<canvas id="expensesChart" height="100"></canvas>
</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>
{% 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 %}
{% endblock %}
<div class="info-bar-fixed">
Python: {{ python_version.split()[0] }} | {{ system_info }} | RAM app: {{ app_memory }} |
DB: {{ db_info.engine|upper }}{% if db_info.version %} v{{ db_info.version[0] }}{% endif %} |
Tabele: {{ table_count }} | Rekordy: {{ record_total }} |
Uptime: {{ uptime_minutes }} min
</div>
{% endblock %}
<div class="info-bar-fixed">
Python: {{ python_version.split()[0] }} | {{ system_info }} | RAM app: {{ app_memory }} |
DB: {{ db_info.engine|upper }}{% if db_info.version %} v{{ db_info.version[0] }}{% endif %} |
Tabele: {{ table_count }} | Rekordy: {{ record_total }} |
Uptime: {{ uptime_minutes }} min
</div>
{% endblock %}

View File

@@ -6,15 +6,22 @@
<h2 class="mb-2">Statystyki wydatków</h2>
<a href="{{ url_for('main_page') }}" class="btn btn-outline-secondary">← Powrót</a>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="showAllLists" {% if show_all %}checked{% endif %}>
<label class="form-check-label ms-2 text-white" for="showAllLists">Pokaż wszystkie publiczne listy
innych</label>
<div class="d-flex justify-content-center mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="showAllLists" {% if show_all %}checked{% endif %}>
<label class="form-check-label ms-2 text-white" for="showAllLists">
Pokaż wszystkie publiczne listy innych
</label>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-3">
<a href="{{ url_for('user_expenses', show_all='true') }}"
class="btn btn-sm {% if not selected_category %}btn-success{% else %}btn-outline-light{% endif %}">🌐 Wszystkie</a>
<div class="d-flex flex-wrap gap-2 mb-3 justify-content-center">
<a href="{{ url_for('user_expenses') }}"
class="btn btn-sm {% if not selected_category %}btn-success{% else %}btn-outline-light{% endif %}">
🌐 Wszystkie
</a>
{% for cat in categories %}
<a href="{{ url_for('user_expenses', category_id=cat.id) }}"
class="btn btn-sm {% if selected_category == cat.id %}btn-success{% else %}btn-outline-light{% endif %}">
@@ -23,6 +30,7 @@
{% endfor %}
</div>
<div class="card bg-dark text-white mb-5">
<div class="card-body">
<ul class="nav nav-tabs mb-3" id="expenseTabs" role="tablist">
@@ -49,7 +57,7 @@
<div class="card bg-dark text-white mb-4">
<div class="card-body">
<div class="d-flex flex-wrap gap-2 mb-3">
<div class="d-flex flex-wrap gap-2 mb-3 justify-content-center">
<button class="btn btn-outline-light btn-sm range-btn" data-range="day">🗓️ Dzień</button>
<button class="btn btn-outline-light btn-sm range-btn" data-range="week">📆 Tydzień</button>
<button class="btn btn-outline-light btn-sm range-btn active" data-range="month">📅 Miesiąc</button>
@@ -57,19 +65,25 @@
<button class="btn btn-outline-light btn-sm range-btn" data-range="all">🌐 Wszystko</button>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="onlyWithExpenses">
<label class="form-check-label ms-2 text-white" for="onlyWithExpenses">Pokaż tylko listy z
wydatkami</label>
<div class="d-flex justify-content-center mb-3">
<div class="input-group input-group-sm 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="customStart">
<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="customEnd">
<button class="btn btn-outline-success" id="applyCustomRange">📊 Zastosuj zakres</button>
</div>
</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="customStart">
<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="customEnd">
<button class="btn btn-outline-success" id="applyCustomRange">📊 Zastosuj zakres</button>
<div class="d-flex justify-content-center mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="onlyWithExpenses">
<label class="form-check-label ms-2 text-white" for="onlyWithExpenses">
Pokaż tylko listy z wydatkami
</label>
</div>
</div>
<div class="d-flex justify-content-end mb-2">
@@ -123,13 +137,19 @@
<div class="tab-pane fade" id="chartTab" role="tabpanel">
<div class="card bg-dark text-white mb-4">
<div class="card-body">
<button class="btn btn-outline-light w-100 py-2 mb-2 d-flex align-items-center justify-content-center gap-2"
id="toggleCategorySplit">
🎨 Pokaż podział na kategorie
</button>
<p id="chartRangeLabel" class="fw-bold mb-3">Widok: miesięczne</p>
<canvas id="expensesChart" height="120"></canvas>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-3">
<div class="d-flex flex-wrap gap-2 mb-3 justify-content-center">
<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>
@@ -137,14 +157,17 @@
</div>
<!-- Picker daty w formie input-group -->
<div class="input-group input-group-sm mb-4 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 class="d-flex justify-content-center mb-4">
<div class="input-group input-group-sm 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>
</div>
</div>