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 { useAdminTheme } from '../theme';
|
||||
import { useAuth } from '../../auth/context';
|
||||
import { EventSwitcherSheet } from './EventSwitcherSheet';
|
||||
|
||||
type MobileShellProps = {
|
||||
title?: string;
|
||||
@@ -55,6 +56,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
|
||||
const [loadingEvents, setLoadingEvents] = React.useState(false);
|
||||
const [attemptedFetch, setAttemptedFetch] = React.useState(false);
|
||||
const [queuedPhotoCount, setQueuedPhotoCount] = React.useState(0);
|
||||
const [switcherOpen, setSwitcherOpen] = React.useState(false);
|
||||
|
||||
const effectiveEvents = events.length ? events : fallbackEvents;
|
||||
const effectiveActive = activeEvent ?? (effectiveEvents.length === 1 ? effectiveEvents[0] : null);
|
||||
@@ -136,7 +138,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable onPress={() => navigate(ADMIN_EVENTS_PATH)}>
|
||||
<Pressable onPress={() => setSwitcherOpen(true)}>
|
||||
<XStack
|
||||
backgroundColor="rgba(255, 255, 255, 0.12)"
|
||||
paddingHorizontal="$3"
|
||||
@@ -334,6 +336,13 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
|
||||
</YStack>
|
||||
|
||||
<BottomNav active={activeTab} onNavigate={go} />
|
||||
|
||||
<EventSwitcherSheet
|
||||
open={switcherOpen}
|
||||
onClose={() => setSwitcherOpen(false)}
|
||||
events={effectiveEvents}
|
||||
activeSlug={effectiveActive?.slug ?? null}
|
||||
/>
|
||||
</YStack>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user