Report PDF emailing

This commit is contained in:
2026-06-18 21:07:34 +01:00
parent 7254719794
commit 364f4fe57e
16 changed files with 1428 additions and 16 deletions
+10
View File
@@ -15,10 +15,20 @@
<nav class="nav">
<a href="/" {% if request.path == "/" %}aria-current="page"{% endif %}>Dashboard</a>
<a href="/reports" {% if request.path == "/reports" %}aria-current="page"{% endif %}>Reports</a>
<a href="/report-settings" {% if request.path == "/report-settings" %}aria-current="page"{% endif %}>Settings</a>
</nav>
<p class="refresh-status" id="refreshStatus" aria-live="polite"></p>
</header>
<main class="page">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-stack" aria-live="polite">
{% for category, message in messages %}
<p class="flash {{ category }}">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<script src="{{ url_for('static', filename='auto_refresh.js') }}"></script>
+202
View File
@@ -0,0 +1,202 @@
{% extends "base.html" %}
{% block title %}Report Settings - SwitchBot Temps{% endblock %}
{% block content %}
<section class="hero">
<div>
<p class="eyebrow">Report settings</p>
<h1>Scheduled reports</h1>
<p class="muted">Manage recipients and recurring PDF report schedules.</p>
</div>
</section>
<section class="settings-grid">
<div class="panel">
<div class="panel-heading">
<div>
<h2>Recipients</h2>
<p class="muted">Email addresses that can receive scheduled reports.</p>
</div>
</div>
<form class="settings-form" method="post" action="/report-settings/recipients">
<label>
Email
<input type="email" name="email" placeholder="recipient@example.com" required>
</label>
<label>
Name
<input type="text" name="name" placeholder="Optional">
</label>
<button class="button" type="submit">Add recipient</button>
</form>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Email</th>
<th>Name</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{% for recipient in recipients %}
<tr>
<td>{{ recipient.email }}</td>
<td>{{ recipient.name or "n/a" }}</td>
<td>{{ "Active" if recipient.active else "Paused" }}</td>
<td>
<form method="post" action="/report-settings/recipients/{{ recipient.id }}/toggle">
<button class="button secondary small" type="submit">{{ "Pause" if recipient.active else "Resume" }}</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="empty">No recipients configured.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="panel">
<div class="panel-heading">
<div>
<h2>Add schedule</h2>
<p class="muted">Schedules send the previous complete day, week, or month.</p>
</div>
</div>
<form class="settings-form schedule-form" method="post" action="/report-settings/schedules">
<label>
Recipient
<select name="recipient_id" required>
<option value="">Choose recipient</option>
{% for recipient in recipients %}
<option value="{{ recipient.id }}">{{ recipient.email }}</option>
{% endfor %}
</select>
</label>
<label>
Cadence
<select name="cadence" required>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</label>
<label>
Time
<input type="time" name="send_time" value="08:00" required>
</label>
<label>
Weekday
<select name="weekday">
{% for value, label in weekdays %}
<option value="{{ value }}" {% if value == 0 %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<label>
Month day
<input type="number" name="monthday" min="1" max="31" value="1">
</label>
<label>
Device
<select name="device_id">
<option value="">All devices</option>
{% for device in devices %}
<option value="{{ device.id }}">{{ device.name }}</option>
{% endfor %}
</select>
</label>
<button class="button" type="submit">Add schedule</button>
</form>
</div>
</section>
<section class="panel">
<div class="panel-heading">
<div>
<h2>Active schedules</h2>
<p class="muted">Each schedule is guarded by a database run record, so a period is sent once.</p>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Recipient</th>
<th>Cadence</th>
<th>Device</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{% for schedule in schedules %}
<tr>
<td>{{ schedule.recipient.email }}</td>
<td>{{ schedule_labels[schedule.id] }}</td>
<td>{{ schedule.device.name if schedule.device else "All devices" }}</td>
<td>{{ "Active" if schedule.active else "Paused" }}</td>
<td>
<form method="post" action="/report-settings/schedules/{{ schedule.id }}/toggle">
<button class="button secondary small" type="submit">{{ "Pause" if schedule.active else "Resume" }}</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="empty">No scheduled reports configured.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<section class="panel">
<div class="panel-heading">
<div>
<h2>Recent runs</h2>
<p class="muted">The scheduler writes one run record per schedule and report period.</p>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Created</th>
<th>Recipient</th>
<th>Cadence</th>
<th>Period</th>
<th>Status</th>
<th>Error</th>
</tr>
</thead>
<tbody>
{% for run in runs %}
<tr>
<td>{{ run.created_at }}</td>
<td>{{ run.scheduled_report.recipient.email }}</td>
<td>{{ run.scheduled_report.cadence|title }}</td>
<td>{{ run.period_start }} to {{ run.period_end }}</td>
<td>{{ run.status|title }}</td>
<td>{{ run.error or "" }}</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="empty">No scheduled report runs yet.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}
+20 -1
View File
@@ -7,7 +7,7 @@
<div>
<p class="eyebrow">Reports</p>
<h1>Build a temperature report</h1>
<p class="muted">Choose a date range and export the summary as CSV.</p>
<p class="muted">Choose a date range, export CSV, or send a PDF report.</p>
</div>
</section>
@@ -33,6 +33,25 @@
<a class="button secondary" href="/reports.csv?start={{ start }}&end={{ end }}&device_id={{ device_id }}">Download CSV</a>
</form>
<section class="panel report-email-panel">
<div class="panel-heading">
<div>
<h2>Email PDF report</h2>
<p class="muted">Creates a PDF using the selected range and device filter, then sends it via SMTP2GO.</p>
</div>
</div>
<form class="report-form email-report-form" method="post" action="/reports/email-pdf">
<input type="hidden" name="start" value="{{ start }}">
<input type="hidden" name="end" value="{{ end }}">
<input type="hidden" name="device_id" value="{{ device_id }}">
<label>
Email
<input type="email" name="email" placeholder="recipient@example.com" required>
</label>
<button class="button" type="submit">Create and email PDF</button>
</form>
</section>
<section class="panel">
<div class="table-wrap">
<table>