import React from 'react'; import { useNavigate } from 'react-router-dom'; import { CalendarDays, MapPin, Plus, Search } 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 { useTranslation } from 'react-i18next'; import { MobileShell } from './components/MobileShell'; import { MobileCard, PillBadge, CTAButton } from './components/Primitives'; import { getEvents, TenantEvent } from '../api'; import { adminPath } from '../constants'; import { isAuthError } from '../auth/tokens'; import { getApiErrorMessage } from '../lib/apiError'; import { useTheme } from '@tamagui/core'; export default function MobileEventsPage() { const { t } = useTranslation('management'); const navigate = useNavigate(); const [events, setEvents] = React.useState([]); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [query, setQuery] = React.useState(''); const theme = useTheme(); const text = String(theme.color?.val ?? '#111827'); const muted = String(theme.gray?.val ?? '#4b5563'); const subtle = String(theme.gray8?.val ?? '#6b7280'); const border = String(theme.borderColor?.val ?? '#e5e7eb'); const primary = String(theme.primary?.val ?? '#007AFF'); const danger = String(theme.red10?.val ?? '#b91c1c'); const surface = String(theme.surface?.val ?? '#ffffff'); const baseInputStyle = React.useMemo( () => ({ width: '100%', height: 38, borderRadius: 10, border: `1px solid ${border}`, padding: '0 12px', fontSize: 13, background: surface, color: text, }), [border, surface, text], ); React.useEffect(() => { (async () => { try { setEvents(await getEvents()); } catch (err) { if (!isAuthError(err)) { setError(getApiErrorMessage(err, t('events.errors.loadFailed', 'Event konnte nicht geladen werden.'))); } } finally { setLoading(false); } })(); }, [t]); return ( navigate(-1)} headerActions={ } > {error ? ( {error} ) : null} navigate(adminPath('/mobile/events/new'))} /> setQuery(e.target.value)} placeholder={t('events.list.search', 'Search events')} style={{ ...baseInputStyle, marginBottom: 12 }} /> {loading ? ( {Array.from({ length: 3 }).map((_, idx) => ( ))} ) : events.length === 0 ? ( {t('events.list.empty.title', 'Noch kein Event angelegt')} {t('events.list.empty.description', 'Starte jetzt mit deinem ersten Event.')} navigate(adminPath('/events/new'))} /> ) : ( {events .filter((event) => { if (!query.trim()) return true; const hay = `${event.name ?? ''} ${event.location ?? ''}`.toLowerCase(); return hay.includes(query.toLowerCase()); }) .map((event) => ( navigate(adminPath(`/mobile/events/${slug}`))} onEdit={(slug) => navigate(adminPath(`/mobile/events/${slug}/edit`))} /> ))} )} ); } function EventRow({ event, text, muted, subtle, border, primary, onOpen, onEdit, }: { event: TenantEvent; text: string; muted: string; subtle: string; border: string; primary: string; onOpen: (slug: string) => void; onEdit: (slug: string) => void; }) { const status = resolveStatus(event); return ( {renderName(event.name)} {formatDate(event.event_date)} {resolveLocation(event)} {status.label} onEdit(event.slug)}> ˅ onOpen(event.slug)} style={{ marginTop: 8 }}> Open event ); } function resolveStatus(event: TenantEvent): { label: string; tone: 'success' | 'warning' | 'muted' } { if (event.status === 'published') { return { label: 'Upcoming', tone: 'success' }; } if (event.status === 'draft') { return { label: 'Draft', tone: 'warning' }; } return { label: 'Past', tone: 'muted' }; } function renderName(name: TenantEvent['name']): string { if (typeof name === 'string') return name; if (name && typeof name === 'object') { return name.de ?? name.en ?? Object.values(name)[0] ?? 'Unbenanntes Event'; } return 'Unbenanntes Event'; } function resolveLocation(event: TenantEvent): string { const settings = (event.settings ?? {}) as Record; const candidate = (settings.location as string | undefined) ?? (settings.address as string | undefined) ?? (settings.city as string | undefined); if (candidate && candidate.trim()) { return candidate; } return 'Location'; } function formatDate(iso: string | null): string { if (!iso) return 'Date tbd'; const date = new Date(iso); if (Number.isNaN(date.getTime())) { return 'Date tbd'; } return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); }