This commit is contained in:
Timo Knuth
2026-02-27 15:19:24 +01:00
parent b7f8221095
commit 253c3c1c6d
134 changed files with 11188 additions and 1871 deletions

View File

@@ -1,5 +1,5 @@
import {
View, Text, ScrollView, TouchableOpacity, Linking, ActivityIndicator, Alert, StyleSheet, Platform,
View, Text, ScrollView, TouchableOpacity, Linking, ActivityIndicator, Alert, Share, StyleSheet, Platform,
} from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useLocalSearchParams, useRouter, Stack } from 'expo-router'
@@ -10,6 +10,7 @@ import { Badge } from '@/components/ui/Badge'
import { TERMIN_TYP_LABELS } from '@innungsapp/shared/types'
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 }>()
@@ -29,6 +30,63 @@ export default function TerminDetailScreen() {
const datum = new Date(termin.datum)
const isPast = datum < new Date()
const handleAddToCalendar = async () => {
try {
const { status } = await Calendar.requestCalendarPermissionsAsync()
if (status !== 'granted') {
Alert.alert('Fehler', 'Kalender-Berechtigung wurde verweigert.')
return
}
const startDate = new Date(termin.datum)
let endDate = new Date(termin.datum)
if (termin.uhrzeit) {
const [hours, minutes] = termin.uhrzeit.split(':').map(Number)
startDate.setHours(hours || 0, minutes || 0)
if (termin.endeUhrzeit) {
const [endHours, endMinutes] = termin.endeUhrzeit.split(':').map(Number)
endDate.setHours(endHours || 0, endMinutes || 0)
} else {
endDate.setHours((hours || 0) + 1, minutes || 0)
}
} else {
endDate.setDate(startDate.getDate() + 1)
}
let calendarId
if (Platform.OS === 'ios') {
const defaultCalendar = await Calendar.getDefaultCalendarAsync()
calendarId = defaultCalendar.id
} else {
const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT)
// Try to prefer primary or typical default calendar
const primary = calendars.find(c => c.isPrimary) || calendars.find(c => c.accessLevel === 'owner') || calendars[0]
calendarId = primary?.id
}
if (!calendarId) {
Alert.alert('Fehler', 'Kein beschreibbarer Kalender gefunden.')
return
}
await Calendar.createEventAsync(calendarId, {
title: termin.titel,
startDate,
endDate,
allDay: !termin.uhrzeit,
location: [termin.ort, termin.adresse].filter(Boolean).join(', '),
notes: termin.beschreibung || undefined,
})
Alert.alert('Erfolg', 'Der Termin wurde in den Kalender eingetragen.')
} catch (e) {
console.error(e)
Alert.alert('Fehler', 'Der Termin konnte nicht eingetragen werden.')
}
}
return (
<>
<Stack.Screen options={{ headerShown: false }} />
@@ -39,7 +97,21 @@ export default function TerminDetailScreen() {
<Ionicons name="arrow-back" size={24} color="#0F172A" />
</TouchableOpacity>
<View style={styles.headerSpacer} />
<TouchableOpacity style={styles.shareButton}>
<TouchableOpacity
style={styles.shareButton}
onPress={() => {
const lines: string[] = []
lines.push(`📅 ${termin.titel}`)
lines.push(format(datum, 'EEEE, d. MMMM yyyy', { locale: de }))
if (termin.uhrzeit) {
lines.push(`🕐 ${termin.uhrzeit}${termin.endeUhrzeit ? ` ${termin.endeUhrzeit}` : ''} Uhr`)
}
if (termin.ort) lines.push(`📍 ${termin.ort}`)
if (termin.adresse) lines.push(` ${termin.adresse}`)
if (termin.beschreibung) lines.push(`\n${termin.beschreibung}`)
Share.share({ message: lines.join('\n') })
}}
>
<Ionicons name="share-outline" size={24} color="#0F172A" />
</TouchableOpacity>
</View>
@@ -156,11 +228,13 @@ export default function TerminDetailScreen() {
isAngemeldet={termin.isAngemeldet}
onToggle={() => mutate({ terminId: id })}
isLoading={isPending}
maxTeilnehmer={termin.maxTeilnehmer}
teilnehmerAnzahl={termin.teilnehmerAnzahl}
/>
</View>
<TouchableOpacity
style={styles.calendarButton}
onPress={() => Alert.alert('Kalender', 'Funktion folgt in Kürze')}
onPress={handleAddToCalendar}
>
<Ionicons name="calendar-outline" size={24} color="#0F172A" />
</TouchableOpacity>

View File

@@ -52,8 +52,11 @@ export default function TermineScreen() {
data={data ?? []}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.list}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
refreshControl={
<RefreshControl refreshing={isRefetching} onRefresh={refetch} tintColor="#003B7E" />
<RefreshControl refreshing={isRefetching} onRefresh={refetch} tintColor="#003B7E" progressViewOffset={50} />
}
renderItem={({ item }) => (
<TerminCard