statystyki i optymalizacje
This commit is contained in:
@@ -1,2 +1,6 @@
|
||||
ALTER TABLE zbiorka ADD COLUMN typ_zbiorki VARCHAR(20) NOT NULL DEFAULT 'standardowa';
|
||||
CREATE INDEX idx_zbiorka_typ ON zbiorka(typ_zbiorki);
|
||||
|
||||
|
||||
ALTER TABLE ustawienia_globalne
|
||||
ADD COLUMN kolejnosc_rezerwowych VARCHAR(20) NOT NULL DEFAULT 'id';
|
||||
201
app.py
201
app.py
@@ -201,6 +201,7 @@ class UstawieniaGlobalne(db.Model):
|
||||
typ_navbar = db.Column(db.String(10), default="text")
|
||||
typ_stopka = db.Column(db.String(10), default="text")
|
||||
stopka_text = db.Column(db.String(200), nullable=True)
|
||||
kolejnosc_rezerwowych = db.Column(db.String(20), default="id", nullable=False)
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
@@ -336,7 +337,23 @@ def inject_version():
|
||||
# TRASY PUBLICZNE
|
||||
@app.route("/")
|
||||
def index():
|
||||
zbiorki = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False).all()
|
||||
settings = UstawieniaGlobalne.query.first()
|
||||
kolejnosc = settings.kolejnosc_rezerwowych if settings else "id"
|
||||
|
||||
standardowe = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False).filter(
|
||||
Zbiorka.typ_zbiorki != 'rezerwa'
|
||||
).all()
|
||||
|
||||
rezerwowe = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False, typ_zbiorki='rezerwa').all()
|
||||
|
||||
# Sortuj według ustawienia
|
||||
if kolejnosc == "first":
|
||||
zbiorki = rezerwowe + standardowe
|
||||
elif kolejnosc == "last":
|
||||
zbiorki = standardowe + rezerwowe
|
||||
else: # "id"
|
||||
zbiorki = sorted(standardowe + rezerwowe, key=lambda z: z.id)
|
||||
|
||||
return render_template("index.html", zbiorki=zbiorki)
|
||||
|
||||
|
||||
@@ -351,12 +368,19 @@ def page_not_found(e):
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/zbiorka/<int:zbiorka_id>")
|
||||
@app.route("/zbiorka/<int:zbiorka_id>", endpoint='zbiorka')
|
||||
@app.route("/rezerwa/<int:zbiorka_id>", endpoint='rezerwa')
|
||||
def zbiorka(zbiorka_id):
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
if zb is None:
|
||||
abort(404)
|
||||
|
||||
# Zabezpieczenie: sprawdź czy URL pasuje do typu zbiórki
|
||||
poprawny_endpoint = 'rezerwa' if zb.typ_zbiorki == 'rezerwa' else 'zbiorka'
|
||||
|
||||
if request.endpoint != poprawny_endpoint:
|
||||
return redirect(url_for(poprawny_endpoint, zbiorka_id=zbiorka_id), code=301)
|
||||
|
||||
if zb.ukryta and (not current_user.is_authenticated or not current_user.czy_admin):
|
||||
abort(404)
|
||||
|
||||
@@ -948,6 +972,7 @@ def admin_ustawienia():
|
||||
|
||||
client_ip = get_real_ip()
|
||||
settings = UstawieniaGlobalne.query.first()
|
||||
|
||||
if request.method == "POST":
|
||||
numer_konta = request.form.get("numer_konta")
|
||||
numer_telefonu_blik = request.form.get("numer_telefonu_blik")
|
||||
@@ -958,6 +983,7 @@ def admin_ustawienia():
|
||||
typ_stopka = request.form.get("typ_stopka", "text")
|
||||
stopka_text = request.form.get("stopka_text") or None
|
||||
pokaz_logo_w_navbar = (typ_navbar == "logo")
|
||||
kolejnosc_rezerwowych = request.form.get("kolejnosc_rezerwowych", "id")
|
||||
|
||||
if settings is None:
|
||||
settings = UstawieniaGlobalne(
|
||||
@@ -970,6 +996,7 @@ def admin_ustawienia():
|
||||
typ_navbar=typ_navbar,
|
||||
typ_stopka=typ_stopka,
|
||||
stopka_text=stopka_text,
|
||||
kolejnosc_rezerwowych=kolejnosc_rezerwowych,
|
||||
)
|
||||
db.session.add(settings)
|
||||
else:
|
||||
@@ -982,6 +1009,7 @@ def admin_ustawienia():
|
||||
settings.typ_navbar = typ_navbar
|
||||
settings.typ_stopka = typ_stopka
|
||||
settings.stopka_text = stopka_text
|
||||
settings.kolejnosc_rezerwowych = kolejnosc_rezerwowych
|
||||
|
||||
db.session.commit()
|
||||
flash("Ustawienia globalne zostały zaktualizowane", "success")
|
||||
@@ -1416,6 +1444,175 @@ def usun_rezerwe(rezerwa_id):
|
||||
return redirect(url_for("lista_rezerwowych"))
|
||||
|
||||
|
||||
@app.route("/admin/statystyki")
|
||||
@login_required
|
||||
def admin_statystyki():
|
||||
if not current_user.czy_admin:
|
||||
abort(403)
|
||||
|
||||
from sqlalchemy import func, extract
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# ==================== PODSTAWOWE STATYSTYKI ====================
|
||||
total_wplaty = db.session.query(func.sum(Wplata.kwota)).filter(Wplata.ukryta == False).scalar() or 0
|
||||
total_wydatki = db.session.query(func.sum(Wydatek.kwota)).filter(Wydatek.ukryta == False).scalar() or 0
|
||||
bilans = total_wplaty - total_wydatki
|
||||
|
||||
liczba_wplat = db.session.query(func.count(Wplata.id)).filter(Wplata.ukryta == False).scalar() or 0
|
||||
liczba_wydatkow = db.session.query(func.count(Wydatek.id)).filter(Wydatek.ukryta == False).scalar() or 0
|
||||
liczba_zbiorek = db.session.query(func.count(Zbiorka.id)).scalar() or 0
|
||||
|
||||
# Najwyższa wpłata
|
||||
najwyzsza_wplata = db.session.query(Wplata).filter(Wplata.ukryta == False).order_by(Wplata.kwota.desc()).first()
|
||||
|
||||
# Najwyższy wydatek
|
||||
najwyzszy_wydatek = db.session.query(Wydatek).filter(Wydatek.ukryta == False).order_by(Wydatek.kwota.desc()).first()
|
||||
|
||||
# Średnia wpłata i wydatek
|
||||
srednia_wplata = total_wplaty / liczba_wplat if liczba_wplat > 0 else 0
|
||||
sredni_wydatek = total_wydatki / liczba_wydatkow if liczba_wydatkow > 0 else 0
|
||||
|
||||
# ==================== STATYSTYKI PRZESUNIĘĆ ====================
|
||||
total_przesuniec = db.session.query(func.sum(Przesuniecie.kwota)).filter(Przesuniecie.ukryta == False).scalar() or 0
|
||||
liczba_przesuniec = db.session.query(func.count(Przesuniecie.id)).filter(Przesuniecie.ukryta == False).scalar() or 0
|
||||
|
||||
# Top 5 źródeł przesunięć (zbiórki które najczęściej przekazują środki)
|
||||
top_zrodla_przesuniec = db.session.query(
|
||||
Zbiorka.nazwa,
|
||||
func.count(Przesuniecie.id).label('liczba'),
|
||||
func.sum(Przesuniecie.kwota).label('suma')
|
||||
).join(Przesuniecie, Przesuniecie.zbiorka_zrodlo_id == Zbiorka.id)\
|
||||
.filter(Przesuniecie.ukryta == False)\
|
||||
.group_by(Zbiorka.id, Zbiorka.nazwa)\
|
||||
.order_by(func.sum(Przesuniecie.kwota).desc())\
|
||||
.limit(5).all()
|
||||
|
||||
# ==================== TOP 10 WPŁAT ====================
|
||||
top_10_wplat = db.session.query(Wplata)\
|
||||
.filter(Wplata.ukryta == False)\
|
||||
.order_by(Wplata.kwota.desc())\
|
||||
.limit(10).all()
|
||||
|
||||
# ==================== AKTYWNOŚĆ CZASOWA ====================
|
||||
teraz = datetime.now()
|
||||
rok_temu = teraz - timedelta(days=365)
|
||||
miesiac_temu = teraz - timedelta(days=30)
|
||||
tydzien_temu = teraz - timedelta(days=7)
|
||||
|
||||
# Aktywność ostatnie 7 dni
|
||||
wplaty_7dni = db.session.query(
|
||||
func.count(Wplata.id).label('liczba'),
|
||||
func.sum(Wplata.kwota).label('suma')
|
||||
).filter(Wplata.data >= tydzien_temu, Wplata.ukryta == False).first()
|
||||
|
||||
wydatki_7dni = db.session.query(
|
||||
func.count(Wydatek.id).label('liczba'),
|
||||
func.sum(Wydatek.kwota).label('suma')
|
||||
).filter(Wydatek.data >= tydzien_temu, Wydatek.ukryta == False).first()
|
||||
|
||||
# Aktywność ostatnie 30 dni
|
||||
wplaty_30dni = db.session.query(
|
||||
func.count(Wplata.id).label('liczba'),
|
||||
func.sum(Wplata.kwota).label('suma')
|
||||
).filter(Wplata.data >= miesiac_temu, Wplata.ukryta == False).first()
|
||||
|
||||
wydatki_30dni = db.session.query(
|
||||
func.count(Wydatek.id).label('liczba'),
|
||||
func.sum(Wydatek.kwota).label('suma')
|
||||
).filter(Wydatek.data >= miesiac_temu, Wydatek.ukryta == False).first()
|
||||
|
||||
# ==================== STATYSTYKI MIESIĘCZNE (ostatnie 12 miesięcy) ====================
|
||||
wplaty_miesieczne = db.session.query(
|
||||
extract('year', Wplata.data).label('rok'),
|
||||
extract('month', Wplata.data).label('miesiac'),
|
||||
func.sum(Wplata.kwota).label('suma'),
|
||||
func.count(Wplata.id).label('liczba')
|
||||
).filter(
|
||||
Wplata.data >= rok_temu,
|
||||
Wplata.ukryta == False
|
||||
).group_by('rok', 'miesiac').order_by('rok', 'miesiac').all()
|
||||
|
||||
wydatki_miesieczne = db.session.query(
|
||||
extract('year', Wydatek.data).label('rok'),
|
||||
extract('month', Wydatek.data).label('miesiac'),
|
||||
func.sum(Wydatek.kwota).label('suma'),
|
||||
func.count(Wydatek.id).label('liczba')
|
||||
).filter(
|
||||
Wydatek.data >= rok_temu,
|
||||
Wydatek.ukryta == False
|
||||
).group_by('rok', 'miesiac').order_by('rok', 'miesiac').all()
|
||||
|
||||
przesuniecia_miesieczne = db.session.query(
|
||||
extract('year', Przesuniecie.data).label('rok'),
|
||||
extract('month', Przesuniecie.data).label('miesiac'),
|
||||
func.sum(Przesuniecie.kwota).label('suma'),
|
||||
func.count(Przesuniecie.id).label('liczba')
|
||||
).filter(
|
||||
Przesuniecie.data >= rok_temu,
|
||||
Przesuniecie.ukryta == False
|
||||
).group_by('rok', 'miesiac').order_by('rok', 'miesiac').all()
|
||||
|
||||
# ==================== STATYSTYKI ROCZNE ====================
|
||||
wplaty_roczne = db.session.query(
|
||||
extract('year', Wplata.data).label('rok'),
|
||||
func.sum(Wplata.kwota).label('suma'),
|
||||
func.count(Wplata.id).label('liczba')
|
||||
).filter(
|
||||
Wplata.ukryta == False
|
||||
).group_by('rok').order_by('rok').all()
|
||||
|
||||
wydatki_roczne = db.session.query(
|
||||
extract('year', Wydatek.data).label('rok'),
|
||||
func.sum(Wydatek.kwota).label('suma'),
|
||||
func.count(Wydatek.id).label('liczba')
|
||||
).filter(
|
||||
Wydatek.ukryta == False
|
||||
).group_by('rok').order_by('rok').all()
|
||||
|
||||
# ==================== TOP 5 ZBIÓREK ====================
|
||||
top_zbiorki = db.session.query(
|
||||
Zbiorka,
|
||||
func.sum(Wplata.kwota).label('suma_wplat')
|
||||
).join(Wplata).filter(
|
||||
Wplata.ukryta == False
|
||||
).group_by(Zbiorka.id).order_by(func.sum(Wplata.kwota).desc()).limit(5).all()
|
||||
|
||||
return render_template(
|
||||
"admin/statystyki.html",
|
||||
# Podstawowe
|
||||
total_wplaty=total_wplaty,
|
||||
total_wydatki=total_wydatki,
|
||||
bilans=bilans,
|
||||
liczba_wplat=liczba_wplat,
|
||||
liczba_wydatkow=liczba_wydatkow,
|
||||
liczba_zbiorek=liczba_zbiorek,
|
||||
najwyzsza_wplata=najwyzsza_wplata,
|
||||
najwyzszy_wydatek=najwyzszy_wydatek,
|
||||
srednia_wplata=srednia_wplata,
|
||||
sredni_wydatek=sredni_wydatek,
|
||||
# Przesunięcia
|
||||
total_przesuniec=total_przesuniec,
|
||||
liczba_przesuniec=liczba_przesuniec,
|
||||
top_zrodla_przesuniec=top_zrodla_przesuniec,
|
||||
# Top wpłaty
|
||||
top_10_wplat=top_10_wplat,
|
||||
# Aktywność czasowa
|
||||
wplaty_7dni=wplaty_7dni,
|
||||
wydatki_7dni=wydatki_7dni,
|
||||
wplaty_30dni=wplaty_30dni,
|
||||
wydatki_30dni=wydatki_30dni,
|
||||
# Miesięczne
|
||||
wplaty_miesieczne=wplaty_miesieczne,
|
||||
wydatki_miesieczne=wydatki_miesieczne,
|
||||
przesuniecia_miesieczne=przesuniecia_miesieczne,
|
||||
# Roczne
|
||||
wplaty_roczne=wplaty_roczne,
|
||||
wydatki_roczne=wydatki_roczne,
|
||||
# Top zbiórki
|
||||
top_zbiorki=top_zbiorki
|
||||
)
|
||||
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return "", 204
|
||||
|
||||
@@ -6,32 +6,80 @@
|
||||
|
||||
<!-- Nagłówek + akcje globalne -->
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
|
||||
<h2 class="mb-0">Panel Admina</h2>
|
||||
<div>
|
||||
<h2 class="mb-1">Panel Admina</h2>
|
||||
<p class="text-muted mb-0">Zarządzaj zbiórkami i monitoruj finanse</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('admin_statystyki') }}" class="btn btn-info">
|
||||
<i class="bi bi-graph-up"></i> Statystyki
|
||||
</a>
|
||||
<a href="{{ url_for('formularz_zbiorek') }}" class="btn btn-primary">
|
||||
➕ Dodaj zbiórkę
|
||||
<i class="bi bi-plus-circle"></i> Dodaj zbiórkę
|
||||
</a>
|
||||
<a href="{{ url_for('lista_rezerwowych') }}" class="btn btn-outline-light">
|
||||
Listy rezerwowe
|
||||
<i class="bi bi-wallet2"></i> Listy rezerwowe
|
||||
</a>
|
||||
<a href="{{ url_for('admin_ustawienia') }}" class="btn btn-outline-light">
|
||||
Ustawienia główne
|
||||
<i class="bi bi-gear"></i> Ustawienia
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pigułki: Aktywne / Zrealizowane (zakładki w obrębie panelu) -->
|
||||
<!-- Szybkie statystyki (cards) -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-list-check fs-1 text-primary mb-2"></i>
|
||||
<h3 class="mb-0">{{ active_zbiorki|length }}</h3>
|
||||
<small class="text-muted">Aktywnych zbiórek</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-success">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-check-circle fs-1 text-success mb-2"></i>
|
||||
<h3 class="mb-0">{{ completed_zbiorki|length }}</h3>
|
||||
<small class="text-muted">Zrealizowanych</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-info">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-bar-chart fs-1 text-info mb-2"></i>
|
||||
<h3 class="mb-0">{{ active_zbiorki|length + completed_zbiorki|length }}</h3>
|
||||
<small class="text-muted">Łącznie zbiórek</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body text-center">
|
||||
<a href="{{ url_for('admin_statystyki') }}" class="text-decoration-none">
|
||||
<i class="bi bi-graph-up-arrow fs-1 text-warning mb-2"></i>
|
||||
<h5 class="mb-0 text-white">Zobacz pełne</h5>
|
||||
<small class="text-muted">statystyki</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pigułki: Aktywne / Zrealizowane -->
|
||||
<ul class="nav nav-pills mb-3" id="adminTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="tab-aktywne" data-bs-toggle="tab" data-bs-target="#pane-aktywne"
|
||||
type="button" role="tab" aria-controls="pane-aktywne" aria-selected="true">
|
||||
Aktywne zbiórki
|
||||
<i class="bi bi-lightning"></i> Aktywne zbiórki ({{ active_zbiorki|length }})
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="tab-zrealizowane" data-bs-toggle="tab" data-bs-target="#pane-zrealizowane"
|
||||
type="button" role="tab" aria-controls="pane-zrealizowane" aria-selected="false">
|
||||
Zrealizowane
|
||||
<i class="bi bi-check-circle"></i> Zrealizowane ({{ completed_zbiorki|length }})
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -59,12 +107,20 @@
|
||||
<td class="text-muted">{{ z.id }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">{{ z.nazwa }}</span>
|
||||
{# opcjonalnie: mini-meta z celem/stanem jeśli masz te pola #}
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=z.id) }}" class="fw-semibold text-decoration-none">
|
||||
{{ z.nazwa }}
|
||||
</a>
|
||||
{% if z.cel is defined or z.stan is defined %}
|
||||
<small class="text-muted">
|
||||
{% if z.cel is defined %} Cel: {{ z.cel|round(2) }} PLN {% endif %}
|
||||
{% if z.stan is defined %} · Stan: {{ z.stan|round(2) }} PLN {% endif %}
|
||||
{% if z.cel is defined and z.cel > 0 %}
|
||||
Cel: {{ z.cel|round(2) }} PLN
|
||||
{% endif %}
|
||||
{% if z.stan is defined %}
|
||||
· Stan: {{ z.stan|round(2) }} PLN
|
||||
{% endif %}
|
||||
{% if z.cel is defined and z.cel > 0 and z.stan is defined %}
|
||||
· {{ ((z.stan / z.cel) * 100)|round(1) }}%
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -72,65 +128,82 @@
|
||||
<td>
|
||||
{% if z.ukryta %}
|
||||
<span class="badge bg-secondary border"
|
||||
style="border-color: var(--border);">Ukryta</span>
|
||||
style="border-color: var(--border);"><i class="bi bi-eye-slash"></i> Ukryta</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Widoczna</span>
|
||||
<span class="badge bg-success"><i class="bi bi-eye"></i> Widoczna</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<!-- Grupa akcji: główne + rozwijane -->
|
||||
<div class="btn-group">
|
||||
<a href="{{ url_for('formularz_zbiorek', zbiorka_id=z.id) }}"
|
||||
class="btn btn-sm btn-outline-light">Edytuj</a>
|
||||
class="btn btn-sm btn-outline-light">
|
||||
<i class="bi bi-pencil"></i> Edytuj
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-light dropdown-toggle dropdown-toggle-split"
|
||||
data-bs-toggle="dropdown" aria-expanded="false" aria-label="Więcej opcji">
|
||||
<span class="visually-hidden">Więcej</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('zbiorka', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-eye"></i> Podgląd
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">Dodaj
|
||||
wpłatę</a>
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-plus-circle text-success"></i> Dodaj wpłatę
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">Dodaj
|
||||
wydatek</a>
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-dash-circle text-danger"></i> Dodaj wydatek
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=z.id) }}">Transakcje</a>
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-list-ul"></i> Transakcje
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">Edytuj stan</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-currency-dollar"></i> Edytuj stan
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{{ url_for('oznacz_zrealizowana', zbiorka_id=z.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">Oznacz jako
|
||||
zrealizowaną</button>
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="bi bi-check-circle text-success"></i> Oznacz jako zrealizowaną
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="{{ url_for('zmien_widzialnosc', zbiorka_id=z.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">
|
||||
{% if z.ukryta %}Pokaż{% else %}Ukryj{% endif %}
|
||||
{% if z.ukryta %}
|
||||
<i class="bi bi-eye"></i> Pokaż
|
||||
{% else %}
|
||||
<i class="bi bi-eye-slash"></i> Ukryj
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{{ url_for('usun_zbiorka', zbiorka_id=z.id) }}" method="post"
|
||||
class="m-0"
|
||||
onsubmit="return confirm('Czy na pewno chcesz usunąć tę zbiórkę?');">
|
||||
<button type="submit" class="dropdown-item text-danger">Usuń</button>
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="bi bi-trash"></i> Usuń
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -145,9 +218,12 @@
|
||||
<!-- Empty state -->
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-inbox fs-1 text-muted mb-3"></i>
|
||||
<h5 class="mb-2">Brak aktywnych zbiórek</h5>
|
||||
<p class="text-muted mb-4">Wygląda na to, że teraz nic nie zbieramy.</p>
|
||||
<a href="{{ url_for('formularz_zbiorek') }}" class="btn btn-primary">Utwórz nową zbiórkę</a>
|
||||
<a href="{{ url_for('formularz_zbiorek') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Utwórz nową zbiórkę
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -174,7 +250,9 @@
|
||||
<td class="text-muted">{{ z.id }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">{{ z.nazwa }}</span>
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=z.id) }}" class="fw-semibold text-decoration-none">
|
||||
{{ z.nazwa }}
|
||||
</a>
|
||||
{% if z.cel is defined or z.stan is defined %}
|
||||
<small class="text-muted">
|
||||
{% if z.cel is defined %} Cel: {{ z.cel|round(2) }} PLN {% endif %}
|
||||
@@ -186,68 +264,87 @@
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<span class="badge rounded-pill"
|
||||
style="background: var(--accent); color:#111;">Zrealizowana</span>
|
||||
style="background: var(--accent); color:#111;">
|
||||
<i class="bi bi-check-circle"></i> Zrealizowana
|
||||
</span>
|
||||
{% if z.ukryta %}
|
||||
<span class="badge bg-secondary border"
|
||||
style="border-color: var(--border);">Ukryta</span>
|
||||
style="border-color: var(--border);"><i class="bi bi-eye-slash"></i> Ukryta</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Widoczna</span>
|
||||
<span class="badge bg-success"><i class="bi bi-eye"></i> Widoczna</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group">
|
||||
<a href="{{ url_for('formularz_zbiorek', zbiorka_id=z.id) }}"
|
||||
class="btn btn-sm btn-outline-light">Edytuj</a>
|
||||
class="btn btn-sm btn-outline-light">
|
||||
<i class="bi bi-pencil"></i> Edytuj
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-light dropdown-toggle dropdown-toggle-split"
|
||||
data-bs-toggle="dropdown" aria-expanded="false" aria-label="Więcej opcji">
|
||||
<span class="visually-hidden">Więcej</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for('zbiorka', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-eye"></i> Podgląd
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">Dodaj
|
||||
wpłatę</a>
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-plus-circle text-success"></i> Dodaj wpłatę
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">Dodaj
|
||||
wydatek</a>
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-dash-circle text-danger"></i> Dodaj wydatek
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=z.id) }}">Transakcje</a>
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-list-ul"></i> Transakcje
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">Edytuj stan</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">
|
||||
<i class="bi bi-currency-dollar"></i> Edytuj stan
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{{ url_for('oznacz_niezrealizowana', zbiorka_id=z.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">Oznacz jako
|
||||
niezrealizowaną</button>
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> Oznacz jako niezrealizowaną
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<form action="{{ url_for('zmien_widzialnosc', zbiorka_id=z.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">
|
||||
{% if z.ukryta %}Pokaż{% else %}Ukryj{% endif %}
|
||||
{% if z.ukryta %}
|
||||
<i class="bi bi-eye"></i> Pokaż
|
||||
{% else %}
|
||||
<i class="bi bi-eye-slash"></i> Ukryj
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{{ url_for('usun_zbiorka', zbiorka_id=z.id) }}" method="post"
|
||||
class="m-0"
|
||||
onsubmit="return confirm('Czy na pewno chcesz usunąć tę zbiórkę?');">
|
||||
<button type="submit" class="dropdown-item text-danger">Usuń</button>
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="bi bi-trash"></i> Usuń
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -261,10 +358,12 @@
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-trophy fs-1 text-muted mb-3"></i>
|
||||
<h5 class="mb-2">Brak zbiórek zrealizowanych</h5>
|
||||
<p class="text-muted mb-3">Gdy jakaś zbiórka osiągnie 100%, pojawi się tutaj.</p>
|
||||
<a href="{{ url_for('formularz_zbiorek') }}" class="btn btn-outline-light">Utwórz nową
|
||||
zbiórkę</a>
|
||||
<a href="{{ url_for('formularz_zbiorek') }}" class="btn btn-outline-light">
|
||||
<i class="bi bi-plus-circle"></i> Utwórz nową zbiórkę
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -3,141 +3,194 @@
|
||||
{% 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">Listy rezerwowe</h2>
|
||||
<div>
|
||||
<h2 class="mb-1">Listy rezerwowe</h2>
|
||||
<p class="text-muted mb-0">Zarządzaj środkami rezerwowymi i nadpłatami</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('dodaj_rezerwe') }}" class="btn btn-primary">
|
||||
➕ Dodaj listę rezerwową
|
||||
<i class="bi bi-plus-circle"></i> Dodaj listę
|
||||
</a>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-light">
|
||||
Panel Admina
|
||||
<i class="bi bi-arrow-left"></i> Panel Admina
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Szybkie statystyki dla rezerw -->
|
||||
{% if rezerwy %}
|
||||
<div class="table-responsive mb-5">
|
||||
<table class="table table-dark table-striped table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:72px;">ID</th>
|
||||
<th>Nazwa</th>
|
||||
<th style="width:140px;">Widoczność</th>
|
||||
<th style="width:1%;">Opcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rezerwy %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ r.id }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-wallet2"></i> {{ r.nazwa }}
|
||||
</span>
|
||||
<small class="text-muted">
|
||||
Stan: {{ r.stan|round(2) }} PLN
|
||||
{% if r.opis %}
|
||||
· {{ r.opis[:50] }}{% if r.opis|length > 50 %}...{% endif %}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if r.ukryta %}
|
||||
<span class="badge bg-secondary border" style="border-color: var(--border);">Ukryta</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Widoczna</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<!-- Grupa akcji: główne + rozwijane -->
|
||||
<div class="btn-group">
|
||||
<a href="{{ url_for('edytuj_rezerwe', rezerwa_id=r.id) }}"
|
||||
class="btn btn-sm btn-outline-light">Edytuj</a>
|
||||
<button class="btn btn-sm btn-outline-light dropdown-toggle dropdown-toggle-split"
|
||||
data-bs-toggle="dropdown" aria-expanded="false" aria-label="Więcej opcji">
|
||||
<span class="visually-hidden">Więcej</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('zbiorka', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-eye"></i> Otwórz
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-plus-circle"></i> Dodaj wpłatę
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-dash-circle"></i> Dodaj wydatek
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_przesuniecie', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-arrow-left-right"></i> Przesuń środki
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-list-ul"></i> Transakcje
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-pencil-square"></i> Edytuj stan
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<form action="{{ url_for('zmien_widzialnosc', zbiorka_id=r.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="bi bi-eye{% if r.ukryta %}-slash{% endif %}"></i>
|
||||
{% if r.ukryta %}Pokaż{% else %}Ukryj{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<form action="{{ url_for('usun_rezerwe', rezerwa_id=r.id) }}" method="post"
|
||||
class="m-0"
|
||||
onsubmit="return confirm('Czy na pewno usunąć listę \'{{ r.nazwa }}\'?\n\nUWAGA: Zostaną usunięte wszystkie transakcje powiązane z tą listą!');">
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="bi bi-trash"></i> Usuń
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card border-info">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-wallet2 fs-1 text-info mb-2"></i>
|
||||
<h3 class="mb-0">{{ rezerwy|length }}</h3>
|
||||
<small class="text-muted">Aktywnych list</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-success">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-piggy-bank fs-1 text-success mb-2"></i>
|
||||
<h3 class="mb-0">{{ "%.2f"|format(rezerwy|sum(attribute='stan')) }} zł</h3>
|
||||
<small class="text-muted">Łączna rezerwa</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-eye fs-1 text-warning mb-2"></i>
|
||||
<h3 class="mb-0">{{ rezerwy|selectattr('ukryta', 'equalto', False)|list|length }}</h3>
|
||||
<small class="text-muted">Widocznych publicznie</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if rezerwy %}
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-striped table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:72px;">ID</th>
|
||||
<th>Nazwa</th>
|
||||
<th class="text-end" style="width:150px;">Stan</th>
|
||||
<th style="width:140px;">Widoczność</th>
|
||||
<th style="width:1%;">Opcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rezerwy %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ r.id }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=r.id) }}" class="fw-semibold text-decoration-none">
|
||||
<i class="bi bi-wallet2"></i> {{ r.nazwa }}
|
||||
</a>
|
||||
{% if r.opis %}
|
||||
<small class="text-muted">
|
||||
{{ r.opis[:60] }}{% if r.opis|length > 60 %}...{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<strong class="text-success">{{ "%.2f"|format(r.stan) }} zł</strong>
|
||||
</td>
|
||||
<td>
|
||||
{% if r.ukryta %}
|
||||
<span class="badge bg-secondary border" style="border-color: var(--border);">
|
||||
<i class="bi bi-eye-slash"></i> Ukryta
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-eye"></i> Widoczna
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<!-- Grupa akcji: główne + rozwijane -->
|
||||
<div class="btn-group">
|
||||
<a href="{{ url_for('edytuj_rezerwe', rezerwa_id=r.id) }}"
|
||||
class="btn btn-sm btn-outline-light">
|
||||
<i class="bi bi-pencil"></i> Edytuj
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-light dropdown-toggle dropdown-toggle-split"
|
||||
data-bs-toggle="dropdown" aria-expanded="false" aria-label="Więcej opcji">
|
||||
<span class="visually-hidden">Więcej</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('zbiorka', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-eye"></i> Podgląd
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-plus-circle text-success"></i> Dodaj wpłatę
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-dash-circle text-danger"></i> Dodaj wydatek
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_przesuniecie', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-arrow-left-right text-info"></i> Przesuń środki
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-list-ul"></i> Transakcje
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-currency-dollar"></i> Edytuj stan
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{{ url_for('zmien_widzialnosc', zbiorka_id=r.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">
|
||||
{% if r.ukryta %}
|
||||
<i class="bi bi-eye"></i> Pokaż
|
||||
{% else %}
|
||||
<i class="bi bi-eye-slash"></i> Ukryj
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{{ url_for('usun_rezerwe', rezerwa_id=r.id) }}" method="post"
|
||||
class="m-0"
|
||||
onsubmit="return confirm('Czy na pewno usunąć listę \'{{ r.nazwa }}\'?\n\nUWAGA: Zostaną usunięte wszystkie transakcje powiązane z tą listą!');">
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="bi bi-trash"></i> Usuń
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- Empty state -->
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-wallet2 fs-1 text-muted mb-3"></i>
|
||||
<h5 class="mb-2">Brak list rezerwowych</h5>
|
||||
<p class="text-muted mb-4">Nie masz jeszcze żadnych list rezerwowych. Utwórz pierwszą, aby zarządzać nadpłatami i środkami rezerwowymi.</p>
|
||||
<a href="{{ url_for('dodaj_rezerwe') }}" class="btn btn-primary">Dodaj listę rezerwową</a>
|
||||
<p class="text-muted mb-4">
|
||||
Nie masz jeszcze żadnych list rezerwowych.<br>
|
||||
Utwórz pierwszą, aby zarządzać nadpłatami i środkami rezerwowymi.
|
||||
</p>
|
||||
<a href="{{ url_for('dodaj_rezerwe') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Dodaj listę rezerwową
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
461
templates/admin/statystyki.html
Normal file
461
templates/admin/statystyki.html
Normal 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 %}
|
||||
@@ -3,14 +3,30 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-4">
|
||||
|
||||
<!-- Nagłówek -->
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
|
||||
<div>
|
||||
<h2 class="mb-1">Ustawienia globalne</h2>
|
||||
<p class="text-muted mb-0">Konfiguracja systemu, płatności i wyglądu</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('admin_statystyki') }}" class="btn btn-outline-light">
|
||||
Statystyki
|
||||
</a>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-light">
|
||||
← Panel Admina
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" novalidate id="form-global-settings">
|
||||
{# {{ form.csrf_token }} jeśli używasz Flask-WTF #}
|
||||
|
||||
<!-- SEKCJA: Dane płatności -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-secondary text-white d-flex align-items-center justify-content-between gap-2">
|
||||
<h3 class="card-title mb-0">Dane płatności</h3>
|
||||
<small class="opacity-75">Używane jako wartości domyślne przy dodawaniu/edycji zbiórek</small>
|
||||
<small class="opacity-75">Wartości domyślne dla zbiórek</small>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@@ -23,7 +39,7 @@
|
||||
value="{{ settings.numer_konta if settings else '' }}" inputmode="numeric" autocomplete="off"
|
||||
placeholder="12 3456 7890 1234 5678 9012 3456" required aria-describedby="ibanHelp">
|
||||
</div>
|
||||
<div id="ibanHelp" class="form-text">Wpisz ciąg cyfr — spacje dodadzą się automatycznie co 4 znaki.</div>
|
||||
<div id="ibanHelp" class="form-text">Wpisz ciąg cyfr — spacje dodadzą się automatycznie co 4 znaki</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
@@ -34,7 +50,7 @@
|
||||
value="{{ settings.numer_telefonu_blik if settings else '' }}" inputmode="tel" pattern="[0-9 ]{9,13}"
|
||||
placeholder="123 456 789" required aria-describedby="blikHelp">
|
||||
</div>
|
||||
<div id="blikHelp" class="form-text">9 cyfr. Spacje i format 3-3-3 dodajemy dla czytelności.</div>
|
||||
<div id="blikHelp" class="form-text">9 cyfr. Format 3-3-3 dla czytelności</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,8 +59,8 @@
|
||||
<!-- SEKCJA: Dostępy / biała lista IP -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-secondary text-white d-flex align-items-center justify-content-between gap-2">
|
||||
<h3 class="card-title mb-0">Dostęp — dozwolone adresy IP / hosty</h3>
|
||||
<small class="opacity-75">Zależnie od konfiguracji logowanie może wymagać dopasowania do białej listy</small>
|
||||
<h3 class="card-title mb-0">Kontrola dostępu</h3>
|
||||
<small class="opacity-75">Biała lista IP/hostów dla logowania</small>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@@ -59,16 +75,16 @@
|
||||
➕ Dodaj
|
||||
</button>
|
||||
</div>
|
||||
<div id="hostAddHelp" class="form-text">Po wpisaniu kliknij „Dodaj”. Duplikaty są pomijane.</div>
|
||||
<div id="hostAddHelp" class="form-text">Po wpisaniu kliknij „Dodaj". Duplikaty są pomijane</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-lg-end">
|
||||
<button type="button" class="btn btn-light text-dark" id="btn-add-my-ip" data-my-ip="{{ client_ip }}">
|
||||
<i class="fas fa-location-arrow"></i> ➕ Dodaj moje IP ({{ client_ip }})
|
||||
➕ Dodaj moje IP ({{ client_ip }})
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" id="btn-dedupe">
|
||||
Usuń duplikaty
|
||||
Usuń duplikaty
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,12 +102,37 @@
|
||||
placeholder="Adresy IP lub nazwy domen — każdy w osobnej linii lub rozdzielony przecinkiem">{{ settings.dozwolone_hosty_logowania if settings and settings.dozwolone_hosty_logowania else '' }}</textarea>
|
||||
|
||||
<small class="text-muted d-block mt-1">
|
||||
Akceptowane separatory: przecinek (`,`), średnik (`;`) i nowa linia.
|
||||
Akceptowane separatory: przecinek (`,`), średnik (`;`) i nowa linia
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEKCJA: Kolejność list rezerwowych -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-secondary text-white d-flex align-items-center justify-content-between gap-2">
|
||||
<h3 class="card-title mb-0">Kolejność wyświetlania</h3>
|
||||
<small class="opacity-75">Pozycja list rezerwowych na stronie głównej</small>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="kolejnosc_rezerwowych" class="form-label fw-semibold">Kolejność list rezerwowych</label>
|
||||
<select class="form-select" id="kolejnosc_rezerwowych" name="kolejnosc_rezerwowych">
|
||||
<option value="id" {% if settings and settings.kolejnosc_rezerwowych == 'id' %}selected{% endif %}>
|
||||
Według ID (domyślnie)
|
||||
</option>
|
||||
<option value="first" {% if settings and settings.kolejnosc_rezerwowych == 'first' %}selected{% endif %}>
|
||||
Jako pierwsze
|
||||
</option>
|
||||
<option value="last" {% if settings and settings.kolejnosc_rezerwowych == 'last' %}selected{% endif %}>
|
||||
Jako ostatnie
|
||||
</option>
|
||||
</select>
|
||||
<small class="text-muted d-block mt-1">Określa, gdzie na stronie głównej będą wyświetlane listy rezerwowe względem standardowych zbiórek</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEKCJA: Branding -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
@@ -107,7 +148,7 @@
|
||||
<label for="logo_url" class="form-label">Logo (URL PNG/SVG)</label>
|
||||
<input type="text" class="form-control" id="logo_url" name="logo_url"
|
||||
value="{{ settings.logo_url if settings else '' }}" placeholder="https://example.com/logo.svg">
|
||||
<div class="form-text">Transparentne, do ~60px wysokości.</div>
|
||||
<div class="form-text">Transparentne, do ~60px wysokości</div>
|
||||
{% if settings and settings.logo_url %}
|
||||
<div class="mt-2">
|
||||
<img src="{{ settings.logo_url }}" alt="Logo preview" style="max-height:50px">
|
||||
@@ -140,7 +181,7 @@
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="navbar_mode_text">Pokaż tekst</label>
|
||||
</div>
|
||||
<div class="form-text mt-1">Jeśli wybierzesz logo, użyjemy adresu z pola "Tytuł serwisu".</div>
|
||||
<div class="form-text mt-1">Jeśli wybierzesz logo, użyjemy adresu z pola "Logo URL"</div>
|
||||
</div>
|
||||
|
||||
<!-- STOPKA -->
|
||||
@@ -157,17 +198,16 @@
|
||||
<label class="form-check-label" for="footer_mode_text">Tekst</label>
|
||||
</div>
|
||||
|
||||
<label for="stopka_text" class="form-label mt-2">Tekst w stopce (gdy wybrano „Tekst”)</label>
|
||||
<label for="stopka_text" class="form-label mt-2">Tekst w stopce (gdy wybrano „Tekst")</label>
|
||||
<input type="text" class="form-control" id="stopka_text" name="stopka_text"
|
||||
value="{{ settings.stopka_text if settings and settings.stopka_text else '' }}"
|
||||
placeholder="Np. © {{ now().year if now else '2025' }} Zbiórki">
|
||||
<div class="form-text">Pozostaw pusty, by użyć domyślnego.</div>
|
||||
<div class="form-text">Pozostaw pusty, by użyć domyślnego</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- CTA -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-light">Powrót</a>
|
||||
|
||||
@@ -1,124 +1,143 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}{% if request.path == url_for('zbiorki_zrealizowane') %}Zrealizowane zbiórki{% else %}Aktualnie aktywne
|
||||
zbiórki{% endif %}{% endblock %}
|
||||
{% block title %}{% if request.path == url_for('zbiorki_zrealizowane') %}Zrealizowane zbiórki{% else %}Aktualnie aktywne zbiórki{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{# Ustal kontekst listy #}
|
||||
{% set is_completed_view = (request.path == url_for('zbiorki_zrealizowane')) %}
|
||||
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
|
||||
<h2 class="mb-0">
|
||||
{% if is_completed_view %}Zrealizowane zbiórki{% else %}Aktywne zbiórki{% endif %}
|
||||
</h2>
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if not is_completed_view %}active bg-secondary text-light{% endif %}"
|
||||
href="{{ url_for('index') }}">Aktywne</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if is_completed_view %}active bg-secondary text-light{% endif %}"
|
||||
href="{{ url_for('zbiorki_zrealizowane') }}">Zrealizowane</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if zbiorki and zbiorki|length > 0 %}
|
||||
<div class="row g-4 pb-5">
|
||||
{% for z in zbiorki %}
|
||||
{% set progress = (z.stan / z.cel * 100) if z.cel > 0 else 0 %}
|
||||
{% set progress_clamped = 100 if progress > 100 else (0 if progress < 0 else progress) %}
|
||||
<div class="col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="card h-100 position-relative">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex align-items-start justify-content-between gap-2 mb-2">
|
||||
<h5 class="card-title mb-0">{{ z.nazwa }}</h5>
|
||||
|
||||
{% if z.typ_zbiorki == 'rezerwa' %}
|
||||
<span class="badge bg-info">Rezerwa</span>
|
||||
{% elif is_completed_view or progress_clamped >= 100 %}
|
||||
<span class="badge rounded-pill" style="background: var(--accent); color:#111;">Zrealizowana</span>
|
||||
<div class="container my-4">
|
||||
<!-- Nagłówek z przełącznikiem -->
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="mb-0">
|
||||
{% if is_completed_view %}Zrealizowane zbiórki{% else %}Aktywne zbiórki{% endif %}
|
||||
</h2>
|
||||
<p class="text-muted mb-0 small">
|
||||
{% if is_completed_view %}
|
||||
Ukończone projekty i osiągnięte cele
|
||||
{% else %}
|
||||
Trwające zbiórki, które możesz wesprzeć
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if not is_completed_view %}active{% endif %}"
|
||||
href="{{ url_for('index') }}">
|
||||
Aktywne
|
||||
{% if not is_completed_view and zbiorki %}
|
||||
<span class="badge bg-light text-dark ms-1">{{ zbiorki|length }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr class="hr-bw">
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if is_completed_view %}active{% endif %}"
|
||||
href="{{ url_for('zbiorki_zrealizowane') }}">
|
||||
Zrealizowane
|
||||
{% if is_completed_view and zbiorki %}
|
||||
<span class="badge bg-light text-dark ms-1">{{ zbiorki|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-2 d-flex flex-wrap gap-2 justify-content-center">
|
||||
{% if not z.ukryj_kwote %}
|
||||
{% if z.cel > 0 and z.typ_zbiorki != 'rezerwa' %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
Cel: {{ z.cel|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-dark border border-success {% if z.typ_zbiorki == 'rezerwa' %}w-100{% endif %}" style="border-color: var(--border);">
|
||||
Stan: {{ z.stan|round(2) }} PLN
|
||||
</span>
|
||||
{% if zbiorki and zbiorki|length > 0 %}
|
||||
<div class="row g-3 pb-4">
|
||||
{% for z in zbiorki %}
|
||||
{% set progress = (z.stan / z.cel * 100) if z.cel > 0 else 0 %}
|
||||
{% set progress_clamped = 100 if progress > 100 else (0 if progress < 0 else progress) %}
|
||||
<div class="col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="card h-100 position-relative">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex align-items-start justify-content-between gap-2 mb-2">
|
||||
<h5 class="card-title mb-0">{{ z.nazwa }}</h5>
|
||||
|
||||
{% if z.cel > 0 and z.typ_zbiorki != 'rezerwa' %}
|
||||
{% set delta = z.cel - z.stan %}
|
||||
{% if delta > 0 %}
|
||||
<span class="badge bg-dark border border-warning">
|
||||
Brakuje: {{ delta|round(2) }} PLN
|
||||
</span>
|
||||
{% elif delta < 0 %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
Nadwyżka: {{ (-delta)|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Kwoty niepubliczne</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if z.typ_zbiorki == 'rezerwa' %}
|
||||
<span class="badge bg-info">Rezerwa</span>
|
||||
{% elif is_completed_view or progress_clamped >= 100 %}
|
||||
<span class="badge rounded-pill" style="background: var(--accent); color:#111;">Zrealizowana</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr class="hr-bw my-2">
|
||||
|
||||
{# Progress bar TYLKO dla standardowych zbiórek (nie dla rezerwowych) #}
|
||||
{% if z.typ_zbiorki != 'rezerwa' %}
|
||||
<div class="mb-1">
|
||||
<div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuenow="{{ progress_clamped|round(2) if not z.ukryj_kwote else '' }}"
|
||||
aria-label="{% if z.ukryj_kwote %}Postęp ukryty{% else %}Postęp zbiórki {{ progress_clamped|round(0) }} procent{% endif %}">
|
||||
<div class="progress-bar" style="width: {{ progress_clamped }}%;"></div>
|
||||
<div class="mb-2 d-flex flex-wrap gap-2 justify-content-center">
|
||||
{% if not z.ukryj_kwote %}
|
||||
{% if z.cel > 0 and z.typ_zbiorki != 'rezerwa' %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
Cel: {{ z.cel|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-dark border border-success {% if z.typ_zbiorki == 'rezerwa' %}w-100{% endif %}" style="border-color: var(--border);">
|
||||
Stan: {{ z.stan|round(2) }} PLN
|
||||
</span>
|
||||
|
||||
{% if z.cel > 0 and z.typ_zbiorki != 'rezerwa' %}
|
||||
{% set delta = z.cel - z.stan %}
|
||||
{% if delta > 0 %}
|
||||
<span class="badge bg-dark border border-warning">
|
||||
Brakuje: {{ delta|round(2) }} PLN
|
||||
</span>
|
||||
{% elif delta < 0 %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
Nadwyżka: {{ (-delta)|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Kwoty niepubliczne</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not z.ukryj_kwote %}
|
||||
<small class="text-muted d-block text-center">{{ progress_clamped|round(1) }}%</small>
|
||||
{% else %}
|
||||
<small class="text-muted d-block text-center">Postęp ukryty</small>
|
||||
{# Progress bar TYLKO dla standardowych zbiórek (nie dla rezerwowych) #}
|
||||
{% if z.typ_zbiorki != 'rezerwa' %}
|
||||
<div class="mb-2">
|
||||
<div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuenow="{{ progress_clamped|round(2) if not z.ukryj_kwote else '' }}"
|
||||
aria-label="{% if z.ukryj_kwote %}Postęp ukryty{% else %}Postęp zbiórki {{ progress_clamped|round(0) }} procent{% endif %}">
|
||||
<div class="progress-bar" style="width: {{ progress_clamped }}%;"></div>
|
||||
</div>
|
||||
|
||||
{% if not z.ukryj_kwote %}
|
||||
<small class="text-muted d-block text-center">{{ progress_clamped|round(1) }}%</small>
|
||||
{% else %}
|
||||
<small class="text-muted d-block text-center">Postęp ukryty</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-auto pt-2">
|
||||
{# TO POWODUJE ZE BLOK JEST KLIKALNY #}
|
||||
<!-- <a href="{{ url_for('zbiorka', zbiorka_id=z.id) }}" class="stretched-link"></a> -->
|
||||
|
||||
<div class="d-grid">
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=z.id) }}"
|
||||
class="btn btn-outline-light btn-sm w-100 btn-opis">
|
||||
Otwórz
|
||||
</a>
|
||||
<div class="mt-auto pt-1">
|
||||
<div class="d-grid">
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=z.id) }}"
|
||||
class="btn btn-outline-light btn-sm w-100 btn-opis">
|
||||
Otwórz
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
{% if is_completed_view %}
|
||||
<h5 class="mb-2">Brak zrealizowanych zbiórek</h5>
|
||||
<p class="text-muted mb-4">Gdy jakaś zbiórka osiągnie 100%, pojawi się tutaj.</p>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-primary">Zobacz aktywne</a>
|
||||
{% else %}
|
||||
<h5 class="mb-2">Brak aktywnych zbiórek</h5>
|
||||
<p class="text-muted mb-4">Wygląda na to, że teraz nic nie zbieramy.</p>
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-primary">Utwórz nową zbiórkę</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('zbiorki_zrealizowane') }}" class="btn btn-primary">Zobacz zrealizowane</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- Empty state -->
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
{% if is_completed_view %}
|
||||
<h5 class="mb-2">Brak zrealizowanych zbiórek</h5>
|
||||
<p class="text-muted mb-4">Gdy jakaś zbiórka osiągnie 100%, pojawi się tutaj.</p>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-primary">Zobacz aktywne</a>
|
||||
{% else %}
|
||||
<h5 class="mb-2">Brak aktywnych zbiórek</h5>
|
||||
<p class="text-muted mb-4">Wygląda na to, że teraz nic nie zbieramy.</p>
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-primary">Utwórz nową zbiórkę</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('zbiorki_zrealizowane') }}" class="btn btn-outline-light">Zobacz zrealizowane</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -12,12 +12,21 @@
|
||||
|
||||
<!-- Nagłówek -->
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||
<h2 class="mb-0">
|
||||
<div>
|
||||
<h2 class="mb-0">
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
<i class="bi bi-wallet2"></i>
|
||||
{% endif %}
|
||||
{{ zbiorka.nazwa }}
|
||||
</h2>
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
<i class="bi bi-wallet2"></i>
|
||||
<p class="text-muted mb-0 small">Lista rezerwowa środków</p>
|
||||
{% elif is_done %}
|
||||
<p class="text-muted mb-0 small">Zbiórka zrealizowana</p>
|
||||
{% else %}
|
||||
<p class="text-muted mb-0 small">Aktywna zbiórka</p>
|
||||
{% endif %}
|
||||
{{ zbiorka.nazwa }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
<span class="badge bg-info">Lista rezerwowa</span>
|
||||
@@ -33,15 +42,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-3">
|
||||
<!-- Kolumna lewa: Opis + (opcjonalnie) Lista zakupów + Postęp -->
|
||||
<div class="col-md-8">
|
||||
|
||||
<!-- Card: Opis -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-2">Opis</h5>
|
||||
<hr class="hr-bw">
|
||||
<hr class="hr-bw my-2">
|
||||
<div class="mb-0">
|
||||
{{ zbiorka.opis | markdown }}
|
||||
</div>
|
||||
@@ -54,14 +63,17 @@
|
||||
|
||||
<!-- Card: Lista zakupów (tylko gdy są produkty) -->
|
||||
{% if has_items %}
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-2">Lista zakupów</h5>
|
||||
<hr class="hr-bw">
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<h5 class="mb-0">Lista zakupów</h5>
|
||||
<span class="badge bg-secondary">{{ items|length }} pozycji</span>
|
||||
</div>
|
||||
<hr class="hr-bw my-2">
|
||||
{% set posortowane = items|sort(attribute='kupione') %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for it in posortowane %}
|
||||
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-center">
|
||||
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-center py-2">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if it.kupione %}
|
||||
<span class="badge bg-success">Kupione</span>
|
||||
@@ -70,8 +82,7 @@
|
||||
{% endif %}
|
||||
<span class="fw-semibold">{{ it.nazwa }}</span>
|
||||
{% if it.link %}
|
||||
<a href="{{ it.link }}" target="_blank" rel="noopener" class="btn btn-sm btn-outline-light ms-2">Sklep
|
||||
↗</a>
|
||||
<a href="{{ it.link }}" target="_blank" rel="noopener" class="btn btn-sm btn-outline-light ms-2">Sklep ↗</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
@@ -131,11 +142,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="hr-bw">
|
||||
<hr class="hr-bw my-2">
|
||||
|
||||
{# Pasek: Finanse #}
|
||||
{% if zbiorka.pokaz_postep_finanse %}
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Finanse</small>
|
||||
<div class="progress" role="progressbar" aria-valuenow="{{ progress_clamped|round(2) }}" aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
@@ -149,7 +160,7 @@
|
||||
|
||||
{# Pasek: Zakupy sztukami #}
|
||||
{% if has_items and zbiorka.pokaz_postep_pozycje %}
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">Zakupy (liczba pozycji)</small>
|
||||
<div class="progress" role="progressbar" aria-valuenow="{{ items_pct|round(2) }}" aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
@@ -183,7 +194,7 @@
|
||||
<!-- Kolumna prawa: płatności (sticky) -->
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm wspomoz-card sticky-md" style="top: var(--sticky-offset, 1rem);">
|
||||
<div class="card-body d-flex flex-column gap-3">
|
||||
<div class="card-body d-flex flex-column gap-2">
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<h5 class="mb-0">
|
||||
@@ -208,12 +219,12 @@
|
||||
<!-- Numer konta -->
|
||||
<div>
|
||||
<label for="ibanInput" class="form-label fw-semibold mb-1">Numer konta</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<input id="ibanInput" type="text"
|
||||
class="form-control form-control-sm bg-transparent text-light border monospace-input text-truncate"
|
||||
class="form-control bg-transparent text-light border monospace-input text-truncate"
|
||||
value="{{ zbiorka.numer_konta }}" readonly autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||
spellcheck="false" inputmode="text" aria-label="Numer konta do wpłaty">
|
||||
<button class="btn btn-sm btn-outline-light copy-btn" type="button" data-copy-input="#ibanInput"
|
||||
<button class="btn btn-outline-light copy-btn" type="button" data-copy-input="#ibanInput"
|
||||
aria-label="Kopiuj numer konta">Kopiuj</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,12 +234,12 @@
|
||||
<!-- Telefon BLIK -->
|
||||
<div>
|
||||
<label for="blikInput" class="form-label fw-semibold mb-1">Telefon / BLIK</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group input-group-sm">
|
||||
<input id="blikInput" type="text"
|
||||
class="form-control form-control-sm bg-transparent text-light border monospace-input text-truncate"
|
||||
class="form-control bg-transparent text-light border monospace-input text-truncate"
|
||||
value="{{ zbiorka.numer_telefonu_blik }}" readonly autocomplete="off" autocorrect="off"
|
||||
autocapitalize="off" spellcheck="false" inputmode="numeric" aria-label="Telefon BLIK">
|
||||
<button class="btn btn-sm btn-outline-light copy-btn" type="button" data-copy-input="#blikInput"
|
||||
<button class="btn btn-outline-light copy-btn" type="button" data-copy-input="#blikInput"
|
||||
aria-label="Kopiuj numer BLIK">Kopiuj</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,23 +251,23 @@
|
||||
{% endif %}
|
||||
|
||||
{% if not zbiorka.ukryj_kwote %}
|
||||
<ul class="list-group list-group-flush small">
|
||||
<ul class="list-group list-group-flush small mt-2">
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
{% if has_cel %}
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between py-1">
|
||||
<span>Cel</span>
|
||||
<span class="fw-semibold">{{ zbiorka.cel|round(2) }} PLN</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between py-1">
|
||||
<span>Stan</span>
|
||||
<span class="fw-semibold text-success">{{ zbiorka.stan|round(2) }} PLN</span>
|
||||
</li>
|
||||
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' and has_cel %}
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between py-1">
|
||||
<span>
|
||||
{% if brak > 0 %}Brakuje{% elif brak == 0 %}Cel{% else %}Nadwyżka{% endif %}
|
||||
</span>
|
||||
@@ -275,17 +286,14 @@
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<hr>
|
||||
<div class="d-grid gap-2 mt-2">
|
||||
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Dodaj
|
||||
wpłatę</a>
|
||||
<a href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Dodaj
|
||||
wydatek</a>
|
||||
<hr class="my-2">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Dodaj wpłatę</a>
|
||||
<a href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Dodaj wydatek</a>
|
||||
<a href="{{ url_for('dodaj_przesuniecie', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">
|
||||
<i class="bi bi-arrow-left-right"></i> Przesuń środki
|
||||
</a>
|
||||
<a href="{{ url_for('edytuj_stan', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Edytuj
|
||||
stan</a>
|
||||
<a href="{{ url_for('edytuj_stan', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Edytuj stan</a>
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
<a href="{{ url_for('formularz_zbiorek', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-outline-light btn-sm">Edytuj opis</a>
|
||||
@@ -300,101 +308,102 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aktywność -->
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-header bg-transparent d-flex align-items-center justify-content-between">
|
||||
<h5 class="card-title mb-0">Aktywność / Transakcje</h5>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||
<small class="text-muted">Łącznie pozycji: {{ aktywnosci|length }}</small>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<a href="{{ url_for('transakcje_zbiorki', zbiorka_id=zbiorka.id) }}" class="btn btn-sm btn-outline-light">
|
||||
Zarządzaj
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Aktywność -->
|
||||
<div class="card shadow-sm mt-3">
|
||||
<div class="card-header bg-transparent d-flex align-items-center justify-content-between py-2">
|
||||
<h5 class="card-title mb-0">Aktywność / Transakcje</h5>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for a in aktywnosci %}
|
||||
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-start">
|
||||
<div class="me-3 flex-grow-1">
|
||||
<strong>{{ a.data|dt("%d.%m.%Y %H:%M") }}</strong>
|
||||
|
||||
{% if a.typ == 'wpłata' %}
|
||||
<span class="badge bg-success ms-2">Wpłata</span>
|
||||
{% elif a.typ == 'wydatek' %}
|
||||
<span class="badge bg-danger ms-2">Wydatek</span>
|
||||
{% elif a.typ == 'przesunięcie_przych' %}
|
||||
<span class="badge bg-info ms-2">Przesunięcie (↓ przychód)</span>
|
||||
{% elif a.typ == 'przesunięcie_wych' %}
|
||||
<span class="badge bg-warning text-dark ms-2">Przesunięcie (↑ wychód)</span>
|
||||
{% endif %}
|
||||
|
||||
{% if a.opis %}
|
||||
<span class="text-muted">— {{ a.opis }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Informacja o przesunięciu wpłaty #}
|
||||
{% if a.typ == 'wpłata' and a.przesuniecie_z %}
|
||||
<div class="text-muted small mt-1">
|
||||
<span class="badge bg-secondary">Przesunięto</span>
|
||||
Źródło:
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=a.przesuniecie_z.zbiorka_zrodlo_id) }}"
|
||||
class="text-decoration-none">
|
||||
{{ a.przesuniecie_z.zbiorka_zrodlo_nazwa }}
|
||||
</a>
|
||||
{% if a.przesuniecie_z.opis %}
|
||||
<br><span class="text-muted">{{ a.przesuniecie_z.opis }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Link do źródłowej/docelowej zbiórki dla przesunięć ogólnych #}
|
||||
{% if a.typ in ['przesunięcie_przych', 'przesunięcie_wych'] and a.zbiorka_id %}
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=a.zbiorka_id) }}"
|
||||
class="ms-2 text-decoration-none small">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
{% if a.typ == 'przesunięcie_przych' %}
|
||||
z: {{ a.zbiorka_nazwa }}
|
||||
{% else %}
|
||||
do: {{ a.zbiorka_nazwa }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not zbiorka.ukryj_kwote %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
{% if a.typ == 'wpłata' or a.typ == 'przesunięcie_przych' %}
|
||||
+{{ a.kwota|round(2) }} PLN
|
||||
{% else %}
|
||||
-{{ a.kwota|round(2) }} PLN
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<h6 class="mb-1">Brak aktywności</h6>
|
||||
<p class="text-muted mb-0">Gdy pojawią się pierwsze wpłaty lub wydatki, zobaczysz je tutaj.</p>
|
||||
</div>
|
||||
<small class="text-muted">Łącznie pozycji: {{ aktywnosci|length }}</small>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<a href="{{ url_for('transakcje_zbiorki', zbiorka_id=zbiorka.id) }}" class="btn btn-sm btn-outline-light">
|
||||
Zarządzaj
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Akcje dolne -->
|
||||
<div class="d-flex gap-2 justify-content-between mt-3">
|
||||
<div></div>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-outline-light">Powrót do listy</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for a in aktywnosci %}
|
||||
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-start py-2">
|
||||
<div class="me-3 flex-grow-1">
|
||||
<strong>{{ a.data|dt("%d.%m.%Y %H:%M") }}</strong>
|
||||
|
||||
{% if a.typ == 'wpłata' %}
|
||||
<span class="badge bg-success ms-2">Wpłata</span>
|
||||
{% elif a.typ == 'wydatek' %}
|
||||
<span class="badge bg-danger ms-2">Wydatek</span>
|
||||
{% elif a.typ == 'przesunięcie_przych' %}
|
||||
<span class="badge bg-info ms-2">Przesunięcie (↓ przychód)</span>
|
||||
{% elif a.typ == 'przesunięcie_wych' %}
|
||||
<span class="badge bg-warning text-dark ms-2">Przesunięcie (↑ wychód)</span>
|
||||
{% endif %}
|
||||
|
||||
{% if a.opis %}
|
||||
<span class="text-muted">— {{ a.opis }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Informacja o przesunięciu wpłaty #}
|
||||
{% if a.typ == 'wpłata' and a.przesuniecie_z %}
|
||||
<div class="text-muted small mt-1">
|
||||
<span class="badge bg-secondary">Przesunięto</span>
|
||||
Źródło:
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=a.przesuniecie_z.zbiorka_zrodlo_id) }}"
|
||||
class="text-decoration-none">
|
||||
{{ a.przesuniecie_z.zbiorka_zrodlo_nazwa }}
|
||||
</a>
|
||||
{% if a.przesuniecie_z.opis %}
|
||||
<br><span class="text-muted">{{ a.przesuniecie_z.opis }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Link do źródłowej/docelowej zbiórki dla przesunięć ogólnych #}
|
||||
{% if a.typ in ['przesunięcie_przych', 'przesunięcie_wych'] and a.zbiorka_id %}
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=a.zbiorka_id) }}"
|
||||
class="ms-2 text-decoration-none small">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
{% if a.typ == 'przesunięcie_przych' %}
|
||||
z: {{ a.zbiorka_nazwa }}
|
||||
{% else %}
|
||||
do: {{ a.zbiorka_nazwa }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not zbiorka.ukryj_kwote %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
{% if a.typ == 'wpłata' or a.typ == 'przesunięcie_przych' %}
|
||||
+{{ a.kwota|round(2) }} PLN
|
||||
{% else %}
|
||||
-{{ a.kwota|round(2) }} PLN
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<h6 class="mb-1">Brak aktywności</h6>
|
||||
<p class="text-muted mb-0">Gdy pojawią się pierwsze wpłaty lub wydatki, zobaczysz je tutaj.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Akcje dolne -->
|
||||
<div class="d-flex gap-2 justify-content-between mt-3">
|
||||
<div></div>
|
||||
<a href="{{ url_for('index') }}" class="btn btn-outline-light">Powrót do listy</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user