Files
fotospiel-app/resources/js/admin/pages/EngagementPage.tsx
2025-11-07 13:50:55 +01:00

203 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { AdminLayout } from '../components/AdminLayout';
import { CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
TenantHeroCard,
TenantOnboardingChecklistCard,
FrostedSurface,
tenantHeroPrimaryButtonClass,
tenantHeroSecondaryButtonClass,
SectionCard,
SectionHeader,
StatCarousel,
ActionGrid,
} from '../components/tenant';
import { getDashboardSummary } from '../api';
import { TasksSection } from './TasksPage';
import { TaskCollectionsSection } from './TaskCollectionsPage';
import { EmotionsSection } from './EmotionsPage';
const TAB_KEYS = ['tasks', 'collections', 'emotions'] as const;
type EngagementTab = (typeof TAB_KEYS)[number];
function ensureValidTab(value: string | null): EngagementTab {
if (value && (TAB_KEYS as readonly string[]).includes(value)) {
return value as EngagementTab;
}
return 'tasks';
}
export default function EngagementPage() {
const { t } = useTranslation('management');
const { t: tc } = useTranslation('common');
const [searchParams, setSearchParams] = useSearchParams();
const [engagementStats, setEngagementStats] = React.useState<{ tasks?: number; collections?: number; emotions?: number } | null>(null);
const initialTab = React.useMemo(() => ensureValidTab(searchParams.get('tab')), [searchParams]);
const [activeTab, setActiveTab] = React.useState<EngagementTab>(initialTab);
const handleTabChange = React.useCallback(
(next: string) => {
const valid = ensureValidTab(next);
setActiveTab(valid);
setSearchParams((prev) => {
const params = new URLSearchParams(prev);
params.set('tab', valid);
return params;
});
},
[setSearchParams]
);
React.useEffect(() => {
let cancelled = false;
(async () => {
try {
const summary = await getDashboardSummary();
if (!cancelled && summary?.engagement_totals) {
setEngagementStats(summary.engagement_totals);
}
} catch {
/* ignore */
}
})();
return () => {
cancelled = true;
};
}, []);
const heading = tc('navigation.engagement');
const heroDescription = t('engagement.hero.description', {
defaultValue: 'Kuratiere Aufgaben, Moderationskollektionen und Emotionen als kreative Toolbox für jedes Event.'
});
const heroSupporting = [
t('engagement.hero.summary.tasks', { defaultValue: 'Plane Aufgaben, die Gäste motivieren von Upload-Regeln bis zu Story-Prompts.' }),
t('engagement.hero.summary.collections', { defaultValue: 'Sammle Vorlagen und kollektive Inhalte, um Events im Handumdrehen neu zu starten.' })
];
const heroPrimaryAction = (
<Button
size="sm"
className={tenantHeroPrimaryButtonClass}
onClick={() => handleTabChange('tasks')}
>
{t('engagement.hero.actions.tasks', 'Zu Aufgaben wechseln')}
</Button>
);
const heroSecondaryAction = (
<Button
size="sm"
className={tenantHeroSecondaryButtonClass}
onClick={() => handleTabChange('collections')}
>
{t('engagement.hero.actions.collections', 'Kollektionen ansehen')}
</Button>
);
const heroAside = (
<FrostedSurface className="space-y-4 border-slate-200 p-5 text-slate-900 shadow-md shadow-rose-300/20 dark:border-white/20 dark:bg-white/10">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400">{t('engagement.hero.activeTab', { defaultValue: 'Aktiver Bereich' })}</p>
<p className="mt-2 text-lg font-semibold text-slate-900 dark:text-slate-100">{t(`engagement.tabs.${activeTab}.title`, { defaultValue: tc(`navigation.${activeTab}`) })}</p>
</div>
<p className="text-xs text-slate-600 dark:text-slate-400">
{t('engagement.hero.tip', 'Wechsle Tabs, um Aufgaben, Kollektionen oder Emotionen zu bearbeiten und direkt in Events einzubinden.')}
</p>
</FrostedSurface>
);
return (
<AdminLayout
title={heading}
subtitle={t('engagement.subtitle', 'Bündle Aufgaben, Vorlagen und Emotionen für deine Events.')}
>
<TenantHeroCard
badge={t('engagement.hero.badge', 'Engagement')}
title={heading}
description={heroDescription}
supporting={heroSupporting}
primaryAction={heroPrimaryAction}
secondaryAction={heroSecondaryAction}
aside={heroAside}
/>
<SectionCard className="mt-6 space-y-6">
<SectionHeader
eyebrow={t('engagement.sections.active.badge', 'Active Toolkit')}
title={t('engagement.sections.active.title', 'Aufgaben & Co.')}
description={t('engagement.sections.active.description', 'Verwalte Aufgaben, Kollektionen und Emotionen an einem Ort.')}
/>
<StatCarousel
items={[
{
key: 'tasks',
label: t('engagement.stats.tasks', 'Aufgaben'),
value: engagementStats?.tasks ?? '—',
},
{
key: 'collections',
label: t('engagement.stats.collections', 'Kollektionen'),
value: engagementStats?.collections ?? '—',
},
{
key: 'emotions',
label: t('engagement.stats.emotions', 'Emotionen'),
value: engagementStats?.emotions ?? '—',
},
]}
/>
<ActionGrid
items={[
{
key: 'newTask',
label: t('engagement.actions.newTask', 'Neue Aufgabe'),
description: t('engagement.actions.newTask.description', 'Starte mit einer neuen Idee oder Vorlage.'),
onClick: () => handleTabChange('tasks'),
},
{
key: 'collection',
label: t('engagement.actions.collection', 'Kollektion bauen'),
description: t('engagement.actions.collection.description', 'Fasse Aufgaben zu Events zusammen.'),
onClick: () => handleTabChange('collections'),
},
{
key: 'emotion',
label: t('engagement.actions.emotion', 'Emotion hinzufügen'),
description: t('engagement.actions.emotion.description', 'Markiere Momente zur Moderation.'),
onClick: () => handleTabChange('emotions'),
},
]}
/>
<Tabs value={activeTab} onValueChange={handleTabChange} className="space-y-6">
<TabsList className="grid w-full grid-cols-3 rounded-2xl border border-white/25 bg-white/80 p-1 shadow-inner shadow-rose-200/20 dark:border-slate-800/70 dark:bg-slate-900/70">
{(['tasks', 'collections', 'emotions'] as const).map((tab) => (
<TabsTrigger
key={tab}
value={tab}
className="rounded-xl text-sm font-medium transition data-[state=active]:bg-gradient-to-r data-[state=active]:from-[#ff5f87] data-[state=active]:via-[#ec4899] data-[state=active]:to-[#6366f1] data-[state=active]:text-white"
>
{tc(`navigation.${tab}`)}
</TabsTrigger>
))}
</TabsList>
<TabsContent value="tasks" className="space-y-6">
<TasksSection embedded onNavigateToCollections={() => handleTabChange('collections')} />
</TabsContent>
<TabsContent value="collections" className="space-y-6">
<TaskCollectionsSection embedded onNavigateToTasks={() => handleTabChange('tasks')} />
</TabsContent>
<TabsContent value="emotions" className="space-y-6">
<EmotionsSection embedded />
</TabsContent>
</Tabs>
</SectionCard>
</AdminLayout>
);
}