import React from 'react'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { Activity, Bell, CalendarDays, Camera, CheckCircle2, ChevronDown, Download, Image as ImageIcon, Layout, ListTodo, MapPin, Megaphone, MessageCircle, Pencil, QrCode, Settings, ShieldCheck, Smartphone, Sparkles, TrendingUp, Tv, Users, ArrowRight, Play, Clock, AlertCircle } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { Image } from '@tamagui/image'; import { isSameDay, isPast, isFuture, parseISO, differenceInDays, startOfDay } from 'date-fns'; import { MobileShell } from './components/MobileShell'; import { adminPath } from '../constants'; import { useEventContext } from '../context/EventContext'; import { getEventStats, EventStats, TenantEvent, getEventPhotos, TenantPhoto } from '../api'; import { formatEventDate, resolveEventDisplayName } from '../lib/events'; import { useAuth } from '../auth/context'; import { useAdminTheme } from './theme'; import { buildLimitWarnings } from '../lib/limitWarnings'; import { withAlpha } from './components/colors'; import { useEventReadiness } from './hooks/useEventReadiness'; // --- MODERN PRIMITIVES --- function ModernCard({ children, style, ...rest }: any) { const theme = useAdminTheme(); return ( {children} ); } function ModernButton({ label, onPress, tone = 'primary', fullWidth = true, icon, style }: any) { const theme = useAdminTheme(); const isPrimary = tone === 'primary'; const isGhost = tone === 'ghost'; const isAccent = tone === 'accent'; const bg = isPrimary ? theme.primary : isAccent ? theme.accent : isGhost ? 'transparent' : theme.surface; const text = isPrimary || isAccent ? 'white' : isGhost ? theme.text : theme.textStrong; const border = isPrimary || isAccent || isGhost ? 'transparent' : theme.border; return ( {icon} {label} ); } function StatusBadge({ status }: { status: string }) { const theme = useAdminTheme(); const { t } = useTranslation('management'); const config = { published: { bg: '#DCFCE7', text: '#166534', label: t('events.status.published', 'Live') }, draft: { bg: '#FEF3C7', text: '#92400E', label: t('events.status.draft', 'Draft') }, archived: { bg: '#F1F5F9', text: '#475569', label: t('events.status.archived', 'Archived') }, }[status] || { bg: theme.surfaceMuted, text: theme.muted, label: status }; return ( {config.label} ); } // --- MAIN PAGE COMPONENT --- export default function MobileDashboardPage() { const navigate = useNavigate(); const { slug: slugParam } = useParams<{ slug?: string }>(); const { t, i18n } = useTranslation('management'); const { events, activeEvent, hasEvents, hasMultipleEvents, isLoading, selectEvent } = useEventContext(); const { user } = useAuth(); const isMember = user?.role === 'member'; // --- LOGIC --- const memberPermissions = React.useMemo(() => { if (!isMember) return ['*']; return Array.isArray(activeEvent?.member_permissions) ? activeEvent?.member_permissions ?? [] : []; }, [activeEvent?.member_permissions, isMember]); function allowPermission(permissions: string[], permission: string): boolean { if (permissions.includes('*') || permissions.includes(permission)) return true; if (permission.includes(':')) { const [prefix] = permission.split(':'); return permissions.includes(`${prefix}:*`); } return false; } const canManageEvents = React.useMemo(() => allowPermission(memberPermissions, 'events:manage'), [memberPermissions]); const { data: stats } = useQuery({ queryKey: ['mobile', 'dashboard', 'stats', activeEvent?.slug], enabled: Boolean(activeEvent?.slug), queryFn: async () => { if (!activeEvent?.slug) return null; return await getEventStats(activeEvent.slug); }, }); const { data: photoData } = useQuery({ queryKey: ['mobile', 'dashboard', 'recent-photos', activeEvent?.slug], enabled: Boolean(activeEvent?.slug), queryFn: async () => { if (!activeEvent?.slug) return { photos: [] }; return await getEventPhotos(activeEvent.slug, { perPage: 6, sort: 'desc' }); }, }); const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'; React.useEffect(() => { if (!slugParam || slugParam === activeEvent?.slug) return; selectEvent(slugParam); }, [activeEvent?.slug, selectEvent, slugParam]); const [eventSwitcherOpen, setEventSwitcherOpen] = React.useState(false); // --- RENDER --- if (isLoading) { return ( ); } if (!hasEvents && !events.length) { return ( navigate(adminPath('/mobile/events/new'))} /> ); } return ( {/* 1. LIFECYCLE HERO */} setEventSwitcherOpen(true)} canSwitch={hasMultipleEvents} /> {/* 2. PULSE STRIP */} {/* 3. ALERTS */} {/* 4. UNIFIED COMMAND GRID */} {/* 5. RECENT PHOTOS */} ); } // --- SUB COMPONENTS --- type EventPhase = 'setup' | 'live' | 'post'; function getEventPhase(event: TenantEvent): EventPhase { if (!event.event_date) return 'setup'; const today = startOfDay(new Date()); const eventDate = parseISO(event.event_date); if (isSameDay(today, eventDate)) return 'live'; if (isPast(eventDate)) return 'post'; return 'setup'; } function LifecycleHero({ event, stats, locale, navigate, onSwitch, canSwitch }: any) { const theme = useAdminTheme(); const { completedSteps, totalSteps, nextStep } = useEventReadiness(event); if (!event) return null; const phase = getEventPhase(event); const pendingPhotos = stats?.pending_photos ?? event.pending_photo_count ?? 0; // Header Row const Header = () => ( {formatEventDate(event.event_date, locale)} ); if (phase === 'live') { return (
Happening Now {pendingPhotos > 0 ? `${pendingPhotos} Pending Photos` : 'Event is Running'} {pendingPhotos > 0 && ( navigate(adminPath(`/mobile/events/${event.slug}/control-room`))}> Review )} ); } const daysToGo = event.event_date ? differenceInDays(parseISO(event.event_date), new Date()) : 0; if (phase === 'post') { return (
Event Completed Gallery is online for guests. } onPress={() => navigate(adminPath(`/mobile/exports`))} /> ); } // SETUP const ctaLabel = nextStep ? nextStep.ctaLabel : 'Setup Complete'; const ctaAction = nextStep ? () => navigate(adminPath(nextStep.targetPath)) : undefined; return (
Countdown {daysToGo} days Setup Status {completedSteps}/{totalSteps} Steps : } onPress={ctaAction} disabled={!nextStep} /> ); } function PulseStrip({ event, stats }: any) { const theme = useAdminTheme(); const uploadCount = stats?.uploads_total ?? event?.photo_count ?? 0; const guestCount = event?.active_invites_count ?? event?.total_invites_count ?? 0; const pendingCount = stats?.pending_photos ?? event?.pending_photo_count ?? 0; return ( 0 ? 'warning' : 'neutral'} /> ); } function PulseItem({ icon: Icon, value, label, tone = 'neutral' }: any) { const theme = useAdminTheme(); const color = tone === 'warning' ? theme.warningText : theme.textStrong; return ( {value} {label} ); } function UnifiedToolGrid({ event, navigate, permissions, isMember }: any) { const theme = useAdminTheme(); const slug = event?.slug; if (!slug) return null; const sections = [ { title: 'Experience', items: [ { label: 'Photos', icon: ImageIcon, path: `/mobile/events/${slug}/control-room`, color: theme.primary }, { label: 'Slide Show', icon: Tv, path: `/mobile/events/${slug}/live-show/settings`, color: '#F59E0B' }, // Amber { label: 'Tasks', icon: ListTodo, path: `/mobile/events/${slug}/tasks`, color: theme.accent }, { label: 'Photobooth', icon: Camera, path: `/mobile/events/${slug}/photobooth`, color: '#8B5CF6' }, // Violet ] }, { title: 'Operations', items: [ { label: 'QR Codes', icon: QrCode, path: `/mobile/events/${slug}/qr`, color: '#10B981' }, // Emerald { label: 'Guests', icon: Users, path: `/mobile/events/${slug}/members`, color: theme.text }, { label: 'Messages', icon: Megaphone, path: `/mobile/events/${slug}/guest-notifications`, color: theme.text }, { label: 'Branding', icon: Layout, path: `/mobile/events/${slug}/branding`, color: theme.text }, ] }, { title: 'Admin', items: [ { label: 'Analytics', icon: TrendingUp, path: `/mobile/events/${slug}/analytics` }, { label: 'Exports', icon: Download, path: `/mobile/exports` }, { label: 'Settings', icon: Settings, path: `/mobile/events/${slug}/edit` }, ] } ]; return ( {sections.map((section) => ( {section.title} {section.items.map((item) => ( navigate(adminPath(item.path))} style={{ width: '48%', flexGrow: 1 }} > {item.label} ))} ))} ); } function RecentPhotosSection({ photos, navigate, slug }: { photos: TenantPhoto[], navigate: any, slug: string }) { const theme = useAdminTheme(); if (!photos.length) return null; return ( Latest Uploads navigate(adminPath(`/mobile/events/${slug}/control-room`))}> See all {photos.map((photo) => ( navigate(adminPath(`/mobile/events/${slug}/control-room`))}> {photo.thumbnail_url ? ( ) : ( )} ))} ); } function AlertsSection({ event, stats }: any) { const theme = useAdminTheme(); const limitWarnings = buildLimitWarnings(event?.limits ?? null, (k: string) => k); if (!limitWarnings.length) return null; return ( {limitWarnings.map((w: any, idx: number) => ( {w.message} ))} ); } function EmptyState({ canManage, onCreate }: any) { const theme = useAdminTheme(); return ( Welcome to Fotospiel Create your first event to get started. {canManage && } ); }