feat(bereiche): Bereiche verwalten mit Rollenprüfung und Audit

Bereiche mit Farbe, Arbeitsfenster und Tageskapazität – die erste
schreibende Funktion und damit der Beleg, dass die Kette trägt: zod prüft
jede Eingabe, die Rolle wird in der Seite und noch einmal in der Server
Action geprüft, jede Änderung hinterlässt einen Audit-Eintrag.

Die Audit-Details werden vor dem Speichern gesäubert: Felder, deren Name
nach Passwort, Token, Betreff oder Mailinhalt aussieht, werden ersetzt.
Das gilt auch in verschachtelten Objekten und Listen.

Dazu die Protokollseite für die Verwaltung und die Startseite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 13:02:49 +02:00
co-authored by Claude Opus 5
parent cc013fd6a1
commit e9a2b4b463
14 changed files with 1432 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import Link from 'next/link'
import {
Card,
CardBeschreibung,
CardInhalt,
CardKopf,
CardTitel,
} from '@/components/ui/card'
import { wochenKapazitaet } from '@/lib/bereich'
import { alsDatum, alsDauer } from '@/lib/zeit'
import { verlangeAnmeldung } from '@/server/berechtigung'
import { db } from '@/server/db'
export default async function StartSeite() {
const benutzer = await verlangeAnmeldung('/')
const jetzt = new Date()
const bereiche = await db.bereich.findMany({
where: { aktiv: true },
orderBy: [{ reihenfolge: 'asc' }, { name: 'asc' }],
})
const anrede = benutzer.name?.split(' ')[0] ?? benutzer.email
return (
<div className="flex flex-col gap-8">
<header>
<h1 className="text-2xl font-semibold tracking-tight">Guten Tag, {anrede}</h1>
<p className="text-gedaempft-vordergrund mt-1 text-sm">{alsDatum(jetzt)}</p>
</header>
<section className="flex flex-col gap-4">
<h2 className="text-lg font-medium">Deine Bereiche</h2>
{bereiche.length === 0 ? (
<Card>
<CardInhalt className="text-gedaempft-vordergrund py-8 text-center text-sm">
Noch keine Bereiche angelegt.{' '}
<Link href="/bereiche" className="text-betont underline underline-offset-4">
Jetzt einrichten
</Link>
</CardInhalt>
</Card>
) : (
<ul className="grid gap-4 sm:grid-cols-2">
{bereiche.map((bereich) => (
<li key={bereich.id}>
<Card>
<CardKopf className="flex-row items-center gap-3">
<span
aria-hidden
className="size-3 shrink-0 rounded-full ring-1 ring-black/10"
style={{ backgroundColor: bereich.farbe }}
/>
<CardTitel className="text-base">{bereich.name}</CardTitel>
</CardKopf>
<CardInhalt>
<p className="text-gedaempft-vordergrund text-sm">
{alsDauer(wochenKapazitaet(bereich))} in der Woche eingeplant
</p>
</CardInhalt>
</Card>
</li>
))}
</ul>
)}
</section>
<Card>
<CardKopf>
<CardTitel className="text-base">Was als Nächstes kommt</CardTitel>
<CardBeschreibung>
Aufgaben und Ideen folgen mit M2, das Lesen der Kalender mit M3, die
Wochenplanung per Ziehen und Ablegen mit M4.
</CardBeschreibung>
</CardKopf>
</Card>
</div>
)
}