diff --git a/mobile/app/(tabs)/_layout.tsx b/mobile/app/(tabs)/_layout.tsx index fab5ee7..3f1c880 100644 --- a/mobile/app/(tabs)/_layout.tsx +++ b/mobile/app/(tabs)/_layout.tsx @@ -47,6 +47,7 @@ export default function TabLayout() { name="urgence" options={{ title: 'Urgence', + headerShown: false, tabBarIcon: ({ color, size }) => , }} /> @@ -54,6 +55,7 @@ export default function TabLayout() { name="profile" options={{ title: 'Profil', + headerShown: false, tabBarIcon: ({ color, size }) => , }} /> diff --git a/mobile/app/(tabs)/index.tsx b/mobile/app/(tabs)/index.tsx index 17b11f4..ce71133 100644 --- a/mobile/app/(tabs)/index.tsx +++ b/mobile/app/(tabs)/index.tsx @@ -1,126 +1,160 @@ -import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native'; +import { View, Text, StyleSheet, ScrollView } from 'react-native'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useAuthStore } from '@/lib/auth-store'; import { useRapport } from '@/hooks/useRapport'; import { useSaisies } from '@/hooks/useTracker'; import { Colors } from '@/lib/colors'; -import RepubliqueHeader from '@/components/RepubliqueHeader'; +import { Spacing, Radius, FontSize, FontWeight, MIN_TOUCH } from '@/lib/theme'; +import { AppBar, Card, Badge, SectionTitle, EmptyState, Loader } from '@/components/ui'; export default function DashboardScreen() { const router = useRouter(); const { user } = useAuthStore(); const { data: rapport } = useRapport('month'); - const { data: saisies } = useSaisies(); + const { data: saisies, isLoading } = useSaisies(); const recentSaisies = (saisies || []).slice(0, 3); return ( - + - Bonjour, {user?.prenom} 👋 - Comment allez-vous aujourd'hui ? - - {/* Actions rapides */} - - router.push('/(tabs)/journal/new')}> - - - - Nouvelle saisie - - router.push('/(tabs)/journal/reports')}> - - - - Mes rapports - - - - {/* Stats du mois */} - {rapport && ( - - Ce mois-ci - - - {rapport.stats.total_saisies} - Saisies + {/* Actions rapides */} + + router.push('/(tabs)/journal/new')} padding={Spacing.lg} style={styles.actionPrimary}> + + - - {rapport.stats.intensite_moyenne?.toFixed(1) || '—'} - Intensité moy. + Nouvelle saisie + + router.push('/(tabs)/journal/reports')} padding={Spacing.lg} style={styles.actionCard}> + + - - {rapport.stats.emotion_dominante?.icone || '—'} - {rapport.stats.emotion_dominante?.nom || 'Aucune'} - - + Mes rapports + - )} - {/* Dernieres saisies */} - {recentSaisies.length > 0 && ( - + {/* Stats du mois */} + {rapport && ( + + Ce mois-ci + + + + {rapport.stats.total_saisies} + Saisies + + + + + {rapport.stats.intensite_moyenne?.toFixed(1) || '—'} + + Intensité moy. + + + + {rapport.stats.emotion_dominante?.icone || '—'} + + {rapport.stats.emotion_dominante?.nom || 'Aucune'} + + + + + + )} + + {/* Dernières saisies */} + - Dernières émotions - router.push('/(tabs)/journal')}> - Tout voir - + Dernières saisies + {recentSaisies.length > 0 && ( + router.push('/(tabs)/journal')}> + Tout voir + + )} - {recentSaisies.map((saisie) => ( - - - {saisie.emotion?.icone || '🔵'} - - - {saisie.emotion?.nom} - - {new Date(saisie.date_saisie).toLocaleDateString('fr-FR')} — Intensité: {saisie.intensite}/10 - - - - ))} + + {isLoading ? ( + + ) : recentSaisies.length === 0 ? ( + router.push('/(tabs)/journal/new')} + /> + ) : ( + recentSaisies.map((saisie) => ( + router.push('/(tabs)/journal')} + padding={Spacing.md} + style={styles.saisieCard} + > + + {saisie.emotion?.icone || '🔵'} + + + {saisie.emotion?.couleur ? ( + + ) : ( + + )} + + {new Date(saisie.date_saisie).toLocaleDateString('fr-FR')} — Intensité {saisie.intensite}/10 + + + + )) + )} - )} ); } const styles = StyleSheet.create({ - screen: { flex: 1, backgroundColor: Colors.white }, - container: { flex: 1, backgroundColor: Colors.white }, - content: { padding: 20 }, - greeting: { fontSize: 28, fontWeight: '800', color: Colors.black, marginTop: 8 }, - subtitle: { fontSize: 16, color: Colors.gray[500], marginTop: 4, marginBottom: 24 }, - actionsRow: { flexDirection: 'row', gap: 12, marginBottom: 24 }, - actionCard: { - flex: 1, backgroundColor: Colors.white, borderRadius: 4, padding: 20, - alignItems: 'center', gap: 12, borderWidth: 1, borderColor: Colors.gray[200], + screen: { flex: 1, backgroundColor: Colors.gray[50] }, + container: { flex: 1 }, + content: { padding: Spacing.lg, paddingBottom: Spacing.xxxl }, + actionsRow: { flexDirection: 'row', gap: Spacing.md, marginBottom: Spacing.xl }, + actionCard: { flex: 1, alignItems: 'center', gap: Spacing.md }, + actionPrimary: { + flex: 1, + alignItems: 'center', + gap: Spacing.md, + backgroundColor: Colors.primary, + borderColor: Colors.primary, }, - actionIcon: { width: 56, height: 56, borderRadius: 4, justifyContent: 'center', alignItems: 'center' }, - actionText: { fontSize: 14, fontWeight: '600', color: Colors.black }, - statsContainer: { marginBottom: 24 }, - sectionTitle: { fontSize: 18, fontWeight: '700', color: Colors.black, marginBottom: 12 }, - statsRow: { flexDirection: 'row', gap: 12 }, - statCard: { - flex: 1, backgroundColor: Colors.gray[50], borderRadius: 4, padding: 16, - alignItems: 'center', borderWidth: 1, borderColor: Colors.gray[200], + actionIcon: { width: 56, height: 56, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' }, + actionIconPrimary: { backgroundColor: Colors.primaryDark }, + actionIconSecondary: { backgroundColor: '#E3E3FD' }, + actionText: { fontSize: FontSize.sm, fontWeight: FontWeight.semibold, color: Colors.black }, + actionTextPrimary: { fontSize: FontSize.sm, fontWeight: FontWeight.bold, color: Colors.white }, + block: { marginBottom: Spacing.xl }, + statsRow: { flexDirection: 'row', alignItems: 'center' }, + statCell: { flex: 1, alignItems: 'center', gap: Spacing.xs }, + statDivider: { width: 1, alignSelf: 'stretch', backgroundColor: Colors.gray[200], marginVertical: Spacing.xs }, + statValue: { fontSize: FontSize.xxl, fontWeight: FontWeight.heavy, color: Colors.primary }, + statLabel: { fontSize: FontSize.tiny, color: Colors.gray[500], textAlign: 'center' }, + sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + seeAll: { + fontSize: FontSize.sm, + fontWeight: FontWeight.semibold, + color: Colors.primary, + textDecorationLine: 'underline', + minHeight: MIN_TOUCH, + paddingTop: Spacing.xs, }, - statValue: { fontSize: 24, fontWeight: '800', color: Colors.primary }, - statLabel: { fontSize: 12, color: Colors.gray[500], marginTop: 4 }, - recentContainer: { marginBottom: 24 }, - sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }, - seeAll: { fontSize: 14, fontWeight: '600', color: Colors.primary, textDecorationLine: 'underline' }, - saisieItem: { - flexDirection: 'row', alignItems: 'center', gap: 12, padding: 14, - backgroundColor: Colors.white, borderRadius: 4, marginBottom: 8, - borderWidth: 1, borderColor: Colors.gray[200], - }, - emotionIcon: { width: 44, height: 44, borderRadius: 4, justifyContent: 'center', alignItems: 'center' }, + saisieCard: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md, marginBottom: Spacing.sm }, + emotionIcon: { width: 44, height: 44, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' }, emotionEmoji: { fontSize: 22 }, - saisieContent: { flex: 1 }, - saisieEmotion: { fontSize: 15, fontWeight: '600', color: Colors.black }, - saisieDate: { fontSize: 13, color: Colors.gray[500], marginTop: 2 }, + saisieContent: { flex: 1, gap: Spacing.xs, alignItems: 'flex-start' }, + saisieDate: { fontSize: FontSize.xs, color: Colors.gray[500] }, }); diff --git a/mobile/app/(tabs)/info/[slug].tsx b/mobile/app/(tabs)/info/[slug].tsx index 0700195..ac1ba77 100644 --- a/mobile/app/(tabs)/info/[slug].tsx +++ b/mobile/app/(tabs)/info/[slug].tsx @@ -1,8 +1,9 @@ -import { View, Text, StyleSheet, ScrollView, ActivityIndicator } from 'react-native'; +import { View, Text, StyleSheet, ScrollView } from 'react-native'; import { useLocalSearchParams, Stack } from 'expo-router'; -import { Ionicons } from '@expo/vector-icons'; import { useFeed } from '@/hooks/useFeeds'; import { Colors } from '@/lib/colors'; +import { FontSize, FontWeight, Spacing } from '@/lib/theme'; +import { Badge, EmptyState, Loader } from '@/components/ui'; export default function FeedDetailScreen() { const { slug } = useLocalSearchParams<{ slug: string }>(); @@ -10,17 +11,22 @@ export default function FeedDetailScreen() { if (isLoading) { return ( - - + + + ); } if (isError || !feed) { return ( - - - Article introuvable + + + ); } @@ -28,34 +34,40 @@ export default function FeedDetailScreen() { const texteNettoye = (feed.contenu ?? '').replace(/<[^>]*>/g, ''); return ( - <> - + + - - - Information santé mentale - + {feed.titre} - Publié le {new Date(feed.created_at).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })} + Publié le{' '} + {new Date(feed.created_at).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric', + })} {texteNettoye} - + ); } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: Colors.white }, - content: { padding: 20, paddingBottom: 40 }, - centered: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: Colors.white }, - badge: { flexDirection: 'row', alignItems: 'center', gap: 6, alignSelf: 'flex-start', backgroundColor: `${Colors.primary}15`, paddingHorizontal: 10, paddingVertical: 5, borderRadius: 4, marginBottom: 12 }, - badgeText: { fontSize: 12, fontWeight: '600', color: Colors.primary }, - title: { fontSize: 24, fontWeight: '800', color: Colors.black, marginBottom: 8, lineHeight: 30 }, - date: { fontSize: 13, color: Colors.gray[400], marginBottom: 20 }, - body: { fontSize: 15, lineHeight: 24, color: Colors.black }, - errorText: { marginTop: 12, fontSize: 16, color: Colors.gray[400] }, + fill: { flex: 1, backgroundColor: Colors.gray[50] }, + container: { flex: 1, backgroundColor: Colors.gray[50] }, + content: { padding: Spacing.xl, paddingBottom: Spacing.xxxl }, + title: { + fontSize: FontSize.xxl, + fontWeight: FontWeight.bold, + color: Colors.black, + marginTop: Spacing.md, + marginBottom: Spacing.sm, + lineHeight: 30, + }, + date: { fontSize: FontSize.xs, color: Colors.gray[400], marginBottom: Spacing.xl }, + body: { fontSize: FontSize.body, lineHeight: 24, color: Colors.black }, }); diff --git a/mobile/app/(tabs)/info/_layout.tsx b/mobile/app/(tabs)/info/_layout.tsx index 16af280..a259e6d 100644 --- a/mobile/app/(tabs)/info/_layout.tsx +++ b/mobile/app/(tabs)/info/_layout.tsx @@ -1,17 +1,20 @@ import { Stack } from 'expo-router'; import { Colors } from '@/lib/colors'; +import { FontWeight } from '@/lib/theme'; export default function InfoLayout() { return ( - - + {/* La liste rend son propre , on masque le header natif. */} + + {/* Le détail garde un header natif avec bouton retour. */} + ); } diff --git a/mobile/app/(tabs)/info/index.tsx b/mobile/app/(tabs)/info/index.tsx index 702e6a7..b41a7d3 100644 --- a/mobile/app/(tabs)/info/index.tsx +++ b/mobile/app/(tabs)/info/index.tsx @@ -1,81 +1,84 @@ -import { View, Text, StyleSheet, FlatList, TouchableOpacity } from 'react-native'; +import { View, Text, StyleSheet, FlatList } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { useFeeds } from '@/hooks/useFeeds'; import { Colors } from '@/lib/colors'; +import { FontSize, FontWeight, Radius, Spacing } from '@/lib/theme'; +import { AppBar, Card, EmptyState, Loader } from '@/components/ui'; import type { Feed } from '@/types'; export default function InfoListScreen() { const { data: feeds, isLoading } = useFeeds(); const router = useRouter(); - const renderItem = ({ item }: { item: Feed }) => ( - router.push(`/(tabs)/info/${item.slug}`)} - > - - - - - {item.titre} - - {item.contenu?.replace(/<[^>]*>/g, '').substring(0, 120)}... - - - {new Date(item.created_at).toLocaleDateString('fr-FR')} - - - - - ); + const renderItem = ({ item }: { item: Feed }) => { + const extrait = (item.contenu ?? '').replace(/<[^>]*>/g, '').trim(); + return ( + router.push(`/(tabs)/info/${item.slug}`)} style={styles.card}> + + + + + + {item.titre} + {extrait ? ( + + {extrait} + + ) : null} + + {new Date(item.created_at).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric', + })} + + + + + + ); + }; return ( - String(item.id)} - renderItem={renderItem} - contentContainerStyle={styles.listContent} - ListHeaderComponent={ - - Santé mentale - Ressources et informations pour votre bien-être - - } - ListEmptyComponent={ - isLoading ? ( - Chargement... - ) : ( - - - Aucun article disponible - - ) - } - /> + + {isLoading ? ( + + ) : ( + String(item.id)} + renderItem={renderItem} + contentContainerStyle={styles.listContent} + ListEmptyComponent={ + + } + /> + )} ); } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: Colors.white }, - listContent: { padding: 16 }, - header: { marginBottom: 20 }, - headerTitle: { fontSize: 14, fontWeight: '700', color: Colors.primary, textTransform: 'uppercase', letterSpacing: 1 }, - headerSubtitle: { fontSize: 15, color: Colors.gray[500], marginTop: 4 }, - card: { - flexDirection: 'row', alignItems: 'center', gap: 14, padding: 16, - backgroundColor: Colors.white, borderRadius: 4, marginBottom: 10, - borderWidth: 1, borderColor: Colors.gray[200], + container: { flex: 1, backgroundColor: Colors.gray[50] }, + listContent: { padding: Spacing.lg, gap: Spacing.md }, + card: { padding: Spacing.lg }, + cardRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md }, + cardIcon: { + width: 48, + height: 48, + borderRadius: Radius.sm, + backgroundColor: '#E3E3FD', + justifyContent: 'center', + alignItems: 'center', }, - cardIcon: { width: 48, height: 48, borderRadius: 4, backgroundColor: `${Colors.primary}15`, justifyContent: 'center', alignItems: 'center' }, cardContent: { flex: 1 }, - cardTitle: { fontSize: 15, fontWeight: '700', color: Colors.black, marginBottom: 4 }, - cardExcerpt: { fontSize: 13, color: Colors.gray[500], lineHeight: 18 }, - cardDate: { fontSize: 11, color: Colors.gray[400], marginTop: 4 }, - loading: { padding: 40, alignItems: 'center' }, - loadingText: { color: Colors.gray[400] }, - empty: { alignItems: 'center', paddingTop: 60 }, - emptyText: { fontSize: 16, color: Colors.gray[400], marginTop: 12 }, + cardTitle: { fontSize: FontSize.body, fontWeight: FontWeight.bold, color: Colors.black, marginBottom: Spacing.xs }, + cardExcerpt: { fontSize: FontSize.xs, color: Colors.gray[500], lineHeight: 18 }, + cardDate: { fontSize: FontSize.tiny, color: Colors.gray[400], marginTop: Spacing.xs }, }); diff --git a/mobile/app/(tabs)/journal/_layout.tsx b/mobile/app/(tabs)/journal/_layout.tsx index 426faf7..3741465 100644 --- a/mobile/app/(tabs)/journal/_layout.tsx +++ b/mobile/app/(tabs)/journal/_layout.tsx @@ -1,18 +1,20 @@ import { Stack } from 'expo-router'; import { Colors } from '@/lib/colors'; +import { FontWeight } from '@/lib/theme'; export default function JournalLayout() { return ( - + + - ); } diff --git a/mobile/app/(tabs)/journal/edit.tsx b/mobile/app/(tabs)/journal/edit.tsx index 22a32e3..a844729 100644 --- a/mobile/app/(tabs)/journal/edit.tsx +++ b/mobile/app/(tabs)/journal/edit.tsx @@ -1,10 +1,12 @@ import { useState, useEffect } from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, TextInput, ScrollView, Alert } from 'react-native'; +import { View, Text, StyleSheet, ScrollView, Alert } from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; -import { Ionicons } from '@expo/vector-icons'; import { useSaisies, useUpdateSaisie } from '@/hooks/useTracker'; import EmotionPicker from '@/components/EmotionPicker'; +import { IntensityPicker } from './new'; import { Colors } from '@/lib/colors'; +import { Spacing, FontSize, FontWeight } from '@/lib/theme'; +import { Button, TextField, Loader } from '@/components/ui'; export default function EditEntryScreen() { const router = useRouter(); @@ -38,126 +40,76 @@ export default function EditEntryScreen() { note: note.trim() || undefined, date_saisie: dateSaisie, }); - Alert.alert('Succès', 'Saisie modifiée !', [ - { text: 'OK', onPress: () => router.back() }, - ]); + Alert.alert('Succès', 'Saisie modifiée !', [{ text: 'OK', onPress: () => router.back() }]); } catch { Alert.alert('Erreur', 'Impossible de modifier la saisie'); } }; if (!saisie) { - return ( - - Chargement... - - ); + return ; } return ( - Modifier la saisie - - {/* Emotion */} + {/* Émotion */} Émotion - {/* Intensite */} + {/* Intensité */} Intensité {intensite} /10 - - {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((v) => ( - setIntensite(v)} - > - {v} - - ))} - - - Faible - Forte + + + Faible + Forte {/* Note */} - Note (optionnel) - {/* Actions */} - router.back()}> - Annuler - - router.back()} style={styles.flex} /> +