Files
steve 7914ac1ed7 feat: add summary stats bar to admin page
Displays total entries, this week, this month, and newsletter opt-in
count and percentage at the top of the admin view. Week/month
boundaries are computed in America/Denver time and converted to UTC
for SQLite comparison, handling DST correctly.
2026-03-29 19:48:24 -06:00

151 lines
7.0 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Guestbook Admin</title>
<!-- PWA -->
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/static/images/logo.png" />
<meta name="theme-color" content="#3a9cb8" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<style>
html, body { overscroll-behavior: none; }
body { padding-bottom: env(safe-area-inset-bottom); }
input, select, textarea { font-size: 16px !important; }
</style>
</head>
<body class="bg-light">
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">Guestbook Admin</h1>
<div class="d-flex align-items-center gap-3">
<span class="text-muted">{{ current_user.username }}</span>
{% if current_user.role != 'viewer' %}
<a href="{{ url_for('admin_export_csv') }}" class="btn btn-outline-success btn-sm">Export CSV</a>
{% endif %}
{% if current_user.role == 'superadmin' %}
<a href="{{ url_for('admin_users') }}" class="btn btn-outline-secondary btn-sm">Manage Users</a>
{% endif %}
<a href="{{ url_for('admin_logout') }}" class="btn btn-outline-danger btn-sm">Logout</a>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mb-3 align-items-center">
<span class="badge bg-primary" style="font-size:0.9rem;">{{ stats.total }} total</span>
<span class="badge bg-secondary">{{ stats.week }} this week</span>
<span class="badge bg-secondary">{{ stats.month }} this month</span>
<span class="badge bg-success">{{ stats.newsletter_count }} newsletter ({{ stats.newsletter_pct }}%)</span>
</div>
<!-- Mobile card list (phones) -->
<div class="d-md-none">
{% for g in guests %}
<div class="card mb-2 shadow-sm">
<div class="card-body py-2 px-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<strong>{{ g[1] }} {{ g[2] }}</strong>
<div class="text-muted small">{{ g[4] }} &middot; {{ g[7] | localtime }}</div>
<div class="small mt-1">Newsletter: {{ 'Yes' if g[6] else 'No' }}</div>
{% if g[3] %}
<div class="text-muted small">{{ g[3] }}</div>
{% endif %}
{% if g[5] %}
<div class="text-muted small fst-italic">{{ g[5] }}</div>
{% endif %}
</div>
{% if current_user.role != 'viewer' %}
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button type="submit" class="btn btn-danger btn-sm ms-2">Delete</button>
</form>
{% endif %}
</div>
</div>
</div>
{% else %}
<p class="text-muted text-center">No entries found.</p>
{% endfor %}
</div>
<!-- Desktop table (hidden on phones) -->
<div class="d-none d-md-block">
<div class="table-responsive">
<table class="table table-bordered table-hover bg-white">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Location</th>
<th>Comment</th>
<th>Newsletter</th>
<th>Timestamp</th>
<th></th>
</tr>
</thead>
<tbody>
{% for g in guests %}
<tr>
<td class="text-muted">{{ g[0] }}</td>
<td>{{ g[1] }} {{ g[2] }}</td>
<td>{{ g[3] or '—' }}</td>
<td>{{ g[4] }}</td>
<td>{{ g[5] or '—' }}</td>
<td>{{ 'Yes' if g[6] else 'No' }}</td>
<td class="text-nowrap">{{ g[7] | localtime }}</td>
<td>
{% if current_user.role != 'viewer' %}
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="8" class="text-center text-muted">No entries found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if total_pages > 1 %}
<nav>
<ul class="pagination">
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('admin', page=page-1) }}">Previous</a>
</li>
{% for p in range(1, total_pages + 1) %}
<li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('admin', page=p) }}">{{ p }}</a>
</li>
{% endfor %}
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('admin', page=page+1) }}">Next</a>
</li>
</ul>
</nav>
{% endif %}
</div>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
});
}
</script>
</body>
</html>