From 0d0f8503ccf8efcd5aa9dc26f8f7430c2e08cb8d Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Fri, 11 Sep 2026 23:04:35 +0200 Subject: [PATCH] feat: Gespiegelte Belegung in der Wochenansicht zeigen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sie zaehlt gegen die Tageskapazitaet wie jeder fremde Termin, traegt aber keinen Betreff und ist gestreift gezeichnet — damit niemand darin einen Termin vermutet, dem der Titel fehlt. Co-Authored-By: Claude Opus 5 --- src/app/(app)/woche/page.tsx | 1 + src/app/(app)/woche/wochenraster.tsx | 17 ++++++++-- src/server/abfragen/woche.ts | 48 +++++++++++++++++++++------- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/app/(app)/woche/page.tsx b/src/app/(app)/woche/page.tsx index a7c7430..19f7ef2 100644 --- a/src/app/(app)/woche/page.tsx +++ b/src/app/(app)/woche/page.tsx @@ -187,6 +187,7 @@ export default async function WocheSeite({ ganztaegig: t.ganztaegig, frei: t.zeigtAls === 'FREI', kalenderName: t.kalenderName, + gespiegelt: t.gespiegelt, }))} aufgaben={aufgaben.map((a) => ({ id: a.id, diff --git a/src/app/(app)/woche/wochenraster.tsx b/src/app/(app)/woche/wochenraster.tsx index d1de1ed..a4926cc 100644 --- a/src/app/(app)/woche/wochenraster.tsx +++ b/src/app/(app)/woche/wochenraster.tsx @@ -64,6 +64,8 @@ export type RasterTermin = { ganztaegig: boolean frei: boolean kalenderName: string + /** Aus der anderen Instanz gespiegelt: belegt, aber ohne Betreff. */ + gespiegelt?: boolean } export type RasterSpalte = { @@ -321,6 +323,11 @@ export function Wochenraster({ eintrag.frei ? 'bg-gedaempft/60 text-gedaempft-vordergrund' : 'bg-gedaempft text-gedaempft-vordergrund', + // Gespiegeltes gehört sichtbar in eine eigene + // Klasse: gestreift, damit niemand darin einen + // Termin mit fehlendem Betreff vermutet. + eintrag.gespiegelt && + 'border-rand border border-dashed bg-[repeating-linear-gradient(45deg,transparent,transparent_4px,color-mix(in_oklch,currentColor_12%,transparent)_4px,color-mix(in_oklch,currentColor_12%,transparent)_8px)]', )} style={{ top: lage.oben, @@ -328,10 +335,16 @@ export function Wochenraster({ left: `${(spur / spuren) * 100}%`, width: `${100 / spuren}%`, }} - title={`${eintrag.betreff ?? 'Termin'} · ${eintrag.kalenderName}`} + title={ + eintrag.gespiegelt + ? 'Dienstlich belegt – gespiegelt aus der anderen Instanz' + : `${eintrag.betreff ?? 'Termin'} · ${eintrag.kalenderName}` + } > - {eintrag.betreff ?? 'Termin'} + {eintrag.gespiegelt + ? 'Dienstlich belegt' + : (eintrag.betreff ?? 'Termin')} ) diff --git a/src/server/abfragen/woche.ts b/src/server/abfragen/woche.ts index bbb7f0d..5493985 100644 --- a/src/server/abfragen/woche.ts +++ b/src/server/abfragen/woche.ts @@ -33,6 +33,13 @@ export type WochenTermin = { ganztaegig: boolean zeigtAls: ZeigtAls kalenderName: string + /** + * Kommt aus der anderen Instanz und trägt darum keinen Betreff. + * + * Für die Ansicht ist so eine Belegung ein fremder Termin wie jeder andere: + * Sie blockiert Zeit und lässt sich hier nicht verschieben. + */ + gespiegelt: boolean } export type WocheDaten = { @@ -55,7 +62,7 @@ export async function wocheLaden( ): Promise { const fenster = wochenFenster(zeitpunkt) - const [zeitbloecke, termine] = await Promise.all([ + const [zeitbloecke, termine, belegungen] = await Promise.all([ db.zeitblock.findMany({ where: { kalenderkonto: { benutzerId }, @@ -97,6 +104,11 @@ export async function wocheLaden( kalenderkonto: { select: { anzeigename: true } }, }, }), + db.spiegelbelegung.findMany({ + where: { beginn: { lt: fenster.bis }, ende: { gt: fenster.von } }, + orderBy: { beginn: 'asc' }, + select: { id: true, beginn: true, ende: true }, + }), ]) // Ein Zeitblock steht auch im Kalender und damit im Spiegel. Er würde sonst @@ -118,17 +130,31 @@ export async function wocheLaden( letzterFehler: z.letzterFehler, offen: z.extId === null, })), - termine: termine - .filter((t) => !eigeneExtIds.has(t.extId)) - .map((t) => ({ - id: t.id, - beginn: t.beginn, - ende: t.ende, - betreff: t.betreff, - ganztaegig: t.ganztaegig, - zeigtAls: t.zeigtAls, - kalenderName: t.kalenderkonto.anzeigename, + termine: [ + ...termine + .filter((t) => !eigeneExtIds.has(t.extId)) + .map((t) => ({ + id: t.id, + beginn: t.beginn, + ende: t.ende, + betreff: t.betreff, + ganztaegig: t.ganztaegig, + zeigtAls: t.zeigtAls, + kalenderName: t.kalenderkonto.anzeigename, + gespiegelt: false, + })), + ...belegungen.map((b) => ({ + id: b.id, + beginn: b.beginn, + ende: b.ende, + // Kein Betreff: Die andere Instanz schickt bewusst keinen. + betreff: null, + ganztaegig: false, + zeigtAls: 'GEBUCHT' as const, + kalenderName: 'Dienstlich', + gespiegelt: true, })), + ].sort((a, b) => a.beginn.getTime() - b.beginn.getTime()), } }