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, }, })