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>
150 lines
4.5 KiB
TypeScript
150 lines
4.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useTransition } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { Loader2, RefreshCw, Unplug } from 'lucide-react'
|
|
import { toast } from 'sonner'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardInhalt, CardKopf, CardTitel } from '@/components/ui/card'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Kennzeichen } from '@/components/ui/kennzeichen'
|
|
import {
|
|
jetztAbgleichen,
|
|
kontoTrennen,
|
|
kontoUmbenennen,
|
|
type Rueckmeldung,
|
|
} from '@/server/aktionen/kalenderkonten'
|
|
|
|
export type KontoAnzeige = {
|
|
id: string
|
|
anbieter: 'GRAPH' | 'GOOGLE'
|
|
anbieterName: string
|
|
anzeigename: string
|
|
extKontoId: string
|
|
termine: number
|
|
letzterAbgleich: string | null
|
|
letzterFehler: string | null
|
|
}
|
|
|
|
export function KontoKarte({ konto }: { konto: KontoAnzeige }) {
|
|
const router = useRouter()
|
|
const [laeuft, starte] = useTransition()
|
|
const [name, setName] = useState(konto.anzeigename)
|
|
const [trennenBestaetigt, setTrennenBestaetigt] = useState(false)
|
|
|
|
function mitRueckmeldung(tun: () => Promise<Rueckmeldung>) {
|
|
starte(async () => {
|
|
const antwort = await tun()
|
|
if (antwort.ok) {
|
|
toast.success(antwort.meldung)
|
|
router.refresh()
|
|
} else {
|
|
toast.error(antwort.fehler)
|
|
}
|
|
})
|
|
}
|
|
|
|
function felder(werte: Record<string, string>): FormData {
|
|
const daten = new FormData()
|
|
for (const [schluessel, wert] of Object.entries(werte)) daten.set(schluessel, wert)
|
|
return daten
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardKopf className="flex-row flex-wrap items-center gap-2">
|
|
<CardTitel className="text-base">{konto.anzeigename}</CardTitel>
|
|
<Kennzeichen>{konto.anbieterName}</Kennzeichen>
|
|
{konto.letzterFehler && <Kennzeichen art="gefahr">Fehler</Kennzeichen>}
|
|
</CardKopf>
|
|
|
|
<CardInhalt className="flex flex-col gap-4">
|
|
<dl className="grid gap-1 text-sm sm:grid-cols-[10rem_1fr]">
|
|
<dt className="text-gedaempft-vordergrund">Konto</dt>
|
|
<dd>{konto.extKontoId}</dd>
|
|
<dt className="text-gedaempft-vordergrund">Gespiegelte Termine</dt>
|
|
<dd>{konto.termine}</dd>
|
|
<dt className="text-gedaempft-vordergrund">Letzter Abgleich</dt>
|
|
<dd>{konto.letzterAbgleich ?? 'noch nie'}</dd>
|
|
</dl>
|
|
|
|
{konto.letzterFehler && (
|
|
<p className="bg-gefahr/10 text-gefahr rounded-md px-3 py-2 text-sm">
|
|
{konto.letzterFehler}
|
|
</p>
|
|
)}
|
|
|
|
<div className="flex flex-wrap items-end gap-2">
|
|
<div className="flex flex-1 flex-col gap-1">
|
|
<label
|
|
htmlFor={`name-${konto.id}`}
|
|
className="text-gedaempft-vordergrund text-xs"
|
|
>
|
|
Bezeichnung
|
|
</label>
|
|
<Input
|
|
id={`name-${konto.id}`}
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
maxLength={60}
|
|
disabled={laeuft}
|
|
/>
|
|
</div>
|
|
<Button
|
|
art="umriss"
|
|
disabled={laeuft || name.trim() === konto.anzeigename}
|
|
onClick={() =>
|
|
mitRueckmeldung(() =>
|
|
kontoUmbenennen(felder({ id: konto.id, anzeigename: name })),
|
|
)
|
|
}
|
|
>
|
|
Speichern
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button
|
|
art="umriss"
|
|
disabled={laeuft}
|
|
onClick={() =>
|
|
mitRueckmeldung(() => jetztAbgleichen(felder({ id: konto.id })))
|
|
}
|
|
>
|
|
{laeuft ? (
|
|
<Loader2 className="animate-spin" aria-hidden />
|
|
) : (
|
|
<RefreshCw aria-hidden />
|
|
)}
|
|
Jetzt abgleichen
|
|
</Button>
|
|
|
|
<Button
|
|
art="gefahr"
|
|
className="ml-auto"
|
|
disabled={laeuft}
|
|
onBlur={() => setTrennenBestaetigt(false)}
|
|
onClick={() => {
|
|
if (!trennenBestaetigt) {
|
|
setTrennenBestaetigt(true)
|
|
return
|
|
}
|
|
mitRueckmeldung(() => kontoTrennen(felder({ id: konto.id })))
|
|
}}
|
|
>
|
|
<Unplug aria-hidden />
|
|
{trennenBestaetigt ? 'Wirklich trennen?' : 'Trennen'}
|
|
</Button>
|
|
</div>
|
|
|
|
{trennenBestaetigt && (
|
|
<p className="text-gedaempft-vordergrund text-xs">
|
|
Die gespiegelten Termine verschwinden dabei. Im Kalender selbst bleibt alles
|
|
unangetastet.
|
|
</p>
|
|
)}
|
|
</CardInhalt>
|
|
</Card>
|
|
)
|
|
}
|