feat(mobile): implement event switcher sheet in header
- Replaced direct navigation with a bottom sheet for event switching - Created reusable EventSwitcherSheet component - Preserves context when switching events
This commit is contained in:
86
resources/js/admin/mobile/components/EventSwitcherSheet.tsx
Normal file
86
resources/js/admin/mobile/components/EventSwitcherSheet.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { YStack, XStack } from '@tamagui/stacks';
|
||||||
|
import { SizableText as Text } from '@tamagui/text';
|
||||||
|
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||||
|
import { CheckCircle2, Circle } from 'lucide-react';
|
||||||
|
import { MobileSheet } from './Sheet';
|
||||||
|
import { useAdminTheme } from '../theme';
|
||||||
|
import { TenantEvent } from '../../api';
|
||||||
|
import { resolveEventDisplayName, formatEventDate } from '../../lib/events';
|
||||||
|
import { adminPath } from '../../constants';
|
||||||
|
|
||||||
|
export function EventSwitcherSheet({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
events,
|
||||||
|
activeSlug,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
events: TenantEvent[];
|
||||||
|
activeSlug: string | null;
|
||||||
|
}) {
|
||||||
|
const { t, i18n } = useTranslation(['management', 'mobile']);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const theme = useAdminTheme();
|
||||||
|
|
||||||
|
const locale = i18n.language;
|
||||||
|
|
||||||
|
const handleSelect = (slug: string) => {
|
||||||
|
onClose();
|
||||||
|
// Navigate to the dashboard of the selected event
|
||||||
|
navigate(adminPath(`/mobile/events/${slug}`));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MobileSheet
|
||||||
|
open={open}
|
||||||
|
title={t('mobile:header.eventSwitcher', 'Switch Event')}
|
||||||
|
onClose={onClose}
|
||||||
|
snapPoints={[65]}
|
||||||
|
>
|
||||||
|
<YStack space="$2">
|
||||||
|
{events.map((event) => {
|
||||||
|
const isActive = event.slug === activeSlug;
|
||||||
|
return (
|
||||||
|
<Pressable key={event.slug} onPress={() => handleSelect(event.slug)}>
|
||||||
|
<XStack
|
||||||
|
alignItems="center"
|
||||||
|
space="$3"
|
||||||
|
padding="$3"
|
||||||
|
borderRadius={14}
|
||||||
|
backgroundColor={isActive ? theme.surfaceMuted : 'transparent'}
|
||||||
|
borderWidth={1}
|
||||||
|
borderColor={isActive ? theme.border : 'transparent'}
|
||||||
|
>
|
||||||
|
<YStack flex={1}>
|
||||||
|
<Text fontSize="$sm" fontWeight="700" color={theme.textStrong}>
|
||||||
|
{resolveEventDisplayName(event)}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="$xs" color={theme.muted}>
|
||||||
|
{formatEventDate(event.event_date, locale)}
|
||||||
|
</Text>
|
||||||
|
</YStack>
|
||||||
|
{isActive ? (
|
||||||
|
<CheckCircle2 size={20} color={theme.primary} />
|
||||||
|
) : (
|
||||||
|
<Circle size={20} color={theme.border} />
|
||||||
|
)}
|
||||||
|
</XStack>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Pressable onPress={() => { onClose(); navigate(adminPath('/mobile/events/new')); }}>
|
||||||
|
<XStack padding="$3" justifyContent="center">
|
||||||
|
<Text fontSize="$sm" fontWeight="700" color={theme.primary}>
|
||||||
|
{t('mobile:header.createEvent', 'Create Event')}
|
||||||
|
</Text>
|
||||||
|
</XStack>
|
||||||
|
</Pressable>
|
||||||
|
</YStack>
|
||||||
|
</MobileSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import { loadPhotoQueue } from '../lib/photoModerationQueue';
|
|||||||
import { countQueuedPhotoActions } from '../lib/queueStatus';
|
import { countQueuedPhotoActions } from '../lib/queueStatus';
|
||||||
import { useAdminTheme } from '../theme';
|
import { useAdminTheme } from '../theme';
|
||||||
import { useAuth } from '../../auth/context';
|
import { useAuth } from '../../auth/context';
|
||||||
|
import { EventSwitcherSheet } from './EventSwitcherSheet';
|
||||||
|
|
||||||
type MobileShellProps = {
|
type MobileShellProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -55,6 +56,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
|
|||||||
const [loadingEvents, setLoadingEvents] = React.useState(false);
|
const [loadingEvents, setLoadingEvents] = React.useState(false);
|
||||||
const [attemptedFetch, setAttemptedFetch] = React.useState(false);
|
const [attemptedFetch, setAttemptedFetch] = React.useState(false);
|
||||||
const [queuedPhotoCount, setQueuedPhotoCount] = React.useState(0);
|
const [queuedPhotoCount, setQueuedPhotoCount] = React.useState(0);
|
||||||
|
const [switcherOpen, setSwitcherOpen] = React.useState(false);
|
||||||
|
|
||||||
const effectiveEvents = events.length ? events : fallbackEvents;
|
const effectiveEvents = events.length ? events : fallbackEvents;
|
||||||
const effectiveActive = activeEvent ?? (effectiveEvents.length === 1 ? effectiveEvents[0] : null);
|
const effectiveActive = activeEvent ?? (effectiveEvents.length === 1 ? effectiveEvents[0] : null);
|
||||||
@@ -136,7 +138,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable onPress={() => navigate(ADMIN_EVENTS_PATH)}>
|
<Pressable onPress={() => setSwitcherOpen(true)}>
|
||||||
<XStack
|
<XStack
|
||||||
backgroundColor="rgba(255, 255, 255, 0.12)"
|
backgroundColor="rgba(255, 255, 255, 0.12)"
|
||||||
paddingHorizontal="$3"
|
paddingHorizontal="$3"
|
||||||
@@ -334,6 +336,13 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
|
|||||||
</YStack>
|
</YStack>
|
||||||
|
|
||||||
<BottomNav active={activeTab} onNavigate={go} />
|
<BottomNav active={activeTab} onNavigate={go} />
|
||||||
|
|
||||||
|
<EventSwitcherSheet
|
||||||
|
open={switcherOpen}
|
||||||
|
onClose={() => setSwitcherOpen(false)}
|
||||||
|
events={effectiveEvents}
|
||||||
|
activeSlug={effectiveActive?.slug ?? null}
|
||||||
|
/>
|
||||||
</YStack>
|
</YStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -364,4 +373,4 @@ export function HeaderActionButton({
|
|||||||
{children}
|
{children}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user