new file: .env.example

new file:   .gitignore
	new file:   Dockerfile
	new file:   README.md
	new file:   app.py
	new file:   auth.py
	new file:   config.py
	new file:   db.py
	new file:   db_helpers.py
	new file:   docker-compose.yml
	new file:   requirements.txt
	new file:   routes/__init__.py
	new file:   routes/admin_routes.py
	new file:   routes/auth_routes.py
	new file:   routes/journal_routes.py
	new file:   static/css/style.css
	new file:   static/js/main.js
	new file:   templates/admin/dashboard.html
	new file:   templates/admin/users.html
	new file:   templates/base.html
	new file:   templates/day.html
	new file:   templates/future.html
	new file:   templates/index.html
	new file:   templates/login.html
	new file:   templates/week.html
This commit is contained in:
SimolZimol
2026-08-02 20:41:24 +02:00
commit 0e7efe4b74
25 changed files with 1590 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% block title %}Admin Bullet Journal{% endblock %}
{% block content %}
<div class="page-head">
<div>
<h1>Admin-Panel</h1>
<p class="muted">Zentrales Dashboard</p>
</div>
<a href="{{ url_for('admin.users') }}" class="btn">Benutzer verwalten</a>
</div>
<section class="stat-grid">
<div class="stat-card">
<span class="stat-number">{{ total_users }}</span>
<span class="stat-label">Benutzer</span>
</div>
<div class="stat-card">
<span class="stat-number">{{ total_admins }}</span>
<span class="stat-label">Admins</span>
</div>
<div class="stat-card">
<span class="stat-number">{{ total_notes }}</span>
<span class="stat-label">Notizen</span>
</div>
</section>
<section class="card">
<h2>Kürzlich angelegte Benutzer</h2>
{% if recent %}
<table class="table">
<thead>
<tr><th>Benutzername</th><th>E-Mail</th><th>Rolle</th><th>Erstellt</th></tr>
</thead>
<tbody>
{% for u in recent %}
<tr>
<td>{{ u.username }}</td>
<td>{{ u.email or '' }}</td>
<td>{{ 'Admin' if u.is_admin else 'Benutzer' }}</td>
<td>{{ u.created_at.strftime('%d.%m.%Y %H:%M') if u.created_at else '' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="muted">Noch keine Benutzer.</p>
{% endif %}
</section>
{% endblock %}

View File

@@ -0,0 +1,79 @@
{% extends "base.html" %}
{% block title %}Benutzerverwaltung Bullet Journal{% endblock %}
{% block content %}
<div class="page-head">
<div>
<h1>Benutzerverwaltung</h1>
<p class="muted">Accounts erstellen und verwalten</p>
</div>
</div>
<section class="card">
<h2>Neuen Benutzer anlegen</h2>
<form method="post" action="{{ url_for('admin.users') }}" class="note-form">
<div class="note-form-row">
<label>
Benutzername
<input type="text" name="username" required>
</label>
<label>
Passwort
<input type="text" name="password" required>
</label>
<label>
E-Mail
<input type="email" name="email">
</label>
<label class="checkbox-label">
<input type="checkbox" name="is_admin" value="1"> Admin
</label>
<button type="submit" class="btn btn-primary">Anlegen</button>
</div>
</form>
</section>
<section class="card">
<h2>Alle Benutzer</h2>
{% if users %}
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Benutzername</th>
<th>E-Mail</th>
<th>Rolle</th>
<th>Notizen</th>
<th>Erstellt</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>{{ u.id }}</td>
<td>{{ u.username }}</td>
<td>{{ u.email or '' }}</td>
<td>{{ 'Admin' if u.is_admin else 'Benutzer' }}</td>
<td>{{ u.note_count }}</td>
<td>{{ u.created_at.strftime('%d.%m.%Y') if u.created_at else '' }}</td>
<td class="table-actions">
<form method="post" action="{{ url_for('admin.reset_password', user_id=u.id) }}" class="inline">
<input type="text" name="new_password" placeholder="Neues Passwort" required>
<button class="btn btn-small">PW zurücksetzen</button>
</form>
{% if u.id != session.get('user_id') %}
<form method="post" action="{{ url_for('admin.delete_user', user_id=u.id) }}" class="inline"
onsubmit="return confirm('Benutzer wirklich löschen? Alle Notizen gehen verloren.');">
<button class="btn btn-small btn-danger">Löschen</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="muted">Keine Benutzer vorhanden.</p>
{% endif %}
</section>
{% endblock %}

52
templates/base.html Normal file
View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Bullet Journal{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<header class="topbar">
<div class="topbar-inner">
<a href="{{ url_for('journal.index') }}" class="brand">📓 Bullet Journal</a>
{% if session.get('user_id') %}
<nav class="nav">
<a href="{{ url_for('journal.index') }}">Dashboard</a>
<a href="{{ url_for('journal.week') }}">Wochenübersicht</a>
<a href="{{ url_for('journal.future') }}">Future Log</a>
<a href="{{ url_for('journal.day', date_str=today_str) }}" class="nav-today">Heute</a>
{% if session.get('is_admin') %}
<a href="{{ url_for('admin.dashboard') }}" class="nav-admin">Admin</a>
{% endif %}
</nav>
<div class="user-area">
<span class="username">{{ session.get('username') }}</span>
<form method="post" action="{{ url_for('auth.logout') }}" class="inline">
<button type="submit" class="btn btn-link">Abmelden</button>
</form>
</div>
{% endif %}
</div>
</header>
<main class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer class="footer">
<p>Bullet Journal &copy; {{ now.year if now else 2026 }}</p>
</footer>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>

81
templates/day.html Normal file
View File

@@ -0,0 +1,81 @@
{% extends "base.html" %}
{% block title %}{{ weekday }} Bullet Journal{% endblock %}
{% block content %}
<div class="page-head">
<div>
<h1>{{ weekday }}, {{ target.strftime('%d.%m.%Y') }}</h1>
<p class="muted">
<a href="{{ url_for('journal.day', date_str=prev.strftime('%Y-%m-%d')) }}" class="btn btn-small">← Vortag</a>
{% if target.strftime('%Y-%m-%d') != today_str %}
<a href="{{ url_for('journal.day', date_str=today_str) }}" class="btn btn-small">Heute</a>
{% endif %}
<a href="{{ url_for('journal.day', date_str=nxt.strftime('%Y-%m-%d')) }}" class="btn btn-small">Nächster Tag →</a>
</p>
</div>
</div>
<section class="card">
<h2>Neue Notiz</h2>
<form method="post" action="{{ url_for('journal.day', date_str=target.strftime('%Y-%m-%d')) }}" class="note-form">
<div class="note-form-row">
<label>
Bullet
<select name="bullet">
<option value=".">• Aufgabe</option>
<option value="x">✕ Termin</option>
<option value="-"> Notiz</option>
<option value="?">? Frage</option>
</select>
</label>
<label class="grow">
Notiz
<input type="text" name="note_text" required placeholder="Was ist zu tun?">
</label>
<label>
Priorität
<select name="priority">
<option value="0">Niedrig</option>
<option value="1">Mittel</option>
<option value="2">Hoch</option>
</select>
</label>
<button type="submit" class="btn btn-primary">Hinzufügen</button>
</div>
</form>
</section>
<section class="card">
<h2>Notizen</h2>
{% if notes %}
<div class="notes-table">
<div class="notes-header">
<span>Bullet</span>
<span>Notiz</span>
<span>Priorität</span>
<span>Aktionen</span>
</div>
{% for n in notes %}
<div class="notes-row {{ 'is-done' if n.status == 'done' else '' }}">
<span class="bullet bullet-{{ n.bullet_type }}" title="{{ bullet_labels.get(n.bullet_type, '') }}">{{ n.bullet_type }}</span>
<span class="note-text">{{ n.note_text }}</span>
<span class="priority-badge priority-{{ n.priority }}">{{ ['Niedrig','Mittel','Hoch'][n.priority] if n.priority <= 2 else n.priority }}</span>
<span class="actions">
<form method="post" action="{{ url_for('journal.mark_done', note_id=n.id) }}" class="inline">
<button class="btn btn-small {{ 'btn-success' if n.status != 'done' else '' }}">
{{ 'Erledigt' if n.status != 'done' else 'Wieder öffnen' }}
</button>
</form>
<form method="post" action="{{ url_for('journal.move_note', note_id=n.id) }}" class="inline move-form">
<input type="date" name="target_date" class="move-date" title="In anderen Tag verschieben">
<button type="submit" class="btn btn-small" name="target" value="date">Verschieben</button>
<button type="submit" class="btn btn-small btn-future" name="target" value="future">→ Future Log</button>
</form>
</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="muted">Noch keine Notizen für diesen Tag.</p>
{% endif %}
</section>
{% endblock %}

50
templates/future.html Normal file
View File

@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block title %}Future Log Bullet Journal{% endblock %}
{% block content %}
<div class="page-head">
<div>
<h1>Future Log</h1>
<p class="muted">Langfristiger Kalender Aufgaben, die später erledigt werden.</p>
</div>
</div>
<section class="card">
<h2>Neuer Eintrag</h2>
<form method="post" action="{{ url_for('journal.future') }}" class="note-form">
<div class="note-form-row">
<label>
Monat
<input type="month" name="month" required>
</label>
<label class="grow">
Notiz
<input type="text" name="note_text" required placeholder="Eintrag ins Future Log">
</label>
<button type="submit" class="btn btn-primary">Hinzufügen</button>
</div>
</form>
</section>
{% if grouped %}
{% for month_key, entries in grouped.items() %}
<section class="card">
<h2>{{ month_key[5:7]|int }} / {{ month_key[0:4] }}</h2>
<ul class="note-list">
{% for e in entries %}
<li class="note-item {{ 'is-done' if e.status == 'done' else '' }}">
<span class="bullet">{{ '✓' if e.status == 'done' else '' }}</span>
<span class="note-text">{{ e.note_text }}</span>
<form method="post" action="{{ url_for('journal.future') }}" class="inline">
<input type="hidden" name="toggle_id" value="{{ e.id }}">
</form>
</li>
{% endfor %}
</ul>
</section>
{% endfor %}
{% else %}
<section class="card">
<p class="muted">Noch keine Einträge im Future Log.</p>
</section>
{% endif %}
{% endblock %}

49
templates/index.html Normal file
View File

@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% block title %}Dashboard Bullet Journal{% endblock %}
{% block content %}
<div class="page-head">
<div>
<h1>Dashboard</h1>
<p class="muted">Woche ab {{ monday.strftime('%d.%m.%Y') }}</p>
</div>
<a href="{{ url_for('journal.week') }}" class="btn">Zur Wochenübersicht</a>
</div>
<section class="card">
<h2>Diese Woche</h2>
<div class="week-grid">
{% for day in day_summary %}
<a class="day-card" href="{{ url_for('journal.day', date_str=day.date.strftime('%Y-%m-%d')) }}">
<span class="day-name">{{ day.weekday }}</span>
<span class="day-date">{{ day.date.strftime('%d.%m.') }}</span>
<span class="day-stats">
{% if day.total > 0 %}
<span class="pill open">{{ day.open }} offen</span>
<span class="pill done">{{ day.done }} erledigt</span>
{% else %}
<span class="pill none">leer</span>
{% endif %}
</span>
</a>
{% endfor %}
</div>
</section>
{% if overdue %}
<section class="card">
<h2>Überfällige Aufgaben</h2>
<ul class="note-list">
{% for n in overdue %}
<li class="note-item">
<span class="bullet bullet-{{ n.bullet_type|urlencode }}">{{ n.bullet_type }}</span>
<span class="note-text">{{ n.note_text }}</span>
<span class="note-date">{{ n.note_date.strftime('%d.%m.%Y') }}</span>
<form method="post" action="{{ url_for('journal.mark_done', note_id=n.id) }}" class="inline">
<button class="btn btn-small">Erledigt</button>
</form>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% endblock %}

