From 3e9e7081162378e8d96b39c9d6b92a302796caa3 Mon Sep 17 00:00:00 2001 From: pr-daaif Date: Sat, 1 Aug 2026 22:09:17 +0100 Subject: [PATCH] =?UTF-8?q?fix(mobile):=20d=C3=A9connexion=20accessible=20?= =?UTF-8?q?depuis=20tous=20les=20onglets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug remonté par le référent : une fois authentifié, impossible de revenir en arrière ou de se déconnecter. Seule « Ma journée » portait ce contrôle (onLongPress non découvrable) ; les 3 autres onglets n'avaient rien. EnteteTabs() factorise l'entête (composants/ui.tsx) : tap simple + confirmation Alert, rôle affiché = celui du compte connecté (plus de libellé "Technicien" figé — le mobile n'est plus réservé aux techniciens). Réutilisée dans les 4 onglets. Co-Authored-By: Claude Sonnet 5 --- apps/mobile/app/(tabs)/journee.tsx | 57 ++------------------- apps/mobile/app/(tabs)/preventif.tsx | 3 +- apps/mobile/app/(tabs)/scanner.tsx | 3 +- apps/mobile/app/(tabs)/synchro.tsx | 3 +- apps/mobile/src/composants/ui.tsx | 76 +++++++++++++++++++++++++++- 5 files changed, 84 insertions(+), 58 deletions(-) diff --git a/apps/mobile/app/(tabs)/journee.tsx b/apps/mobile/app/(tabs)/journee.tsx index 884de68..7c1081d 100644 --- a/apps/mobile/app/(tabs)/journee.tsx +++ b/apps/mobile/app/(tabs)/journee.tsx @@ -10,7 +10,8 @@ import { type WorkOrderStatus, } from '@siop/shared'; import { api, unwrap } from '@/api/client'; -import { useHorsLigne, useLogout, useMe } from '@/auth/session'; +import { useHorsLigne } from '@/auth/session'; +import { EnteteTabs } from '@/composants/ui'; import { triJournee } from '@/lib/journee'; import { useTokens, type Tokens } from '@/theme/tokens'; @@ -43,9 +44,7 @@ const STRIE_PRIORITE: Record string> = { export default function PageJournee() { const t = useTokens(); const horsLigne = useHorsLigne(); - const { data: me } = useMe(); const { data: workOrders, refetch, isFetching, dataUpdatedAt } = useWorkOrders(); - const logout = useLogout(); const ots = triJournee(workOrders ?? []); const urgences = ots.filter((o) => o.priority === 'PERSON_TRAPPED'); @@ -55,57 +54,7 @@ export default function PageJournee() { return ( - {/* Entête app : marque + pastille synchro + compte */} - - - - SIOP - - - Technicien - - - - {horsLigne ? 'Hors-ligne' : 'Synchro à jour'} - - { - void logout().then(() => router.replace('/connexion')); - }} - style={{ - width: 26, - height: 26, - borderRadius: 13, - backgroundColor: t.stEncours, - alignItems: 'center', - justifyContent: 'center', - }} - > - - {(me?.displayName ?? '·') - .split(' ') - .map((m) => m[0]) - .join('') - .slice(0, 2) - .toUpperCase()} - - - - + diff --git a/apps/mobile/app/(tabs)/preventif.tsx b/apps/mobile/app/(tabs)/preventif.tsx index 7567bae..62056e2 100644 --- a/apps/mobile/app/(tabs)/preventif.tsx +++ b/apps/mobile/app/(tabs)/preventif.tsx @@ -2,7 +2,7 @@ import { router } from 'expo-router'; import { FlatList, Pressable, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useWorkOrders } from '@/api/exploitation'; -import { ChipStatut } from '@/composants/ui'; +import { ChipStatut, EnteteTabs } from '@/composants/ui'; import { useTokens } from '@/theme/tokens'; /** Onglet Préventif : mes grilles du moment — chaque tuile mène à la @@ -18,6 +18,7 @@ export default function PagePreventif() { return ( + Préventif diff --git a/apps/mobile/app/(tabs)/scanner.tsx b/apps/mobile/app/(tabs)/scanner.tsx index aafd997..3e8dd36 100644 --- a/apps/mobile/app/(tabs)/scanner.tsx +++ b/apps/mobile/app/(tabs)/scanner.tsx @@ -5,7 +5,7 @@ import { Platform, Text, TextInput, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useAssets } from '@/api/exploitation'; import { useHorsLigne } from '@/auth/session'; -import { BoutonTel } from '@/composants/ui'; +import { BoutonTel, EnteteTabs } from '@/composants/ui'; import { analyseScan } from '@/lib/scan'; import { useTokens } from '@/theme/tokens'; @@ -59,6 +59,7 @@ export default function PageScanner() { return ( + Scanner diff --git a/apps/mobile/app/(tabs)/synchro.tsx b/apps/mobile/app/(tabs)/synchro.tsx index 3e39e47..3855c39 100644 --- a/apps/mobile/app/(tabs)/synchro.tsx +++ b/apps/mobile/app/(tabs)/synchro.tsx @@ -3,7 +3,7 @@ import { router } from 'expo-router'; import { FlatList, Pressable, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useHorsLigne } from '@/auth/session'; -import { BoutonTel } from '@/composants/ui'; +import { BoutonTel, EnteteTabs } from '@/composants/ui'; import { useFile, type Saisie } from '@/file/store'; import { abandonnerSaisie, rejouer, rejouerSurVersionAJour } from '@/file/synchro'; import { useTokens } from '@/theme/tokens'; @@ -33,6 +33,7 @@ export default function PageSynchro() { return ( + Synchro diff --git a/apps/mobile/src/composants/ui.tsx b/apps/mobile/src/composants/ui.tsx index 038d1ea..7e885f0 100644 --- a/apps/mobile/src/composants/ui.tsx +++ b/apps/mobile/src/composants/ui.tsx @@ -1,7 +1,8 @@ import { router } from 'expo-router'; import { useState, type ReactNode } from 'react'; -import { Modal, Pressable, ScrollView, Text, View } from 'react-native'; +import { Alert, Modal, Pressable, ScrollView, Text, View } from 'react-native'; import { WORK_ORDER_STATUS_LABELS, type WorkOrderStatus } from '@siop/shared'; +import { useHorsLigne, useLogout, useMe } from '@/auth/session'; import { useTokens, type Tokens } from '@/theme/tokens'; /** Briques d'écran de la maquette R4 — cartes, chips, boutons, sélecteur. */ @@ -263,3 +264,76 @@ export function ChoixTel({ ); } + +/** Entête commune à tous les onglets (D'où qu'on parte, on peut se + * déconnecter — trouvée manquante en recette : seule « Ma journée » avait + * ce contrôle, ailleurs l'utilisateur se sentait bloqué). Le rôle affiché + * est CELUI du compte connecté, pas un libellé figé — le mobile n'est plus + * réservé aux techniciens (voir décision élargissant le périmètre). */ +export function EnteteTabs() { + const t = useTokens(); + const horsLigne = useHorsLigne(); + const { data: me } = useMe(); + const logout = useLogout(); + + const seDeconnecter = () => { + Alert.alert('Se déconnecter ?', me?.displayName ? `Compte : ${me.displayName}` : undefined, [ + { text: 'Annuler', style: 'cancel' }, + { + text: 'Se déconnecter', + style: 'destructive', + onPress: () => void logout().then(() => router.replace('/connexion')), + }, + ]); + }; + + return ( + + + SIOP + + {me?.role.name ?? '·'} + + + + {horsLigne ? 'Hors-ligne' : 'Synchro à jour'} + + + + {(me?.displayName ?? '·') + .split(' ') + .map((m) => m[0]) + .join('') + .slice(0, 2) + .toUpperCase()} + + + + + ); +}