weiterer fortschritt mit tamagui und dem neuen mobile event admin

This commit is contained in:
Codex Agent
2025-12-10 20:01:47 +01:00
parent 73e550ee87
commit 7b01a77083
26 changed files with 761 additions and 139 deletions

View File

@@ -10,13 +10,16 @@ import { MobileShell, renderEventLocation } from './components/MobileShell';
import { MobileCard, CTAButton, KpiTile, ActionTile, PillBadge } from './components/Primitives';
import { adminPath } from '../constants';
import { useEventContext } from '../context/EventContext';
import { getEventStats, EventStats, TenantEvent } from '../api';
import { getEventStats, EventStats, TenantEvent, getEvents } from '../api';
import { formatEventDate, resolveEventDisplayName } from '../lib/events';
export default function MobileDashboardPage() {
const navigate = useNavigate();
const { t, i18n } = useTranslation('management');
const { events, activeEvent, hasEvents, hasMultipleEvents, isLoading } = useEventContext();
const { events, activeEvent, hasEvents, hasMultipleEvents, isLoading, selectEvent } = useEventContext();
const [fallbackEvents, setFallbackEvents] = React.useState<TenantEvent[]>([]);
const [fallbackLoading, setFallbackLoading] = React.useState(false);
const [fallbackAttempted, setFallbackAttempted] = React.useState(false);
const { data: stats, isLoading: statsLoading } = useQuery<EventStats | null>({
queryKey: ['mobile', 'dashboard', 'stats', activeEvent?.slug],
@@ -28,8 +31,36 @@ export default function MobileDashboardPage() {
});
const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE';
const { data: dashboardEvents } = useQuery<TenantEvent[]>({
queryKey: ['mobile', 'dashboard', 'events'],
queryFn: () => getEvents({ force: true }),
staleTime: 60_000,
});
const effectiveEvents = events.length ? events : dashboardEvents?.length ? dashboardEvents : fallbackEvents;
const effectiveHasEvents = hasEvents || Boolean(dashboardEvents?.length) || fallbackEvents.length > 0;
const effectiveMultiple =
hasMultipleEvents || (dashboardEvents?.length ?? 0) > 1 || fallbackEvents.length > 1;
if (isLoading) {
React.useEffect(() => {
if (events.length || isLoading || fallbackLoading || fallbackAttempted) {
return;
}
setFallbackAttempted(true);
setFallbackLoading(true);
getEvents({ force: true })
.then((list: TenantEvent[]) => {
setFallbackEvents(list ?? []);
if (list?.length === 1 && !activeEvent) {
selectEvent(list[0]?.slug ?? null);
}
})
.catch(() => {
setFallbackEvents([]);
})
.finally(() => setFallbackLoading(false));
}, [events.length, isLoading, activeEvent, selectEvent, fallbackLoading, fallbackAttempted]);
if (isLoading || fallbackLoading) {
return (
<MobileShell activeTab="home" title={t('events.list.dashboardTitle', 'Dashboard')}>
<YStack space="$2">
@@ -41,7 +72,7 @@ export default function MobileDashboardPage() {
);
}
if (!hasEvents) {
if (!effectiveHasEvents) {
return (
<MobileShell activeTab="home" title={t('events.list.dashboardTitle', 'Dashboard')}>
<OnboardingEmptyState />
@@ -49,10 +80,14 @@ export default function MobileDashboardPage() {
);
}
if (hasMultipleEvents && !activeEvent) {
if (effectiveMultiple && !activeEvent) {
return (
<MobileShell activeTab="home" title={t('events.list.dashboardTitle', 'Dashboard')}>
<EventPickerList events={events} locale={locale} />
<MobileShell
activeTab="home"
title={t('events.list.dashboardTitle', 'Dashboard')}
subtitle={t('header.selectEvent', 'Select an event to continue')}
>
<EventPickerList events={effectiveEvents} locale={locale} />
</MobileShell>
);
}
@@ -123,16 +158,39 @@ function OnboardingEmptyState() {
function EventPickerList({ events, locale }: { events: TenantEvent[]; locale: string }) {
const { t } = useTranslation('management');
const { selectEvent } = useEventContext();
const navigate = useNavigate();
const [localEvents, setLocalEvents] = React.useState<TenantEvent[]>(events);
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
setLocalEvents(events);
}, [events]);
React.useEffect(() => {
if (events.length > 0 || loading) {
return;
}
setLoading(true);
getEvents({ force: true })
.then((list) => setLocalEvents(list ?? []))
.catch(() => setLocalEvents([]))
.finally(() => setLoading(false));
}, [events.length, loading]);
return (
<YStack space="$2">
<Text fontSize="$sm" color="#111827" fontWeight="700">
{t('events.detail.pickEvent', 'Select an event')}
</Text>
{events.map((event) => (
{localEvents.map((event) => (
<Pressable
key={event.slug}
onPress={() => selectEvent(event.slug ?? null)}
onPress={() => {
selectEvent(event.slug ?? null);
if (event.slug) {
navigate(adminPath(`/mobile/events/${event.slug}`));
}
}}
>
<MobileCard borderColor="#e5e7eb" space="$2">
<XStack alignItems="center" justifyContent="space-between">