added missing translations
This commit is contained in:
@@ -59,6 +59,8 @@ import {
|
||||
ADMIN_EVENT_BRANDING_PATH,
|
||||
buildEngagementTabPath,
|
||||
} from '../constants';
|
||||
import { buildEventTabs } from '../lib/eventTabs';
|
||||
import { formatEventDate } from '../lib/events';
|
||||
import {
|
||||
SectionCard,
|
||||
SectionHeader,
|
||||
@@ -207,6 +209,18 @@ export default function EventDetailPage() {
|
||||
|
||||
const eventName = event ? resolveName(event.name) : t('events.placeholders.untitled', 'Unbenanntes Event');
|
||||
const subtitle = t('events.workspace.detailSubtitle', 'Behalte Status, Aufgaben und Einladungen deines Events im Blick.');
|
||||
const currentTabKey = 'overview';
|
||||
|
||||
const eventTabs = React.useMemo(() => {
|
||||
if (!event) return [];
|
||||
const translateMenu = (key: string, fallback: string) => t(key, { defaultValue: fallback });
|
||||
const counts = {
|
||||
photos: stats?.uploads_total ?? event.photo_count ?? undefined,
|
||||
tasks: toolkitData?.tasks?.summary?.total ?? event.tasks_count ?? undefined,
|
||||
invites: event.active_invites_count ?? event.total_invites_count ?? undefined,
|
||||
};
|
||||
return buildEventTabs(event, translateMenu, counts);
|
||||
}, [event, stats?.uploads_total, toolkitData?.tasks?.summary?.total, t]);
|
||||
|
||||
const limitWarnings = React.useMemo(
|
||||
() => (event?.limits ? buildLimitWarnings(event.limits, (key, options) => tCommon(`limits.${key}`, options)) : []),
|
||||
@@ -352,6 +366,8 @@ const shownWarningToasts = React.useRef<Set<string>>(new Set());
|
||||
<AdminLayout
|
||||
title={eventName}
|
||||
subtitle={subtitle}
|
||||
tabs={eventTabs}
|
||||
currentTabKey={currentTabKey}
|
||||
>
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
@@ -538,6 +554,159 @@ function resolveName(name: TenantEvent['name']): string {
|
||||
return 'Event';
|
||||
}
|
||||
|
||||
type RecapContentProps = {
|
||||
event: TenantEvent;
|
||||
stats: EventStats;
|
||||
busy: boolean;
|
||||
onToggleEvent: () => void;
|
||||
onExtendGallery: () => void;
|
||||
};
|
||||
|
||||
function RecapContent({ event, stats, busy, onToggleEvent, onExtendGallery }: RecapContentProps) {
|
||||
const { t } = useTranslation('management');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const galleryExpiresAt = event.package?.expires_at ?? event.limits?.gallery?.expires_at ?? null;
|
||||
const galleryStatusLabel = event.is_active
|
||||
? t('events.recap.galleryOpen', 'Galerie geöffnet')
|
||||
: t('events.recap.galleryClosed', 'Galerie geschlossen');
|
||||
|
||||
const counts = {
|
||||
photos: stats.uploads_total ?? stats.total ?? 0,
|
||||
pending: stats.pending_photos ?? 0,
|
||||
likes: stats.likes_total ?? stats.likes ?? 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<div className="rounded-3xl border border-slate-200 bg-white/85 p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-emerald-500">
|
||||
{t('events.recap.galleryTitle', 'Galerie-Status')}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-semibold text-slate-900 dark:text-white">{galleryStatusLabel}</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||
{t('events.recap.galleryCounts', '{{photos}} Fotos, {{pending}} offen, {{likes}} Likes', counts)}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant={event.is_active ? 'default' : 'outline'} className="bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-100">
|
||||
{event.is_active ? t('events.recap.open', 'Offen') : t('events.recap.closed', 'Geschlossen')}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<Button size="sm" variant={event.is_active ? 'secondary' : 'default'} disabled={busy} onClick={onToggleEvent}>
|
||||
{busy ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Sparkles className="mr-2 h-4 w-4" />}
|
||||
{event.is_active ? t('events.recap.closeGallery', 'Galerie schließen') : t('events.recap.openGallery', 'Galerie öffnen')}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => navigate(ADMIN_EVENT_PHOTOS_PATH(event.slug))}>
|
||||
<Camera className="mr-2 h-4 w-4" />
|
||||
{t('events.recap.moderate', 'Uploads ansehen')}
|
||||
</Button>
|
||||
</div>
|
||||
{event.public_url ? (
|
||||
<div className="mt-4 flex items-center gap-2 text-sm text-slate-600 dark:text-slate-300">
|
||||
<span className="truncate" title={event.public_url}>{event.public_url}</span>
|
||||
<Button size="icon" variant="ghost" className="h-8 w-8" onClick={() => navigator.clipboard.writeText(event.public_url!)}>
|
||||
<QrCode className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="rounded-3xl border border-slate-200 bg-white/85 p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-indigo-500">
|
||||
{t('events.recap.exportTitle', 'Export & Backup')}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-semibold text-slate-900 dark:text-white">
|
||||
{t('events.recap.exportCopy', 'Alle Assets sichern')}
|
||||
</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||
{t('events.recap.exportHint', 'Zip/CSV Export und Backup anstoßen.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="border-indigo-200 text-indigo-700 dark:border-indigo-800 dark:text-indigo-200">
|
||||
{t('events.recap.backup', 'Backup')}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<Button size="sm" onClick={() => navigate(ADMIN_EVENT_PHOTOS_PATH(event.slug))}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4 rotate-180" />
|
||||
{t('events.recap.exportAll', 'Alles exportieren')}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => navigate(ADMIN_EVENT_PHOTOS_PATH(event.slug))}>
|
||||
<Printer className="mr-2 h-4 w-4" />
|
||||
{t('events.recap.exportHighlights', 'Highlights exportieren')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<div className="rounded-3xl border border-slate-200 bg-white/85 p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-amber-500">
|
||||
{t('events.recap.retentionTitle', 'Aufbewahrung & Verlängerung')}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-semibold text-slate-900 dark:text-white">
|
||||
{galleryExpiresAt
|
||||
? t('events.recap.expiresAt', 'Läuft ab am {{date}}', { date: formatEventDate(galleryExpiresAt, undefined) })
|
||||
: t('events.recap.noExpiry', 'Ablaufdatum nicht gesetzt')}
|
||||
</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||
{t('events.recap.retentionHint', 'Verlängere die Galerie-Laufzeit mit einem Add-on. Verlängerungen addieren sich.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="border-amber-200 text-amber-700 dark:border-amber-800 dark:text-amber-100">
|
||||
{t('events.recap.expiry', 'Ablauf')}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<Button size="sm" variant="default" onClick={onExtendGallery}>
|
||||
<Clock3 className="mr-2 h-4 w-4" />
|
||||
{t('events.recap.extend30', '+30 Tage verlängern')}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => navigate(ADMIN_EVENT_EDIT_PATH(event.slug))}>
|
||||
{t('events.recap.archive', 'Archivieren/Löschen')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-3xl border border-slate-200 bg-white/85 p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-rose-500">
|
||||
{t('events.recap.commsTitle', 'Kommunikation')}
|
||||
</p>
|
||||
<p className="mt-1 text-lg font-semibold text-slate-900 dark:text-white">
|
||||
{t('events.recap.commsCopy', 'Kein Gast-Newsletter aktiv')}
|
||||
</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||
{t('events.recap.commsHint', 'Push wirkt vor allem live. Teile den Link manuell mit deinem Team oder auf Social Media.')}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="border-rose-200 text-rose-700 dark:border-rose-800 dark:text-rose-100">
|
||||
{t('events.recap.manual', 'Manuell')}
|
||||
</Badge>
|
||||
</div>
|
||||
{event.public_url ? (
|
||||
<div className="mt-4 flex items-center gap-2 rounded-2xl border border-dashed border-slate-200 bg-white/70 p-3 text-sm text-slate-700 dark:border-white/10 dark:bg-white/10 dark:text-slate-200">
|
||||
<div className="flex-1 truncate" title={event.public_url}>{event.public_url}</div>
|
||||
<Button size="sm" variant="ghost" onClick={() => navigator.clipboard.writeText(event.public_url!)}>
|
||||
{t('events.recap.copyLink', 'Link kopieren')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function QuickActionsMenu({ slug, navigate }: { slug: string; navigate: ReturnType<typeof useNavigate> }) {
|
||||
const { t } = useTranslation('management');
|
||||
|
||||
@@ -664,6 +833,7 @@ function TaskOverviewCard({ tasks, navigateToTasks }: { tasks: EventToolkit['tas
|
||||
}
|
||||
|
||||
function TaskRow({ task }: { task: EventToolkitTask }) {
|
||||
const { t } = useTranslation('management');
|
||||
return (
|
||||
<div className="flex items-start justify-between rounded-lg border border-pink-100 bg-white/80 px-3 py-2 text-xs text-slate-600">
|
||||
<div className="space-y-1">
|
||||
@@ -671,7 +841,7 @@ function TaskRow({ task }: { task: EventToolkitTask }) {
|
||||
{task.description ? <p>{task.description}</p> : null}
|
||||
</div>
|
||||
<Badge variant={task.is_completed ? 'default' : 'outline'} className={task.is_completed ? 'bg-emerald-500/20 text-emerald-600' : 'border-pink-200 text-pink-600'}>
|
||||
{task.is_completed ? 'Erledigt' : 'Offen'}
|
||||
{task.is_completed ? t('events.tasks.status.completed', 'Done') : t('events.tasks.status.open', 'Open')}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user