Erste lauffähige Version

This commit is contained in:
2026-05-21 10:36:16 +02:00
commit 6a103adac4
98 changed files with 4107 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Serienbrief{% endblock %}</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #222; }
nav { display: flex; gap: 1rem; padding-bottom: 1rem; border-bottom: 1px solid #ddd; margin-bottom: 1.5rem; }
nav a { text-decoration: none; color: #0366d6; }
h1, h2 { color: #111; }
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
.status-pending { color: #888; }
.status-running { color: #d97706; }
.status-done { color: #059669; }
.status-failed { color: #dc2626; }
.btn { display: inline-block; padding: 0.4rem 0.8rem; background: #0366d6; color: white; border-radius: 4px; text-decoration: none; border: none; cursor: pointer; }
.messages li { padding: 0.5rem 1rem; background: #fef3c7; border-left: 4px solid #f59e0b; margin: 0.5rem 0; list-style: none; }
.log { font-family: monospace; font-size: 0.85rem; background: #f6f8fa; padding: 0.75rem; border-radius: 4px; }
.log .level-error { color: #dc2626; }
.log .level-warning { color: #d97706; }
form p { margin: 0.75rem 0; }
label { display: block; font-weight: 600; }
</style>
</head>
<body>
<nav>
<a href="{% url 'dashboard' %}">Übersicht</a>
<a href="{% url 'template-upload' %}">Neue Vorlage</a>
<a href="{% url 'job-create' %}">Neuer Serienbrief</a>
<span style="margin-left:auto">
{% if user.is_authenticated %}
{{ user.username }} · <a href="{% url 'logout' %}">Abmelden</a>
{% endif %}
</span>
</nav>
{% if messages %}<ul class="messages">{% for m in messages %}<li>{{ m }}</li>{% endfor %}</ul>{% endif %}
{% block content %}{% endblock %}
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
<div hx-get="{% url 'job-detail' job.id %}" hx-trigger="every 2s" hx-swap="outerHTML">
<p><strong>Vorlage:</strong> {{ job.template.name }}</p>
<p><strong>Status:</strong>
<span class="status-{{ job.status }}">{{ job.get_status_display }}</span>
</p>
<p><strong>Fortschritt:</strong> {{ job.processed_rows }} / {{ job.total_rows }}</p>
{% if job.status == "done" %}
<p><a class="btn" href="{% url 'job-download' job.id %}">PDF herunterladen</a></p>
{% endif %}
{% if job.error_message %}
<p style="color:#dc2626"><strong>Fehler:</strong> {{ job.error_message }}</p>
{% endif %}
<h3>Log</h3>
<div class="log">
{% for entry in logs %}
<div class="level-{{ entry.level }}">
[{{ entry.timestamp|date:"H:i:s" }}] {{ entry.level|upper }} {{ entry.message }}
</div>
{% empty %}
<em>Keine Einträge.</em>
{% endfor %}
</div>
</div>
+40
View File
@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block content %}
<h1>Übersicht</h1>
<h2>Vorlagen</h2>
<a class="btn" href="{% url 'template-upload' %}">Neue Vorlage hochladen</a>
<table>
<thead><tr><th>Name</th><th>Platzhalter</th><th>Erstellt</th></tr></thead>
<tbody>
{% for t in templates %}
<tr>
<td><a href="{% url 'template-detail' t.id %}">{{ t.name }}</a></td>
<td>{{ t.placeholders|join:", " }}</td>
<td>{{ t.created_at|date:"d.m.Y H:i" }}</td>
</tr>
{% empty %}
<tr><td colspan="3"><em>Noch keine Vorlagen vorhanden.</em></td></tr>
{% endfor %}
</tbody>
</table>
<h2>Aufträge</h2>
<a class="btn" href="{% url 'job-create' %}">Neuen Serienbrief erstellen</a>
<table>
<thead><tr><th>ID</th><th>Vorlage</th><th>Status</th><th>Fortschritt</th><th>Erstellt</th></tr></thead>
<tbody>
{% for j in jobs %}
<tr>
<td><a href="{% url 'job-detail' j.id %}">{{ j.id|stringformat:"s"|slice:":8" }}…</a></td>
<td>{{ j.template.name }}</td>
<td class="status-{{ j.status }}">{{ j.get_status_display }}</td>
<td>{{ j.processed_rows }} / {{ j.total_rows }}</td>
<td>{{ j.created_at|date:"d.m.Y H:i" }}</td>
</tr>
{% empty %}
<tr><td colspan="5"><em>Noch keine Aufträge.</em></td></tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
+12
View File
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block content %}
<h1>Auftrag {{ job.id|stringformat:"s"|slice:":8" }}…</h1>
<div hx-get="{% url 'job-detail' job.id %}"
hx-trigger="every 2s"
hx-swap="outerHTML">
{% include "mailmerge/_job_status.html" %}
</div>
<script src="https://unpkg.com/htmx.org@2.0.3" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h1>Neuer Serienbrief</h1>
<p>Vorlage und Empfänger-CSV auswählen. Die Spaltennamen der CSV müssen mit den Platzhaltern der Vorlage übereinstimmen (erste Zeile = Spaltennamen).</p>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn">Erstellen und starten</button>
</form>
{% endblock %}
@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ template.name }}</h1>
<p>{{ template.description }}</p>
<p><strong>Datei:</strong> {{ template.file.name }}</p>
<p><strong>Erkannte Platzhalter:</strong></p>
<ul>
{% for p in template.placeholders %}<li><code>{{ p }}</code></li>{% empty %}<li><em>Keine gefunden.</em></li>{% endfor %}
</ul>
<a class="btn" href="{% url 'job-create' %}">Serienbrief mit dieser Vorlage erstellen</a>
{% endblock %}
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<h1>Neue Vorlage hochladen</h1>
<p>DOCX-Datei mit Platzhaltern wie <code>&#123;&#123; vorname &#125;&#125;</code>, <code>&#123;&#123; nachname &#125;&#125;</code>, … Die Spaltennamen der späteren CSV müssen mit den Platzhaltern übereinstimmen.</p>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn">Hochladen</button>
</form>
{% endblock %}
+10
View File
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block title %}Anmelden{% endblock %}
{% block content %}
<h1>Anmelden</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn">Anmelden</button>
</form>
{% endblock %}