mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
fix(mobile): déconnexion accessible depuis tous les onglets
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<WorkOrderPriority, (t: Tokens) => 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 (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: t.fond }} edges={['top']}>
|
||||
<View style={{ padding: 14, gap: 10, flex: 1 }}>
|
||||
{/* Entête app : marque + pastille synchro + compte */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<Text style={{ color: t.safran, fontSize: 15 }}>♦</Text>
|
||||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 17, color: t.encre }}>
|
||||
SIOP
|
||||
</Text>
|
||||
<Text style={{ fontFamily: 'Manrope_600SemiBold', fontSize: 10.5, color: t.encre3 }}>
|
||||
Technicien
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, marginLeft: 'auto' }}>
|
||||
<Text
|
||||
accessibilityLabel="État de synchronisation"
|
||||
style={{
|
||||
fontFamily: 'Manrope_700Bold',
|
||||
fontSize: 10.5,
|
||||
color: horsLigne ? t.stAttente : t.stTermine,
|
||||
backgroundColor: horsLigne ? t.stAttenteFond : t.stTermineFond,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 999,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{horsLigne ? 'Hors-ligne' : 'Synchro à jour'}
|
||||
</Text>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Se déconnecter"
|
||||
onLongPress={() => {
|
||||
void logout().then(() => router.replace('/connexion'));
|
||||
}}
|
||||
style={{
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
backgroundColor: t.stEncours,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontFamily: 'Manrope_700Bold', fontSize: 10 }}>
|
||||
{(me?.displayName ?? '·')
|
||||
.split(' ')
|
||||
.map((m) => m[0])
|
||||
.join('')
|
||||
.slice(0, 2)
|
||||
.toUpperCase()}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<EnteteTabs />
|
||||
|
||||
<View style={{ flexDirection: 'row', alignItems: 'baseline' }}>
|
||||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 19, color: t.encre }}>
|
||||
|
||||
@@ -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 (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: t.fond }} edges={['top']}>
|
||||
<View style={{ flex: 1, padding: 14, gap: 10 }}>
|
||||
<EnteteTabs />
|
||||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 19, color: t.encre }}>
|
||||
Préventif
|
||||
</Text>
|
||||
|
||||
@@ -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 (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: t.fond }} edges={['top']}>
|
||||
<View style={{ flex: 1, padding: 14, gap: 10 }}>
|
||||
<EnteteTabs />
|
||||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 19, color: t.encre }}>
|
||||
Scanner
|
||||
</Text>
|
||||
|
||||
@@ -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 (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: t.fond }} edges={['top']}>
|
||||
<View style={{ flex: 1, padding: 14, gap: 10 }}>
|
||||
<EnteteTabs />
|
||||
<View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 8 }}>
|
||||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 19, color: t.encre }}>
|
||||
Synchro
|
||||
|
||||
@@ -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({
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/** 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 (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<Text style={{ color: t.safran, fontSize: 15 }}>♦</Text>
|
||||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 17, color: t.encre }}>SIOP</Text>
|
||||
<Text style={{ fontFamily: 'Manrope_600SemiBold', fontSize: 10.5, color: t.encre3 }}>
|
||||
{me?.role.name ?? '·'}
|
||||
</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, marginLeft: 'auto' }}>
|
||||
<Text
|
||||
accessibilityLabel="État de synchronisation"
|
||||
style={{
|
||||
fontFamily: 'Manrope_700Bold',
|
||||
fontSize: 10.5,
|
||||
color: horsLigne ? t.stAttente : t.stTermine,
|
||||
backgroundColor: horsLigne ? t.stAttenteFond : t.stTermineFond,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 999,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{horsLigne ? 'Hors-ligne' : 'Synchro à jour'}
|
||||
</Text>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Compte et déconnexion"
|
||||
onPress={seDeconnecter}
|
||||
hitSlop={8}
|
||||
style={{
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
backgroundColor: t.stEncours,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: '#fff', fontFamily: 'Manrope_700Bold', fontSize: 10 }}>
|
||||
{(me?.displayName ?? '·')
|
||||
.split(' ')
|
||||
.map((m) => m[0])
|
||||
.join('')
|
||||
.slice(0, 2)
|
||||
.toUpperCase()}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user