From 8aa2efdd9a56700241bc355cc33a07b7fa31ead0 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Thu, 22 Jan 2026 16:24:48 +0100 Subject: [PATCH] Refine dashboard overview layout --- resources/js/admin/mobile/DashboardPage.tsx | 148 ++++++++++-------- .../mobile/__tests__/DashboardPage.test.tsx | 2 + .../mobile/components/SetupChecklist.tsx | 121 +++++++------- 3 files changed, 155 insertions(+), 116 deletions(-) diff --git a/resources/js/admin/mobile/DashboardPage.tsx b/resources/js/admin/mobile/DashboardPage.tsx index 79f31bf..73a3129 100644 --- a/resources/js/admin/mobile/DashboardPage.tsx +++ b/resources/js/admin/mobile/DashboardPage.tsx @@ -35,19 +35,25 @@ function translateLimits(t: any) { // --- TAMAGUI-ALIGNED PRIMITIVES --- -function DashboardCard({ children, style, ...rest }: React.ComponentProps) { +function DashboardCard({ + children, + style, + variant = 'default', + ...rest +}: React.ComponentProps & { variant?: 'default' | 'embedded' }) { const theme = useAdminTheme(); + const isEmbedded = variant === 'embedded'; return ( @@ -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 ( - + - + {title} {action ?? null} {subtitle ? ( - + {subtitle} ) : null} - + {showSeparator ? : null} ); } @@ -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 ( - - - {/* 1. LIFECYCLE HERO */} - + + + + + + + {/* 1. LIFECYCLE HERO */} + - {/* 1b. SETUP CHECKLIST */} - {phase === 'setup' && ( - - )} + {/* 1b. SETUP CHECKLIST */} + {phase === 'setup' && ( + + )} - {/* 2. PULSE STRIP */} - + {/* 2. PULSE STRIP */} + + + {/* 3. ALERTS */} @@ -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) {
- + @@ -321,8 +350,8 @@ function LifecycleHero({ event, stats, locale, navigate, readiness }: any) { return (
- - + + @@ -379,8 +408,8 @@ function LifecycleHero({ event, stats, locale, navigate, readiness }: any) { return (
- - + + @@ -437,30 +466,27 @@ function PulseStrip({ event, stats }: any) { const pendingCount = stats?.pending_photos ?? event?.pending_photo_count ?? 0; return ( - - - 0 ? t('management:common.actionNeeded', 'Review') : undefined, - color: pendingCount > 0 ? '#8B5CF6' : theme.textMuted - } - ]} /> - + 0 ? t('management:common.actionNeeded', 'Review') : undefined, + color: pendingCount > 0 ? '#8B5CF6' : theme.textMuted, + }, + ]} /> ); } diff --git a/resources/js/admin/mobile/__tests__/DashboardPage.test.tsx b/resources/js/admin/mobile/__tests__/DashboardPage.test.tsx index b4ea8e1..21dfdb8 100644 --- a/resources/js/admin/mobile/__tests__/DashboardPage.test.tsx +++ b/resources/js/admin/mobile/__tests__/DashboardPage.test.tsx @@ -259,6 +259,8 @@ vi.mock('../theme', () => ({ useAdminTheme: () => ({ textStrong: '#0f172a', text: '#0f172a', + textMuted: '#94a3b8', + accent: '#7c3aed', muted: '#64748b', border: '#e2e8f0', surface: '#ffffff', diff --git a/resources/js/admin/mobile/components/SetupChecklist.tsx b/resources/js/admin/mobile/components/SetupChecklist.tsx index a3c0557..9401db8 100644 --- a/resources/js/admin/mobile/components/SetupChecklist.tsx +++ b/resources/js/admin/mobile/components/SetupChecklist.tsx @@ -1,33 +1,50 @@ import React from 'react'; import { useNavigate } from 'react-router-dom'; +import { Card } from '@tamagui/card'; +import { YGroup } from '@tamagui/group'; +import { ListItem } from '@tamagui/list-item'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { CheckCircle2, Circle, ChevronDown, ChevronUp } from 'lucide-react'; +import { Separator } from 'tamagui'; import { useAdminTheme } from '../theme'; import { ReadinessStep } from '../hooks/useEventReadiness'; import { adminPath } from '../../constants'; +import { PillBadge } from './Primitives'; -export function SetupChecklist({ steps, title }: { steps: ReadinessStep[]; title: string }) { +export function SetupChecklist({ + steps, + title, + variant = 'card', +}: { + steps: ReadinessStep[]; + title: string; + variant?: 'card' | 'embedded'; +}) { const theme = useAdminTheme(); const navigate = useNavigate(); const isAllComplete = steps.every(s => s.isComplete); const [collapsed, setCollapsed] = React.useState(isAllComplete); + const isEmbedded = variant === 'embedded'; const completedCount = steps.filter(s => s.isComplete).length; return ( - setCollapsed(!collapsed)}> - + {title} @@ -48,60 +65,54 @@ export function SetupChecklist({ steps, title }: { steps: ReadinessStep[]; title {!collapsed && ( - {steps.map((step, index) => { - const isNext = !step.isComplete && steps.slice(0, index).every(s => s.isComplete); - - return ( - navigate(adminPath(step.targetPath))} - style={{ - backgroundColor: isNext ? theme.surface : 'transparent', - }} - > - - {step.isComplete ? ( - - ) : isNext ? ( - - ) : ( - - )} + + + {steps.map((step, index) => { + const isNext = !step.isComplete && steps.slice(0, index).every(s => s.isComplete); - - + navigate(adminPath(step.targetPath))} + title={ + + {step.isComplete ? ( + + ) : isNext ? ( + + ) : ( + + )} + + > {step.label} - - {step.description && !step.isComplete && ( - - {step.description} - - )} - - - {isNext && ( - - Start - - )} - - - ); - })} + + + } + subTitle={ + step.description && !step.isComplete ? ( + + {step.description} + + ) : undefined + } + iconAfter={isNext ? Start : undefined} + /> + + ); + })} + )} - + ); -} \ No newline at end of file +}