nordabiz/templates/reports/fees.html
Maciej Pienczyn 4ac39471f6
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat(reports): membership fees report for board and council
Dynamic report at /raporty/skladki showing:
- Yearly execution bar (% collected)
- Company breakdown: fully paid / partial / no payments / wrong amounts
- Monthly execution bars per month
- Summary totals (collected / outstanding / plan)
Access: Rada Izby + OFFICE_MANAGER+. Numbers only, no company names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:30:42 +01:00

167 lines
8.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Raport składek {{ year }} - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.report-container { max-width: 900px; margin: 0 auto; }
.report-header { margin-bottom: var(--spacing-xl); }
.report-header h1 { font-size: var(--font-size-2xl); }
.report-meta { font-size: var(--font-size-sm); color: var(--text-secondary); margin-top: var(--spacing-xs); }
.stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--spacing-md); margin-bottom: var(--spacing-xl); }
.stat-box { background: var(--surface); padding: var(--spacing-md); border-radius: var(--radius-lg); box-shadow: var(--shadow); text-align: center; border-top: 3px solid var(--border); }
.stat-box.green { border-top-color: var(--success); }
.stat-box.orange { border-top-color: var(--warning); }
.stat-box.red { border-top-color: var(--error); }
.stat-box.blue { border-top-color: var(--primary); }
.stat-num { font-size: var(--font-size-2xl); font-weight: 700; color: var(--text-primary); }
.stat-label { font-size: var(--font-size-xs); color: var(--text-secondary); margin-top: 2px; }
.section { background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-lg); box-shadow: var(--shadow); margin-bottom: var(--spacing-xl); }
.section h2 { font-size: var(--font-size-lg); margin-bottom: var(--spacing-md); }
.progress-bar-bg { background: #e5e7eb; border-radius: 999px; height: 28px; overflow: hidden; position: relative; }
.progress-bar-fill { height: 100%; border-radius: 999px; display: flex; align-items: center; justify-content: center; font-size: var(--font-size-sm); font-weight: 600; color: white; transition: width 0.5s; }
.progress-bar-fill.green { background: var(--success); }
.progress-bar-fill.orange { background: var(--warning); }
.progress-bar-fill.red { background: var(--error); }
.month-row { display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-sm) 0; border-bottom: 1px solid var(--border); }
.month-row:last-child { border-bottom: none; }
.month-name { width: 90px; font-weight: 500; font-size: var(--font-size-sm); }
.month-bar { flex: 1; }
.month-nums { width: 180px; text-align: right; font-size: var(--font-size-sm); color: var(--text-secondary); }
.category-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-md); }
.cat-card { padding: var(--spacing-md); border-radius: var(--radius); border: 1px solid var(--border); }
.cat-card h3 { font-size: var(--font-size-base); margin-bottom: var(--spacing-xs); }
.cat-num { font-size: var(--font-size-2xl); font-weight: 700; }
.cat-num.green { color: var(--success); }
.cat-num.orange { color: var(--warning); }
.cat-num.red { color: var(--error); }
.cat-num.blue { color: #3b82f6; }
@media (max-width: 640px) {
.stats-row { grid-template-columns: repeat(2, 1fr); }
.category-grid { grid-template-columns: 1fr; }
}
</style>
{% endblock %}
{% block content %}
<div class="report-container">
<div class="report-header">
<a href="{{ url_for('reports.reports_index') }}" style="color: var(--text-secondary); text-decoration: none; font-size: var(--font-size-sm);">← Raporty</a>
<h1>Składki członkowskie {{ year }}</h1>
<div class="report-meta">
Wygenerowano: {{ generated_at.strftime('%d.%m.%Y %H:%M') }} |
<select onchange="location.href='?year='+this.value" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:2px 6px;font-size:var(--font-size-sm);">
{% for y in years %}<option value="{{ y }}" {{ 'selected' if y == year }}>{{ y }}</option>{% endfor %}
</select>
</div>
</div>
<!-- Execution bar -->
<div class="section">
<h2>Wykonanie roczne</h2>
<div style="display:flex;justify-content:space-between;margin-bottom:var(--spacing-xs);font-size:var(--font-size-sm);">
<span>Zebrano: <strong>{{ total_paid }} zł</strong></span>
<span>Do zebrania: <strong>{{ outstanding }} zł</strong></span>
</div>
<div class="progress-bar-bg">
<div class="progress-bar-fill {{ 'green' if execution_pct >= 70 else 'orange' if execution_pct >= 40 else 'red' }}" style="width: {{ execution_pct }}%; min-width: 40px;">
{{ execution_pct }}%
</div>
</div>
<div style="text-align:center;margin-top:var(--spacing-xs);font-size:var(--font-size-sm);color:var(--text-secondary);">
Plan roczny: {{ total_due }} zł
</div>
</div>
<!-- Summary stats -->
<div class="stats-row">
<div class="stat-box blue">
<div class="stat-num">{{ total_companies_with_fees }}</div>
<div class="stat-label">Firm z danymi</div>
</div>
<div class="stat-box green">
<div class="stat-num">{{ fully_paid }}</div>
<div class="stat-label">Opłacone w całości</div>
</div>
<div class="stat-box orange">
<div class="stat-num">{{ partially_paid }}</div>
<div class="stat-label">Częściowo opłacone</div>
</div>
<div class="stat-box red">
<div class="stat-num">{{ no_payments }}</div>
<div class="stat-label">Brak wpłat</div>
</div>
</div>
<!-- Categories -->
<div class="section">
<h2>Podział firm</h2>
<div class="category-grid">
<div class="cat-card">
<h3>Opłacone w całości</h3>
<div class="cat-num green">{{ fully_paid }}</div>
<div class="stat-label">firm uregulowało składki za cały rok</div>
</div>
<div class="cat-card">
<h3>Częściowo opłacone</h3>
<div class="cat-num orange">{{ partially_paid }}</div>
<div class="stat-label">firm ma wpłaty, ale nie za wszystkie miesiące</div>
</div>
<div class="cat-card">
<h3>Brak wpłat</h3>
<div class="cat-num red">{{ no_payments }}</div>
<div class="stat-label">firm nie dokonało żadnej wpłaty</div>
</div>
<div class="cat-card">
<h3>Nieprawidłowe kwoty</h3>
<div class="cat-num blue">{{ wrong_amounts }}</div>
<div class="stat-label">firm z niedopłatami (niepełne kwoty)</div>
</div>
</div>
</div>
<!-- Monthly breakdown -->
<div class="section">
<h2>Wykonanie miesięczne</h2>
{% for ms in monthly_stats %}
<div class="month-row">
<div class="month-name">{{ months_names[ms.month] }}</div>
<div class="month-bar">
<div class="progress-bar-bg" style="height:20px;">
<div class="progress-bar-fill {{ 'green' if ms.pct >= 70 else 'orange' if ms.pct >= 40 else 'red' }}" style="width:{{ ms.pct }}%;min-width:30px;font-size:11px;">
{{ ms.pct }}%
</div>
</div>
</div>
<div class="month-nums">{{ ms.paid }} / {{ ms.due }} zł</div>
</div>
{% endfor %}
</div>
<!-- Key numbers -->
<div class="section" style="text-align:center;">
<h2>Podsumowanie</h2>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:var(--spacing-lg);margin-top:var(--spacing-md);">
<div>
<div style="font-size:var(--font-size-3xl);font-weight:700;color:var(--success);">{{ total_paid }} zł</div>
<div class="stat-label">Zebrano</div>
</div>
<div>
<div style="font-size:var(--font-size-3xl);font-weight:700;color:var(--warning);">{{ outstanding }} zł</div>
<div class="stat-label">Pozostało do zebrania</div>
</div>
<div>
<div style="font-size:var(--font-size-3xl);font-weight:700;color:var(--primary);">{{ total_due }} zł</div>
<div class="stat-label">Plan roczny</div>
</div>
</div>
</div>
</div>
{% endblock %}