feat: Implement mobile application and lead processing utilities.
This commit is contained in:
133
innungsapp/apps/mobile/components/news/NewsCard.tsx
Normal file
133
innungsapp/apps/mobile/components/news/NewsCard.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
import { Badge } from '@/components/ui/Badge'
|
||||
import { NEWS_KATEGORIE_LABELS } from '@innungsapp/shared/types'
|
||||
import { format } from 'date-fns'
|
||||
import { de } from 'date-fns/locale'
|
||||
import { useNewsReadStore } from '@/store/news.store'
|
||||
|
||||
interface NewsCardProps {
|
||||
news: {
|
||||
id: string
|
||||
title: string
|
||||
kategorie: string
|
||||
publishedAt: Date | null
|
||||
isRead: boolean
|
||||
author: { name: string } | null
|
||||
}
|
||||
onPress: () => void
|
||||
}
|
||||
|
||||
export function NewsCard({ news, onPress }: NewsCardProps) {
|
||||
const localReadIds = useNewsReadStore((s) => s.readIds)
|
||||
const isRead = news.isRead || localReadIds.has(news.id)
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} style={styles.card} activeOpacity={0.84}>
|
||||
{!isRead && (
|
||||
<View style={styles.newBadge}>
|
||||
<Text style={styles.newBadgeText}>Neu</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.content}>
|
||||
<View style={styles.topRow}>
|
||||
<Badge label={NEWS_KATEGORIE_LABELS[news.kategorie]} kategorie={news.kategorie} />
|
||||
<Text style={styles.dateText}>
|
||||
{news.publishedAt
|
||||
? format(new Date(news.publishedAt), 'dd. MMM', { locale: de })
|
||||
: 'Entwurf'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={isRead ? styles.titleRead : styles.titleUnread} numberOfLines={2}>
|
||||
{news.title}
|
||||
</Text>
|
||||
|
||||
<View style={styles.metaRow}>
|
||||
<View style={styles.dot} />
|
||||
<Text style={styles.authorText}>{news.author?.name ?? 'Innung'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Ionicons name="chevron-forward" size={16} color="#94A3B8" />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderWidth: 1,
|
||||
borderColor: '#E2E8F0',
|
||||
borderRadius: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 14,
|
||||
shadowColor: '#0F172A',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 10,
|
||||
elevation: 2,
|
||||
position: 'relative',
|
||||
},
|
||||
newBadge: {
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
top: 8,
|
||||
borderRadius: 999,
|
||||
backgroundColor: '#EF4444',
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
zIndex: 2,
|
||||
},
|
||||
newBadgeText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 10,
|
||||
fontWeight: '700',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
marginRight: 10,
|
||||
},
|
||||
topRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 8,
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 11,
|
||||
color: '#94A3B8',
|
||||
fontWeight: '600',
|
||||
},
|
||||
titleUnread: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
lineHeight: 21,
|
||||
marginBottom: 8,
|
||||
},
|
||||
titleRead: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
color: '#475569',
|
||||
lineHeight: 20,
|
||||
marginBottom: 8,
|
||||
},
|
||||
metaRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
dot: {
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: '#CBD5E1',
|
||||
},
|
||||
authorText: {
|
||||
fontSize: 11,
|
||||
color: '#64748B',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user