feat: unify tenant admin ui and add photo moderation
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user