Wasserzähler QR-Code System + Serienbrief + Admin Import
- Wasserzähler-Stammdaten Import (Drag & Drop Excel/CSV) - QR-Code Druckseite mit Browser-Vorschau - QR-Code Excel Download (ExcelJS, eingebettete QR-PNGs) - Serienbrief wie Vorlage wasserablesung.pdf - HTML-Vorschau (max 20) + PDF Download (PDFKit, 1000+ skalierbar) - Antwortkarte mit QR-Code, Briefmarke, Zählerdaten - Bürgerseite: nur Kundennr./Zählernr./Stand (Datenschutz) - Kundennummer + letzter_stand + letzte_ablesung zum Schema - Bürgermeister: Patrick Krutzler - CAPTCHA verify API Route Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -359,20 +359,59 @@ export default function AdminDashboardPage() {
|
||||
{/* Wasserzähler Tab */}
|
||||
{activeTab === 'wasserzaehler' && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-primary">Wasserzähler</h3>
|
||||
<button
|
||||
onClick={() => exportCSV(zaehler as unknown as Record<string, unknown>[], 'wasserzaehler')}
|
||||
className="border border-border rounded-xl px-3 py-2.5 text-sm font-medium hover:bg-bg transition-colors"
|
||||
>
|
||||
CSV Export
|
||||
</button>
|
||||
<div className="flex items-center justify-between gap-2 flex-wrap">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-primary">Wasserzähler</h3>
|
||||
{zaehler.length > 0 && (
|
||||
<p className="text-[11px] text-text-muted">
|
||||
Letzter Import: {new Date(
|
||||
Math.max(...zaehler.map(z => new Date(z.erstellt_am).getTime()))
|
||||
).toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<button
|
||||
onClick={() => router.push('/admin/zaehler-import')}
|
||||
className="bg-accent text-white px-3 py-2.5 rounded-xl text-sm font-semibold hover:bg-accent-light active:scale-[0.98] transition-all flex items-center gap-1.5"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
||||
</svg>
|
||||
Stammdaten importieren
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push('/admin/serienbrief')}
|
||||
className="border border-border bg-white px-3 py-2.5 rounded-xl text-sm font-semibold hover:bg-bg active:scale-[0.98] transition-all flex items-center gap-1.5"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
Serienbrief
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push('/admin/qrcodes')}
|
||||
className="border border-border bg-white px-3 py-2.5 rounded-xl text-sm font-semibold hover:bg-bg active:scale-[0.98] transition-all flex items-center gap-1.5"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
|
||||
</svg>
|
||||
QR-Codes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => exportCSV(zaehler as unknown as Record<string, unknown>[], 'wasserzaehler')}
|
||||
className="border border-border rounded-xl px-3 py-2.5 text-sm font-medium hover:bg-bg transition-colors"
|
||||
>
|
||||
CSV Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl border border-border overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-bg/80 text-text-muted text-[11px] uppercase tracking-wider">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-semibold">Kundennr.</th>
|
||||
<th className="px-4 py-3 text-left font-semibold">Haushalt</th>
|
||||
<th className="px-4 py-3 text-left font-semibold">Adresse</th>
|
||||
<th className="px-4 py-3 text-left font-semibold">Zählernr.</th>
|
||||
@@ -386,13 +425,14 @@ export default function AdminDashboardPage() {
|
||||
<tbody className="divide-y divide-border/50">
|
||||
{zaehler.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-4 py-12 text-center text-text-muted">
|
||||
<td colSpan={9} className="px-4 py-12 text-center text-text-muted">
|
||||
Keine Wasserzähler gefunden.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
zaehler.map((z) => (
|
||||
<tr key={z.id} className="hover:bg-bg/30 transition-colors">
|
||||
<td className="px-4 py-3 font-mono text-xs font-semibold">{z.kundennummer}</td>
|
||||
<td className="px-4 py-3 font-medium">{z.haushalt_name}</td>
|
||||
<td className="px-4 py-3 text-text-muted">{z.adresse}</td>
|
||||
<td className="px-4 py-3 font-mono text-xs">{z.zaehlernummer}</td>
|
||||
|
||||
Reference in New Issue
Block a user