Files
cesizen-individuel/mobile/app/(tabs)/journal/edit.tsx
T
Azmog 91ceca2be5 polish(mobile): audit design DSFR multi-agents — tokens, a11y, états, finitions
- tokens centralisés (Tint, IconSize, LineHeight, LetterSpacing), zéro hex en dur
- accessibilité (rôles, labels, live regions, afficher/masquer mot de passe)
- états vides/erreur complétés (ex. saisie introuvable), cibles tactiles >=44px
- suppression du composant mort RepubliqueHeader

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

133 lines
4.6 KiB
TypeScript

import { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, Alert } from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router';
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, EmptyState } from '@/components/ui';
/** Hauteur de ligne du grand chiffre d'intensité. */
const INTENSITY_LINE_HEIGHT = 56;
export default function EditEntryScreen() {
const router = useRouter();
const { id } = useLocalSearchParams<{ id: string }>();
const { data: saisies, isLoading } = useSaisies();
const updateSaisie = useUpdateSaisie();
const saisie = saisies?.find((s) => s.id === Number(id));
const [emotionId, setEmotionId] = useState<number | undefined>();
const [intensite, setIntensite] = useState(5);
const [note, setNote] = useState('');
const [dateSaisie, setDateSaisie] = useState('');
useEffect(() => {
if (saisie) {
setEmotionId(saisie.emotion_id);
setIntensite(saisie.intensite);
setNote(saisie.note || '');
setDateSaisie(saisie.date_saisie.split('T')[0]);
}
}, [saisie]);
const handleSubmit = async () => {
if (!emotionId || !id) return;
try {
await updateSaisie.mutateAsync({
id: Number(id),
emotion_id: emotionId,
intensite,
note: note.trim() || undefined,
date_saisie: dateSaisie,
});
Alert.alert('Succès', 'Saisie modifiée !', [{ text: 'OK', onPress: () => router.back() }]);
} catch {
Alert.alert('Erreur', 'Impossible de modifier la saisie');
}
};
if (isLoading) {
return <Loader label="Chargement…" />;
}
if (!saisie) {
return (
<View style={styles.container}>
<EmptyState
icon="alert-circle-outline"
title="Saisie introuvable"
message="Cette saisie n'existe plus ou a été supprimée."
actionLabel="Retour"
onAction={() => router.back()}
/>
</View>
);
}
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* Émotion */}
<View style={styles.section}>
<Text style={styles.label}>Émotion</Text>
<EmotionPicker value={emotionId} onChange={setEmotionId} />
</View>
{/* Intensité */}
<View style={styles.section}>
<Text style={styles.label}>Intensité</Text>
<View style={styles.intensityContainer}>
<Text style={styles.intensityValue}>{intensite}</Text>
<Text style={styles.intensityUnit}>/10</Text>
</View>
<IntensityPicker value={intensite} onChange={setIntensite} />
<View style={styles.scaleLabels}>
<Text style={styles.scaleLabel}>Faible</Text>
<Text style={styles.scaleLabel}>Forte</Text>
</View>
</View>
{/* Note */}
<View style={styles.section}>
<TextField
label="Note (optionnel)"
value={note}
onChangeText={setNote}
placeholder="Décrivez ce que vous ressentez…"
multiline
numberOfLines={4}
/>
</View>
{/* Actions */}
<View style={styles.actionsRow}>
<Button title="Annuler" variant="secondary" onPress={() => router.back()} style={styles.flex} />
<Button
title="Enregistrer"
icon="checkmark"
loading={updateSaisie.isPending}
disabled={!emotionId}
onPress={handleSubmit}
style={styles.flex}
/>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: Colors.gray[50] },
content: { padding: Spacing.lg, paddingBottom: Spacing.xxxl },
section: { marginBottom: Spacing.xl },
label: { fontSize: FontSize.body, fontWeight: FontWeight.bold, color: Colors.black, marginBottom: Spacing.md },
intensityContainer: { flexDirection: 'row', alignItems: 'baseline', justifyContent: 'center', marginBottom: Spacing.lg },
intensityValue: { fontSize: FontSize.display, fontWeight: FontWeight.heavy, color: Colors.primary, lineHeight: INTENSITY_LINE_HEIGHT },
intensityUnit: { fontSize: FontSize.xl, color: Colors.gray[400], marginLeft: Spacing.xs },
scaleLabels: { flexDirection: 'row', justifyContent: 'space-between', marginTop: Spacing.md },
scaleLabel: { fontSize: FontSize.xs, color: Colors.gray[500] },
actionsRow: { flexDirection: 'row', justifyContent: 'space-between', gap: Spacing.md, marginTop: Spacing.sm },
flex: { flex: 1 },
});