19
templates/login.html Normal file
View File

@@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block title %}Anmelden Bullet Journal{% endblock %}
{% block content %}
<div class="auth-card">
<h1>Anmelden</h1>
<p class="muted">Bitte melde dich mit deinem Konto an.</p>
<form method="post" action="{{ url_for('auth.login') }}" class="stack">
<label>
Benutzername
<input type="text" name="username" required autofocus>
</label>
<label>
Passwort
<input type="password" name="password" required>
</label>
<button type="submit" class="btn btn-primary">Anmelden</button>
</form>
</div>
{% endblock %}

61
templates/week.html Normal file
View File

@@ -0,0 +1,61 @@
{% extends "base.html" %}
{% block title %}Wochenübersicht Bullet Journal{% endblock %}
{% block content %}
<div class="page-head">
<div>
<h1>Wochenübersicht</h1>
<p class="muted">
<a href="{{ url_for('journal.week', week=prev_monday.strftime('%G-W%V')) }}" class="btn btn-small">← Vorige</a>
Woche ab {{ monday.strftime('%d.%m.%Y') }}
<a href="{{ url_for('journal.week', week=next_monday.strftime('%G-W%V')) }}" class="btn btn-small">Nächste →</a>
</p>
</div>
</div>
<section class="card">
<table class="table">
<thead>
<tr>
<th>Tag</th>
<th>Datum</th>
<th>Offen</th>
<th>Erledigt</th>
<th>Gesamt</th>
<th></th>
</tr>
</thead>
<tbody>
{% for day in days_detail %}
<tr>
<td>{{ day.weekday }}</td>
<td>{{ day.date.strftime('%d.%m.%Y') }}</td>
<td class="num open">{{ day.open }}</td>
<td class="num done">{{ day.done }}</td>
<td class="num">{{ day.total }}</td>
<td><a href="{{ url_for('journal.day', date_str=day.date.strftime('%Y-%m-%d')) }}" class="btn btn-small">Öffnen</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
<section class="card">
<h2>Offene Aufgaben dieser Woche</h2>
{% if open_notes %}
<ul class="note-list">
{% for n in open_notes %}
<li class="note-item">
<span class="bullet bullet-{{ n.bullet_type }}">{{ n.bullet_type }}</span>
<span class="note-text">{{ n.note_text }}</span>
<span class="note-date">{{ n.note_date.strftime('%d.%m.') }} {{ 'Aufgabe' if n.bullet_type == '.' else ('Termin' if n.bullet_type == 'x' else ('Notiz' if n.bullet_type == '-' else 'Frage')) }}</span>
<form method="post" action="{{ url_for('journal.mark_done', note_id=n.id) }}" class="inline">
<button class="btn btn-small">Erledigt</button>
</form>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Keine offenen Aufgaben in dieser Woche. 🎉</p>
{% endif %}
</section>
{% endblock %}