import { useMemo, useState } from 'react'; import { Alert, KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, StyleSheet, Switch, Text, View, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { Button, Card, EmptyState, Loader, SectionTitle, TextField } from '@/components/ui'; import { useAdminEmotions, useSaveEmotion, useDeleteEmotion } from '@/hooks/useAdmin'; import { Colors } from '@/lib/colors'; import { FontSize, FontWeight, HIT_SLOP, MIN_TOUCH, Radius, Spacing, Tint } from '@/lib/theme'; import type { Emotion } from '@/types'; /** Voile sombre derrière la modale (noir DSFR à 40 % d'opacité). */ const OVERLAY = 'rgba(22, 22, 22, 0.4)'; /** Format hexadécimal #RRGGBB attendu pour une couleur d'émotion. */ const HEX_RE = /^#([0-9A-Fa-f]{6})$/; /** Diamètre de la pastille de couleur d'une émotion en liste. */ const PASTILLE = 36; /** Côté de l'aperçu de couleur dans le formulaire (cible tactile). */ const APERCU = MIN_TOUCH; /** Diamètre de la pastille de couleur d'une émotion parente (chip). */ const PARENT_DOT = 14; interface FormState { id?: number; nom: string; couleur: string; icone: string; niveau: number; parent_id: number | null; est_actif: boolean; } const FORM_VIDE: FormState = { nom: '', couleur: Colors.primary, icone: '', niveau: 1, parent_id: null, est_actif: true }; function messageErreur(e: unknown, defaut: string): string { const err = e as { response?: { data?: { message?: string } } }; return err?.response?.data?.message ?? defaut; } /** Regroupe une liste d'émotions (plate ou imbriquée) en niveau 1 + leurs enfants. */ function organiser(emotions: Emotion[] | undefined): { parent: Emotion; enfants: Emotion[] }[] { if (!emotions) return []; const racines = emotions.filter((e) => e.niveau === 1); return racines.map((parent) => { const depuisEnfants = parent.enfants ?? []; const depuisPlat = emotions.filter((e) => e.niveau === 2 && e.parent_id === parent.id); // On dédoublonne par id au cas où les deux sources coexistent. const map = new Map(); [...depuisEnfants, ...depuisPlat].forEach((e) => map.set(e.id, e)); return { parent, enfants: Array.from(map.values()) }; }); } export default function AdminEmotionsScreen() { const { data: emotions, isLoading } = useAdminEmotions(); const saveMut = useSaveEmotion(); const deleteMut = useDeleteEmotion(); const [modalVisible, setModalVisible] = useState(false); const [form, setForm] = useState(FORM_VIDE); const groupes = useMemo(() => organiser(emotions), [emotions]); const parents = useMemo(() => (emotions ?? []).filter((e) => e.niveau === 1), [emotions]); const ouvrirCreation = () => { setForm(FORM_VIDE); setModalVisible(true); }; const ouvrirEdition = (e: Emotion) => { setForm({ id: e.id, nom: e.nom, couleur: e.couleur, icone: e.icone ?? '', niveau: e.niveau, parent_id: e.parent_id, est_actif: e.est_actif, }); setModalVisible(true); }; const soumettre = async () => { if (!form.nom.trim()) { Alert.alert('Champs requis', 'Le nom est obligatoire.'); return; } if (!HEX_RE.test(form.couleur.trim())) { Alert.alert('Couleur', 'Saisissez une couleur hexadécimale (ex. #000091).'); return; } if (form.niveau === 2 && !form.parent_id) { Alert.alert('Émotion parente', 'Choisissez l’émotion parente pour une sous-émotion.'); return; } try { await saveMut.mutateAsync({ id: form.id, nom: form.nom.trim(), couleur: form.couleur.trim(), icone: form.icone.trim() || null, niveau: form.niveau, parent_id: form.niveau === 2 ? form.parent_id : null, est_actif: form.est_actif, }); setModalVisible(false); } catch (e) { Alert.alert('Erreur', messageErreur(e, 'Enregistrement impossible.')); } }; const supprimer = (e: Emotion) => { Alert.alert('Supprimer', `Supprimer « ${e.nom} » ?`, [ { text: 'Annuler', style: 'cancel' }, { text: 'Supprimer', style: 'destructive', onPress: async () => { try { await deleteMut.mutateAsync(e.id); } catch (err) { Alert.alert('Erreur', messageErreur(err, 'Suppression impossible.')); } }, }, ]); }; const renderEmotion = (e: Emotion, estEnfant: boolean) => ( {e.icone ? {e.icone} : null} {e.nom} Niveau {e.niveau} · {e.est_actif ? 'Actif' : 'Inactif'} · {e.couleur}