Verbinden, umbenennen, von Hand abgleichen und trennen. Alle Aktionen arbeiten nur auf Konten des angemeldeten Benutzers — die Kennung geht in die where-Bedingung, nicht in einen nachträglichen Vergleich. Ein Anbieter erscheint nur, wenn die Instanz ihn erlaubt *und* er konfiguriert ist. Ist er erlaubt, aber nicht konfiguriert, sagt die Seite das ausdrücklich, statt den Knopf wortlos wegzulassen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
4.7 KiB
TypeScript
141 lines
4.7 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { CalendarDays, Plus } from 'lucide-react'
|
|
import { KontoKarte } from '@/app/(app)/kalenderkonten/konto-karte'
|
|
import { Meldung } from '@/app/(app)/kalenderkonten/meldung'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
Card,
|
|
CardBeschreibung,
|
|
CardInhalt,
|
|
CardKopf,
|
|
CardTitel,
|
|
} from '@/components/ui/card'
|
|
import type { KalenderAnbieter } from '@/generated/prisma/enums'
|
|
import { alsDatumUhrzeit } from '@/lib/zeit'
|
|
import { verlangeAnmeldung } from '@/server/berechtigung'
|
|
import { db } from '@/server/db'
|
|
import { erlaubteKalenderAnbieter } from '@/server/instanz'
|
|
import { umgebung } from '@/server/umgebung'
|
|
|
|
export const metadata: Metadata = { title: 'Kalenderkonten' }
|
|
|
|
const ANBIETER_NAME: Record<KalenderAnbieter, string> = {
|
|
GRAPH: 'Microsoft 365',
|
|
GOOGLE: 'Google',
|
|
}
|
|
|
|
export default async function KalenderkontenSeite({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ ok?: string; fehler?: string }>
|
|
}) {
|
|
const benutzer = await verlangeAnmeldung('/kalenderkonten')
|
|
const parameter = await searchParams
|
|
const einst = umgebung()
|
|
|
|
// Welche Anbieter es hier überhaupt gibt, entscheidet die Instanz; ob sie
|
|
// benutzbar sind, hängt zusätzlich an der Konfiguration.
|
|
const erlaubt = erlaubteKalenderAnbieter()
|
|
const verfuegbar = erlaubt.filter((anbieter) =>
|
|
anbieter === 'GRAPH' ? einst.graphAktiv : einst.googleAktiv,
|
|
)
|
|
const fehlendKonfiguriert = erlaubt.filter((a) => !verfuegbar.includes(a))
|
|
|
|
const konten = await db.kalenderkonto.findMany({
|
|
where: { benutzerId: benutzer.id },
|
|
orderBy: [{ anbieter: 'asc' }, { anzeigename: 'asc' }],
|
|
select: {
|
|
id: true,
|
|
anbieter: true,
|
|
anzeigename: true,
|
|
extKontoId: true,
|
|
letzterAbgleich: true,
|
|
letzterFehler: true,
|
|
_count: { select: { termine: true } },
|
|
},
|
|
})
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
<header>
|
|
<h1 className="flex items-center gap-2 text-2xl font-semibold tracking-tight">
|
|
<CalendarDays className="size-6" aria-hidden />
|
|
Kalenderkonten
|
|
</h1>
|
|
<p className="text-gedaempft-vordergrund mt-1 text-sm">
|
|
Der Planer liest die Termine, um zu wissen, wann überhaupt Platz ist. Ab M4
|
|
schreibt er Zeitblöcke in denselben Kalender zurück.
|
|
</p>
|
|
</header>
|
|
|
|
<Meldung ok={parameter.ok} fehler={parameter.fehler} />
|
|
|
|
<Card>
|
|
<CardKopf>
|
|
<CardTitel className="text-base">Kalender verbinden</CardTitel>
|
|
<CardBeschreibung>
|
|
Du wirst zum Anbieter geschickt und kommst nach der Zustimmung hierher zurück.
|
|
Der Planer speichert keine Zugangsdaten im Klartext.
|
|
</CardBeschreibung>
|
|
</CardKopf>
|
|
<CardInhalt className="flex flex-col gap-3">
|
|
{verfuegbar.length === 0 ? (
|
|
<p className="text-gedaempft-vordergrund text-sm">
|
|
In dieser Instanz ist kein Kalenderanbieter eingerichtet.
|
|
</p>
|
|
) : (
|
|
<div className="flex flex-wrap gap-2">
|
|
{verfuegbar.map((anbieter) => (
|
|
<Button key={anbieter} alsKind>
|
|
<a href={`/api/kalender/verbinden/${anbieter.toLowerCase()}`}>
|
|
<Plus aria-hidden />
|
|
{ANBIETER_NAME[anbieter]} verbinden
|
|
</a>
|
|
</Button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{fehlendKonfiguriert.length > 0 && (
|
|
<p className="text-gedaempft-vordergrund text-xs">
|
|
{fehlendKonfiguriert.map((a) => ANBIETER_NAME[a]).join(' und ')} wäre hier
|
|
erlaubt, ist aber noch nicht konfiguriert. Siehe README, Abschnitt
|
|
Umgebungsvariablen.
|
|
</p>
|
|
)}
|
|
</CardInhalt>
|
|
</Card>
|
|
|
|
<section className="flex flex-col gap-4">
|
|
<h2 className="text-lg font-medium">Verbundene Konten</h2>
|
|
|
|
{konten.length === 0 ? (
|
|
<Card>
|
|
<CardInhalt className="text-gedaempft-vordergrund py-10 text-center text-sm">
|
|
Noch kein Kalender verbunden.
|
|
</CardInhalt>
|
|
</Card>
|
|
) : (
|
|
konten.map((konto) => (
|
|
<KontoKarte
|
|
key={konto.id}
|
|
konto={{
|
|
id: konto.id,
|
|
anbieter: konto.anbieter,
|
|
anbieterName: ANBIETER_NAME[konto.anbieter],
|
|
anzeigename: konto.anzeigename,
|
|
extKontoId: konto.extKontoId,
|
|
termine: konto._count.termine,
|
|
letzterAbgleich: konto.letzterAbgleich
|
|
? alsDatumUhrzeit(konto.letzterAbgleich)
|
|
: null,
|
|
letzterFehler: konto.letzterFehler,
|
|
}}
|
|
/>
|
|
))
|
|
)}
|
|
</section>
|
|
</div>
|
|
)
|
|
}
|