462 lines
24 KiB
HTML
462 lines
24 KiB
HTML
{% 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 %}
|