Files
cesizen-individuel/mobile/components/EmotionPicker.tsx
T
Azmog 4f54340e89 fix: corrections post-audit (traçabilité, identité État, accents, graphique mobile)
Backend:
- table audits réellement alimentée via trait Auditable (observers Eloquent)
  sur Utilisateur, SaisieTracker, Feed, Emotion ; mot de passe jamais loggé
- 4 nouveaux tests (19/19 au total)

Frontend web:
- en-tête/pied République Française ajoutés aux pages connexion/inscription
- bouton variant danger réellement rouge (tokens erreur DSFR)

Mobile:
- ~30 libellés ré-accentués (Intensité, Succès, Déconnexion, Répartition...)
- Colors.secondary = rouge Marianne #E1000F (différenciation cartes/icônes)
- vrai camembert SVG des rapports (react-native-svg)

Docs: plans oraux mis à jour (audits actif, 29 tests, stockage JWT en Q/R)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:17:40 +02:00

107 lines
3.5 KiB
TypeScript

import { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
import { useEmotions } from '@/hooks/useEmotions';
import { Colors } from '@/lib/colors';
import type { Emotion } from '@/types';
interface EmotionPickerProps {
value?: number;
onChange: (emotionId: number) => void;
}
export default function EmotionPicker({ value, onChange }: EmotionPickerProps) {
const { data: emotions, isLoading } = useEmotions();
const [selectedParent, setSelectedParent] = useState<Emotion | null>(null);
if (isLoading) {
return (
<View style={styles.loading}>
<ActivityIndicator size="large" color={Colors.primary} />
</View>
);
}
if (selectedParent?.enfants && selectedParent.enfants.length > 0) {
return (
<View>
<TouchableOpacity style={styles.backBtn} onPress={() => setSelectedParent(null)}>
<Text style={styles.backText}>← Retour aux émotions</Text>
</TouchableOpacity>
<Text style={styles.subTitle}>
Précisez votre émotion de{' '}
<Text style={{ color: selectedParent.couleur, fontWeight: '700' }}>{selectedParent.nom}</Text> :
</Text>
<View style={styles.grid}>
{selectedParent.enfants.map((child) => {
const isSelected = value === child.id;
return (
<TouchableOpacity
key={child.id}
style={[
styles.emotionCard,
isSelected && { borderColor: child.couleur, borderWidth: 2.5 },
]}
onPress={() => onChange(child.id)}
>
<Text style={styles.emotionEmoji}>{child.icone || '🔵'}</Text>
<Text style={styles.emotionName}>{child.nom}</Text>
</TouchableOpacity>
);
})}
</View>
</View>
);
}
return (
<View style={styles.grid}>
{emotions?.map((emotion) => {
const hasChildren = emotion.enfants && emotion.enfants.length > 0;
const isSelected = value === emotion.id || emotion.enfants?.some((e) => e.id === value);
return (
<TouchableOpacity
key={emotion.id}
style={[
styles.emotionCard,
isSelected && { borderColor: emotion.couleur, borderWidth: 2.5 },
]}
onPress={() => {
if (hasChildren) {
setSelectedParent(emotion);
} else {
onChange(emotion.id);
}
}}
>
<Text style={styles.emotionEmoji}>{emotion.icone || '🔵'}</Text>
<Text style={styles.emotionName}>{emotion.nom}</Text>
{hasChildren && <Text style={styles.arrow}>▼</Text>}
</TouchableOpacity>
);
})}
</View>
);
}
const styles = StyleSheet.create({
loading: { padding: 40, alignItems: 'center' },
backBtn: { marginBottom: 12 },
backText: { fontSize: 14, color: Colors.primary, textDecorationLine: 'underline' },
subTitle: { fontSize: 14, color: Colors.gray[600], marginBottom: 12 },
grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
emotionCard: {
width: '30%',
backgroundColor: Colors.white,
borderRadius: 4,
padding: 14,
alignItems: 'center',
gap: 6,
borderWidth: 1.5,
borderColor: Colors.gray[200],
},
emotionEmoji: { fontSize: 30 },
emotionName: { fontSize: 12, fontWeight: '600', color: Colors.black, textAlign: 'center' },
arrow: { fontSize: 10, color: Colors.gray[400] },
});