import React, { useEffect, useRef } from 'react'
import { View, Text, ScrollView, TouchableOpacity, TextInput, StyleSheet, Platform, Image, Animated } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { Ionicons } from '@expo/vector-icons'
import { useRouter } from 'expo-router'
import { format } from 'date-fns'
import { de } from 'date-fns/locale'
import { NEWS_KATEGORIE_LABELS } from '@innungsapp/shared/types'
import { useNewsList } from '@/hooks/useNews'
import { useTermineListe } from '@/hooks/useTermine'
import { trpc } from '@/lib/trpc'
import { getOrgSlug } from '@/lib/org-config'
// Helper to truncate text
function getNewsExcerpt(value: string) {
const normalized = value
.replace(/[#*_`>-]/g, ' ')
.replace(/\s+/g, ' ')
.trim()
return normalized.length > 85 ? `${normalized.slice(0, 85)}...` : normalized
}
const AVATAR_PALETTES = [
{ bg: '#003B7E' }, { bg: '#1D4ED8' }, { bg: '#059669' },
{ bg: '#4338CA' }, { bg: '#B45309' }, { bg: '#0F766E' },
]
function Skeleton({ style }: { style?: any }) {
const anim = useRef(new Animated.Value(0.4)).current
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(anim, { toValue: 1, duration: 1000, useNativeDriver: true }),
Animated.timing(anim, { toValue: 0.4, duration: 1000, useNativeDriver: true }),
])
).start()
}, [anim])
return
}
function getInitials(name: string) {
return name.split(' ').slice(0, 2).map((w) => w[0]?.toUpperCase() ?? '').join('')
}
function getAvatarBg(name: string) {
return AVATAR_PALETTES[name.charCodeAt(0) % AVATAR_PALETTES.length].bg
}
export default function HomeScreen() {
const router = useRouter()
const { data: newsItems = [], isLoading: newsLoading } = useNewsList()
const { data: termine = [], isLoading: termineLoading } = useTermineListe(true)
const { data: me } = trpc.members.me.useQuery()
const { data: unread } = trpc.messages.unreadCount.useQuery(undefined, { refetchInterval: 15_000 })
const { data: org } = trpc.organizations.bySlug.useQuery({ slug: getOrgSlug() })
const userName = me?.name ?? ''
const orgName = org?.name ?? 'InnungsApp'
const latestNews = newsItems.slice(0, 2)
const upcomingEvents = termine.slice(0, 3)
const unreadCount = unread?.count ?? 0
const QUICK_ACTIONS = [
{ label: 'Mitglieder', icon: 'people-circle', color: '#2563EB', bg: '#DBEAFE', route: '/(app)/members' },
{ label: 'Termine', icon: 'alarm', color: '#EA580C', bg: '#FFEDD5', route: '/(app)/termine' },
{ label: 'Stellen', icon: 'construct', color: '#0F766E', bg: '#CCFBF1', route: '/(app)/stellen' },
{ label: 'Aktuelles', icon: 'megaphone', color: '#BE185D', bg: '#FCE7F3', route: '/(app)/news' },
]
return (
{/* Decorative Background Element */}
{/* Header Section */}
router.push('/(app)/profil' as never)}
activeOpacity={0.85}
>
{userName ? getInitials(userName) : '?'}
{orgName}
{userName}
router.push('/(app)/chat' as never)}
>
{unreadCount > 0 && (
{unreadCount}
)}
{/* Search Bar */}
{/* Quick Actions Grid */}
Schnellzugriff
{QUICK_ACTIONS.map((action, i) => (
router.push(action.route as never)}
>
{action.label}
))}
{/* News Section */}
Aktuelles
router.push('/(app)/news' as never)}>
Alle anzeigen
{newsLoading ? (
<>
>
) : latestNews.length > 0 ? (
latestNews.map((item) => (
router.push(`/(app)/news/${item.id}` as never)}
>
{NEWS_KATEGORIE_LABELS[item.kategorie]}
{item.publishedAt ? format(item.publishedAt, 'dd. MMM', { locale: de }) : 'Entwurf'}
{item.title}
{getNewsExcerpt(item.body)}
))
) : (
Keine aktuellen Neuigkeiten
)}
{/* Upcoming Events */}
Anstehende Termine
router.push('/(app)/termine' as never)}>
Kalender
{termineLoading ? (
<>
>
) : upcomingEvents.length > 0 ? (
upcomingEvents.map((event, index) => (
router.push(`/(app)/termine/${event.id}` as never)}
activeOpacity={0.7}
>
{format(event.datum, 'MMM', { locale: de })}
{format(event.datum, 'dd')}
{event.titel}
{event.uhrzeit} • {event.ort}
))
) : (
Keine anstehenden Termine
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F8FAFC', // Slate-50
},
bgDecoration: {
position: 'absolute',
top: -100,
left: 0,
right: 0,
height: 400,
backgroundColor: '#003B7E', // Primary brand color
opacity: 0.05,
transform: [{ scaleX: 1.5 }, { scaleY: 1 }],
borderBottomLeftRadius: 200,
borderBottomRightRadius: 200,
},
safeArea: {
flex: 1,
},
scrollContent: {
padding: 20,
paddingBottom: 40,
gap: 24,
},
// Header
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 4,
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
avatar: {
width: 44,
height: 44,
borderRadius: 22,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 6,
elevation: 3,
},
avatarText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '800',
lineHeight: 20,
includeFontPadding: false,
},
greeting: {
fontSize: 13,
color: '#64748B',
fontWeight: '500',
},
username: {
fontSize: 18,
fontWeight: '700',
color: '#0F172A',
},
notificationBtn: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#FFFFFF',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#E2E8F0',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 1,
},
badge: {
position: 'absolute',
top: -2,
right: -2,
backgroundColor: '#EF4444',
width: 16,
height: 16,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1.5,
borderColor: '#FFFFFF',
},
badgeText: {
color: '#FFF',
fontSize: 9,
fontWeight: 'bold',
},
// Search
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E2E8F0',
borderRadius: 16,
paddingHorizontal: 14,
paddingVertical: 12,
gap: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.03,
shadowRadius: 4,
elevation: 1,
},
searchInput: {
flex: 1,
fontSize: 15,
color: '#0F172A',
},
// Sections
section: {
gap: 12,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
sectionTitle: {
fontSize: 18,
fontWeight: '700',
color: '#0F172A',
},
linkText: {
fontSize: 14,
fontWeight: '600',
color: '#003B7E',
},
// Grid
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 12,
},
gridItem: {
width: '48%', // Approx half with gap
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
gap: 10,
borderWidth: 1,
borderColor: '#E2E8F0',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.02,
shadowRadius: 8,
elevation: 2,
},
gridIcon: {
width: 48,
height: 48,
borderRadius: 14,
alignItems: 'center',
justifyContent: 'center',
},
gridLabel: {
fontSize: 14,
fontWeight: '600',
color: '#334155',
},
// News Cards
cardsColumn: {
gap: 12,
},
newsCard: {
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 18,
borderWidth: 1,
borderColor: '#E2E8F0',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.04,
shadowRadius: 6,
elevation: 2,
},
newsHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
categoryBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
backgroundColor: '#F1F5F9',
borderRadius: 8,
},
categoryText: {
fontSize: 10,
fontWeight: '700',
color: '#475569',
textTransform: 'uppercase',
},
dateText: {
fontSize: 12,
color: '#94A3B8',
},
newsTitle: {
fontSize: 16,
fontWeight: '700',
color: '#0F172A',
marginBottom: 6,
lineHeight: 22,
},
newsBody: {
fontSize: 14,
color: '#64748B',
lineHeight: 20,
},
// Events List
eventsList: {
backgroundColor: '#FFFFFF',
borderRadius: 20,
borderWidth: 1,
borderColor: '#E2E8F0',
paddingVertical: 4,
},
eventRow: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
gap: 14,
},
eventBorder: {
borderBottomWidth: 1,
borderBottomColor: '#F1F5F9',
},
dateBox: {
width: 50,
height: 50,
borderRadius: 14,
backgroundColor: '#F8FAFC',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#E2E8F0',
},
dateMonth: {
fontSize: 10,
fontWeight: '700',
textTransform: 'uppercase',
color: '#64748B',
marginBottom: -2,
},
dateDay: {
fontSize: 18,
fontWeight: '800',
color: '#0F172A',
},
eventInfo: {
flex: 1,
gap: 2,
},
eventTitle: {
fontSize: 15,
fontWeight: '700',
color: '#0F172A',
},
eventMeta: {
fontSize: 12,
color: '#64748B',
fontWeight: '500',
},
emptyText: {
fontSize: 14,
color: '#94A3B8',
textAlign: 'center',
paddingVertical: 12,
},
})