Aktueller Stand
This commit is contained in:
@@ -9,18 +9,40 @@ type EventItem = {
|
||||
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<EventItem[]>([]);
|
||||
const [allEvents, setAllEvents] = useState<EventItem[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [categories, setCategories] = useState<{ id: string; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [categoryError, setCategoryError] = useState<string | null>(null);
|
||||
const [categoryStatus, setCategoryStatus] = useState<string | null>(null);
|
||||
const [categoryModalOpen, setCategoryModalOpen] = useState(false);
|
||||
const [categoryModalError, setCategoryModalError] = useState<string | null>(null);
|
||||
const [categoryModalStatus, setCategoryModalStatus] = useState<string | null>(null);
|
||||
const [editingCategory, setEditingCategory] = useState<{ id: string; name: string } | null>(null);
|
||||
const [editEvent, setEditEvent] = useState<EventItem | null>(null);
|
||||
const [editStatus, setEditStatus] = useState<string | null>(null);
|
||||
const [editError, setEditError] = useState<string | null>(null);
|
||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
||||
const [importFile, setImportFile] = useState<File | null>(null);
|
||||
const [importCategoryId, setImportCategoryId] = useState("");
|
||||
const [importStatus, setImportStatus] = useState<string | null>(null);
|
||||
const [importError, setImportError] = useState<string | null>(null);
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/events?status=PENDING");
|
||||
if (!response.ok) {
|
||||
throw new Error("Vorschlaege konnten nicht geladen werden.");
|
||||
throw new Error("Vorschläge konnten nicht geladen werden.");
|
||||
}
|
||||
setEvents(await response.json());
|
||||
} catch (err) {
|
||||
@@ -28,8 +50,34 @@ export default function AdminPanel() {
|
||||
}
|
||||
};
|
||||
|
||||
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") => {
|
||||
@@ -39,18 +87,230 @@ export default function AdminPanel() {
|
||||
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<HTMLFormElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<section className="space-y-4">
|
||||
<h1 className="text-2xl font-semibold">Adminfreigaben</h1>
|
||||
<section className="space-y-4 fade-up">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-slate-500">Admin</p>
|
||||
<h1 className="text-2xl font-semibold">Offene Vorschläge</h1>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-600">{error}</p>}
|
||||
{events.length === 0 ? (
|
||||
<p className="text-slate-600">Keine offenen Vorschlaege.</p>
|
||||
<div className="card-muted">
|
||||
<p className="text-slate-600">Keine offenen Vorschläge.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{events.map((event) => (
|
||||
<div key={event.id} className="rounded border border-slate-200 bg-white p-4">
|
||||
<div key={event.id} className="card">
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">{event.title}</h2>
|
||||
@@ -65,14 +325,14 @@ export default function AdminPanel() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateStatus(event.id, "APPROVED")}
|
||||
className="rounded bg-emerald-600 px-3 py-1.5 text-white"
|
||||
className="rounded-full bg-emerald-600 px-3 py-1.5 text-sm font-semibold text-white"
|
||||
>
|
||||
Freigeben
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateStatus(event.id, "REJECTED")}
|
||||
className="rounded bg-red-600 px-3 py-1.5 text-white"
|
||||
className="rounded-full bg-red-600 px-3 py-1.5 text-sm font-semibold text-white"
|
||||
>
|
||||
Ablehnen
|
||||
</button>
|
||||
@@ -85,6 +345,331 @@ export default function AdminPanel() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<section className="card space-y-4">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
Kategorien
|
||||
</p>
|
||||
<h2 className="text-lg font-semibold">Kategorien verwalten</h2>
|
||||
</div>
|
||||
<form onSubmit={createCategory} className="flex flex-wrap gap-2">
|
||||
<input
|
||||
name="name"
|
||||
required
|
||||
placeholder="z.B. Training"
|
||||
className="flex-1 rounded-xl border border-slate-300 px-3 py-2"
|
||||
/>
|
||||
<button className="btn-accent" type="submit">
|
||||
Anlegen
|
||||
</button>
|
||||
</form>
|
||||
{categoryStatus && (
|
||||
<p className="text-sm text-emerald-600">{categoryStatus}</p>
|
||||
)}
|
||||
{categoryError && (
|
||||
<p className="text-sm text-red-600">{categoryError}</p>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{categories.length === 0 ? (
|
||||
<span className="text-sm text-slate-600">
|
||||
Noch keine Kategorien.
|
||||
</span>
|
||||
) : (
|
||||
categories.map((category) => (
|
||||
<div
|
||||
key={category.id}
|
||||
className="flex items-center justify-between rounded-xl border border-slate-200 px-3 py-2 text-sm text-slate-700"
|
||||
>
|
||||
<span>{category.name}</span>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-slate-200 px-3 py-1 text-xs text-slate-700"
|
||||
onClick={() => openCategoryModal(category)}
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-red-200 px-3 py-1 text-xs text-red-600"
|
||||
onClick={() => deleteCategory(category.id)}
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
{categoryModalOpen && editingCategory && (
|
||||
<div className="fixed inset-0 z-30 flex items-center justify-center bg-black/40 px-4 py-6">
|
||||
<div className="card w-full max-w-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">Kategorie bearbeiten</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-slate-600"
|
||||
onClick={closeCategoryModal}
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={updateCategory} className="mt-4 space-y-3">
|
||||
<input
|
||||
name="name"
|
||||
defaultValue={editingCategory.name}
|
||||
required
|
||||
className="w-full rounded-xl border border-slate-300 px-3 py-2"
|
||||
/>
|
||||
<button type="submit" className="btn-accent w-full">
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
{categoryModalStatus && (
|
||||
<p className="mt-3 text-sm text-emerald-600">{categoryModalStatus}</p>
|
||||
)}
|
||||
{categoryModalError && (
|
||||
<p className="mt-3 text-sm text-red-600">{categoryModalError}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<section className="card space-y-4">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
iCal
|
||||
</p>
|
||||
<h2 className="text-lg font-semibold">iCal-Import</h2>
|
||||
<p className="text-sm text-slate-600">
|
||||
Lade eine iCal-Datei hoch, um Termine direkt zu importieren.
|
||||
</p>
|
||||
</div>
|
||||
<form onSubmit={importIcal} className="space-y-3">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<input
|
||||
type="file"
|
||||
accept=".ics,text/calendar"
|
||||
onChange={(event) =>
|
||||
setImportFile(event.currentTarget.files?.[0] || null)
|
||||
}
|
||||
className="block text-sm text-slate-600"
|
||||
/>
|
||||
<select
|
||||
value={importCategoryId}
|
||||
onChange={(event) => setImportCategoryId(event.target.value)}
|
||||
className="rounded-xl border border-slate-300 px-3 py-2 text-sm"
|
||||
required
|
||||
>
|
||||
<option value="" disabled>
|
||||
Kategorie wählen
|
||||
</option>
|
||||
{categories.map((category) => (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button type="submit" className="btn-accent">
|
||||
Import starten
|
||||
</button>
|
||||
</div>
|
||||
{importStatus && <p className="text-sm text-emerald-600">{importStatus}</p>}
|
||||
{importError && <p className="text-sm text-red-600">{importError}</p>}
|
||||
</form>
|
||||
</section>
|
||||
<section className="card space-y-4">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
Termine
|
||||
</p>
|
||||
<h2 className="text-lg font-semibold">Alle Termine verwalten</h2>
|
||||
</div>
|
||||
{isEditOpen && editEvent && (
|
||||
<div className="fixed inset-0 z-30 flex items-center justify-center bg-black/40 px-4 py-6">
|
||||
<div className="card w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">Termin bearbeiten</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-slate-600"
|
||||
onClick={() => {
|
||||
setEditEvent(null);
|
||||
setEditError(null);
|
||||
setEditStatus(null);
|
||||
setIsEditOpen(false);
|
||||
}}
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={updateEvent} className="mt-4 space-y-3">
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<input
|
||||
name="title"
|
||||
required
|
||||
defaultValue={editEvent.title}
|
||||
className="rounded-xl border border-slate-300 px-3 py-2"
|
||||
placeholder="Titel"
|
||||
/>
|
||||
<input
|
||||
name="location"
|
||||
defaultValue={editEvent.location || ""}
|
||||
className="rounded-xl border border-slate-300 px-3 py-2"
|
||||
placeholder="Ort"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="locationPlaceId"
|
||||
value={editEvent.locationPlaceId || ""}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="locationLat"
|
||||
value={editEvent.locationLat ?? ""}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="locationLng"
|
||||
value={editEvent.locationLng ?? ""}
|
||||
/>
|
||||
<input
|
||||
name="startAt"
|
||||
type="datetime-local"
|
||||
required
|
||||
defaultValue={formatLocalDateTime(editEvent.startAt)}
|
||||
className="rounded-xl border border-slate-300 px-3 py-2"
|
||||
/>
|
||||
<input
|
||||
name="endAt"
|
||||
type="datetime-local"
|
||||
defaultValue={formatLocalDateTime(editEvent.endAt)}
|
||||
className="rounded-xl border border-slate-300 px-3 py-2"
|
||||
/>
|
||||
<select
|
||||
name="categoryId"
|
||||
required
|
||||
defaultValue={editEvent.category?.id || ""}
|
||||
className="rounded-xl border border-slate-300 px-3 py-2 md:col-span-2"
|
||||
>
|
||||
<option value="" disabled>
|
||||
Kategorie waehlen
|
||||
</option>
|
||||
{categories.map((category) => (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<textarea
|
||||
name="description"
|
||||
defaultValue={editEvent.description || ""}
|
||||
placeholder="Beschreibung"
|
||||
className="min-h-[96px] rounded-xl border border-slate-300 px-3 py-2"
|
||||
/>
|
||||
<button type="submit" className="btn-accent">
|
||||
Speichern
|
||||
</button>
|
||||
{editStatus && <p className="text-sm text-emerald-600">{editStatus}</p>}
|
||||
{editError && <p className="text-sm text-red-600">{editError}</p>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full text-left text-sm">
|
||||
<thead className="text-xs uppercase tracking-[0.2em] text-slate-500">
|
||||
<tr>
|
||||
<th className="pb-2">Datum</th>
|
||||
<th className="pb-2">Titel</th>
|
||||
<th className="pb-2">Kategorie</th>
|
||||
<th className="pb-2">Status</th>
|
||||
<th className="pb-2">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{allEvents.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={5} className="py-4 text-slate-600">
|
||||
Keine Termine vorhanden.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
allEvents.map((event) => (
|
||||
<tr key={event.id} className="border-t border-slate-200">
|
||||
<td className="py-3 pr-3">
|
||||
{new Date(event.startAt).toLocaleString("de-DE", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short"
|
||||
})}
|
||||
</td>
|
||||
<td className="py-3 pr-3 font-medium">{event.title}</td>
|
||||
<td className="py-3 pr-3">
|
||||
{event.category?.name || "Ohne Kategorie"}
|
||||
</td>
|
||||
<td className="py-3 pr-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusIcon status={event.status} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 pr-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-slate-200 px-3 py-1 text-xs text-slate-700"
|
||||
onClick={() => {
|
||||
setEditEvent(event);
|
||||
setIsEditOpen(true);
|
||||
}}
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-red-200 px-3 py-1 text-xs text-red-600"
|
||||
onClick={() => deleteEvent(event.id)}
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusIcon({ status }: { status: string }) {
|
||||
if (status === "APPROVED") {
|
||||
return (
|
||||
<span title="Freigegeben" aria-label="Freigegeben" className="text-emerald-600">
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M5 12l4 4L19 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === "REJECTED") {
|
||||
return (
|
||||
<span title="Abgelehnt" aria-label="Abgelehnt" className="text-red-600">
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M6 6l12 12M18 6l-12 12" strokeLinecap="round" />
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span title="Offen" aria-label="Offen" className="text-amber-600">
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M12 6v6l4 2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user