201 lines
7.7 KiB
TypeScript
201 lines
7.7 KiB
TypeScript
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 {
|
||
TenantHeroCard,
|
||
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>
|
||
);
|
||
}
|