206 lines
8.0 KiB
TypeScript
206 lines
8.0 KiB
TypeScript
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
AlertTriangle,
|
|
Camera,
|
|
ClipboardList,
|
|
PlugZap,
|
|
QrCode,
|
|
Sparkles,
|
|
CalendarDays,
|
|
} from 'lucide-react';
|
|
|
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
|
import type { TenantEvent, DashboardSummary } from '../../api';
|
|
import type { LimitWarning } from '../../lib/limitWarnings';
|
|
import { resolveEventDisplayName, formatEventDate, formatEventStatusLabel, resolveEngagementMode } from '../../lib/events';
|
|
|
|
type DashboardEventFocusCardProps = {
|
|
event: TenantEvent | null;
|
|
limitWarnings: LimitWarning[];
|
|
summary: DashboardSummary | null;
|
|
dateLocale: string;
|
|
onCreateEvent: () => void;
|
|
onOpenEvent: () => void;
|
|
onOpenPhotos: () => void;
|
|
onOpenInvites: () => void;
|
|
onOpenTasks: () => void;
|
|
onOpenPhotobooth: () => void;
|
|
};
|
|
|
|
export function DashboardEventFocusCard({
|
|
event,
|
|
limitWarnings,
|
|
summary,
|
|
dateLocale,
|
|
onCreateEvent,
|
|
onOpenEvent,
|
|
onOpenPhotos,
|
|
onOpenInvites,
|
|
onOpenTasks,
|
|
onOpenPhotobooth,
|
|
}: DashboardEventFocusCardProps) {
|
|
const { t } = useTranslation('dashboard', { keyPrefix: 'dashboard.eventFocus' });
|
|
const { t: tc } = useTranslation('common');
|
|
|
|
if (!event) {
|
|
return (
|
|
<Card className="border border-dashed border-rose-200/80 bg-white/80 shadow-sm shadow-rose-100/40 dark:border-white/20 dark:bg-white/5">
|
|
<CardHeader>
|
|
<div className="flex items-center gap-2 text-sm font-semibold text-rose-600 dark:text-rose-200">
|
|
<Sparkles className="h-4 w-4" />
|
|
{t('empty.eyebrow', 'Noch kein Event aktiv')}
|
|
</div>
|
|
<CardTitle className="text-lg text-slate-900 dark:text-white">
|
|
{t('empty.title', 'Leg mit deinem ersten Event los')}
|
|
</CardTitle>
|
|
<CardDescription className="text-sm text-slate-600 dark:text-slate-300">
|
|
{t('empty.description', 'Importiere ein Aufgaben-Set, lege Branding fest und teile sofort den Gästelink.')}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Button className="rounded-full bg-brand-rose px-6 text-white shadow-lg shadow-rose-400/40 hover:bg-[var(--brand-rose-strong)]" onClick={onCreateEvent}>
|
|
{t('empty.cta', 'Event anlegen')}
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const eventName = resolveEventDisplayName(event);
|
|
const dateLabel = formatEventDate(event.event_date, dateLocale) ?? t('noDate', 'Kein Datum gesetzt');
|
|
const statusLabel = formatEventStatusLabel(event.status ?? null, tc);
|
|
const isLive = Boolean(event.is_active || event.status === 'published');
|
|
const engagementMode = resolveEngagementMode(event);
|
|
|
|
const overviewStats = [
|
|
{
|
|
key: 'uploads',
|
|
label: t('stats.uploads', 'Uploads gesamt'),
|
|
value: Number(event.photo_count ?? 0).toLocaleString(),
|
|
},
|
|
{
|
|
key: 'likes',
|
|
label: t('stats.likes', 'Likes'),
|
|
value: Number(event.like_count ?? 0).toLocaleString(),
|
|
},
|
|
{
|
|
key: 'tasks',
|
|
label: t('stats.tasks', 'Aktive Aufgaben'),
|
|
value: Number(event.tasks_count ?? 0).toLocaleString(),
|
|
},
|
|
{
|
|
key: 'invites',
|
|
label: t('stats.invites', 'QR-Codes live'),
|
|
value: Number(event.active_invites_count ?? event.total_invites_count ?? 0).toLocaleString(),
|
|
},
|
|
];
|
|
|
|
const quickActions = [
|
|
{
|
|
key: 'photos',
|
|
label: t('actions.photos', 'Uploads prüfen'),
|
|
description: t('actions.photosHint', 'Neueste Uploads ansehen und verstecken.'),
|
|
icon: Camera,
|
|
handler: onOpenPhotos,
|
|
disabled: !isLive,
|
|
},
|
|
{
|
|
key: 'invites',
|
|
label: t('actions.invites', 'QR-Codes'),
|
|
description: t('actions.invitesHint', 'Layouts exportieren oder Links kopieren.'),
|
|
icon: QrCode,
|
|
handler: onOpenInvites,
|
|
},
|
|
{
|
|
key: 'tasks',
|
|
label: t('actions.tasks', 'Aufgaben-Sets & Emotionen'),
|
|
description: t('actions.tasksHint', 'Kollektionen importieren und Emotionen aktivieren.'),
|
|
icon: ClipboardList,
|
|
handler: onOpenTasks,
|
|
},
|
|
{
|
|
key: 'photobooth',
|
|
label: t('actions.photobooth', 'Photobooth binden'),
|
|
description: t('actions.photoboothHint', 'FTP-Daten freigeben und Rate-Limit prüfen.'),
|
|
icon: PlugZap,
|
|
handler: onOpenPhotobooth,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<Card className="border border-slate-200 bg-white/90 shadow-lg shadow-rose-100/40 dark:border-white/10 dark:bg-white/5">
|
|
<CardHeader className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
<div>
|
|
<div className="flex flex-wrap items-center gap-2 text-xs uppercase tracking-[0.35em] text-rose-500 dark:text-rose-200">
|
|
{t('eyebrow', 'Aktuelles Event')}
|
|
</div>
|
|
<CardTitle className="mt-2 text-2xl font-semibold text-slate-900 dark:text-white">
|
|
{eventName}
|
|
</CardTitle>
|
|
<CardDescription className="mt-1 text-sm text-slate-600 dark:text-slate-300">
|
|
{t('dateLabel', { defaultValue: 'Eventdatum: {{date}}', date: dateLabel })}
|
|
</CardDescription>
|
|
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
<Badge className={isLive ? 'bg-emerald-500 text-white' : 'bg-slate-200 text-slate-800'}>
|
|
{statusLabel}
|
|
</Badge>
|
|
<Badge variant="outline" className="text-xs font-semibold">
|
|
{isLive ? t('badges.live', 'Live für Gäste') : t('badges.hidden', 'Noch versteckt')}
|
|
</Badge>
|
|
{engagementMode === 'photo_only' ? (
|
|
<Badge variant="outline" className="text-xs font-semibold">
|
|
{t('badges.photoOnly', 'Nur Foto-Modus')}
|
|
</Badge>
|
|
) : (
|
|
<Badge variant="outline" className="text-xs font-semibold">
|
|
{t('badges.missionMode', 'Mission Cards aktiv')}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<Button variant="outline" size="sm" className="rounded-full border-brand-rose-soft text-brand-rose hover:bg-brand-rose-soft/40" onClick={onOpenEvent}>
|
|
<CalendarDays className="mr-2 h-4 w-4" />
|
|
{t('viewEvent', 'Event öffnen')}
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
|
|
{overviewStats.map((stat) => (
|
|
<div key={stat.key} className="rounded-2xl border border-slate-200 bg-white/80 p-3 text-xs text-slate-600 dark:border-white/10 dark:bg-white/5">
|
|
<p className="text-[11px] uppercase tracking-wide text-slate-500 dark:text-slate-300">{stat.label}</p>
|
|
<p className="mt-1 text-lg font-semibold text-slate-900 dark:text-white">{stat.value}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="grid gap-3 lg:grid-cols-2">
|
|
{quickActions.map((action) => (
|
|
<button
|
|
key={action.key}
|
|
type="button"
|
|
onClick={action.handler}
|
|
disabled={action.disabled}
|
|
className="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white/90 p-4 text-left transition hover:border-rose-200 hover:bg-rose-50 disabled:cursor-not-allowed disabled:opacity-60 dark:border-white/10 dark:bg-white/5"
|
|
>
|
|
<action.icon className="mt-1 h-5 w-5 text-rose-500 dark:text-rose-200" />
|
|
<div>
|
|
<p className="text-sm font-semibold text-slate-900 dark:text-white">{action.label}</p>
|
|
<p className="text-xs text-slate-500 dark:text-slate-300">{action.description}</p>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
</div>
|
|
);
|
|
}
|