diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 87b1017f..f6e14c1e 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -64,19 +64,12 @@ import { CompanionCollectionScreen } from "./src/screens/CompanionCollectionScre
import { MedicationsScreen } from "./src/screens/MedicationsScreen";
import { CalmMinutesScreen } from "./src/screens/CalmMinutesScreen";
import { InsightsScreen } from "./src/screens/InsightsScreen";
-import { ActivityScreen } from "./src/screens/ActivityScreen";
import { ReportScreen } from "./src/screens/ReportScreen";
import { BarkleyScreen } from "./src/screens/BarkleyScreen";
import { BarkleyStepScreen } from "./src/screens/BarkleyStepScreen";
import { RewardsScreen } from "./src/screens/RewardsScreen";
-import { DecodeurScreen } from "./src/screens/DecodeurScreen";
-import { ScriptsScreen } from "./src/screens/ScriptsScreen";
-import { StrengthsScreen } from "./src/screens/StrengthsScreen";
import { CrisisListScreen } from "./src/screens/CrisisListScreen";
-import { CarePathwayScreen } from "./src/screens/CarePathwayScreen";
-import { AchievementsScreen } from "./src/screens/AchievementsScreen";
import { SettingsScreen } from "./src/screens/SettingsScreen";
-import { BurnoutScreen } from "./src/screens/BurnoutScreen";
import { ConnaissancesScreen } from "./src/screens/ConnaissancesScreen";
import { ConnaissancesArticleScreen } from "./src/screens/ConnaissancesArticleScreen";
// Auth
@@ -149,19 +142,12 @@ function PlusNavigator() {
-
-
-
-
-
-
-
{
- const unlocked = new Set();
-
- // first_child is always unlocked — we're on a child-scoped screen so a
- // child necessarily exists.
- unlocked.add("first_child");
-
- if (signals.journalCount >= 1) unlocked.add("first_journal");
- if (signals.strengthCount >= 1) unlocked.add("first_strength");
- if (signals.strengthCount >= 5) unlocked.add("five_strengths");
- if (signals.crisisItemCount >= 1) unlocked.add("first_crisis_list");
- if (signals.routineCount >= 1) unlocked.add("first_routine");
- if (signals.streakDays >= 7) unlocked.add("streak_7");
- if (signals.streakDays >= 30) unlocked.add("streak_30");
-
- // Explorer: 5+ distinct features touched.
- let score = 1; // child already exists
- if (signals.journalCount >= 1) score++;
- if (signals.strengthCount >= 1) score++;
- if (signals.crisisItemCount >= 1) score++;
- if (signals.routineCount >= 1) score++;
- if (score >= 5) unlocked.add("explorer");
-
- return unlocked;
-}
-
-// ── Hook ─────────────────────────────────────────────────────────────────────
-
-/** Returns the set of unlocked achievement ids for the given child. */
-export function useAchievements(childId: string): {
- unlocked: Set;
- total: number;
- isLoading: boolean;
-} {
- const journal = useJournal(childId);
- const strengths = useStrengths(childId);
- const crisis = useCrisisItems(childId);
- const routines = useRoutines(childId);
- const stats = useInsights(childId, "week");
-
- const isLoading =
- journal.isLoading ||
- strengths.isLoading ||
- crisis.isLoading ||
- routines.isLoading ||
- stats.isLoading;
-
- const unlocked = useMemo(() => {
- const signals: Signals = {
- journalCount: journal.data?.length ?? 0,
- strengthCount: strengths.data?.length ?? 0,
- crisisItemCount: crisis.data?.length ?? 0,
- routineCount: routines.data?.length ?? 0,
- streakDays: stats.data?.streak ?? 0,
- };
- return compute(signals);
- }, [
- journal.data,
- strengths.data,
- crisis.data,
- routines.data,
- stats.data,
- ]);
-
- return { unlocked, total: ACHIEVEMENTS.length, isLoading };
-}
diff --git a/apps/mobile/src/hooks/use-activity.ts b/apps/mobile/src/hooks/use-activity.ts
deleted file mode 100644
index c47cd83b..00000000
--- a/apps/mobile/src/hooks/use-activity.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-
-import { api } from "../lib/api";
-
-// ─── Local types (mirrors apps/web/src/hooks/use-audit-log.ts) ───────────────
-
-export type AuditEntityType =
- | "child"
- | "symptom"
- | "journal"
- | "medication"
- | "medication_log"
- | "crisis_item"
- | "child_access"
- | "child_invitation"
- | "strength"
- | "routine"
- | "routine_completion"
- | "admin_document";
-
-export type AuditAction = "create" | "update" | "delete" | "accept" | "revoke";
-
-export interface AuditEntry {
- id: string;
- actorId: string | null;
- actorName: string | null;
- childId: string | null;
- entityType: AuditEntityType;
- entityId: string | null;
- action: AuditAction;
- summary: string | null;
- createdAt: string;
-}
-
-// ─── Query key ────────────────────────────────────────────────────────────────
-
-const key = (childId: string, limit: number) =>
- ["activity", "child", childId, limit] as const;
-
-// ─── Hook ─────────────────────────────────────────────────────────────────────
-
-/**
- * Fetches the activity feed for a child.
- * Endpoint: GET /api/audit-log/child/:childId?limit=
- * Returns entries ordered most-recent first (the API returns them that way).
- */
-export function useActivity(childId: string, limit = 50) {
- return useQuery({
- queryKey: key(childId, limit),
- queryFn: () =>
- api.get(`/audit-log/child/${childId}?limit=${limit}`),
- enabled: !!childId,
- });
-}
diff --git a/apps/mobile/src/hooks/use-care-pathway.ts b/apps/mobile/src/hooks/use-care-pathway.ts
deleted file mode 100644
index 816958b2..00000000
--- a/apps/mobile/src/hooks/use-care-pathway.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type {
- CarePathwayProgress,
- UpsertCarePathwayProgress,
-} from "@focusflow/validators";
-import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-
-import { api } from "../lib/api";
-
-// Mirrors apps/web/src/hooks/use-care-pathway.ts: fetch progress rows for a
-// child and upsert a step's status (todo → doing → done, or any transition).
-const key = (childId: string) => ["care-pathway", childId] as const;
-
-export function useCarePathwayProgress(childId: string) {
- return useQuery({
- queryKey: key(childId),
- queryFn: () => api.get(`/care-pathway/${childId}`),
- enabled: !!childId,
- });
-}
-
-export function useUpsertCarePathwayStep(childId: string) {
- const qc = useQueryClient();
- return useMutation({
- mutationFn: (data: UpsertCarePathwayProgress) =>
- api.post("/care-pathway", data),
- onSettled: () => qc.invalidateQueries({ queryKey: key(childId) }),
- });
-}
diff --git a/apps/mobile/src/hooks/use-decodeur.ts b/apps/mobile/src/hooks/use-decodeur.ts
deleted file mode 100644
index c00e7934..00000000
--- a/apps/mobile/src/hooks/use-decodeur.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-// Décodeur de comportements — données 100 % statiques.
-// Le contenu est adapté du catalogue web (apps/web/src/lib/behavior-decoder-data.ts
-// et les traductions fr.json). Aucun appel API n'est nécessaire.
-
-export type BehaviorEntry = {
- id: string;
- behavior: string;
- explanation: string;
- tip: string;
- tags: readonly string[];
-};
-
-export const BEHAVIOR_ENTRIES: readonly BehaviorEntry[] = [
- {
- id: "throwsBackpack",
- behavior: "Il jette ses affaires en rentrant de l'école.",
- explanation:
- "Toute la journée, son cortex préfrontal a freiné les impulsions, l'agitation, les colères. Le retour à la maison, c'est l'endroit safe : le frein lâche. Ce n'est pas dirigé contre vous, c'est une décharge.",
- tip: "Prévoyez un rituel de décompression d'abord (5 minutes seul·e dans sa chambre, un encas, un câlin silencieux) avant toute exigence. Les consignes attendront 15 minutes.",
- tags: ["école", "retour", "agressif", "affaires", "transition"],
- },
- {
- id: "interrupts",
- behavior: "Il interrompt sans cesse quand je parle.",
- explanation:
- "Sa mémoire de travail est courte. S'il garde son idée pour son tour, elle s'évanouit. Il interrompt pour ne pas la perdre, pas pour vous manquer de respect.",
- tip: "Offrez-lui un papier ou un petit carnet : il note son idée, vous finissez. Vous lui rendez la parole ensuite. Ça apaise l'impulsion sans la réprimer.",
- tags: ["interrompt", "coupe la parole", "impulsivité", "discussion"],
- },
- {
- id: "losesStuff",
- behavior: "Il perd ses affaires (cartable, gourde, doudou).",
- explanation:
- "Ranger un objet demande d'inhiber d'autres pensées au même moment. Son cerveau passe à autre chose avant que le geste de « poser » soit enregistré. L'objet existait, puis a disparu — pas par négligence, par défaut d'ancrage.",
- tip: "Une seule place pour chaque chose, visible, sans tiroir. Une étiquette ou une photo. Préférez les rituels visuels aux rappels verbaux.",
- tags: ["perd", "cartable", "doudou", "gourde", "oubli", "mémoire"],
- },
- {
- id: "forgetsInstructions",
- behavior: "La même consigne ne se grave pas, même après trois fois.",
- explanation:
- "Entendre une consigne et l'exécuter passent par deux circuits différents. Chez l'enfant TDAH, le pont entre les deux est fragile. Il n'a pas désobéi : il n'a pas pu traduire ce qu'il a entendu en action.",
- tip: "Une consigne à la fois, formulée en regardant l'enfant dans les yeux. Faites-la répéter. Pour les enchaînements, écrivez/dessinez la séquence.",
- tags: ["oublie", "consigne", "répéter", "mémoire", "écoute"],
- },
- {
- id: "wontFinish",
- behavior: "Il refuse de finir une activité commencée.",
- explanation:
- "Le cerveau TDAH carbure à la dopamine de nouveauté. Une fois l'activité connue, la motivation chute brutalement — comme une voiture en panne sèche. Ce n'est pas un manque de volonté, c'est un manque de carburant chimique.",
- tip: "Découpez en mini-étapes très courtes (3 minutes, 5 minutes), avec une transition claire à chaque palier. Une mini-récompense ou une variation suffit à relancer la dopamine.",
- tags: ["finir", "abandonne", "motivation", "lassitude"],
- },
- {
- id: "fidgets",
- behavior: "Il agite ses mains, ses pieds, son corps en permanence.",
- explanation:
- "Pour rester attentif, son cerveau a besoin d'un stimulus moteur de fond. Bouger, c'est sa façon d'éveiller son cortex préfrontal. Le forcer à l'immobilité, c'est l'épuiser pour rien.",
- tip: "Autorisez le mouvement utile : ballon, coussin pneumatique, élastique de chaise, fidget. La concentration monte, pas l'inverse.",
- tags: ["bouge", "agite", "mains", "pieds", "attention"],
- },
- {
- id: "explosiveAnger",
- behavior: "Il a des explosions disproportionnées pour rien.",
- explanation:
- "Sa régulation émotionnelle est plus lente à mûrir. Quand la fatigue cognitive monte, son seuil de tolérance s'effondre. Une « broutille » est en réalité la dixième micro-frustration de la journée.",
- tip: "Ne raisonnez pas pendant la crise — son cortex est hors-ligne. Restez calme, baissez la voix, attendez la redescente. Le débrief, c'est plus tard, à froid.",
- tags: ["crise", "colère", "explosion", "émotion", "régulation"],
- },
- {
- id: "liesToAvoid",
- behavior: "Il ment pour éviter ses devoirs ou une corvée.",
- explanation:
- "Anticiper l'effort déclenche chez lui une vraie souffrance physique. Le mensonge est un évitement, pas une malveillance. Il préfère la honte du mensonge à l'angoisse de la tâche.",
- tip: "Réduisez le seuil d'entrée de la tâche (« on fait juste la première ligne ensemble »). La honte recule quand la tâche redevient atteignable.",
- tags: ["mensonge", "ment", "évite", "corvée", "devoirs"],
- },
- {
- id: "cantStartHomework",
- behavior: "Il ne peut pas se mettre à ses devoirs.",
- explanation:
- "L'amorçage d'une tâche non-stimulante demande un effort exécutif énorme. C'est ce qu'on appelle l'inertie de démarrage. Il n'est pas paresseux : il est bloqué sur le seuil, comme une voiture qui patine.",
- tip: "Démarrez avec lui pendant 90 secondes (lire la consigne à voix haute, faire le premier mot). Une fois le moteur lancé, il peut continuer seul.",
- tags: ["devoirs", "commencer", "démarrer", "procrastination"],
- },
- {
- id: "screenTransition",
- behavior: "Il s'énerve quand je lui demande d'arrêter le jeu vidéo.",
- explanation:
- "L'écran lui fournit en continu la dopamine qui lui manque. Quand vous l'arrêtez, c'est une chute neurochimique violente. Ce n'est pas une addiction de caprice, c'est un sevrage.",
- tip: "Annoncez la fin 5 minutes avant, puis 2 minutes avant (minuteur visuel idéalement). Prévoyez une transition agréable (musique, jeu d'extérieur) — pas un saut direct vers une corvée.",
- tags: ["écran", "jeu vidéo", "tablette", "arrêter", "transition"],
- },
- {
- id: "eveningMeltdown",
- behavior: "Il pleure ou s'effondre pour des broutilles le soir.",
- explanation:
- "C'est l'effet « cocotte-minute ». Il a tenu toute la journée à l'école, retenu ses émotions. Le soir, à la maison où il se sent en sécurité, tout sort en même temps.",
- tip: "Diminuez les exigences du soir au minimum vital. Plus de questions sur la journée, plus de devoirs après 18 h si possible. Un câlin, une histoire, un coucher tôt.",
- tags: ["soir", "pleure", "effondrement", "fatigue"],
- },
- {
- id: "tableMovement",
- behavior: "Il ne tient pas en place à table.",
- explanation:
- "Rester immobile pendant un repas demande une régulation motrice qui le coûte énormément. Pour ne pas exploser, il libère le trop-plein par des micro-mouvements continus.",
- tip: "Acceptez certains mouvements (les pieds qui bougent, un fidget sur les genoux). Les repas plus courts, plus fréquents marchent mieux que les longs repas familiaux pour les petits.",
- tags: ["table", "repas", "bouge", "agité"],
- },
- {
- id: "doesntListen",
- behavior: "L'instituteur dit qu'il « n'écoute pas » en classe.",
- explanation:
- "Son attention sélective est défaillante. Le moindre stimulus environnant (un bruit, un mouvement) capte sa concentration. Il entend autant que les autres — il ne peut pas filtrer comme les autres.",
- tip: "Demandez à l'enseignant·e une place près du tableau, loin de la fenêtre. Un signal discret (toucher l'épaule, code main) pour le rappeler à la tâche, plutôt que la réprimande publique.",
- tags: ["école", "n'écoute pas", "attention", "instit", "maîtresse"],
- },
- {
- id: "slowToGetDressed",
- behavior: "Il met une heure à s'habiller le matin.",
- explanation:
- "Il s'habille, puis voit un jouet, puis pense à autre chose, puis revient. Son attention saute en permanence. Ce n'est pas un sabotage : c'est qu'aucune ancre extérieure ne le maintient sur la tâche.",
- tip: "Préparez les vêtements la veille, dans l'ordre. Un minuteur visuel sur l'étape (« 5 minutes pour le pantalon »). Le matin, peu de mots, beaucoup de gestes.",
- tags: ["habille", "matin", "lent", "distraction"],
- },
- {
- id: "givesUpEarly",
- behavior: "Il abandonne avant même d'avoir essayé.",
- explanation:
- "Les déceptions passées laissent une trace. Pour se protéger, il préfère « ne pas essayer » plutôt que « essayer et ne pas y arriver ». Ce n'est pas de la paresse, c'est une protection contre une estime de soi déjà fragilisée.",
- tip: "Célébrez l'amorçage, pas le résultat. Une étape minuscule franchie vaut mieux qu'un grand projet abandonné. Évitez les comparaisons avec les autres enfants.",
- tags: ["abandonne", "échec", "renoncement", "estime"],
- },
-];
-
-function normalize(s: string): string {
- return s
- .toLocaleLowerCase("fr-FR")
- .normalize("NFD")
- .replace(/\p{Diacritic}/gu, "");
-}
-
-/** Filter entries by a free-text query (French, diacritic-insensitive). */
-export function filterEntries(
- entries: readonly BehaviorEntry[],
- query: string,
-): readonly BehaviorEntry[] {
- const q = normalize(query.trim());
- if (q.length === 0) return entries;
- return entries.filter((e) => {
- const haystack = [e.behavior, ...e.tags].map(normalize).join(" ");
- return haystack.includes(q);
- });
-}
diff --git a/apps/mobile/src/hooks/use-scripts.ts b/apps/mobile/src/hooks/use-scripts.ts
deleted file mode 100644
index 984d6407..00000000
--- a/apps/mobile/src/hooks/use-scripts.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-// Scripts de communication — données 100 % statiques.
-// Adapté du catalogue web (apps/web/src/lib/communication-scripts-data.ts
-// et les traductions fr.json). Aucun appel API.
-
-export type ScriptEntry = {
- id: string;
- title: string;
- whyHard: string;
- principles: string[];
- phrases: string[];
- pitfalls: string[];
-};
-
-export const SCRIPT_ENTRIES: readonly ScriptEntry[] = [
- {
- id: "schoolCall",
- title: "Quand l'école appelle pour un comportement",
- whyHard:
- "Un mélange de honte et de culpabilité, pas le temps de préparer une réponse posée.",
- principles: [
- "Demander un fait précis (heure, contexte) plutôt qu'accepter un jugement global",
- "Ne pas s'excuser à la place de l'enfant",
- "Repositionner sur l'aide à apporter, pas sur la sanction",
- ],
- phrases: [
- "Merci de m'appeler. Pour bien comprendre, à quel moment précis cela s'est passé ?",
- "Mon enfant a un TDAH suivi médicalement. Ce que vous décrivez correspond aux moments où il a le plus de difficulté à se réguler. On peut en parler ensemble pour trouver une réponse qui marche pour lui et pour la classe.",
- "Je vais reprendre cela à la maison avec lui, calmement. De votre côté, qu'est-ce qui pourrait l'aider à l'école dans ce type de moment ?",
- ],
- pitfalls: [
- "« Excusez-le, on traverse une période difficile » — minimise la situation et porte la culpabilité seul",
- "« Je vais le punir ce soir » — engage une sanction sans avoir compris ce qui s'est passé",
- ],
- },
- {
- id: "grandparent",
- title: "Expliquer le TDAH à un grand-parent qui minimise",
- whyHard:
- "On a besoin de soutien familial, et on entend « de mon temps on ne faisait pas tant d'histoires ».",
- principles: [
- "Court-circuiter le débat éducatif, ramener au médical",
- "Donner une seule info concrète plutôt qu'un cours sur les neurosciences",
- "Préserver le lien sans céder sur le fond",
- ],
- phrases: [
- "Le TDAH, c'est un fonctionnement neurologique. Ce n'est pas une question d'éducation.",
- "On suit un médecin pour ça. On fait ce qu'on peut, et ce que tu vois n'est pas de la mauvaise volonté.",
- "Tu peux nous aider en l'acceptant tel qu'il est quand on est ensemble. C'est tout ce dont on a besoin pour l'instant.",
- ],
- pitfalls: [
- "Entrer dans le débat sur les neurosciences — épuise et n'aboutit jamais",
- "« Tu ne comprends rien » — coupe un lien dont on aura besoin un jour",
- ],
- },
- {
- id: "caprice",
- title: "Répondre à « c'est juste un caprice »",
- whyHard:
- "Cette phrase nie ce qu'on vit au quotidien. Elle vient souvent de gens qui ne voient l'enfant que deux heures le dimanche.",
- principles: [
- "Pas de débat ouvert avec quelqu'un qui n'écoute pas vraiment",
- "Une phrase de cadrage, puis on change de sujet",
- "Pas d'argument scientifique en réponse à un jugement émotionnel",
- ],
- phrases: [
- "Non, c'est un trouble neurologique diagnostiqué. On en parlera plus longuement une autre fois.",
- "Si tu le voyais 24h/24, tu verrais que ce n'est pas du tout ça.",
- "Je préfère qu'on ne juge pas mon enfant, surtout en sa présence.",
- ],
- pitfalls: [
- "Justifier en détail — c'est ce que la personne attend pour démonter chaque argument",
- "Laisser passer en silence devant l'enfant — il enregistre le manque de soutien",
- ],
- },
- {
- id: "pedopsyPrep",
- title: "Préparer une consultation pédopsy en 10 minutes",
- whyHard:
- "Le rendez-vous est court, on perd l'essentiel sous le stress. On en sort avec l'impression de ne pas avoir tout dit.",
- principles: [
- "Trois sujets maximum, écrits sur papier avant d'entrer",
- "Faits concrets datés, pas d'interprétation",
- "Une question prioritaire pour soi (pas pour l'enfant)",
- ],
- phrases: [
- "Depuis la dernière fois, voici les trois choses qu'on a observées : [liste avec dates].",
- "Ma question prioritaire aujourd'hui, c'est : [une seule].",
- "Avant qu'on se quitte, est-ce qu'il y a un point que vous voulez qu'on surveille d'ici la prochaine fois ?",
- ],
- pitfalls: [
- "Tout dire dans l'ordre où ça vient — dilue le message principal",
- "Parler uniquement des difficultés — on perd la trace des améliorations",
- ],
- },
- {
- id: "announceCondition",
- title: "Annoncer le TDAH à votre enfant",
- whyHard:
- "On a peur de l'étiqueter, ou de lui faire croire qu'il est cassé.",
- principles: [
- "Pas le mot « maladie »",
- "Cadrer comme une explication, pas une fatalité",
- "Lui demander ce qu'il en pense, ne pas faire un monologue",
- ],
- phrases: [
- "Tu sais quand tu n'arrives pas à te concentrer ou à rester assis ? Ce n'est pas que tu ne veux pas. Ton cerveau fonctionne différemment, c'est ce qu'on appelle un TDAH.",
- "Beaucoup d'enfants ont ça. C'est pour ça qu'on va t'aider avec des routines et un suivi médical — pas pour te changer, pour te donner des outils.",
- "Qu'est-ce que ça te fait, ce que je te dis là ?",
- ],
- pitfalls: [
- "« Tu es malade » — fausse représentation, qui s'imprime durablement",
- "En faire un sujet tabou — l'enfant entend le silence",
- ],
- },
- {
- id: "papRequest",
- title: "Demander un PAP/PPRE à l'école",
- whyHard:
- "L'école n'a pas le temps. On doit pousser sans devenir le « parent pénible ».",
- principles: [
- "Demander un rendez-vous écrit, par mail",
- "Citer le médecin — c'est lui qui prescrit l'aménagement",
- "Proposer une réunion de 30 minutes, pas plus",
- ],
- phrases: [
- "Madame, Monsieur, mon enfant est suivi pour un TDAH. Le médecin recommande un PAP. Je souhaiterais qu'on en parle ensemble. Quand seriez-vous disponible pour une réunion de 30 minutes ?",
- "Voici les recommandations du médecin (en pièce jointe). Nous sommes ouverts à vos remarques et à vos contraintes.",
- "Pouvez-vous me confirmer par mail ce qu'on décide ensemble, pour qu'on en garde une trace ?",
- ],
- pitfalls: [
- "Demander oralement à la sortie de l'école — pas de trace, pas de suite",
- "Arriver avec une liste d'exigences fermées — ferme le dialogue dès la première minute",
- ],
- },
- {
- id: "misplacedRemark",
- title: "Gérer une remarque déplacée en famille / au parc",
- whyHard:
- "Sur le moment on est sidéré, on ne sait pas quoi dire, et on s'en veut après.",
- principles: [
- "Une phrase courte, ferme, qu'on apprend par cœur",
- "Protéger l'enfant qui entend",
- "Pas de débat, on coupe court",
- ],
- phrases: [
- "Merci, on gère.",
- "Ce que tu dis là n'est pas vrai pour mon enfant.",
- "Je préfère qu'on ne le juge pas, surtout devant lui.",
- "On en reparlera entre adultes, pas maintenant.",
- ],
- pitfalls: [
- "Se justifier longuement — installe le débat à la place du recadrage",
- "Rire jaune — l'enfant entend l'acquiescement implicite",
- ],
- },
- {
- id: "presentTreatment",
- title: "Présenter le traitement à votre enfant",
- whyHard:
- "On veut qu'il accepte le médicament sans en faire un sujet de honte.",
- principles: [
- "Nommer ce que le médicament aide, pas ce qu'il « corrige »",
- "Comparer à une aide banale (lunettes, asthmateur)",
- "Lui laisser un mot pour exprimer ce qu'il ressent, à tout moment",
- ],
- phrases: [
- "Le médicament aide ton cerveau à faire ce qu'il a du mal à faire seul. C'est un coup de pouce, pas un changement de toi.",
- "Comme des lunettes pour mieux voir : ça ne change pas tes yeux, ça t'aide à voir net.",
- "Si à un moment tu sens que ça te gêne, tu me le dis et on en parle ensemble au médecin.",
- ],
- pitfalls: [
- "« Tu seras plus sage » ou « tu seras plus calme » — confond effet du médicament et identité de l'enfant",
- "En cacher l'existence à l'enfant — il découvrira un jour avec un sentiment de trahison",
- ],
- },
-];
diff --git a/apps/mobile/src/hooks/use-strengths.ts b/apps/mobile/src/hooks/use-strengths.ts
deleted file mode 100644
index 01f38633..00000000
--- a/apps/mobile/src/hooks/use-strengths.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import type {
- CreateStrength,
- Strength,
- UpdateStrength,
-} from "@focusflow/validators";
-import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-
-import { api } from "../lib/api";
-
-// Mirrors apps/web/src/hooks/use-strengths.ts: list a child's strengths and
-// add / edit / remove them. Simple CRUD, fully native.
-const key = (childId: string) => ["strengths", childId] as const;
-
-export function useStrengths(childId: string) {
- return useQuery({
- queryKey: key(childId),
- queryFn: () => api.get(`/strengths/${childId}`),
- enabled: !!childId,
- });
-}
-
-export function useCreateStrength(childId: string) {
- const qc = useQueryClient();
- return useMutation({
- mutationFn: (data: CreateStrength) =>
- api.post("/strengths", data),
- onSettled: () => qc.invalidateQueries({ queryKey: key(childId) }),
- });
-}
-
-export function useUpdateStrength(childId: string) {
- const qc = useQueryClient();
- return useMutation({
- mutationFn: ({ id, ...data }: UpdateStrength & { id: string }) =>
- api.patch(`/strengths/${id}`, data),
- onSettled: () => qc.invalidateQueries({ queryKey: key(childId) }),
- });
-}
-
-export function useDeleteStrength(childId: string) {
- const qc = useQueryClient();
- return useMutation({
- mutationFn: (id: string) => api.delete<{ ok: true }>(`/strengths/${id}`),
- onSettled: () => qc.invalidateQueries({ queryKey: key(childId) }),
- });
-}
diff --git a/apps/mobile/src/navigation/types.ts b/apps/mobile/src/navigation/types.ts
index 10d94faa..388ede3c 100644
--- a/apps/mobile/src/navigation/types.ts
+++ b/apps/mobile/src/navigation/types.ts
@@ -25,7 +25,6 @@ export type RootStackParamList = {
Medications: ChildParams;
CalmMinutes: ChildParams;
Insights: ChildParams;
- Activity: ChildParams;
Report: ChildParams;
// Plus (grouped menu)
PlusMenu: undefined;
@@ -48,15 +47,9 @@ export type RootStackParamList = {
Barkley: ChildParams;
BarkleyStep: ChildParams & { stepNumber: number };
Rewards: ChildParams;
- Decodeur: ChildParams;
- Scripts: ChildParams;
- Strengths: ChildParams;
CrisisList: ChildParams;
- CarePathway: ChildParams;
- Achievements: ChildParams;
// Compte / account
Settings: undefined;
- Burnout: undefined;
Connaissances: undefined;
ConnaissancesArticle: { slug: string; title: string };
};
@@ -86,7 +79,6 @@ export type MedicationsProps = S<"Medications">;
export type JournalProps = S<"Journal">;
export type CalmMinutesProps = S<"CalmMinutes">;
export type InsightsProps = S<"Insights">;
-export type ActivityProps = S<"Activity">;
export type ReportProps = S<"Report">;
export type RoutinesProps = S<"Routines">;
export type AddRoutineProps = S<"AddRoutine">;
@@ -94,14 +86,8 @@ export type EditRoutineProps = S<"EditRoutine">;
export type BarkleyProps = S<"Barkley">;
export type BarkleyStepProps = S<"BarkleyStep">;
export type RewardsProps = S<"Rewards">;
-export type DecodeurProps = S<"Decodeur">;
-export type ScriptsProps = S<"Scripts">;
-export type StrengthsProps = S<"Strengths">;
export type CrisisListProps = S<"CrisisList">;
-export type CarePathwayProps = S<"CarePathway">;
-export type AchievementsProps = S<"Achievements">;
export type SettingsProps = S<"Settings">;
-export type BurnoutProps = S<"Burnout">;
export type ConnaissancesProps = S<"Connaissances">;
export type ConnaissancesArticleProps = S<"ConnaissancesArticle">;
diff --git a/apps/mobile/src/screens/AchievementsScreen.tsx b/apps/mobile/src/screens/AchievementsScreen.tsx
deleted file mode 100644
index ee143458..00000000
--- a/apps/mobile/src/screens/AchievementsScreen.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-import { useEffect, useMemo, useState } from "react";
-import AsyncStorage from "@react-native-async-storage/async-storage";
-import { StyleSheet, Text, View } from "react-native";
-
-import {
- CalloutCard,
- Card,
- Loader,
- Screen,
- ScreenHeader,
- fonts,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import {
- ACHIEVEMENTS,
- useAchievements,
- type AchievementId,
-} from "../hooks/use-achievements";
-import type { AchievementsProps } from "../navigation/types";
-
-export function AchievementsScreen({ navigation, route }: AchievementsProps) {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- const { childId, childName } = route.params;
- const { unlocked, total, isLoading } = useAchievements(childId);
- const unlockedCount = unlocked.size;
- const pct = total > 0 ? Math.round((unlockedCount / total) * 100) : 0;
-
- // One-shot celebration of badges unlocked since the last visit. The seen set
- // is persisted per child so a revisit stays calm (no repeated fanfare).
- const [celebrated, setCelebrated] = useState([]);
- useEffect(() => {
- if (isLoading || unlocked.size === 0) return;
- const key = `toko:achievements:seen:${childId}`;
- let cancelled = false;
- void AsyncStorage.getItem(key).then((raw) => {
- if (cancelled) return;
- const seen = new Set(raw ? JSON.parse(raw) : []);
- const fresh = [...unlocked].filter((id) => !seen.has(id));
- if (fresh.length > 0) setCelebrated(fresh);
- void AsyncStorage.setItem(key, JSON.stringify([...unlocked]));
- });
- return () => {
- cancelled = true;
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isLoading, childId]);
-
- const celebratedBadges = celebrated
- .map((id) => ACHIEVEMENTS.find((b) => b.id === id))
- .filter((b): b is (typeof ACHIEVEMENTS)[number] => !!b);
-
- return (
-
- navigation.goBack()}
- />
-
- {/* One-shot celebration of newly unlocked badges */}
- {celebratedBadges.length > 0 ? (
- 1
- ? `${celebratedBadges.length} nouveaux badges 🎉`
- : "Nouveau badge 🎉"
- }
- >
- {celebratedBadges.map((b) => (
-
- {b.emoji} {b.title}
-
- ))}
-
- ) : null}
-
- {/* Progress summary */}
-
-
-
- {unlockedCount} badge{unlockedCount > 1 ? "s" : ""} sur {total}{" "}
- débloqué{unlockedCount > 1 ? "s" : ""}
-
- {pct} %
-
-
- {/* Progress bar */}
-
-
-
-
-
- Pas de classement, pas de pression — juste un miroir de ce que vous
- avez construit.
-
-
-
- {isLoading ? (
-
- ) : (
- ACHIEVEMENTS.map((badge) => {
- const isUnlocked = unlocked.has(badge.id);
- return (
-
-
-
- {badge.emoji}
-
-
-
- {badge.title}
-
- {badge.description}
-
-
-
- {isUnlocked ? "Débloqué" : "À venir"}
-
-
-
-
- );
- })
- )}
-
- );
-}
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- celebrateLine: { fontSize: 15, color: c.text, fontFamily: fonts.semibold },
- progressCard: {
- gap: 10,
- backgroundColor: c.card,
- borderColor: c.border,
- },
- progressRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- },
- progressLabel: {
- fontSize: 15,
- fontWeight: "600",
- color: c.text,
- },
- progressPct: {
- fontSize: 22,
- fontWeight: "700",
- color: "#7c3aed",
- },
- barTrack: {
- height: 8,
- backgroundColor: c.border,
- borderRadius: 999,
- overflow: "hidden",
- },
- barFill: {
- height: "100%",
- backgroundColor: "#7c3aed",
- borderRadius: 999,
- },
- progressNote: {
- fontSize: 12,
- color: c.muted,
- lineHeight: 16,
- },
- cardUnlocked: {
- backgroundColor: c.card,
- borderColor: c.brand,
- },
- cardLocked: {
- backgroundColor: c.bg,
- borderColor: c.border,
- borderStyle: "dashed",
- },
- badgeRow: {
- flexDirection: "row",
- alignItems: "flex-start",
- gap: 12,
- },
- emoji: {
- fontSize: 32,
- lineHeight: 40,
- },
- emojiLocked: {
- opacity: 0.35,
- },
- badgeContent: {
- flex: 1,
- gap: 2,
- },
- badgeTitle: {
- fontSize: 15,
- fontWeight: "600",
- color: c.text,
- lineHeight: 20,
- },
- badgeTitleLocked: {
- color: c.muted,
- },
- badgeDesc: {
- fontSize: 13,
- color: c.subtext,
- lineHeight: 18,
- },
- pill: {
- paddingHorizontal: 8,
- paddingVertical: 3,
- borderRadius: 999,
- borderWidth: 1,
- },
- pillUnlocked: {
- backgroundColor: c.successSurface,
- borderColor: c.successBorder,
- },
- pillLocked: {
- backgroundColor: "transparent",
- borderColor: c.border,
- },
- pillText: {
- fontSize: 11,
- fontWeight: "600",
- },
- pillTextUnlocked: {
- color: c.successFg,
- },
- pillTextLocked: {
- color: c.muted,
- },
- });
diff --git a/apps/mobile/src/screens/ActivityScreen.tsx b/apps/mobile/src/screens/ActivityScreen.tsx
deleted file mode 100644
index 5b291d50..00000000
--- a/apps/mobile/src/screens/ActivityScreen.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import { useMemo } from "react";
-import { StyleSheet, Text, View } from "react-native";
-
-import {
- Card,
- EmptyState,
- ErrorNote,
- Loader,
- Screen,
- ScreenHeader,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import { useActivity, type AuditEntry, type AuditEntityType } from "../hooks/use-activity";
-import type { ActivityProps } from "../navigation/types";
-
-// ─── Emoji per entity type — avoids importing Lucide icons ───────────────────
-
-const ENTITY_EMOJI: Record = {
- child: "👶",
- symptom: "📊",
- journal: "📖",
- medication: "💊",
- medication_log: "💊",
- crisis_item: "🤝",
- child_access: "🔒",
- child_invitation: "✉️",
- strength: "✨",
- routine: "📋",
- routine_completion: "✅",
- admin_document: "📄",
-};
-
-// ─── Relative time formatter (FR, no external dep) ───────────────────────────
-
-function formatRelativeFr(date: Date): string {
- const diff = Date.now() - date.getTime();
- const min = Math.floor(diff / 60_000);
- if (min < 1) return "à l'instant";
- if (min < 60) return `il y a ${min} min`;
- const hr = Math.floor(min / 60);
- if (hr < 24) return `il y a ${hr} h`;
- const d = Math.floor(hr / 24);
- if (d === 1) return "hier";
- if (d < 7) return `il y a ${d} j`;
- return date.toLocaleDateString("fr-FR", { day: "numeric", month: "short" });
-}
-
-// ─── Single activity row ──────────────────────────────────────────────────────
-
-function ActivityRow({
- entry,
- styles,
-}: {
- entry: AuditEntry;
- styles: ReturnType;
-}) {
- const emoji = ENTITY_EMOJI[entry.entityType] ?? "🔔";
- const actor = entry.actorName ?? "Quelqu'un";
- const text = entry.summary ?? `${entry.entityType} — ${entry.action}`;
- const relative = formatRelativeFr(new Date(entry.createdAt));
-
- return (
-
-
- {emoji}
-
-
- {actor}
- {text}
-
- {relative}
-
-
-
- );
-}
-
-// ─── Screen ───────────────────────────────────────────────────────────────────
-
-export function ActivityScreen({ navigation, route }: ActivityProps) {
- const { childId, childName } = route.params;
- const { data, isLoading, isError } = useActivity(childId, 100);
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- return (
-
- navigation.goBack()}
- />
-
- {isError ? (
-
- ) : isLoading ? (
-
- ) : data && data.length > 0 ? (
- data.map((entry) => )
- ) : (
-
- )}
-
- );
-}
-
-// ─── Styles ───────────────────────────────────────────────────────────────────
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- row: {
- // Card already has padding + gap; we just tweak internals
- },
- rowInner: {
- flexDirection: "row",
- alignItems: "flex-start",
- gap: 12,
- },
- emoji: {
- fontSize: 22,
- lineHeight: 28,
- },
- rowBody: {
- flex: 1,
- gap: 2,
- },
- rowText: {
- fontSize: 14,
- color: c.text,
- lineHeight: 20,
- },
- actor: {
- fontWeight: "600",
- color: c.text,
- },
- summary: {
- color: c.subtext,
- },
- time: {
- fontSize: 12,
- color: c.muted,
- },
- });
diff --git a/apps/mobile/src/screens/BurnoutScreen.tsx b/apps/mobile/src/screens/BurnoutScreen.tsx
deleted file mode 100644
index 4a05be11..00000000
--- a/apps/mobile/src/screens/BurnoutScreen.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import { useMemo, useState } from "react";
-import { Linking, Pressable, StyleSheet, Text, View } from "react-native";
-
-import {
- Button,
- CalloutCard,
- Card,
- Screen,
- ScreenHeader,
- fonts,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import type { BurnoutProps } from "../navigation/types";
-
-// Parental burn-out self-assessment, ported from the PWA /burnout (Roskam &
-// Mikolajczak inspired). 7 questions on a 0–3 scale, total /21, three zones.
-// Client-side only — nothing is stored (a guilt-free mirror, not a diagnosis).
-// The parent mood logger (1–5) lives on the Home dashboard, not here.
-const QUESTIONS = [
- "Je me sens épuisé·e dès le matin quand je pense à la journée avec mon enfant.",
- "À la fin de la journée, mes ressources émotionnelles sont vides.",
- "Je me sens distant·e affectivement avec mon enfant, comme en retrait.",
- "Je ne reconnais plus le parent que j'étais avant.",
- "Je culpabilise souvent d'être un·e mauvais·e parent.",
- "Je n'ai plus d'énergie pour les moments simples : jeu, câlin, rire.",
- "Je pense parfois que je ne peux plus assumer ce rôle.",
-];
-const SCALE = ["Jamais", "Parfois", "Souvent", "Tout le temps"];
-
-type Zone = "green" | "orange" | "red";
-function zoneFromScore(score: number): Zone {
- if (score <= 6) return "green";
- if (score <= 13) return "orange";
- return "red";
-}
-const ZONE = {
- green: {
- variant: "success" as const,
- label: "Vous tenez bon",
- title: "Pas de signaux d'épuisement marqués.",
- body: "Vous traversez la parentalité TDAH avec des ressources. Ne sous-estimez pas pour autant la fatigue : prenez les pauses dont vous avez besoin, même quand tout semble aller.",
- },
- orange: {
- variant: "alert" as const,
- label: "Fatigue notable",
- title: "Des signes de fatigue qui méritent attention.",
- body: "Ce que vous ressentez est réel, et c'est le bon moment pour ralentir. Diminuez ce qui peut l'être, parlez-en à un proche, et envisagez d'en parler à votre médecin si la fatigue s'installe.",
- },
- red: {
- variant: "alert" as const,
- label: "Signaux forts",
- title: "Vous portez beaucoup. Vous n'êtes pas seul·e.",
- body: "Ce que vous traversez ressemble à un épuisement parental significatif. Ce n'est pas une faiblesse, ce n'est pas votre responsabilité. C'est un signal d'alerte qui mérite un échange avec un professionnel — médecin traitant, psychologue, ou un des numéros d'écoute ci-dessous.",
- },
-};
-
-const SUPPORT = [
- { label: "3114 — Prévention du suicide", hint: "Gratuit, 24h/24, anonyme", url: "tel:3114" },
- { label: "Allô Parents Bébé", hint: "0 800 235 236 · écoute parentale", url: "tel:0800235236" },
- { label: "HyperSupers TDAH France", hint: "Soutien et orientation TDAH", url: "https://www.tdah-france.fr/" },
-];
-
-export function BurnoutScreen({ navigation }: BurnoutProps) {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- const [answers, setAnswers] = useState<(number | null)[]>(
- () => QUESTIONS.map(() => null),
- );
- const [submitted, setSubmitted] = useState(false);
-
- const total = answers.reduce((sum, v) => sum + (v ?? 0), 0);
- const allAnswered = answers.every((a) => a !== null);
-
- function setAnswer(i: number, v: number) {
- setAnswers((prev) => {
- const next = [...prev];
- next[i] = v;
- return next;
- });
- }
- function reset() {
- setAnswers(QUESTIONS.map(() => null));
- setSubmitted(false);
- }
-
- if (submitted) {
- const zone = ZONE[zoneFromScore(total)];
- return (
-
- navigation.goBack()}
- />
-
- Score : {total} sur 21
- {zone.title}
- {zone.body}
-
-
-
- Vous pouvez en parler maintenant
-
- Ces lignes sont gratuites, anonymes et ouvertes aux parents en
- difficulté. Vous n'avez pas besoin d'avoir trouvé les bons mots pour
- appeler.
-
- {SUPPORT.map((s) => (
- Linking.openURL(s.url)}
- style={styles.supportRow}
- accessibilityRole="button"
- >
- {s.label}
- {s.hint}
-
- ))}
-
-
-
-
- );
- }
-
- return (
-
- navigation.goBack()}
- />
-
- Sept questions courtes pour mettre des mots sur ce que vous traversez.
- Aucun jugement, aucune mémorisation de votre réponse.
-
-
-
-
- Ce test n'est pas un diagnostic médical. C'est un miroir : il vous aide
- à reconnaître ce que vous ressentez. Seul un médecin ou un psychologue
- peut évaluer un burn-out parental.
-
-
-
- Au cours des deux dernières semaines
-
- {QUESTIONS.map((q, i) => (
-
-
- {i + 1}. {q}
-
-
- {SCALE.map((label, v) => {
- const on = answers[i] === v;
- return (
- setAnswer(i, v)}
- style={[styles.scaleChip, on && styles.scaleChipOn]}
- accessibilityRole="button"
- accessibilityState={{ selected: on }}
- >
-
- {label}
-
-
- );
- })}
-
-
- ))}
-
-
- );
-}
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- subtitle: { fontSize: 14, color: c.muted, fontFamily: fonts.body, lineHeight: 20 },
- disclaimer: { fontSize: 13, color: c.infoFg, fontFamily: fonts.body, lineHeight: 19 },
- formTitle: { fontSize: 16, color: c.text, fontFamily: fonts.semibold, marginTop: 4 },
- question: { fontSize: 15, color: c.text, fontFamily: fonts.medium, lineHeight: 21 },
- scaleRow: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 4 },
- scaleChip: {
- flexGrow: 1,
- paddingVertical: 9,
- paddingHorizontal: 10,
- borderRadius: 10,
- borderWidth: 1,
- borderColor: c.border,
- alignItems: "center",
- },
- scaleChipOn: { backgroundColor: c.brand, borderColor: c.brand },
- scaleText: { fontSize: 13, color: c.subtext, fontFamily: fonts.medium },
- scaleTextOn: { color: "#fff", fontFamily: fonts.semibold },
- score: { fontSize: 15, color: c.text, fontFamily: fonts.bold },
- zoneTitle: { fontSize: 16, color: c.text, fontFamily: fonts.semibold },
- zoneBody: { fontSize: 14, color: c.subtext, fontFamily: fonts.body, lineHeight: 21 },
- supportTitle: { fontSize: 16, color: c.text, fontFamily: fonts.semibold },
- supportBody: { fontSize: 13, color: c.muted, fontFamily: fonts.body, lineHeight: 19 },
- supportRow: {
- paddingVertical: 8,
- borderTopWidth: StyleSheet.hairlineWidth,
- borderTopColor: c.border,
- },
- supportLink: { fontSize: 15, color: c.brand, fontFamily: fonts.semibold },
- supportHint: { fontSize: 12, color: c.muted, fontFamily: fonts.body, marginTop: 1 },
- });
diff --git a/apps/mobile/src/screens/CarePathwayScreen.tsx b/apps/mobile/src/screens/CarePathwayScreen.tsx
deleted file mode 100644
index 3016a84e..00000000
--- a/apps/mobile/src/screens/CarePathwayScreen.tsx
+++ /dev/null
@@ -1,400 +0,0 @@
-import type { CareStepStatus } from "@focusflow/validators";
-import { useMemo } from "react";
-import { Linking, Pressable, StyleSheet, Text, View } from "react-native";
-import { ExternalLink } from "lucide-react-native";
-
-import {
- Card,
- EmptyState,
- ErrorNote,
- Loader,
- Screen,
- ScreenHeader,
- fonts,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import {
- useCarePathwayProgress,
- useUpsertCarePathwayStep,
-} from "../hooks/use-care-pathway";
-import type { CarePathwayProps } from "../navigation/types";
-
-// ─── Static step catalogue (stable IDs — must match DB stepId values) ────────
-type Phase = "screening" | "diagnosis" | "support";
-
-interface Step {
- id: string;
- phase: Phase;
- emoji: string;
- title: string;
- description: string;
- externalLink?: { href: string; label: string };
-}
-
-const STEPS: Step[] = [
- // Phase 1 — Repérage
- {
- id: "school_signal",
- phase: "screening",
- emoji: "🏫",
- title: "Signal scolaire",
- description: "L'enseignant ou le directeur a signalé des difficultés.",
- },
- {
- id: "gp_consultation",
- phase: "screening",
- emoji: "🩺",
- title: "Consultation médecin",
- description: "Premier rendez-vous avec le médecin généraliste ou pédiatre.",
- },
- {
- id: "ent_audition",
- phase: "screening",
- emoji: "👂",
- title: "Bilan auditif (ORL)",
- description: "Vérification de l'audition pour écarter une cause ORL.",
- },
- {
- id: "ophtalmo_vision",
- phase: "screening",
- emoji: "👁️",
- title: "Bilan visuel",
- description: "Consultation ophtalmologique pour écarter une cause visuelle.",
- },
- {
- id: "sleep_study",
- phase: "screening",
- emoji: "🌙",
- title: "Bilan du sommeil",
- description: "Évaluation des troubles du sommeil pouvant mimer le TDAH.",
- externalLink: {
- href: "https://www.has-sante.fr/jcms/c_2025618",
- label: "Recommandation HAS",
- },
- },
- {
- id: "speech_therapy_assessment",
- phase: "screening",
- emoji: "💬",
- title: "Bilan orthophonique",
- description: "Évaluation du langage oral et écrit.",
- },
- {
- id: "psychomotor_assessment",
- phase: "screening",
- emoji: "🤸",
- title: "Bilan psychomoteur",
- description: "Évaluation de la coordination et de la motricité.",
- },
-
- // Phase 2 — Diagnostic
- {
- id: "neuropsy_assessment",
- phase: "diagnosis",
- emoji: "🧠",
- title: "Bilan neuropsychologique",
- description: "Tests cognitifs et comportementaux réalisés par un psychologue spécialisé.",
- },
- {
- id: "specialist_consultation",
- phase: "diagnosis",
- emoji: "👨⚕️",
- title: "Consultation spécialiste",
- description: "Rendez-vous avec neuropédiatre ou pédopsychiatre.",
- },
- {
- id: "diagnosis_announcement",
- phase: "diagnosis",
- emoji: "📋",
- title: "Annonce du diagnostic",
- description: "Le médecin confirme (ou non) le TDAH et explique le diagnostic.",
- },
- {
- id: "second_opinion",
- phase: "diagnosis",
- emoji: "🤝",
- title: "Deuxième avis",
- description: "Consultation d'un second spécialiste si vous avez des doutes.",
- },
-
- // Phase 3 — Soutien
- {
- id: "mdph_application",
- phase: "support",
- emoji: "📝",
- title: "Dossier MDPH",
- description: "Dépôt du dossier auprès de la Maison Départementale des Personnes Handicapées.",
- externalLink: { href: "https://www.mdph.fr/", label: "Trouver ma MDPH" },
- },
- {
- id: "aeeh_request",
- phase: "support",
- emoji: "💶",
- title: "Demande AEEH",
- description: "Allocation d'Éducation de l'Enfant Handicapé auprès de la CAF.",
- externalLink: {
- href: "https://www.service-public.fr/particuliers/vosdroits/F14809",
- label: "Voir sur service-public.fr",
- },
- },
- {
- id: "pch_request",
- phase: "support",
- emoji: "🛟",
- title: "Demande PCH",
- description: "Prestation de Compensation du Handicap si éligible.",
- externalLink: {
- href: "https://www.service-public.fr/particuliers/vosdroits/F14202",
- label: "Voir sur service-public.fr",
- },
- },
- {
- id: "school_pap_pps",
- phase: "support",
- emoji: "🎒",
- title: "PAP / PPS scolaire",
- description: "Plan d'Accompagnement Personnalisé ou Projet Personnalisé de Scolarisation.",
- },
- {
- id: "occupational_therapy",
- phase: "support",
- emoji: "✋",
- title: "Ergothérapie",
- description: "Suivi en ergothérapie pour les difficultés pratiques du quotidien.",
- },
- {
- id: "ongoing_followup",
- phase: "support",
- emoji: "🔄",
- title: "Suivi régulier",
- description: "Rendez-vous de suivi pluridisciplinaire au fil du temps.",
- },
-];
-
-const PHASES: { id: Phase; label: string; emoji: string }[] = [
- { id: "screening", label: "Repérage", emoji: "🔍" },
- { id: "diagnosis", label: "Diagnostic", emoji: "🩺" },
- { id: "support", label: "Soutien", emoji: "🤝" },
-];
-
-const stepsByPhase = (phase: Phase) => STEPS.filter((s) => s.phase === phase);
-
-// ─── Status helpers ───────────────────────────────────────────────────────────
-const STATUS_LABELS: Record = {
- todo: "À faire",
- doing: "En cours",
- done: "Fait ✓",
-};
-
-function statusColor(status: CareStepStatus, c: Palette): string {
- if (status === "done") return c.success;
- if (status === "doing") return c.action;
- return c.muted;
-}
-
-function nextStatus(current: CareStepStatus): CareStepStatus {
- if (current === "todo") return "doing";
- if (current === "doing") return "done";
- return "todo";
-}
-
-// ─── Step card ────────────────────────────────────────────────────────────────
-function StepCard({
- step,
- status,
- onToggle,
- isPending,
- styles,
- palette,
-}: {
- step: Step;
- status: CareStepStatus;
- onToggle: () => void;
- isPending: boolean;
- styles: ReturnType;
- palette: Palette;
-}) {
- const color = statusColor(status, palette);
- return (
-
-
- {step.emoji}
-
-
- {step.title}
-
- {step.description}
-
-
-
-
- {STATUS_LABELS[status]}
-
-
- → {STATUS_LABELS[nextStatus(status)]}
-
-
- {step.externalLink ? (
- Linking.openURL(step.externalLink!.href)}
- style={styles.linkRow}
- accessibilityRole="link"
- accessibilityLabel={step.externalLink.label}
- >
-
- {step.externalLink.label}
-
- ) : null}
-
- );
-}
-
-// ─── Screen ───────────────────────────────────────────────────────────────────
-export function CarePathwayScreen({ navigation, route }: CarePathwayProps) {
- const { childId, childName } = route.params;
- const list = useCarePathwayProgress(childId);
- const upsert = useUpsertCarePathwayStep(childId);
-
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- const progressMap = new Map(
- (list.data ?? []).map((p) => [p.stepId, p.status as CareStepStatus]),
- );
-
- const completedCount = STEPS.filter(
- (s) => progressMap.get(s.id) === "done",
- ).length;
- const pct = Math.round((completedCount / STEPS.length) * 100);
-
- function toggle(stepId: string) {
- const current = progressMap.get(stepId) ?? "todo";
- upsert.mutate({ childId, stepId, status: nextStatus(current) });
- }
-
- return (
-
- navigation.goBack()}
- />
-
- {/* Progress summary */}
-
-
-
- {completedCount} / {STEPS.length} étapes terminées
-
- {pct}%
-
-
-
-
-
- Ce parcours est indicatif — chaque enfant est différent.
-
-
-
- {upsert.isError ? (
-
- ) : null}
-
- {list.isLoading ? (
-
- ) : list.isError ? (
-
- ) : (
- PHASES.map((phase) => (
-
-
- {phase.emoji}
- {phase.label}
-
- {stepsByPhase(phase.id).map((step) => {
- const status = progressMap.get(step.id) ?? "todo";
- return (
- toggle(step.id)}
- isPending={upsert.isPending}
- styles={styles}
- palette={c}
- />
- );
- })}
-
- ))
- )}
-
- );
-}
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- progressRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- },
- progressLabel: { fontSize: 15, fontWeight: "600", color: c.text },
- progressPct: { fontSize: 22, fontWeight: "700", color: c.brand },
- progressTrack: {
- height: 8,
- backgroundColor: c.border,
- borderRadius: 999,
- overflow: "hidden",
- },
- progressFill: {
- height: "100%",
- backgroundColor: c.brand,
- borderRadius: 999,
- },
- disclaimer: { fontSize: 12, color: c.muted },
- phase: { gap: 10 },
- phaseHeader: {
- flexDirection: "row",
- alignItems: "center",
- gap: 8,
- paddingTop: 4,
- },
- phaseEmoji: { fontSize: 20 },
- phaseLabel: { fontSize: 17, fontWeight: "700", color: c.text },
- stepRow: { flexDirection: "row", gap: 12, alignItems: "flex-start" },
- stepEmoji: { fontSize: 24, lineHeight: 28 },
- stepBody: { flex: 1 },
- stepTitle: { fontSize: 15, fontWeight: "600", color: c.text },
- stepTitleDone: { color: c.success },
- stepDesc: { fontSize: 13, color: c.subtext, lineHeight: 18, marginTop: 2 },
- cardDone: { borderColor: c.success, opacity: 0.9 },
- statusBtn: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
- borderWidth: 1,
- borderRadius: 8,
- paddingHorizontal: 12,
- paddingVertical: 8,
- marginTop: 4,
- },
- statusLabel: { fontSize: 13, fontWeight: "600" },
- statusHint: { fontSize: 12, opacity: 0.7 },
- linkRow: {
- flexDirection: "row",
- alignItems: "center",
- gap: 6,
- paddingVertical: 8,
- marginTop: 2,
- },
- linkText: { fontSize: 14, color: c.action, fontFamily: fonts.semibold },
- });
diff --git a/apps/mobile/src/screens/DecodeurScreen.tsx b/apps/mobile/src/screens/DecodeurScreen.tsx
deleted file mode 100644
index 42bd49d2..00000000
--- a/apps/mobile/src/screens/DecodeurScreen.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { useMemo, useState } from "react";
-import { StyleSheet, Text, TextInput, View } from "react-native";
-
-import {
- Card,
- EmptyState,
- Screen,
- ScreenHeader,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import {
- BEHAVIOR_ENTRIES,
- filterEntries,
-} from "../hooks/use-decodeur";
-import type { DecodeurProps } from "../navigation/types";
-
-export function DecodeurScreen({ navigation }: DecodeurProps) {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- const [query, setQuery] = useState("");
- const matches = filterEntries(BEHAVIOR_ENTRIES, query);
-
- return (
-
- navigation.goBack()}
- />
-
- {/* Disclaimer */}
-
-
- Outil pédagogique — ne remplace pas un avis professionnel. Chaque
- enfant reste unique.
-
-
-
- {/* Search */}
-
-
- {/* Results */}
- {matches.length === 0 ? (
-
- ) : (
- matches.map((entry) => (
-
- {/* Behaviour */}
- {entry.behavior}
-
- {/* Explanation */}
-
- Ce qui se passe dans son cerveau
- {entry.explanation}
-
-
- {/* Tip */}
-
-
- Ce qui peut aider
-
- {entry.tip}
-
-
- ))
- )}
-
- );
-}
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- info: {
- backgroundColor: c.infoSurface,
- borderColor: c.infoBorder,
- },
- infoText: {
- fontSize: 13,
- color: c.infoFg,
- lineHeight: 18,
- },
- input: {
- borderWidth: 1,
- borderColor: c.border,
- borderRadius: 10,
- padding: 12,
- fontSize: 16,
- color: c.text,
- backgroundColor: c.card,
- },
- behavior: {
- fontSize: 16,
- fontWeight: "600",
- color: c.text,
- lineHeight: 22,
- },
- section: {
- gap: 4,
- },
- sectionLabel: {
- fontSize: 11,
- fontWeight: "600",
- color: c.alertFg,
- textTransform: "uppercase",
- letterSpacing: 0.5,
- },
- tipLabel: {
- color: c.success,
- },
- sectionBody: {
- fontSize: 14,
- color: c.subtext,
- lineHeight: 20,
- },
- });
diff --git a/apps/mobile/src/screens/PlusMenuScreen.tsx b/apps/mobile/src/screens/PlusMenuScreen.tsx
index 23aaadc9..f4b0d92d 100644
--- a/apps/mobile/src/screens/PlusMenuScreen.tsx
+++ b/apps/mobile/src/screens/PlusMenuScreen.tsx
@@ -2,21 +2,14 @@ import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import * as WebBrowser from "expo-web-browser";
import {
- Activity,
- Award,
Book,
- Brain,
HandHeart,
- HeartPulse,
Leaf,
Library,
LogOut,
- MessageSquareText,
Pill,
MessagesSquare,
Settings as SettingsIcon,
- Sparkles,
- Stethoscope,
Timer,
Trophy,
TrendingUp,
@@ -69,22 +62,16 @@ export function PlusMenuScreen({ navigation }: PlusMenuProps) {
Ressources
-
-
Suivi
-
-
Soins
-
-
Communauté
Compte
-
{billing.isSuccess && !isPremium ? (
diff --git a/apps/mobile/src/screens/ScriptsScreen.tsx b/apps/mobile/src/screens/ScriptsScreen.tsx
deleted file mode 100644
index e0a01a18..00000000
--- a/apps/mobile/src/screens/ScriptsScreen.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-import { useMemo, useState } from "react";
-import { Pressable, Share, StyleSheet, Text, View } from "react-native";
-
-import {
- Card,
- Screen,
- ScreenHeader,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import { SCRIPT_ENTRIES } from "../hooks/use-scripts";
-import type { ScriptsProps } from "../navigation/types";
-
-export function ScriptsScreen({ navigation }: ScriptsProps) {
- return (
-
- navigation.goBack()}
- />
-
- {/* Disclaimer */}
-
-
- {SCRIPT_ENTRIES.map((entry) => (
-
- ))}
-
- );
-}
-
-function InfoCard() {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
- return (
-
-
- Ces scripts sont des points de départ, pas des recettes. Adaptez-les
- à votre ton et à votre énergie du jour.
-
-
- );
-}
-
-function ScriptCard({
- entry,
-}: {
- entry: (typeof SCRIPT_ENTRIES)[number];
-}) {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
- const [expanded, setExpanded] = useState(false);
-
- return (
-
- {/* Header — always visible */}
- setExpanded((v) => !v)} hitSlop={8}>
-
- {entry.title}
- {expanded ? "▲" : "▼"}
-
- {entry.whyHard}
-
-
- {expanded ? (
- <>
- {/* Principles */}
-
-
- {/* Phrases prêtes */}
-
-
- Phrases prêtes
-
- {entry.phrases.map((phrase) => (
-
- ))}
-
-
- {/* Pitfalls */}
-
- >
- ) : null}
-
- );
-}
-
-function Section({
- label,
- labelColor,
- items,
- bullet,
-}: {
- label: string;
- labelColor: string;
- items: string[];
- bullet: string;
-}) {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
- return (
-
- {label}
- {items.map((item) => (
-
- {bullet}
- {item}
-
- ))}
-
- );
-}
-
-function PhraseRow({ phrase }: { phrase: string }) {
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- function handleShare() {
- Share.share({ message: phrase }).catch(() => {
- // Share cancelled or unavailable — silent.
- });
- }
-
- return (
-
- {phrase}
- Partager
-
- );
-}
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- info: {
- backgroundColor: c.infoSurface,
- borderColor: c.infoBorder,
- },
- infoText: {
- fontSize: 13,
- color: c.infoFg,
- lineHeight: 18,
- },
- cardHeader: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "flex-start",
- gap: 8,
- },
- cardTitle: {
- flex: 1,
- fontSize: 16,
- fontWeight: "600",
- color: c.text,
- lineHeight: 22,
- },
- chevron: {
- fontSize: 12,
- color: c.muted,
- marginTop: 4,
- },
- whyHard: {
- fontSize: 13,
- color: c.muted,
- lineHeight: 18,
- marginTop: 2,
- },
- section: {
- gap: 6,
- paddingTop: 4,
- borderTopWidth: 1,
- borderTopColor: c.border,
- marginTop: 4,
- },
- sectionLabel: {
- fontSize: 11,
- fontWeight: "600",
- textTransform: "uppercase",
- letterSpacing: 0.5,
- },
- bulletRow: {
- flexDirection: "row",
- gap: 6,
- alignItems: "flex-start",
- },
- bullet: {
- fontSize: 14,
- fontWeight: "700",
- lineHeight: 20,
- width: 14,
- textAlign: "center",
- },
- bulletText: {
- flex: 1,
- fontSize: 13,
- color: c.subtext,
- lineHeight: 20,
- },
- phraseRow: {
- backgroundColor: c.bg,
- borderRadius: 8,
- padding: 10,
- gap: 4,
- },
- phraseText: {
- fontSize: 14,
- color: c.text,
- lineHeight: 20,
- },
- shareHint: {
- fontSize: 11,
- color: c.action,
- fontWeight: "500",
- },
- });
diff --git a/apps/mobile/src/screens/StrengthsScreen.tsx b/apps/mobile/src/screens/StrengthsScreen.tsx
deleted file mode 100644
index 6ee6315f..00000000
--- a/apps/mobile/src/screens/StrengthsScreen.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-import type { Strength, StrengthCategory } from "@focusflow/validators";
-import { useMemo, useState } from "react";
-import { Pressable, StyleSheet, Text, TextInput, View } from "react-native";
-import { Pencil, Trash2 } from "lucide-react-native";
-
-import {
- Card,
- EmptyState,
- ErrorNote,
- Loader,
- PrimaryButton,
- Screen,
- ScreenHeader,
- confirmDelete,
-} from "../components/ui";
-import { useTheme, type Palette } from "../lib/theme";
-import {
- useCreateStrength,
- useDeleteStrength,
- useStrengths,
- useUpdateStrength,
-} from "../hooks/use-strengths";
-import type { StrengthsProps } from "../navigation/types";
-
-const CATEGORIES: { value: StrengthCategory; label: string; emoji: string }[] =
- [
- { value: "talent", label: "Talent", emoji: "🌟" },
- { value: "achievement", label: "Réussite", emoji: "🏆" },
- { value: "quality", label: "Qualité", emoji: "💎" },
- { value: "progress", label: "Progrès", emoji: "📈" },
- ];
-
-const categoryEmoji = (c: StrengthCategory) =>
- CATEGORIES.find((x) => x.value === c)?.emoji ?? "✨";
-const categoryLabel = (c: StrengthCategory) =>
- CATEGORIES.find((x) => x.value === c)?.label ?? c;
-
-function todayISO() {
- return new Date().toISOString().slice(0, 10);
-}
-
-export function StrengthsScreen({ navigation, route }: StrengthsProps) {
- const { childId, childName } = route.params;
- const list = useStrengths(childId);
- const create = useCreateStrength(childId);
- const update = useUpdateStrength(childId);
- const remove = useDeleteStrength(childId);
-
- const c = useTheme();
- const styles = useMemo(() => makeStyles(c), [c]);
-
- const [adding, setAdding] = useState(false);
- const [editing, setEditing] = useState(null);
- const [title, setTitle] = useState("");
- const [description, setDescription] = useState("");
- const [emoji, setEmoji] = useState("");
- const [category, setCategory] = useState("talent");
-
- const isEditing = !!editing;
- const pending = create.isPending || update.isPending;
-
- function reset() {
- setEditing(null);
- setTitle("");
- setDescription("");
- setEmoji("");
- setCategory("talent");
- setAdding(false);
- }
-
- function startCreate() {
- if (adding && !isEditing) {
- reset();
- } else {
- setEditing(null);
- setTitle("");
- setDescription("");
- setEmoji("");
- setCategory("talent");
- setAdding(true);
- }
- }
-
- function startEdit(s: Strength) {
- setEditing(s);
- setTitle(s.title);
- setDescription(s.description ?? "");
- setEmoji(s.emoji ?? "");
- setCategory(s.category);
- setAdding(true);
- }
-
- function submit() {
- if (!title.trim()) return;
- if (isEditing && editing) {
- update.mutate(
- {
- id: editing.id,
- category,
- title: title.trim(),
- description: description.trim() || undefined,
- emoji: emoji.trim() || undefined,
- },
- { onSuccess: reset },
- );
- } else {
- create.mutate(
- {
- childId,
- category,
- title: title.trim(),
- description: description.trim() || undefined,
- emoji: emoji.trim() || undefined,
- occurredOn: todayISO(),
- },
- { onSuccess: reset },
- );
- }
- }
-
- return (
-
- navigation.goBack()}
- right={
-
-
- {adding && !isEditing ? "Fermer" : "+ Ajouter"}
-
-
- }
- />
-
- {adding ? (
-
-
- {isEditing ? "Modifier la force" : "Nouvelle force"}
-
-
-
-
-
- {CATEGORIES.map((cat) => {
- const on = cat.value === category;
- return (
- setCategory(cat.value)}
- style={[styles.pill, on && styles.pillOn]}
- >
-
- {cat.emoji} {cat.label}
-
-
- );
- })}
-
- {create.isError || update.isError ? (
-
- ) : null}
-
- {isEditing ? (
-
- Annuler
-
- ) : null}
-
- ) : null}
-
- {list.isLoading ? (
-
- ) : list.data && list.data.length > 0 ? (
- list.data.map((s) => (
-
-
-
- {s.emoji ?? categoryEmoji(s.category)}
-
-
- {s.title}
-
- {categoryLabel(s.category)}
-
-
-
- startEdit(s)}
- style={styles.iconBtn}
- accessibilityRole="button"
- accessibilityLabel="Modifier cette force"
- hitSlop={8}
- >
-
-
- confirmDelete(() => remove.mutate(s.id))}
- style={styles.iconBtn}
- accessibilityRole="button"
- accessibilityLabel="Supprimer cette force"
- hitSlop={8}
- >
-
-
-
-
- {s.description ? (
- {s.description}
- ) : null}
-
- ))
- ) : (
-
- )}
-
- );
-}
-
-const makeStyles = (c: Palette) =>
- StyleSheet.create({
- add: { color: c.action, fontSize: 16, fontWeight: "600" },
- formTitle: { fontSize: 16, fontWeight: "600", color: c.text },
- input: {
- borderWidth: 1,
- borderColor: c.border,
- borderRadius: 10,
- padding: 12,
- fontSize: 16,
- color: c.text,
- backgroundColor: c.card,
- },
- multiline: {
- minHeight: 80,
- textAlignVertical: "top",
- },
- pills: { flexDirection: "row", flexWrap: "wrap", gap: 8 },
- pill: {
- paddingHorizontal: 14,
- paddingVertical: 8,
- borderRadius: 999,
- borderWidth: 1,
- borderColor: c.border,
- },
- pillOn: { backgroundColor: c.brand, borderColor: c.brand },
- pillText: { color: c.subtext },
- pillTextOn: { color: "#fff", fontWeight: "600" },
- cardHead: { flexDirection: "row", alignItems: "center", gap: 12 },
- cardActions: { flexDirection: "row", alignItems: "center", marginRight: -10 },
- cancelRow: { alignItems: "center", paddingVertical: 8 },
- cancelText: { color: c.muted },
- iconBtn: { width: 44, height: 44, alignItems: "center", justifyContent: "center" },
- cardEmoji: { fontSize: 28 },
- cardBody: { flex: 1 },
- name: { fontSize: 17, fontWeight: "600", color: c.text },
- meta: { color: c.subtext, fontSize: 13 },
- description: { color: c.subtext, fontSize: 14, lineHeight: 20 },
- delete: { color: c.danger, marginTop: 4 },
- });