"use client"; import { useEffect, useState } from "react"; type EventItem = { id: string; title: string; startAt: string; endAt: string; status: string; location?: string | null; locationPlaceId?: string | null; locationLat?: number | null; locationLng?: number | null; description?: string | null; category?: { id: string; name: string } | null; }; export default function AdminPanel() { const [events, setEvents] = useState([]); const [allEvents, setAllEvents] = useState([]); const [error, setError] = useState(null); const [categories, setCategories] = useState<{ id: string; name: string }[]>( [] ); const [categoryError, setCategoryError] = useState(null); const [categoryStatus, setCategoryStatus] = useState(null); const [categoryModalOpen, setCategoryModalOpen] = useState(false); const [categoryModalError, setCategoryModalError] = useState(null); const [categoryModalStatus, setCategoryModalStatus] = useState(null); const [editingCategory, setEditingCategory] = useState<{ id: string; name: string } | null>(null); const [editEvent, setEditEvent] = useState(null); const [editStatus, setEditStatus] = useState(null); const [editError, setEditError] = useState(null); const [isEditOpen, setIsEditOpen] = useState(false); const [importFile, setImportFile] = useState(null); const [importCategoryId, setImportCategoryId] = useState(""); const [importStatus, setImportStatus] = useState(null); const [importError, setImportError] = useState(null); const load = async () => { try { const response = await fetch("/api/events?status=PENDING"); if (!response.ok) { throw new Error("Vorschläge konnten nicht geladen werden."); } setEvents(await response.json()); } catch (err) { setError((err as Error).message); } }; const loadAllEvents = async () => { try { const response = await fetch("/api/events"); if (!response.ok) { throw new Error("Termine konnten nicht geladen werden."); } setAllEvents(await response.json()); } catch (err) { setError((err as Error).message); } }; const loadCategories = async () => { try { const response = await fetch("/api/categories"); if (!response.ok) { throw new Error("Kategorien konnten nicht geladen werden."); } setCategories(await response.json()); } catch (err) { setCategoryError((err as Error).message); } }; useEffect(() => { load(); loadCategories(); loadAllEvents(); }, []); const updateStatus = async (id: string, status: "APPROVED" | "REJECTED") => { await fetch(`/api/events/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status }) }); load(); loadAllEvents(); }; const deleteEvent = async (id: string) => { const ok = window.confirm("Termin wirklich löschen?"); if (!ok) return; await fetch(`/api/events/${id}`, { method: "DELETE" }); load(); loadAllEvents(); }; const formatLocalDateTime = (value?: string | null) => { if (!value) return ""; const date = new Date(value); const offset = date.getTimezoneOffset() * 60000; return new Date(date.getTime() - offset).toISOString().slice(0, 16); }; const toIsoString = (value: FormDataEntryValue | null) => { if (!value) return null; const raw = String(value); if (!raw) return null; const date = new Date(raw); if (Number.isNaN(date.getTime())) return null; return date.toISOString(); }; const updateEvent = async (event: React.FormEvent) => { event.preventDefault(); setEditStatus(null); setEditError(null); if (!editEvent) return; const formData = new FormData(event.currentTarget); const payload = { title: formData.get("title"), description: formData.get("description"), location: formData.get("location"), locationPlaceId: formData.get("locationPlaceId"), locationLat: formData.get("locationLat"), locationLng: formData.get("locationLng"), startAt: toIsoString(formData.get("startAt")), endAt: toIsoString(formData.get("endAt")), categoryId: formData.get("categoryId") }; const response = await fetch(`/api/events/${editEvent.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (!response.ok) { const data = await response.json(); setEditError(data.error || "Termin konnte nicht aktualisiert werden."); return; } const updated = await response.json(); setEditStatus("Termin aktualisiert."); setEditEvent(updated); load(); loadAllEvents(); setIsEditOpen(false); }; const createCategory = async (event: React.FormEvent) => { event.preventDefault(); setCategoryError(null); setCategoryStatus(null); const formData = new FormData(event.currentTarget); const rawName = String(formData.get("name") || "").trim(); if (!rawName) { setCategoryError("Name erforderlich."); return; } const response = await fetch("/api/categories", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: rawName }) }); if (!response.ok) { const data = await response.json(); setCategoryError(data.error || "Kategorie konnte nicht angelegt werden."); return; } const created = await response.json(); setCategories((prev) => { if (prev.some((item) => item.id === created.id)) return prev; return [...prev, created].sort((a, b) => a.name.localeCompare(b.name)); }); event.currentTarget.reset(); setCategoryStatus("Kategorie angelegt."); }; const openCategoryModal = (category: { id: string; name: string }) => { setEditingCategory(category); setCategoryModalError(null); setCategoryModalStatus(null); setCategoryModalOpen(true); }; const closeCategoryModal = () => { setEditingCategory(null); setCategoryModalOpen(false); }; const updateCategory = async (event: React.FormEvent) => { event.preventDefault(); if (!editingCategory) return; setCategoryModalError(null); setCategoryModalStatus(null); const formData = new FormData(event.currentTarget); const name = String(formData.get("name") || "").trim(); if (!name) { setCategoryModalError("Name erforderlich."); return; } const response = await fetch("/api/categories", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: editingCategory.id, name }) }); if (!response.ok) { const data = await response.json(); setCategoryModalError(data.error || "Kategorie konnte nicht aktualisiert werden."); return; } const updated = await response.json(); setCategories((prev) => prev .map((item) => (item.id === updated.id ? updated : item)) .sort((a, b) => a.name.localeCompare(b.name)) ); setCategoryModalStatus("Kategorie aktualisiert."); setCategoryModalOpen(false); }; const deleteCategory = async (categoryId: string) => { const ok = window.confirm("Kategorie wirklich löschen? Zugeordnete Termine bleiben erhalten."); if (!ok) return; const response = await fetch(`/api/categories?id=${categoryId}`, { method: "DELETE" }); if (!response.ok) { const data = await response.json(); setCategoryError(data.error || "Kategorie konnte nicht gelöscht werden."); return; } setCategories((prev) => prev.filter((item) => item.id !== categoryId)); setCategoryStatus("Kategorie gelöscht."); }; const importIcal = async (event: React.FormEvent) => { event.preventDefault(); setImportStatus(null); setImportError(null); if (!importFile) { setImportError("Bitte eine iCal-Datei auswählen."); return; } if (!importCategoryId) { setImportError("Bitte eine Kategorie auswählen."); return; } const formData = new FormData(); formData.append("file", importFile); formData.append("categoryId", importCategoryId); const response = await fetch("/api/ical/import", { method: "POST", body: formData }); if (!response.ok) { const data = await response.json(); setImportError(data.error || "Import fehlgeschlagen."); return; } const data = await response.json(); const details = [ `${data.created || 0} importiert`, `${data.duplicates || 0} doppelt`, `${data.skipped || 0} übersprungen` ]; if (data.recurringSkipped) { details.push(`${data.recurringSkipped} wiederkehrend`); } setImportStatus(`Import abgeschlossen: ${details.join(", ")}.`); setImportFile(null); setImportCategoryId(""); load(); loadAllEvents(); }; return (

Admin

Offene Vorschläge

{error &&

{error}

} {events.length === 0 ? (

Keine offenen Vorschläge.

) : (
{events.map((event) => (

{event.title}

{new Date(event.startAt).toLocaleString()} - {new Date(event.endAt).toLocaleString()}

{event.location && (

Ort: {event.location}

)}
{event.description && (

{event.description}

)}
))}
)}

Kategorien

Kategorien verwalten

{categoryStatus && (

{categoryStatus}

)} {categoryError && (

{categoryError}

)}
{categories.length === 0 ? ( Noch keine Kategorien. ) : ( categories.map((category) => (
{category.name}
)) )}
{categoryModalOpen && editingCategory && (

Kategorie bearbeiten

{categoryModalStatus && (

{categoryModalStatus}

)} {categoryModalError && (

{categoryModalError}

)}
)}

iCal

iCal-Import

Lade eine iCal-Datei hoch, um Termine direkt zu importieren.

setImportFile(event.currentTarget.files?.[0] || null) } className="block text-sm text-slate-600" />
{importStatus &&

{importStatus}

} {importError &&

{importError}

}

Termine

Alle Termine verwalten

{isEditOpen && editEvent && (

Termin bearbeiten