feat: unify tenant admin ui and add photo moderation

This commit is contained in:
Codex Agent
2025-11-07 13:50:55 +01:00
parent 9cc9950b0c
commit 253239455b
14 changed files with 995 additions and 583 deletions

View File

@@ -9,11 +9,16 @@ import { AdminLayout } from '../components/AdminLayout';
import { CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
TenantHeroCard,
FrostedCard,
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';
@@ -32,6 +37,7 @@ 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);
@@ -48,6 +54,22 @@ export default function EngagementPage() {
},
[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', {
@@ -76,7 +98,7 @@ export default function EngagementPage() {
</Button>
);
const heroAside = (
<FrostedSurface className="space-y-4 border-white/20 p-5 text-slate-900 shadow-md shadow-rose-300/20 dark:border-slate-800/70 dark:bg-slate-950/80">
<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>
@@ -102,13 +124,55 @@ export default function EngagementPage() {
aside={heroAside}
/>
<FrostedCard className="mt-6 border border-white/20">
<CardHeader className="px-0 pt-0">
<CardTitle className="sr-only">{heading}</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<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">
<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}
@@ -132,8 +196,7 @@ export default function EngagementPage() {
<EmotionsSection embedded />
</TabsContent>
</Tabs>
</CardContent>
</FrostedCard>
</SectionCard>
</AdminLayout>
);
}