event admin verfeinert und UI reduziert.

This commit is contained in:
Codex Agent
2025-11-25 15:50:34 +01:00
parent 596dcbf18a
commit 4d31eb4d42
6 changed files with 245 additions and 360 deletions

View File

@@ -3,11 +3,10 @@ import React from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
CalendarDays,
Camera,
AlertTriangle,
Sparkles,
Users,
CalendarDays,
Plus,
Settings,
QrCode,
@@ -20,11 +19,11 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import {
TenantOnboardingChecklistCard,
SectionCard,
SectionHeader,
StatCarousel,
ActionGrid,
} from '../components/tenant';
import type { ChecklistStep } from '../components/tenant';
@@ -83,7 +82,7 @@ export default function DashboardPage() {
const navigate = useNavigate();
const location = useLocation();
const { user } = useAuth();
const { events: ctxEvents, activeEvent: ctxActiveEvent } = useEventContext();
const { events: ctxEvents, activeEvent: ctxActiveEvent, selectEvent } = useEventContext();
const { progress, markStep } = useOnboardingProgress();
const { t, i18n } = useTranslation('dashboard', { keyPrefix: 'dashboard' });
const { t: tc } = useTranslation('common');
@@ -226,11 +225,19 @@ export default function DashboardPage() {
return activePackage.remaining_events > 0;
}, [activePackage]);
const upcomingEvents = getUpcomingEvents(ctxEvents.length ? ctxEvents : events);
const publishedEvents = (ctxEvents.length ? ctxEvents : events).filter((event) => event.status === 'published');
const primaryEvent = ctxActiveEvent ?? (ctxEvents[0] ?? events[0] ?? null);
const eventOptions = ctxEvents.length ? ctxEvents : events;
const [selectedSlug, setSelectedSlug] = React.useState<string | null>(() => ctxActiveEvent?.slug ?? eventOptions[0]?.slug ?? null);
React.useEffect(() => {
setSelectedSlug(ctxActiveEvent?.slug ?? eventOptions[0]?.slug ?? null);
}, [ctxActiveEvent?.slug, eventOptions]);
const upcomingEvents = getUpcomingEvents(eventOptions);
const publishedEvents = eventOptions.filter((event) => event.status === 'published');
const primaryEvent = React.useMemo(
() => eventOptions.find((event) => event.slug === selectedSlug) ?? eventOptions[0] ?? null,
[eventOptions, selectedSlug],
);
const primaryEventName = primaryEvent ? resolveEventName(primaryEvent.name, primaryEvent.slug) : null;
const singleEvent = ctxEvents.length === 1 ? ctxEvents[0] : (events.length === 1 ? events[0] : null);
const singleEvent = eventOptions.length === 1 ? eventOptions[0] : null;
const singleEventName = singleEvent ? resolveEventName(singleEvent.name, singleEvent.slug) : null;
const singleEventDateLabel = singleEvent?.event_date ? formatDate(singleEvent.event_date, dateLocale) : null;
const primaryEventLimits = primaryEvent?.limits ?? null;
@@ -305,45 +312,6 @@ export default function DashboardPage() {
return now >= eventStart && now <= eventStart + windowLengthMs;
});
}, [events]);
const statItems = React.useMemo(
() => ([
{
key: 'activeEvents',
label: translate('overview.stats.activeEvents'),
value: summary?.active_events ?? publishedEvents.length,
hint: translate('overview.stats.publishedHint', { count: publishedEvents.length }),
icon: <CalendarDays className="h-4 w-4" />,
},
{
key: 'newPhotos',
label: translate('overview.stats.newPhotos', 'Neueste Uploads'),
value: summary?.new_photos ?? 0,
icon: <Camera className="h-4 w-4" />,
},
{
key: 'taskProgress',
label: translate('overview.stats.taskProgress'),
value: `${Math.round(summary?.task_progress ?? 0)}%`,
icon: <Users className="h-4 w-4" />,
},
activePackage
? {
key: 'package',
label: translate('overview.stats.activePackage', 'Aktives Paket'),
value: activePackage.package_name,
icon: <PackageIcon className="h-4 w-4" />,
}
: null,
].filter(Boolean) as {
key: string;
label: string;
value: string | number;
hint?: string;
icon?: React.ReactNode;
}[]),
[summary, publishedEvents.length, translate, activePackage],
);
const onboardingChecklist = React.useMemo<ChecklistStep[]>(() => {
const steps: ChecklistStep[] = [
{
@@ -577,6 +545,34 @@ export default function DashboardPage() {
) : (
<>
<div id="overview" className="space-y-6 scroll-mt-32">
{eventOptions.length > 1 ? (
<SectionCard className="space-y-3">
<SectionHeader
eyebrow={translate('overview.eventSwitcherEyebrow', 'Events')}
title={translate('overview.eventSwitcherTitle', 'Event auswählen')}
description={translate('overview.eventSwitcherDescription', 'Wechsle das Event, für das das Dashboard Daten anzeigt.')}
/>
<Select
value={selectedSlug ?? ''}
onValueChange={(value) => {
setSelectedSlug(value);
selectEvent(value);
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={translate('overview.eventSwitcherPlaceholder', 'Event auswählen')} />
</SelectTrigger>
<SelectContent>
{eventOptions.map((event) => (
<SelectItem key={event.slug} value={event.slug}>
{resolveEventName(event.name, event.slug)}
{event.event_date ? `${formatDate(event.event_date, dateLocale) ?? ''}` : ''}
</SelectItem>
))}
</SelectContent>
</Select>
</SectionCard>
) : null}
<DashboardEventFocusCard
event={primaryEvent}
limitWarnings={limitWarnings}
@@ -589,20 +585,6 @@ export default function DashboardPage() {
onOpenTasks={focusActions.openTasks}
onOpenPhotobooth={focusActions.openPhotobooth}
/>
<SectionCard className="space-y-3">
<SectionHeader
eyebrow={translate('overview.title')}
title={translate('overview.title')}
description={translate('overview.description')}
endSlot={(
<Badge className="bg-brand-rose-soft text-brand-rose">
{activePackage?.package_name ?? translate('overview.noPackage')}
</Badge>
)}
/>
<StatCarousel items={statItems} />
</SectionCard>
</div>
<div id="live" className="space-y-6 scroll-mt-32">