Refine dashboard overview layout
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-22 16:24:48 +01:00
parent 4f3503e3f4
commit 8aa2efdd9a
3 changed files with 155 additions and 116 deletions

View File

@@ -35,19 +35,25 @@ function translateLimits(t: any) {
// --- TAMAGUI-ALIGNED PRIMITIVES ---
function DashboardCard({ children, style, ...rest }: React.ComponentProps<typeof Card>) {
function DashboardCard({
children,
style,
variant = 'default',
...rest
}: React.ComponentProps<typeof Card> & { variant?: 'default' | 'embedded' }) {
const theme = useAdminTheme();
const isEmbedded = variant === 'embedded';
return (
<Card
backgroundColor={theme.surface}
borderRadius={20}
borderRadius={isEmbedded ? 16 : 20}
borderWidth={1}
borderColor={theme.border}
padding="$3.5"
shadowColor={theme.shadow}
shadowOpacity={0.16}
shadowRadius={16}
shadowOffset={{ width: 0, height: 10 }}
shadowOpacity={isEmbedded ? 0 : 0.16}
shadowRadius={isEmbedded ? 0 : 16}
shadowOffset={isEmbedded ? { width: 0, height: 0 } : { width: 0, height: 10 }}
style={style}
{...rest}
>
@@ -60,26 +66,33 @@ function SectionHeader({
title,
subtitle,
action,
showSeparator = true,
compact = false,
}: {
title: string;
subtitle?: string;
action?: React.ReactNode;
showSeparator?: boolean;
compact?: boolean;
}) {
const theme = useAdminTheme();
const titleSize = compact ? '$md' : '$lg';
const subtitleSize = compact ? '$xs' : '$sm';
const spacing = compact ? '$1' : '$1.5';
return (
<YStack space="$1.5">
<YStack space={spacing}>
<XStack alignItems="center" justifyContent="space-between">
<Text fontSize="$lg" fontWeight="800" color={theme.textStrong}>
<Text fontSize={titleSize} fontWeight="800" color={theme.textStrong}>
{title}
</Text>
{action ?? null}
</XStack>
{subtitle ? (
<Text fontSize="$sm" color={theme.muted}>
<Text fontSize={subtitleSize} color={theme.muted}>
{subtitle}
</Text>
) : null}
<Separator backgroundColor={theme.border} opacity={0.6} />
{showSeparator ? <Separator backgroundColor={theme.border} opacity={0.6} /> : null}
</YStack>
);
}
@@ -104,6 +117,7 @@ export default function MobileDashboardPage() {
const { t, i18n } = useTranslation(['management', 'dashboard', 'mobile']);
const { events, activeEvent, hasEvents, isLoading, selectEvent } = useEventContext();
const { user } = useAuth();
const theme = useAdminTheme();
const isMember = user?.role === 'member';
// --- LOGIC ---
@@ -184,30 +198,40 @@ export default function MobileDashboardPage() {
return (
<MobileShell activeTab="home" title={t('mobileDashboard.title', 'Dashboard')}>
<SectionHeader
title={t('dashboard:overview.title', 'At a glance')}
subtitle={t('dashboard:overview.description', 'Key customer metrics at a glance.')}
/>
{/* 1. LIFECYCLE HERO */}
<LifecycleHero
event={activeEvent}
stats={stats}
locale={locale}
navigate={navigate}
readiness={readiness}
/>
<DashboardCard padding="$0">
<YStack padding="$3.5" space="$2">
<SectionHeader
title={t('dashboard:overview.title', 'At a glance')}
subtitle={t('dashboard:overview.description', 'Key customer metrics at a glance.')}
showSeparator={false}
compact
/>
</YStack>
<Separator backgroundColor={theme.border} opacity={0.6} />
<YStack padding="$3.5" space="$2.5">
{/* 1. LIFECYCLE HERO */}
<LifecycleHero
event={activeEvent}
stats={stats}
locale={locale}
navigate={navigate}
readiness={readiness}
variant="embedded"
/>
{/* 1b. SETUP CHECKLIST */}
{phase === 'setup' && (
<SetupChecklist
steps={readiness.steps}
title={t('management:photobooth.checklist.title', 'Checklist')}
/>
)}
{/* 1b. SETUP CHECKLIST */}
{phase === 'setup' && (
<SetupChecklist
steps={readiness.steps}
title={t('management:photobooth.checklist.title', 'Checklist')}
variant="embedded"
/>
)}
{/* 2. PULSE STRIP */}
<PulseStrip event={activeEvent} stats={stats} />
{/* 2. PULSE STRIP */}
<PulseStrip event={activeEvent} stats={stats} />
</YStack>
</DashboardCard>
{/* 3. ALERTS */}
<AlertsSection event={activeEvent} stats={stats} t={t} />
@@ -250,9 +274,12 @@ function getEventPhase(event: TenantEvent): EventPhase {
return 'setup';
}
function LifecycleHero({ event, stats, locale, navigate, readiness }: any) {
function LifecycleHero({ event, stats, locale, navigate, readiness, variant = 'default' }: any) {
const theme = useAdminTheme();
const { t } = useTranslation(['management', 'dashboard']);
const isEmbedded = variant === 'embedded';
const cardVariant = isEmbedded ? 'embedded' : 'default';
const cardPadding = isEmbedded ? '$3' : '$3.5';
if (!event) return null;
const phase = getEventPhase(event);
@@ -275,11 +302,13 @@ function LifecycleHero({ event, stats, locale, navigate, readiness }: any) {
<YStack space="$2">
<Header />
<DashboardCard
variant={cardVariant}
padding={cardPadding}
backgroundColor={theme.primary}
borderColor="transparent"
style={{ backgroundImage: 'linear-gradient(135deg, #4F46E5 0%, #4338CA 100%)' }}
>
<YStack space="$3">
<YStack space={isEmbedded ? '$2.5' : '$3'}>
<XStack alignItems="center" justifyContent="space-between">
<YStack space="$1">
<XStack alignItems="center" space="$2">
@@ -321,8 +350,8 @@ function LifecycleHero({ event, stats, locale, navigate, readiness }: any) {
return (
<YStack space="$2">
<Header />
<DashboardCard>
<YStack space="$3">
<DashboardCard variant={cardVariant} padding={cardPadding}>
<YStack space={isEmbedded ? '$2.5' : '$3'}>
<XStack alignItems="center" space="$2.5">
<YStack width={40} height={40} borderRadius={20} backgroundColor={theme.success} alignItems="center" justifyContent="center">
<CheckCircle2 size={20} color="white" />
@@ -379,8 +408,8 @@ function LifecycleHero({ event, stats, locale, navigate, readiness }: any) {
return (
<YStack space="$2">
<Header />
<DashboardCard>
<YStack space="$3">
<DashboardCard variant={cardVariant} padding={cardPadding}>
<YStack space={isEmbedded ? '$2.5' : '$3'}>
<XStack alignItems="center" justifyContent="space-between">
<YStack>
<Text fontSize="$xs" color={theme.muted} fontWeight="700" textTransform="uppercase">
@@ -437,30 +466,27 @@ function PulseStrip({ event, stats }: any) {
const pendingCount = stats?.pending_photos ?? event?.pending_photo_count ?? 0;
return (
<YStack space="$2">
<SectionHeader title={t('management:eventMenu.summary', 'Overview')} />
<KpiStrip items={[
{
icon: ImageIcon,
value: uploadCount,
label: t('management:events.list.stats.photos', 'Photos'),
color: theme.primary
},
{
icon: Users,
value: guestCount,
label: t('management:events.list.stats.guests', 'Guests'),
color: theme.textStrong
},
{
icon: ShieldCheck,
value: pendingCount,
label: t('management:photos.filters.pending', 'Pending'),
note: pendingCount > 0 ? t('management:common.actionNeeded', 'Review') : undefined,
color: pendingCount > 0 ? '#8B5CF6' : theme.textMuted
}
]} />
</YStack>
<KpiStrip items={[
{
icon: ImageIcon,
value: uploadCount,
label: t('management:events.list.stats.photos', 'Photos'),
color: theme.primary,
},
{
icon: Users,
value: guestCount,
label: t('management:events.list.stats.guests', 'Guests'),
color: theme.textStrong,
},
{
icon: ShieldCheck,
value: pendingCount,
label: t('management:photos.filters.pending', 'Pending'),
note: pendingCount > 0 ? t('management:common.actionNeeded', 'Review') : undefined,
color: pendingCount > 0 ? '#8B5CF6' : theme.textMuted,
},
]} />
);
}