feat(kalender): Belegung des Tages auf der Startseite

Jeder Bereich zeigt, wie voll der heutige Tag schon ist: Termine aus allen
verbundenen Kalendern gegen die Tageskapazität dieses Bereichs.

Alle Kalender zählen gegen jeden Bereich, weil ein Termin die Zeit ohnehin
blockiert, gleich zu welchem Lebensbereich er gehört. Wer um 14 Uhr beim
Zahnarzt sitzt, kann zur selben Zeit nichts für die Firma tun.

Ohne verbundenen Kalender bleibt der Balken weg. Er wäre sonst immer bei
null und sähe nach "alles frei" aus — eine Zahl, die lügt, ist schlechter
als keine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 18:12:04 +02:00
co-authored by Claude Opus 5
parent 42c98c8b29
commit d8d3227c82
2 changed files with 113 additions and 12 deletions
+38 -12
View File
@@ -1,6 +1,7 @@
import Link from 'next/link'
import { AufgabenListe } from '@/components/aufgaben/aufgaben-liste'
import { Schnellerfassung } from '@/components/aufgaben/schnellerfassung'
import { Fortschrittsbalken } from '@/components/fortschrittsbalken'
import {
Card,
CardBeschreibung,
@@ -16,6 +17,7 @@ import {
projekteZurAuswahlLaden,
zaehlstaende,
} from '@/server/abfragen/aufgaben'
import { belegungHeute, hatKalenderkonto } from '@/server/abfragen/kalender'
import { verlangeAnmeldung } from '@/server/berechtigung'
import { db } from '@/server/db'
import { jetzt } from '@/server/zeitgeber'
@@ -24,16 +26,19 @@ export default async function StartSeite() {
const benutzer = await verlangeAnmeldung('/')
const heute = jetzt()
const [alleOffenen, bereiche, projekte, zahlen, bereichsListe] = await Promise.all([
aufgabenLaden({ nurOffen: true, ohneIdeen: true }, 200),
bereicheLaden(),
projekteZurAuswahlLaden(),
zaehlstaende(),
db.bereich.findMany({
where: { aktiv: true },
orderBy: [{ reihenfolge: 'asc' }, { name: 'asc' }],
}),
])
const [alleOffenen, bereiche, projekte, zahlen, bereichsListe, belegung, mitKalender] =
await Promise.all([
aufgabenLaden({ nurOffen: true, ohneIdeen: true }, 200),
bereicheLaden(),
projekteZurAuswahlLaden(),
zaehlstaende(),
db.bereich.findMany({
where: { aktiv: true },
orderBy: [{ reihenfolge: 'asc' }, { name: 'asc' }],
}),
belegungHeute(benutzer.id, heute),
hatKalenderkonto(benutzer.id),
])
// Was jetzt zählt: überfällig, heute oder morgen fällig.
const dran = alleOffenen.filter(
@@ -108,9 +113,10 @@ export default async function StartSeite() {
<ul className="grid gap-4 sm:grid-cols-2">
{bereichsListe.map((bereich) => {
const offeneHier = alleOffenen.filter((a) => a.bereich?.id === bereich.id)
const heuteHier = belegung.find((b) => b.id === bereich.id)?.belegung
return (
<li key={bereich.id}>
<Card>
<Card className="h-full">
<CardKopf className="flex-row items-center gap-3">
<span
aria-hidden
@@ -119,13 +125,33 @@ export default async function StartSeite() {
/>
<CardTitel className="text-base">{bereich.name}</CardTitel>
</CardKopf>
<CardInhalt>
<CardInhalt className="flex flex-col gap-2">
<p className="text-gedaempft-vordergrund text-sm">
{offeneHier.length === 0
? 'Nichts offen'
: `${offeneHier.length} offen`}{' '}
· {alsDauer(wochenKapazitaet(bereich))} Kapazität je Woche
</p>
{/*
Ohne verbundenen Kalender wäre die Belegung immer null –
das sähe nach "alles frei" aus und wäre irreführend.
*/}
{mitKalender && heuteHier && (
<>
<Fortschrittsbalken
prozent={heuteHier.prozent}
beschriftung={`Belegung heute in ${bereich.name}`}
/>
<p className="text-gedaempft-vordergrund text-xs">
{heuteHier.kapazitaet === 0
? 'Heute nicht eingeplant'
: heuteHier.ueberbucht
? `Heute überbucht: ${alsDauer(heuteHier.belegt)} Termine bei ${alsDauer(heuteHier.kapazitaet)} Kapazität`
: `Heute noch ${alsDauer(heuteHier.frei)} frei von ${alsDauer(heuteHier.kapazitaet)}`}
</p>
</>
)}
</CardInhalt>
</Card>
</li>