Files
kalendartool/components/admin/CalendarManager.tsx

253 lines
7.9 KiB
TypeScript

'use client';
/**
* Kalenderverwaltung (Admin): alle Kalender als Karten.
*
* - Klick auf die Karte oeffnet das Kalender-Modal (Tag/Woche/Monat
* mit Reservierungen ansehen/anlegen/loeschen).
* - Stift-Icon oeffnet ein Bearbeiten-Modal (PATCH /api/calendars/[id]).
* - "Kalender erstellen" oeffnet ein Erstellen-Modal (POST /api/calendars).
* - Alle Modals sind per X-Icon, ESC und Hintergrund-Klick schliessbar.
*/
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Button, Card, ErrorMessage, Input, Label } from '@/components/ui/ui';
import { ModalShell } from '@/components/ui/ModalShell';
import { CalendarCard, getCardGradient } from '@/components/calendar/CalendarCard';
import { CalendarModal } from '@/components/calendar/CalendarModal';
import { EditCalendarModal } from '@/components/admin/EditCalendarModal';
/** Daten eines Kalenders fuer die Karten-Ansicht (serverseitig geladen). */
export interface ManagedCalendar {
id: string;
title: string;
description: string | null;
timezone: string;
isActive: boolean;
ownerEmail: string;
isOwn: boolean;
memberCount: number;
reservationCount: number;
/** Maximale Reservierungsdauer in Minuten; 0 = unbegrenzt. */
maxReservationMinutes: number;
/** Duerven Member die Titel fremder Reservierungen sehen? */
showTitleToMembers: boolean;
/** Duerven Member die Notizen fremder Reservierungen sehen? */
showNotesToMembers: boolean;
}
/** Formularwerte zum Erstellen/Bearbeiten eines Kalenders. */
interface CalendarFormValues {
title: string;
description: string | null;
timezone: string;
}
export function CalendarManager({
initialCalendars,
currentUserId,
isAdmin,
}: {
initialCalendars: ManagedCalendar[];
currentUserId: string;
isAdmin: boolean;
}) {
const router = useRouter();
const [isCreating, setIsCreating] = useState(false);
/** Aktuell im Bearbeiten-Modal geoeffneter Kalender (null = geschlossen). */
const [editingCalendar, setEditingCalendar] = useState<ManagedCalendar | null>(null);
/** Aktuell im Kalender-Modal geoeffneter Kalender (null = geschlossen). */
const [openCalendar, setOpenCalendar] = useState<ManagedCalendar | null>(null);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
/** Erstellt einen neuen Kalender (POST /api/calendars). */
async function createCalendar(values: CalendarFormValues) {
setIsPending(true);
setError(null);
try {
const response = await fetch('/api/calendars', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
});
if (!response.ok) {
const data = (await response.json().catch(() => ({}))) as { error?: string };
setError(data.error ?? 'Erstellen fehlgeschlagen.');
return;
}
setIsCreating(false);
router.refresh();
} catch {
setError('Netzwerkfehler. Bitte erneut versuchen.');
} finally {
setIsPending(false);
}
}
/**
* Aktualisiert einen Kalender: wird von EditCalendarModal selbst
* ausgefuehrt (PATCH inkl. router.refresh()); hier nur Schliessen.
*/
return (
<div>
<div className="mb-6 flex flex-wrap items-center justify-between gap-3">
<div>
<h1 className="text-2xl font-bold">Kalender verwalten</h1>
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">
{initialCalendars.length} Kalender · eigene und Kalender anderer Benutzer
</p>
</div>
<Button
type="button"
onClick={() => {
setError(null);
setIsCreating(true);
}}
>
+ Kalender erstellen
</Button>
</div>
{error && !isCreating && !editingCalendar && !openCalendar && (
<ErrorMessage message={error} />
)}
{initialCalendars.length === 0 ? (
<Card>
<p className="text-sm text-slate-600 dark:text-slate-400">
Noch keine Kalender vorhanden. Erstelle deinen ersten Kalender.
</p>
</Card>
) : (
<ul className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{initialCalendars.map((calendar, index) => (
<li key={calendar.id}>
<CalendarCard
title={calendar.title}
subtitle={calendar.isOwn ? 'Mein Kalender' : calendar.ownerEmail}
isActive={calendar.isActive}
memberCount={calendar.memberCount}
href={`/admin/calendars/${calendar.id}`}
linkLabel={`Kalender ${calendar.title} verwalten`}
gradient={getCardGradient(index)}
iconTitle={`${calendar.memberCount} Mitglieder · ${calendar.reservationCount} Reservierungen`}
editLabel={`Kalender ${calendar.title} bearbeiten`}
onEdit={() => {
setError(null);
setEditingCalendar(calendar);
}}
onOpen={() => {
setError(null);
setOpenCalendar(calendar);
}}
/>
</li>
))}
</ul>
)}
{openCalendar && (
<CalendarModal
key={openCalendar.id}
calendarId={openCalendar.id}
calendarTitle={openCalendar.title}
currentUserId={currentUserId}
isAdmin={isAdmin}
maxReservationMinutes={openCalendar.maxReservationMinutes}
onClose={() => setOpenCalendar(null)}
/>
)}
{isCreating && (
<CreateCalendarModal
isPending={isPending}
error={error}
onClose={() => {
setIsCreating(false);
setError(null);
}}
onCreate={createCalendar}
/>
)}
{editingCalendar && (
<EditCalendarModal
key={editingCalendar.id}
calendar={editingCalendar}
onClose={() => {
setEditingCalendar(null);
setError(null);
}}
/>
)}
</div>
);
}
/** Modal: Neuen Kalender erstellen (POST /api/calendars). */
function CreateCalendarModal({
isPending,
error,
onClose,
onCreate,
}: {
isPending: boolean;
error: string | null;
onClose: () => void;
onCreate: (values: CalendarFormValues) => void;
}) {
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
onCreate({
title: String(formData.get('title') ?? '').trim(),
description: String(formData.get('description') ?? '').trim() || null,
timezone: String(formData.get('timezone') ?? '').trim(),
});
}
return (
<ModalShell
title="Kalender erstellen"
ariaLabel="Kalender erstellen"
isPending={isPending}
onClose={onClose}
>
<form onSubmit={handleSubmit} className="space-y-4">
<ErrorMessage message={error} />
<div>
<Label htmlFor="calendar-title">Titel</Label>
<Input id="calendar-title" name="title" type="text" required maxLength={120} />
</div>
<div>
<Label htmlFor="calendar-description">Beschreibung (optional)</Label>
<Input
id="calendar-description"
name="description"
type="text"
maxLength={2000}
/>
</div>
<div>
<Label htmlFor="calendar-timezone">Zeitzone</Label>
<Input
id="calendar-timezone"
name="timezone"
type="text"
required
defaultValue="Europe/Berlin"
maxLength={64}
/>
</div>
<div className="flex justify-end gap-2">
<Button type="button" variant="secondary" disabled={isPending} onClick={onClose}>
Abbrechen
</Button>
<Button disabled={isPending}>{isPending ? 'Erstellen …' : 'Erstellen'}</Button>
</div>
</form>
</ModalShell>
);
}