2026-01-21 14:32:22 +01:00

275 lines
11 KiB
TypeScript

import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { DropdownMenuItem, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
import { type Event } from '@/features/event/api/eventService';
import { ArrowLeft, LayoutGrid } from 'lucide-react';
import { useHeader } from '@/contexts/HeaderContext';
import EventFormInline from './EventFormInline';
const EVENT_WIZARD_STATE_KEY = 'kgb.eventWizard.state';
interface EventWizardProps {
onComplete: () => void;
onBackToOverview: () => void;
events: Event[];
loading: boolean;
error: string | null;
onReload: () => void;
}
const EventWizard: React.FC<EventWizardProps> = ({ onComplete, onBackToOverview, events, loading, error, onReload }) => {
const [showForm, setShowForm] = useState(false);
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
const { setHeader, resetHeader } = useHeader();
useEffect(() => {
try {
const raw = sessionStorage.getItem(EVENT_WIZARD_STATE_KEY);
if (!raw) return;
const parsed = JSON.parse(raw) as { showForm?: boolean; activeEventId?: string | number | null };
if (typeof parsed.showForm === 'boolean') setShowForm(parsed.showForm);
if (parsed.activeEventId !== undefined) setActiveEventId(parsed.activeEventId);
} catch {
// ignore
}
// only on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
try {
sessionStorage.setItem(
EVENT_WIZARD_STATE_KEY,
JSON.stringify({ showForm, activeEventId: activeEventId ?? null })
);
} catch {
// ignore
}
}, [activeEventId, showForm]);
const activeEvent = useMemo(() => {
if (!activeEventId) return null;
return events.find((e) => String(e.id) === String(activeEventId)) || null;
}, [activeEventId, events]);
const canGoTimeline = showForm;
const handleBackToTimeline = () => {
setShowForm(false);
setActiveEventId(null);
};
useEffect(() => {
onReload();
}, [onReload]);
useEffect(() => {
const eventName = (activeEvent as any)?.title || (activeEvent as any)?.name || null;
const title = showForm
? activeEventId
? (
<div className="flex flex-col items-center leading-tight">
<div>Event bearbeiten</div>
{eventName ? <div className="text-xs text-muted-foreground font-normal">{eventName}</div> : null}
</div>
)
: 'Event erstellen'
: 'Veranstaltungen';
setHeader({
title,
left: (
<>
<Button type="button" variant="ghost" size="sm" onClick={onBackToOverview} className="h-10 px-2">
<span className="flex flex-col items-center leading-tight">
<LayoutGrid className="h-4 w-4" />
<span className="text-[10px] mt-0.5">Dashboard</span>
</span>
</Button>
{showForm && (
<Button type="button" variant="ghost" size="sm" onClick={handleBackToTimeline} className="h-10 px-2">
<span className="flex flex-col items-center leading-tight">
<ArrowLeft className="h-4 w-4" />
<span className="text-[10px] mt-0.5">Timeline</span>
</span>
</Button>
)}
</>
),
mobileMenu: (
<>
<DropdownMenuItem onClick={onBackToOverview} className="gap-2">
<LayoutGrid className="h-4 w-4" />
<span>Zurück zum Dashboard</span>
</DropdownMenuItem>
{showForm ? <DropdownMenuSeparator /> : null}
{showForm && (
<DropdownMenuItem onClick={handleBackToTimeline} className="gap-2">
<ArrowLeft className="h-4 w-4" />
<span>Zurück zur Timeline</span>
</DropdownMenuItem>
)}
</>
),
});
return () => {
resetHeader();
};
}, [activeEvent, activeEventId, onBackToOverview, resetHeader, setHeader, showForm]);
const sortedEvents = useMemo(() => {
if (!events.length) return [] as Event[];
return [...events].sort((a, b) => {
const aDate = a?.eventDate || a?.event_date || a?.begin_date;
const bDate = b?.eventDate || b?.event_date || b?.begin_date;
const aTime = aDate ? new Date(aDate).getTime() : 0;
const bTime = bDate ? new Date(bDate).getTime() : 0;
return aTime - bTime;
});
}, [events]);
const activeRef = useRef<HTMLDivElement | null>(null);
const todayIndex = useMemo(() => {
if (!sortedEvents.length) return -1;
const today = new Date();
today.setHours(0, 0, 0, 0);
// Finde das erste Event mit Datum heute oder in der Zukunft
const upcomingIndex = sortedEvents.findIndex((event) => {
const baseDate = event?.eventDate || event?.event_date || event?.begin_date;
if (!baseDate) return false;
const date = new Date(baseDate);
if (Number.isNaN(date.getTime())) return false;
date.setHours(0, 0, 0, 0);
return date.getTime() >= today.getTime();
});
// Wenn nichts in der Zukunft liegt, scrolle zum letzten Event (jüngstes vergangenes)
if (upcomingIndex === -1) {
return sortedEvents.length - 1;
}
return upcomingIndex;
}, [sortedEvents]);
useEffect(() => {
if (todayIndex === -1) return;
if (!activeRef.current) return;
activeRef.current.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
}, [todayIndex, sortedEvents]);
const formatDate = (dateString?: string) => {
if (!dateString) return '';
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) return dateString;
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
};
return (
<Card className="w-full mx-auto md:max-w-5xl">
<CardContent>
{showForm ? (
<div className="space-y-4">
<EventFormInline
mode={activeEventId ? 'edit' : 'create'}
eventId={activeEventId}
onSuccess={() => {
setShowForm(false);
setActiveEventId(null);
onReload();
}}
onCancel={() => {
setShowForm(false);
setActiveEventId(null);
}}
/>
</div>
) : loading ? (
<div className="text-sm text-muted-foreground py-10">Events werden geladen...</div>
) : error ? (
<div className="rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">{error}</div>
) : (
<div className="space-y-6">
<div className="pb-2">
<div className="mb-2 text-xs text-muted-foreground">
{sortedEvents.length} Veranstaltung{sortedEvents.length === 1 ? '' : 'en'} geladen
</div>
<div className="relative sm:min-h-[200px]">
<div className="hidden sm:block absolute left-0 right-0 top-1/2 h-px -translate-y-1/2 bg-muted-foreground/30" />
<div className="relative -mx-4 sm:mx-0 px-4 sm:px-0 overflow-x-auto py-4 sm:py-8">
<div className="flex flex-row gap-4 sm:gap-6">
{sortedEvents.map((event, index) => {
const isToday = index === todayIndex && todayIndex !== -1;
const title = (event as any).title || event.name || 'Unbenanntes Event';
const singleDate = (event as any).eventDate || (event as any).event_date || event.begin_date;
const status = ((event as any).status || (event as any).overall_status) as 'PENDING' | 'ON_AIR' | undefined;
// Status nur für heutige oder zukünftige Events anzeigen
let statusToShow: 'PENDING' | 'ON_AIR' | undefined = status;
if (singleDate) {
const d = new Date(singleDate);
if (!Number.isNaN(d.getTime())) {
const today = new Date();
today.setHours(0, 0, 0, 0);
d.setHours(0, 0, 0, 0);
if (d.getTime() < today.getTime()) {
statusToShow = undefined;
}
}
}
const statusLabel = statusToShow === 'ON_AIR' ? 'On Air' : 'Pending';
const statusClass = statusToShow === 'ON_AIR' ? 'bg-emerald-100 text-emerald-700' : 'bg-amber-100 text-amber-700';
return (
<div
key={event.id || `${event.name}-${index}`}
ref={isToday ? activeRef : undefined}
className="flex w-[220px] sm:w-auto flex-shrink-0 sm:flex-shrink min-w-0 sm:min-w-[240px] flex-col items-stretch sm:items-center text-left sm:text-center cursor-pointer"
onClick={() => {
const eid = event.id ?? `${event.name || 'event'}-${index}`;
setActiveEventId(eid as any);
setShowForm(true);
}}
>
<div className={`hidden sm:block h-3 w-3 rounded-full border-2 ${isToday ? 'border-primary bg-primary' : 'border-muted-foreground/40 bg-background'}`} />
<div className={`mt-2 sm:mt-4 w-full rounded-lg border p-4 text-left transition-shadow ${isToday ? 'border-primary/60 shadow-lg' : 'border-border'}`}>
<div className="flex items-center justify-between gap-2">
<div className="text-sm font-semibold leading-tight line-clamp-2">{title}</div>
{statusToShow && (
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${statusClass}`}>
{statusLabel}
</span>
)}
</div>
<div className="mt-2 text-xs text-muted-foreground">{formatDate(singleDate)}</div>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
onClick={() => {
setActiveEventId(null);
setShowForm(true);
}}
>
Neu erstellen
</Button>
</div>
</div>
)}
</CardContent>
</Card>
);
};
export default EventWizard;