Aktueller Stand

This commit is contained in:
2026-01-18 00:40:01 +01:00
parent 68b63b8f06
commit 31aef02558
16 changed files with 352 additions and 43 deletions

View File

@@ -1582,7 +1582,134 @@ export default function CalendarBoard() {
</div>
)}
</div>
<div className="overflow-x-auto">
<div className="md:hidden space-y-2">
{displayedEvents.length === 0 ? (
<p className="rounded-xl border border-slate-200 bg-white px-3 py-4 text-sm text-slate-600">
Keine Termine für die aktuelle Auswahl.
</p>
) : (
displayedEvents.map((event) => {
const categoryName = event.category?.name || "Ohne Kategorie";
const locationLabel = formatLocation(event.location);
const dateLabel = new Date(event.startAt).toLocaleString("de-DE", {
dateStyle: "medium",
timeStyle: "short"
});
const bucket = getDateBucket(event.startAt);
const bucketClass =
bucket === "past"
? "bg-slate-50 text-slate-500"
: bucket === "today"
? "bg-amber-50/60"
: bucket === "tomorrow"
? "bg-emerald-50/60"
: "bg-sky-50/40";
return (
<div
key={event.id}
className={`rounded-xl border border-slate-200 p-2 ${bucketClass}`}
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 space-y-0.5">
<p className="text-[11px] uppercase tracking-[0.2em] text-slate-500">
{dateLabel}
</p>
<p className="text-sm font-semibold" title={event.title}>
{event.title}
</p>
<div className="flex flex-wrap items-center gap-2 text-[11px] text-slate-600">
<span className="truncate" title={categoryName}>
{categoryName}
</span>
<span></span>
<span className="truncate" title={locationLabel}>
{locationLabel}
</span>
</div>
</div>
{isAdmin && (
<input
type="checkbox"
aria-label={`${event.title} auswählen`}
checked={bulkSelection.has(event.id)}
onChange={() => toggleBulkSelection(event.id)}
/>
)}
</div>
<div className="mt-2 flex items-center justify-between gap-2">
<div className="flex flex-nowrap items-center gap-1">
{canManageView && (
<ViewToggleButton
isSelected={selectedEventIds.has(event.id)}
onClick={() => toggleEvent(event.id)}
size="sm"
/>
)}
{isAdmin && (
<button
type="button"
className="rounded-full border border-slate-200 p-1.5 text-slate-600"
onClick={() => {
setEditStatus(null);
setEditError(null);
setEditEvent(event);
setIsEditOpen(true);
}}
aria-label="Termin bearbeiten"
>
<svg
viewBox="0 0 24 24"
className="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path
d="M4 20h4l10-10-4-4L4 16v4z"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M13 7l4 4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
)}
<button
type="button"
className="rounded-full border border-slate-200 p-1.5 text-slate-600"
onClick={() => setDetailsEvent(event)}
aria-label="Termin Details"
>
<svg
viewBox="0 0 24 24"
className="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<circle cx="12" cy="12" r="9" />
<path d="M12 8h.01M12 12v4" strokeLinecap="round" />
</svg>
</button>
</div>
{event.locationLat && event.locationLng && (
<a
className="inline-flex items-center justify-center rounded-full border border-slate-200 p-1.5 text-slate-600"
href={`https://maps.google.com/?q=${event.locationLat},${event.locationLng}&z=14`}
target="_blank"
rel="noreferrer"
title="Google Maps"
aria-label="Google Maps"
>
<IconMapPin className="h-3.5 w-3.5" />
</a>
)}
</div>
</div>
);
})
)}
</div>
<div className="hidden overflow-x-auto md:block">
<table className="list-table w-full table-fixed text-left text-sm">
<thead className="text-xs uppercase tracking-[0.2em] text-slate-500">
<tr>
@@ -2551,9 +2678,9 @@ function SortIcon({
);
}
function IconMapPin() {
function IconMapPin({ className = "h-4 w-4" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2">
<svg viewBox="0 0 24 24" className={className} fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 21s6-6.5 6-11a6 6 0 10-12 0c0 4.5 6 11 6 11z" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="12" cy="10" r="2.5" />
</svg>