statystyki i optymalizacje

This commit is contained in:
Mateusz Gruszczyński
2025-12-12 09:23:34 +01:00
parent 20910fa898
commit fe48f589f0
8 changed files with 1303 additions and 421 deletions

View File

@@ -0,0 +1,461 @@
{% extends 'base.html' %}
{% block title %}Statystyki - Panel Admina{% endblock %}
{% block content %}
<div class="container my-4">
<!-- Nagłówek + akcje globalne -->
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
<h2 class="mb-0">Statystyki systemu</h2>
<div class="d-flex flex-wrap gap-2">
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-light">
← Powrót do panelu
</a>
<a href="{{ url_for('admin_ustawienia') }}" class="btn btn-outline-light">
Ustawienia główne
</a>
</div>
</div>
<!-- KARTY PODSUMOWANIA -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card border-success">
<div class="card-body text-center">
<h6 class="text-success mb-2">Suma wpłat</h6>
<h3 class="mb-1">{{ "%.2f"|format(total_wplaty) }} zł</h3>
<small class="text-muted">{{ liczba_wplat }} wpłat</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-danger">
<div class="card-body text-center">
<h6 class="text-danger mb-2">Suma wydatków</h6>
<h3 class="mb-1">{{ "%.2f"|format(total_wydatki) }} zł</h3>
<small class="text-muted">{{ liczba_wydatkow }} wydatków</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-info">
<div class="card-body text-center">
<h6 class="text-info mb-2">Suma przesunięć</h6>
<h3 class="mb-1">{{ "%.2f"|format(total_przesuniec) }} zł</h3>
<small class="text-muted">{{ liczba_przesuniec }} operacji</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card {% if bilans >= 0 %}border-success{% else %}border-danger{% endif %}">
<div class="card-body text-center">
<h6 class="{% if bilans >= 0 %}text-success{% else %}text-danger{% endif %} mb-2">Bilans</h6>
<h3 class="mb-1 {% if bilans >= 0 %}text-success{% else %}text-danger{% endif %}">
{{ "%.2f"|format(bilans) }} zł
</h3>
<small class="text-muted">{{ liczba_zbiorek }} zbiórek</small>
</div>
</div>
</div>
</div>
<!-- Pigułki: Podsumowanie / Aktywność / Miesięczne / Roczne -->
<ul class="nav nav-pills mb-3" id="statsTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="tab-podsumowanie" data-bs-toggle="tab" data-bs-target="#pane-podsumowanie"
type="button" role="tab" aria-controls="pane-podsumowanie" aria-selected="true">
Podsumowanie
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-aktywnosc" data-bs-toggle="tab" data-bs-target="#pane-aktywnosc"
type="button" role="tab" aria-controls="pane-aktywnosc" aria-selected="false">
Aktywność 7/30 dni
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-miesieczne" data-bs-toggle="tab" data-bs-target="#pane-miesieczne"
type="button" role="tab" aria-controls="pane-miesieczne" aria-selected="false">
Miesięczne
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-roczne" data-bs-toggle="tab" data-bs-target="#pane-roczne"
type="button" role="tab" aria-controls="pane-roczne" aria-selected="false">
Roczne
</button>
</li>
</ul>
<div class="tab-content">
<!-- PANE: Podsumowanie -->
<div class="tab-pane fade show active" id="pane-podsumowanie" role="tabpanel" aria-labelledby="tab-podsumowanie" tabindex="0">
<!-- Rekordy -->
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card">
<div class="card-header bg-success text-white">
<h6 class="mb-0">Najwyższa wpłata</h6>
</div>
<div class="card-body">
{% if najwyzsza_wplata %}
<h4 class="mb-2">{{ "%.2f"|format(najwyzsza_wplata.kwota) }} zł</h4>
<p class="mb-1"><strong>Opis:</strong> {{ najwyzsza_wplata.opis or "Brak opisu" }}</p>
<p class="mb-1"><strong>Data:</strong> {{ najwyzsza_wplata.data.strftime('%d.%m.%Y') }}</p>
<p class="mb-0"><strong>Zbiórka:</strong>
<a href="{{ url_for('zbiorka', zbiorka_id=najwyzsza_wplata.zbiorka_id) }}" class="text-decoration-none">
{{ najwyzsza_wplata.zbiorka.nazwa }}
</a>
</p>
{% else %}
<p class="text-muted mb-0">Brak wpłat</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-danger text-white">
<h6 class="mb-0">Najwyższy wydatek</h6>
</div>
<div class="card-body">
{% if najwyzszy_wydatek %}
<h4 class="mb-2">{{ "%.2f"|format(najwyzszy_wydatek.kwota) }} zł</h4>
<p class="mb-1"><strong>Opis:</strong> {{ najwyzszy_wydatek.opis or "Brak opisu" }}</p>
<p class="mb-1"><strong>Data:</strong> {{ najwyzszy_wydatek.data.strftime('%d.%m.%Y') }}</p>
<p class="mb-0"><strong>Zbiórka:</strong>
<a href="{{ url_for('zbiorka', zbiorka_id=najwyzszy_wydatek.zbiorka_id) }}" class="text-decoration-none">
{{ najwyzszy_wydatek.zbiorka.nazwa }}
</a>
</p>
{% else %}
<p class="text-muted mb-0">Brak wydatków</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-info text-white">
<h6 class="mb-0">Średnie wartości</h6>
</div>
<div class="card-body">
<p class="mb-2"><strong>Średnia wpłata:</strong> {{ "%.2f"|format(srednia_wplata) }} zł</p>
<p class="mb-0"><strong>Średni wydatek:</strong> {{ "%.2f"|format(sredni_wydatek) }} zł</p>
</div>
</div>
</div>
</div>
<!-- Top 10 wpłat -->
<div class="card mb-4">
<div class="card-header" style="background: var(--accent); color: #111;">
<h6 class="mb-0">Top 10 najwyższych wpłat</h6>
</div>
<div class="card-body p-0">
{% if top_10_wplat %}
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead>
<tr>
<th style="width:60px;">#</th>
<th>Kwota</th>
<th>Opis</th>
<th>Data</th>
<th>Zbiórka</th>
</tr>
</thead>
<tbody>
{% for wplata in top_10_wplat %}
<tr>
<td class="text-muted">{{ loop.index }}</td>
<td><strong class="text-success">{{ "%.2f"|format(wplata.kwota) }} zł</strong></td>
<td>{{ wplata.opis or "Brak opisu" }}</td>
<td class="text-muted">{{ wplata.data.strftime('%d.%m.%Y') }}</td>
<td>
<a href="{{ url_for('zbiorka', zbiorka_id=wplata.zbiorka_id) }}" class="text-decoration-none">
{{ wplata.zbiorka.nazwa }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body text-center py-4">
<p class="text-muted mb-0">Brak danych</p>
</div>
{% endif %}
</div>
</div>
<!-- Top 5 zbiórek -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h6 class="mb-0">Top 5 zbiórek (największe wpłaty)</h6>
</div>
<div class="card-body p-0">
{% if top_zbiorki %}
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead>
<tr>
<th style="width:60px;">#</th>
<th>Nazwa zbiórki</th>
<th class="text-end" style="width:180px;">Suma wpłat</th>
</tr>
</thead>
<tbody>
{% for zbiorka, suma in top_zbiorki %}
<tr>
<td class="text-muted">{{ loop.index }}</td>
<td>
<a href="{{ url_for('zbiorka', zbiorka_id=zbiorka.id) }}" class="text-decoration-none">
{{ zbiorka.nazwa }}
</a>
</td>
<td class="text-end"><strong>{{ "%.2f"|format(suma) }} zł</strong></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body text-center py-4">
<p class="text-muted mb-0">Brak danych</p>
</div>
{% endif %}
</div>
</div>
<!-- Top 5 źródeł przesunięć -->
<div class="card">
<div class="card-header bg-info text-white">
<h6 class="mb-0">Top 5 źródeł przesunięć</h6>
</div>
<div class="card-body p-0">
{% if top_zrodla_przesuniec %}
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead>
<tr>
<th style="width:60px;">#</th>
<th>Zbiórka źródłowa</th>
<th class="text-end">Liczba przesunięć</th>
<th class="text-end" style="width:180px;">Suma</th>
</tr>
</thead>
<tbody>
{% for nazwa, liczba, suma in top_zrodla_przesuniec %}
<tr>
<td class="text-muted">{{ loop.index }}</td>
<td>{{ nazwa }}</td>
<td class="text-end text-muted">{{ liczba }}</td>
<td class="text-end"><strong>{{ "%.2f"|format(suma) }} zł</strong></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body text-center py-4">
<p class="text-muted mb-0">Brak przesunięć</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- PANE: Aktywność 7/30 dni -->
<div class="tab-pane fade" id="pane-aktywnosc" role="tabpanel" aria-labelledby="tab-aktywnosc" tabindex="0">
<div class="row g-3">
<!-- Ostatnie 7 dni -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-success text-white">
<h6 class="mb-0">Ostatnie 7 dni</h6>
</div>
<div class="card-body">
<h5 class="mb-3">Wpłaty</h5>
<p class="mb-1"><strong>Liczba:</strong> {{ wplaty_7dni.liczba or 0 }}</p>
<p class="mb-3"><strong>Suma:</strong> {{ "%.2f"|format(wplaty_7dni.suma or 0) }} zł</p>
<h5 class="mb-3">Wydatki</h5>
<p class="mb-1"><strong>Liczba:</strong> {{ wydatki_7dni.liczba or 0 }}</p>
<p class="mb-0"><strong>Suma:</strong> {{ "%.2f"|format(wydatki_7dni.suma or 0) }} zł</p>
</div>
</div>
</div>
<!-- Ostatnie 30 dni -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h6 class="mb-0">Ostatnie 30 dni</h6>
</div>
<div class="card-body">
<h5 class="mb-3">Wpłaty</h5>
<p class="mb-1"><strong>Liczba:</strong> {{ wplaty_30dni.liczba or 0 }}</p>
<p class="mb-3"><strong>Suma:</strong> {{ "%.2f"|format(wplaty_30dni.suma or 0) }} zł</p>
<h5 class="mb-3">Wydatki</h5>
<p class="mb-1"><strong>Liczba:</strong> {{ wydatki_30dni.liczba or 0 }}</p>
<p class="mb-0"><strong>Suma:</strong> {{ "%.2f"|format(wydatki_30dni.suma or 0) }} zł</p>
</div>
</div>
</div>
</div>
</div>
<!-- PANE: Statystyki miesięczne -->
<div class="tab-pane fade" id="pane-miesieczne" role="tabpanel" aria-labelledby="tab-miesieczne" tabindex="0">
<div class="card">
<div class="card-header" style="background: var(--accent); color: #111;">
<h6 class="mb-0">Ostatnie 12 miesięcy</h6>
</div>
<div class="card-body p-0">
{% if wplaty_miesieczne or wydatki_miesieczne or przesuniecia_miesieczne %}
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead>
<tr>
<th>Miesiąc</th>
<th class="text-end">Wpłaty (suma)</th>
<th class="text-end">Wpłaty (liczba)</th>
<th class="text-end">Wydatki (suma)</th>
<th class="text-end">Wydatki (liczba)</th>
<th class="text-end">Przesunięcia (suma)</th>
<th class="text-end">Bilans</th>
</tr>
</thead>
<tbody>
{% set wplaty_dict = {} %}
{% set wydatki_dict = {} %}
{% set przesuniecia_dict = {} %}
{% for rok, miesiac, suma, liczba in wplaty_miesieczne %}
{% set klucz = "%d-%02d"|format(rok|int, miesiac|int) %}
{% set _ = wplaty_dict.update({klucz: {'suma': suma, 'liczba': liczba}}) %}
{% endfor %}
{% for rok, miesiac, suma, liczba in wydatki_miesieczne %}
{% set klucz = "%d-%02d"|format(rok|int, miesiac|int) %}
{% set _ = wydatki_dict.update({klucz: {'suma': suma, 'liczba': liczba}}) %}
{% endfor %}
{% for rok, miesiac, suma, liczba in przesuniecia_miesieczne %}
{% set klucz = "%d-%02d"|format(rok|int, miesiac|int) %}
{% set _ = przesuniecia_dict.update({klucz: {'suma': suma, 'liczba': liczba}}) %}
{% endfor %}
{% set miesiace = (wplaty_dict.keys() | list + wydatki_dict.keys() | list + przesuniecia_dict.keys() | list) | unique | sort | reverse %}
{% for miesiac_key in miesiace %}
{% set wp = wplaty_dict.get(miesiac_key, {'suma': 0, 'liczba': 0}) %}
{% set wy = wydatki_dict.get(miesiac_key, {'suma': 0, 'liczba': 0}) %}
{% set pr = przesuniecia_dict.get(miesiac_key, {'suma': 0, 'liczba': 0}) %}
{% set bilans_m = wp.suma - wy.suma %}
<tr>
<td><strong>{{ miesiac_key }}</strong></td>
<td class="text-end text-success">{{ "%.2f"|format(wp.suma) }} zł</td>
<td class="text-end text-muted">{{ wp.liczba }}</td>
<td class="text-end text-danger">{{ "%.2f"|format(wy.suma) }} zł</td>
<td class="text-end text-muted">{{ wy.liczba }}</td>
<td class="text-end text-info">{{ "%.2f"|format(pr.suma) }} zł</td>
<td class="text-end {% if bilans_m >= 0 %}text-success{% else %}text-danger{% endif %}">
<strong>{{ "%.2f"|format(bilans_m) }} zł</strong>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body text-center py-4">
<p class="text-muted mb-0">Brak danych</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- PANE: Statystyki roczne -->
<div class="tab-pane fade" id="pane-roczne" role="tabpanel" aria-labelledby="tab-roczne" tabindex="0">
<div class="card">
<div class="card-header bg-secondary text-white">
<h6 class="mb-0">Zestawienie roczne</h6>
</div>
<div class="card-body p-0">
{% if wplaty_roczne or wydatki_roczne %}
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle mb-0">
<thead>
<tr>
<th>Rok</th>
<th class="text-end">Wpłaty (suma)</th>
<th class="text-end">Wpłaty (liczba)</th>
<th class="text-end">Wydatki (suma)</th>
<th class="text-end">Wydatki (liczba)</th>
<th class="text-end">Bilans</th>
</tr>
</thead>
<tbody>
{% set wplaty_dict = {} %}
{% set wydatki_dict = {} %}
{% for rok, suma, liczba in wplaty_roczne %}
{% set _ = wplaty_dict.update({rok|int: {'suma': suma, 'liczba': liczba}}) %}
{% endfor %}
{% for rok, suma, liczba in wydatki_roczne %}
{% set _ = wydatki_dict.update({rok|int: {'suma': suma, 'liczba': liczba}}) %}
{% endfor %}
{% set lata = (wplaty_dict.keys() | list + wydatki_dict.keys() | list) | unique | sort | reverse %}
{% for rok in lata %}
{% set wp = wplaty_dict.get(rok, {'suma': 0, 'liczba': 0}) %}
{% set wy = wydatki_dict.get(rok, {'suma': 0, 'liczba': 0}) %}
{% set bilans_rok = wp.suma - wy.suma %}
<tr>
<td><strong>{{ rok|int }}</strong></td>
<td class="text-end text-success">{{ "%.2f"|format(wp.suma) }} zł</td>
<td class="text-end text-muted">{{ wp.liczba }}</td>
<td class="text-end text-danger">{{ "%.2f"|format(wy.suma) }} zł</td>
<td class="text-end text-muted">{{ wy.liczba }}</td>
<td class="text-end {% if bilans_rok >= 0 %}text-success{% else %}text-danger{% endif %}">
<strong>{{ "%.2f"|format(bilans_rok) }} zł</strong>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body text-center py-4">
<p class="text-muted mb-0">Brak danych</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}