stadtwerke/innungsapp/apps/mobile/app/(app)/termine/[id].tsx

153 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
View,
Text,
ScrollView,
TouchableOpacity,
Linking,
ActivityIndicator,
Alert,
} from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useLocalSearchParams, useRouter } from 'expo-router'
import { trpc } from '@/lib/trpc'
import { AnmeldeButton } from '@/components/termine/AnmeldeButton'
import { Badge } from '@/components/ui/Badge'
import { TERMIN_TYP_LABELS } from '@innungsapp/shared'
import { format } from 'date-fns'
import { de } from 'date-fns/locale'
import * as Calendar from 'expo-calendar'
export default function TerminDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>()
const router = useRouter()
const { data: termin, isLoading } = trpc.termine.byId.useQuery({ id })
const toggleMutation = trpc.termine.toggleAnmeldung.useMutation({
onSuccess: () => {
// Invalidate queries
},
})
async function addToCalendar() {
if (!termin) return
const { status } = await Calendar.requestCalendarPermissionsAsync()
if (status !== 'granted') {
Alert.alert('Keine Berechtigung', 'Bitte erlauben Sie den Kalender-Zugriff in den Einstellungen.')
return
}
const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT)
const defaultCal = calendars.find((c) => c.isPrimary) ?? calendars[0]
if (!defaultCal) {
Alert.alert('Kein Kalender', 'Es wurde kein Kalender gefunden.')
return
}
const startDate = new Date(termin.datum)
if (termin.uhrzeit) {
const [h, m] = termin.uhrzeit.split(':').map(Number)
startDate.setHours(h, m)
}
await Calendar.createEventAsync(defaultCal.id, {
title: termin.titel,
startDate,
endDate: startDate,
location: termin.adresse ?? termin.ort ?? undefined,
notes: termin.beschreibung ?? undefined,
})
Alert.alert('Termin gespeichert', 'Der Termin wurde in Ihren Kalender eingetragen.')
}
if (isLoading) {
return (
<SafeAreaView className="flex-1 bg-white items-center justify-center">
<ActivityIndicator size="large" color="#E63946" />
</SafeAreaView>
)
}
if (!termin) return null
const datumFormatted = format(new Date(termin.datum), 'EEEE, dd. MMMM yyyy', { locale: de })
return (
<SafeAreaView className="flex-1 bg-gray-50" edges={['top']}>
<View className="flex-row items-center px-4 py-3 bg-white border-b border-gray-100">
<TouchableOpacity onPress={() => router.back()} className="mr-3">
<Text className="text-brand-500 text-base"> Zurück</Text>
</TouchableOpacity>
</View>
<ScrollView>
<View className="bg-white px-4 py-6 border-b border-gray-100">
<Badge label={TERMIN_TYP_LABELS[termin.typ]} typ={termin.typ} />
<Text className="text-2xl font-bold text-gray-900 mt-3">{termin.titel}</Text>
<Text className="text-gray-500 mt-2 capitalize">{datumFormatted}</Text>
{termin.uhrzeit && (
<Text className="text-gray-500">
{termin.uhrzeit}{termin.endeUhrzeit ? ` ${termin.endeUhrzeit}` : ''} Uhr
</Text>
)}
</View>
<View className="bg-white mx-4 mt-4 rounded-2xl overflow-hidden border border-gray-100">
{termin.ort && (
<TouchableOpacity
onPress={() =>
termin.adresse &&
Linking.openURL(
`https://maps.google.com/?q=${encodeURIComponent(termin.adresse)}`
)
}
className="flex-row items-center px-4 py-3 border-b border-gray-50"
>
<Text className="text-2xl mr-3">📍</Text>
<View>
<Text className="font-medium text-gray-900">{termin.ort}</Text>
{termin.adresse && (
<Text className="text-sm text-brand-500 mt-0.5">{termin.adresse}</Text>
)}
</View>
</TouchableOpacity>
)}
<View className="flex-row items-center px-4 py-3">
<Text className="text-2xl mr-3">👥</Text>
<Text className="text-gray-700">
{termin.teilnehmerAnzahl} Anmeldungen
{termin.maxTeilnehmer ? ` / ${termin.maxTeilnehmer} Plätze` : ''}
</Text>
</View>
</View>
{termin.beschreibung && (
<View className="bg-white mx-4 mt-4 rounded-2xl p-4 border border-gray-100">
<Text className="font-semibold text-gray-900 mb-2">Beschreibung</Text>
<Text className="text-gray-600 leading-6">{termin.beschreibung}</Text>
</View>
)}
{/* Actions */}
<View className="mx-4 mt-4 gap-3">
<AnmeldeButton
terminId={id}
isAngemeldet={termin.isAngemeldet}
onToggle={() => toggleMutation.mutate({ terminId: id })}
isLoading={toggleMutation.isPending}
/>
<TouchableOpacity
onPress={addToCalendar}
className="bg-white border border-gray-200 rounded-2xl py-4 flex-row items-center justify-center gap-2"
>
<Text className="text-gray-900 text-xl">📅</Text>
<Text className="text-gray-900 font-semibold">Zum Kalender hinzufügen</Text>
</TouchableOpacity>
</View>
<View className="h-8" />
</ScrollView>
</SafeAreaView>
)
}