Add SQLite passive income dashboard with Excel import and deployment tools

This commit is contained in:
kai
2026-09-09 08:46:27 +02:00
parent 729a13bfca
commit afc6f74f36
27 changed files with 1109 additions and 67 deletions
+28 -57
View File
@@ -1,57 +1,28 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Finance Dashboard</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: 24px;
background: #0c1220;
color: #e8edf5;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
}
.card {
width: 100%;
max-width: 620px;
padding: clamp(24px, 6vw, 48px);
border: 1px solid #2a3549;
border-radius: 20px;
background: #151e2e;
box-shadow: 0 20px 60px #00000040;
}
h1 {
margin: 0 0 24px;
font-size: clamp(1.5rem, 5vw, 2.25rem);
line-height: 1.25;
letter-spacing: -0.03em;
}
.status {
margin: 0 0 16px;
color: #86efac;
font-weight: 600;
}
.description { margin: 0; color: #b7c3d5; }
</style>
</head>
<body>
<main class="card">
<h1>💶 Finance Dashboard</h1>
<p class="status">pinguAurora meldet sich zum Dienst.</p>
<p class="description">Hier entsteht dein persönliches Dashboard für Dividenden, Zinsen, Depot und Notgroschen.</p>
</main>
</body>
</html>
{% extends 'base.html' %}
{% macro delta(value) %}<span class="{{ 'positive' if value is not none and value > 0 else 'negative' if value is not none and value < 0 else 'muted' }}">{{ value|percent }}</span>{% endmacro %}
{% block content %}
<section class="page-heading"><div><p class="eyebrow">PASSIVES EINKOMMEN</p><h1>Deine Erträge im Überblick</h1><p class="muted">Dividenden, Ausschüttungen und Zinsen · Stand {{ stats.today.strftime('%d.%m.%Y') }}</p></div><a class="button" href="/income/new">+ Zahlung eintragen</a></section>
<div class="kpi-grid">
<article class="kpi"><h2>{{ months[stats.today.month-1] }} {{ stats.today.year }}</h2><strong>{{ stats.month|money }}</strong><small>Passives Einkommen aktueller Monat</small></article>
<article class="kpi"><h2>Gleicher Monat Vorjahr</h2><strong>{{ stats.prior_month|money }}</strong><small>{{ months[stats.today.month-1] }} {{ stats.today.year-1 }}</small></article>
<article class="kpi"><h2>Monat zum Vorjahr</h2><strong>{{ delta(stats.month_change) }}</strong><small>Veränderung zum Vorjahresmonat</small></article>
<article class="kpi"><h2>Aktuelles Jahr {{ stats.today.year }}</h2><strong>{{ stats.year|money }}</strong><small>Tatsächlich erhaltene Zahlungen</small></article>
<article class="kpi"><h2>Vorjahr {{ stats.today.year-1 }}</h2><strong>{{ stats.prior_year|money }}</strong><small>Gesamtes Kalenderjahr</small></article>
<article class="kpi"><h2>Jahr zum Vorjahr</h2><strong>{{ delta(stats.year_change) }}</strong><small>Aktuelles Jahr gegen gesamtes Vorjahr</small></article>
<article class="kpi"><h2>Alle Jahre</h2><strong class="positive">{{ stats.all_time|money }}</strong><small>Passives Einkommen insgesamt</small></article>
<article class="kpi"><h2>Zahlungen {{ stats.today.year }}</h2><strong>{{ stats.count }}</strong><small>Anzahl tatsächlich erhaltener Zahlungen</small></article>
</div>
{% if stats.pending.count %}<aside class="notice warning">{{ stats.pending.count }} erwartete, nicht erhaltene Zahlungen: <strong>{{ stats.pending.amount|money }}</strong>. Diese Beträge sind nicht in den tatsächlichen Einnahmen enthalten. <a href="/income">Buchungen ansehen</a></aside>{% endif %}
<section class="panel"><div class="section-heading"><h2>Jahresvergleich</h2><span class="muted">Nur tatsächlich erhaltene Zahlungen</span></div>
<div class="table-scroll"><table class="comparison"><thead><tr><th>Monat</th>{% for year in stats.years %}<th>{{ year }}</th>{% if not loop.first %}<th>{{ year }} vs. {{ year-1 }}</th>{% endif %}{% endfor %}<th>Alle Jahre</th></tr></thead>
<tbody>{% for month in months %}{% set mi = loop.index0 %}<tr><th>{{ month }}</th>{% for year in stats.years %}<td>{{ stats.monthly[year][mi]|money }}</td>{% if not loop.first %}<td>{{ delta(compare(stats.monthly[year][mi], stats.monthly[year-1][mi])) }}</td>{% endif %}{% endfor %}<td>{{ stats.monthly.values()|map(attribute=mi)|sum|money }}</td></tr>{% endfor %}</tbody>
<tfoot><tr><th>Gesamt</th>{% for year in stats.years %}<td>{{ stats.totals[year]|money }}</td>{% if not loop.first %}<td>{{ delta(compare(stats.totals[year], stats.totals[year-1])) }}</td>{% endif %}{% endfor %}<td>{{ stats.all_time|money }}</td></tr></tfoot></table></div></section>
<div class="chart-grid">
<section class="panel chart-wide"><h2>Monatsverlauf</h2><p class="muted">Die Jahre im direkten Vergleich</p><div class="chart"><canvas id="monthly-chart" role="img" aria-label="Monatseinnahmen je Jahr; Zahlen in der Jahresvergleichstabelle"></canvas></div></section>
<section class="panel"><h2>Einkommensanteile</h2><p class="muted">Positionen · alle Jahre</p><div class="chart"><canvas id="shares-chart" role="img" aria-label="Anteile nach Position"></canvas></div><details><summary>Beträge nach Position</summary><ul class="amount-list">{% for row in stats.shares %}<li><span>{{ row.name }}</span><strong>{{ row.amount|money }}</strong></li>{% else %}<li>Noch keine Einnahmen.</li>{% endfor %}</ul></details></section>
<section class="panel"><h2>Einkommensarten</h2><p class="muted">Alle Jahre</p><div class="chart"><canvas id="kinds-chart" role="img" aria-label="Anteile nach Einkommensart"></canvas></div><details><summary>Beträge nach Einkommensart</summary><ul class="amount-list">{% for row in stats.chart.kinds %}<li><span>{{ row.name }}</span><strong>{{ row.amount|money }}</strong></li>{% endfor %}</ul></details></section>
</div>
<p class="muted" id="chart-status" role="status">Diagramme werden geladen. Alle Beträge sind auch als Tabellen verfügbar.</p>
<section class="panel"><div class="section-heading"><h2>Letzte Zahlungen</h2><a href="/income">Alle Zahlungen →</a></div>{% include '_entries.html' %}</section>
{% endblock %}
{% block scripts %}<script id="chart-data" type="application/json">{{ stats.chart|tojson }}</script><script defer src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script><script defer src="{{ url_for('static', path='js/dashboard.js') }}"></script>{% endblock %}