mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 20:51:53 +00:00
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>
184 lines
7.0 KiB
TypeScript
184 lines
7.0 KiB
TypeScript
import { useQueryClient } from '@tanstack/react-query';
|
||
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, EnteteTabs } from '@/composants/ui';
|
||
import { useFile, type Saisie } from '@/file/store';
|
||
import { abandonnerSaisie, rejouer, rejouerSurVersionAJour } from '@/file/synchro';
|
||
import { useTokens } from '@/theme/tokens';
|
||
|
||
/** Écran 7 de la maquette R4 : la file VISIBLE et honnête. Rejeu dans
|
||
* l'ordre ; un conflit (verrou D2) arrête la file sur l'élément, montre le
|
||
* message de l'API (qui, quand) et VOUS tranchez — rien n'est écrasé ni
|
||
* perdu en silence. */
|
||
|
||
const ICONES: Record<Saisie['type'], string> = {
|
||
TRANSITION: '🏁',
|
||
COCHE: '✓',
|
||
BILAN: '📋',
|
||
PHOTO: '🖼',
|
||
};
|
||
|
||
export default function PageSynchro() {
|
||
const t = useTokens();
|
||
const horsLigne = useHorsLigne();
|
||
const file = useFile();
|
||
const queryClient = useQueryClient();
|
||
|
||
const conflit = file.find((s) => s.statut === 'CONFLIT');
|
||
const heure = (iso: string) =>
|
||
new Intl.DateTimeFormat('fr-FR', { hour: '2-digit', minute: '2-digit' }).format(new Date(iso));
|
||
|
||
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
|
||
</Text>
|
||
<Text style={{ marginLeft: 'auto', fontFamily: 'Manrope_600SemiBold', fontSize: 11, color: t.encre3 }}>
|
||
{conflit ? 'arrêtée sur le conflit' : file.length ? 'rejouée dans l’ordre' : ''}
|
||
</Text>
|
||
</View>
|
||
|
||
{conflit ? (
|
||
<View
|
||
style={{
|
||
backgroundColor: t.prioBloqueFond,
|
||
borderColor: t.danger,
|
||
borderWidth: 1.5,
|
||
borderRadius: 12,
|
||
padding: 12,
|
||
gap: 8,
|
||
}}
|
||
>
|
||
<Text style={{ color: t.danger, fontFamily: 'Manrope_800ExtraBold', fontSize: 13 }}>
|
||
⚠ Conflit sur {conflit.otReference}
|
||
</Text>
|
||
<Text style={{ color: t.encre, fontFamily: 'Manrope_600SemiBold', fontSize: 12.5 }}>
|
||
{conflit.erreur ?? 'L’OT a été modifié pendant que vous étiez hors-ligne.'}
|
||
</Text>
|
||
<Text style={{ color: t.encre2, fontFamily: 'Manrope_400Regular', fontSize: 11.5 }}>
|
||
Votre saisie (« {conflit.libelle} », {heure(conflit.creeA)}) est conservée telle
|
||
quelle dans la file — rien n’est perdu.
|
||
</Text>
|
||
<BoutonTel
|
||
libelle="Voir l’OT à jour"
|
||
variante="contour"
|
||
surAppui={() => router.push(`/ot/${conflit.otId}`)}
|
||
/>
|
||
<BoutonTel
|
||
libelle="Rejouer ma saisie sur la version à jour"
|
||
surAppui={() => void rejouerSurVersionAJour(conflit.id, queryClient)}
|
||
/>
|
||
<BoutonTel
|
||
libelle="Abandonner ma saisie"
|
||
variante="gris"
|
||
surAppui={() => void abandonnerSaisie(conflit.id, queryClient)}
|
||
/>
|
||
</View>
|
||
) : null}
|
||
|
||
<FlatList
|
||
data={file}
|
||
keyExtractor={(s) => s.id}
|
||
contentContainerStyle={{ gap: 8, paddingBottom: 12 }}
|
||
ListEmptyComponent={
|
||
<View style={{ padding: 24, alignItems: 'center', gap: 6 }}>
|
||
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 15, color: t.encre }}>
|
||
Rien en attente
|
||
</Text>
|
||
<Text
|
||
style={{
|
||
fontFamily: 'Manrope_400Regular',
|
||
fontSize: 12.5,
|
||
color: t.encre2,
|
||
textAlign: 'center',
|
||
}}
|
||
>
|
||
Toutes vos saisies sont sur le serveur. Les gestes faits hors-ligne apparaîtront
|
||
ici, rejoués dans l’ordre au retour du réseau.
|
||
</Text>
|
||
</View>
|
||
}
|
||
renderItem={({ item: s }) => (
|
||
<View
|
||
style={{
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 10,
|
||
backgroundColor: t.surface,
|
||
borderColor: s.statut === 'CONFLIT' ? t.danger : t.bordure,
|
||
borderWidth: 1,
|
||
borderRadius: 10,
|
||
paddingHorizontal: 12,
|
||
paddingVertical: 10,
|
||
}}
|
||
>
|
||
<View
|
||
style={{
|
||
width: 28,
|
||
height: 28,
|
||
borderRadius: 8,
|
||
backgroundColor: t.surface2,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<Text style={{ fontSize: 13 }}>{ICONES[s.type]}</Text>
|
||
</View>
|
||
<View style={{ flex: 1 }}>
|
||
<Text numberOfLines={1} style={{ fontFamily: 'Manrope_700Bold', fontSize: 12.5, color: t.encre }}>
|
||
{s.libelle}
|
||
</Text>
|
||
<Text style={{ fontFamily: 'Manrope_400Regular', fontSize: 11, color: t.encre2 }}>
|
||
{s.otReference} · {heure(s.creeA)}
|
||
</Text>
|
||
</View>
|
||
<Text
|
||
style={{
|
||
fontFamily: 'Manrope_700Bold',
|
||
fontSize: 10.5,
|
||
paddingHorizontal: 8,
|
||
paddingVertical: 2,
|
||
borderRadius: 999,
|
||
overflow: 'hidden',
|
||
color:
|
||
s.statut === 'CONFLIT' ? t.danger : s.statut === 'ENVOI' ? t.stOuvert : t.stAttente,
|
||
backgroundColor:
|
||
s.statut === 'CONFLIT'
|
||
? t.prioBloqueFond
|
||
: s.statut === 'ENVOI'
|
||
? t.stOuvertFond
|
||
: t.stAttenteFond,
|
||
}}
|
||
>
|
||
{s.statut === 'CONFLIT' ? 'conflit' : s.statut === 'ENVOI' ? 'envoi…' : 'en attente'}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
/>
|
||
|
||
{file.length && !horsLigne ? (
|
||
<Pressable
|
||
accessibilityRole="button"
|
||
onPress={() => void rejouer(queryClient)}
|
||
style={{ backgroundColor: t.primaire, borderRadius: 10, padding: 12, alignItems: 'center' }}
|
||
>
|
||
<Text style={{ color: '#fff', fontFamily: 'Manrope_700Bold', fontSize: 14 }}>
|
||
Rejouer la file maintenant
|
||
</Text>
|
||
</Pressable>
|
||
) : null}
|
||
{horsLigne ? (
|
||
<Text style={{ color: t.stAttente, fontFamily: 'Manrope_600SemiBold', fontSize: 12, textAlign: 'center' }}>
|
||
Hors-ligne — la file repartira seule au retour du réseau.
|
||
</Text>
|
||
) : null}
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|