fix(mobile): R6.8 — la connexion routait toujours vers "Ma journée"

connexion.tsx faisait router.replace('/(tabs)/journee') en dur — trouvé
en recette : Karim (Demandeur) atterrissait sur l'écran du Technicien
après avoir basculé de compte démo. La redirection par rôle
(ongletAccueil) n'était branchée qu'à l'aiguillage initial (app/index.tsx,
R6.1), pas au retour de connexion — le chemin réellement emprunté à
chaque bascule de compte (pas de sélecteur de rôle en direct sur mobile,
changer de compte = se déconnecter puis se reconnecter).

useLogin()/useDemoLogin() (auth/session.tsx) renvoient maintenant la
réponse complète (contient user.role.name) au lieu de la jeter après en
avoir extrait le jeton. connexion.tsx : entrer(role) route vers
/(tabs)/${ongletAccueil(role)} pour les deux chemins (connexion classique
et sélecteur démo).

Au passage : sous-titre "Technicien" et accroche "sur le terrain — même
sans réseau" (reste de R4, mobile alors réservé au Technicien) devenus
trompeurs pour les autres rôles depuis R6 — généricisés.

Typecheck/tests/lint verts sur les 4 paquets.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-08-03 09:37:12 +01:00
parent 19076a91d3
commit 39320d081e
4 changed files with 51 additions and 9 deletions

View File

@@ -82,5 +82,6 @@ pnpm + Turborepo. `apps/api` : NestJS, Prisma, PostgreSQL (pgvector + PostGIS),
- **R6.6 — Demandeur restreint à son site** : trou trouvé en recette `GET /assets/options` n'avait aucun filtre (web ET mobile touchés, pas seulement mobile). Corrigé à la racine : relation `User↔Location` (`assignedSites`, migration `r6_demandeur_sites`, vide = aucune restriction pour les autres rôles) ; `AssetsService.allowedLocationIds()` réutilisée par `options()` et par `RequestsService.create` (défense en profondeur, 400 si hors périmètre) ; gestion des sites d'un Demandeur côté web (`personnes.tsx`, invitation + modale dédiée) ; raccourci scan QR côté mobile (`formulaire-demande.tsx`, résout uniquement contre les options déjà filtrées, jamais de repli sur le parc complet). Karim Doukkali (démo) rattaché à Tour Atlas. Bug trouvé en vérification avant tout commit (comparaison id-de-site vs id-d'appareil dans `create()`, aurait rejeté à tort tout signalement d'un Demandeur affecté) et corrigé, méthode renommée `allowedAssetIds` `allowedLocationIds` pour que le nom dise ce qu'elle retourne. 79/80 tests API (le seul échec est le flake `monthCost` déjà connu, sans rapport) ; typecheck/tests/lint verts sur les 4 paquets.
- **R6.7 — assignation à l'approbation (mobile)** : le Gestionnaire approuvait une demande mobile sans pouvoir assigner de technicien (l'OT partait non assigné) « ce n'est pas à lui d'agir comme un technicien ». `PanneauDemandes` gagne un panneau d'approbation avec `ChoixTel` « Assigner à » (techniciens actifs, même filtre que le web), avant confirmation. Rien à changer côté API (`assigneeIds` déjà supporté). Vérifié de bout en bout (demande approuvée avec assigné OT avec `assignees` correct). Typecheck/tests/lint verts.
- **Périmètre `WORK_ORDERS` du Gestionnaire confirmé acceptable tel quel par le référent** l'accès complet hérité de R2/R3 (peut techniquement démarrer/clôturer n'importe quel OT) reste en l'état, aucun resserrement demandé.
- 🔄 **Reprise ici** : reste de la recette R6 (Sites/Ascenseurs/Fichiers côté Gestionnaire à confirmer explicitement, puis passage Demandeur avec le nouveau périmètre de site et le scan QR) avant de considérer R6 close au même sens que R0R5. `expo-sharing` (ouverture de documents) et `expo-clipboard` (copie du lien d'activation) en réserve pour une prochaine étape native. Restes non bloquants inchangés : redéploiement Dokploy de l'instance ENSET (`AI_SERVICE_TOKEN` à créer runbook §2 puis « Réindexer tout »), recette Android sur appareil physique, calibrage `AI_SEUIL_*` et qualité darija sur corpus SPELEV réel, secret `DOKPLOY_WEBHOOK_URL`, production client SPELEV (attend les accès serveur du partenaire).
- **R6.8 — la connexion routait toujours vers « Ma journée »** : `connexion.tsx` faisait `router.replace('/(tabs)/journee')` en dur la redirection par rôle (`ongletAccueil`) n'était branchée qu'à l'aiguillage initial (R6.1), pas au retour de connexion, le chemin réellement emprunté à chaque bascule de compte démo (pas de sélecteur de rôle en direct sur mobile, changer de compte = se déconnecter puis se reconnecter). Karim (Demandeur) atterrissait sur l'écran du Technicien. `useLogin()`/`useDemoLogin()` renvoient maintenant la réponse complète (rôle inclus) ; `entrer(role)` route vers le bon onglet. Sous-titre « Technicien »/accroche terrain de l'écran de connexion (reste de R4) généricisés au passage. Typecheck/tests/lint verts.
- 🔄 **Reprise ici** : reprendre la recette Demandeur (Accueil, Nouvelle demande filtrée à Tour Atlas, scan QR) une fois ce correctif rechargé sur l'iPhone, puis considérer R6 close au même sens que R0R5. `expo-sharing` (ouverture de documents) et `expo-clipboard` (copie du lien d'activation) en réserve pour une prochaine étape native. Restes non bloquants inchangés : redéploiement Dokploy de l'instance ENSET (`AI_SERVICE_TOKEN` à créer runbook §2 puis « Réindexer tout »), recette Android sur appareil physique, calibrage `AI_SEUIL_*` et qualité darija sur corpus SPELEV réel, secret `DOKPLOY_WEBHOOK_URL`, production client SPELEV (attend les accès serveur du partenaire).
- Détail quotidien : `docs/journal/journal.md`. Dépôt : `siop-spelev/siop2` (privé), jalons R0R5 (v1) + R6 en cours.

View File

@@ -9,12 +9,18 @@ import {
TextInput,
View,
} from 'react-native';
import type { RoleName } from '@siop/shared';
import { ongletAccueil } from '@/auth/roles';
import { useDemoAccounts, useDemoLogin, useLogin } from '@/auth/session';
import { useTokens } from '@/theme/tokens';
/** Connexion mobile — mêmes règles que le web : formulaire e-mail/mot de
* passe, et le sélecteur démo (< 3 s pour changer de rôle, ADR-002)
* UNIQUEMENT si l'API l'expose. */
* UNIQUEMENT si l'API l'expose. Depuis R6, l'app n'est plus réservée au
* Technicien : on route vers l'onglet d'accueil du RÔLE réel (« Ma
* journée » pour le terrain, « Accueil » sinon) — jamais un onglet figé
* (trouvé en recette : un Demandeur atterrissait sur « Ma journée »,
* l'écran du Technicien, faute de lire le rôle retourné par la connexion). */
export default function PageConnexion() {
const t = useTokens();
const [email, setEmail] = useState('');
@@ -23,7 +29,7 @@ export default function PageConnexion() {
const login = useLogin();
const demo = useDemoLogin();
const entrer = () => router.replace('/(tabs)/journee');
const entrer = (role: RoleName) => router.replace(`/(tabs)/${ongletAccueil(role)}`);
return (
<KeyboardAvoidingView
@@ -36,12 +42,9 @@ export default function PageConnexion() {
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 26, color: t.encre }}>
SIOP
</Text>
<Text style={{ fontFamily: 'Manrope_600SemiBold', fontSize: 13, color: t.encre3 }}>
Technicien
</Text>
</View>
<Text style={{ fontFamily: 'Manrope_400Regular', color: t.encre2, marginBottom: 8 }}>
Vos ordres de travail, sur le terrain même sans réseau.
Vos interventions et votre suivi, que vous soyez.
</Text>
<View style={{ gap: 10 }}>
@@ -84,7 +87,10 @@ export default function PageConnexion() {
accessibilityRole="button"
disabled={!email || !motDePasse || login.isPending}
onPress={() =>
login.mutate({ email, password: motDePasse }, { onSuccess: entrer })
login.mutate(
{ email, password: motDePasse },
{ onSuccess: (res) => entrer(res.user.role.name) },
)
}
style={{
backgroundColor: !email || !motDePasse ? t.bordureForte : t.primaire,
@@ -122,7 +128,9 @@ export default function PageConnexion() {
key={c.id}
accessibilityRole="button"
disabled={demo.isPending}
onPress={() => demo.mutate(c.id, { onSuccess: entrer })}
onPress={() =>
demo.mutate(c.id, { onSuccess: (res) => entrer(res.user.role.name) })
}
style={{
backgroundColor: t.surface,
borderColor: t.bordure,

View File

@@ -42,6 +42,7 @@ export function useLogin() {
mutationFn: async (input: { email: string; password: string }) => {
const res = await unwrap(await api.POST('/auth/login', { body: input }));
await apres(res.accessToken);
return res; // le rôle sert à router vers le bon onglet d'accueil
},
});
}
@@ -52,6 +53,7 @@ export function useDemoLogin() {
mutationFn: async (userId: string) => {
const res = await unwrap(await api.POST('/auth/demo-login', { body: { userId } }));
await apres(res.accessToken);
return res; // le rôle sert à router vers le bon onglet d'accueil
},
});
}

View File

@@ -4,6 +4,37 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
---
## 2026-08-03 — Pr. Daaif (+ Claude) — R6.8 : connexion routait toujours vers « Ma journée »
**Contexte** — en recette, le référent a basculé sur Karim Doukkali (Demandeur) et a atterri sur
« Ma journée » (l'écran du Technicien, « Aucun OT en cours ») au lieu d'« Accueil ». Cause :
`app/connexion.tsx` — la fonction `entrer()` faisait `router.replace('/(tabs)/journee')` **en
dur**, quel que soit le rôle du compte choisi. La redirection par rôle (`ongletAccueil`) n'avait
été branchée qu'à l'aiguillage initial (`app/index.tsx`, R6.1) — pas au retour de connexion, le
chemin réellement emprunté à chaque bascule de compte démo (changer de compte = se déconnecter
puis se reconnecter, il n'y a pas de sélecteur de rôle en direct sur mobile).
**Actions**
- `useLogin()`/`useDemoLogin()` (`auth/session.tsx`) renvoient maintenant la réponse complète
(contient `user.role.name`) au lieu de la jeter après en avoir extrait le jeton.
- `connexion.tsx` : `entrer(role)` route vers `/(tabs)/${ongletAccueil(role)}` — les deux
boutons (connexion classique et sélecteur démo) passent le rôle réellement retourné par l'API,
jamais un onglet figé.
- Au passage : le sous-titre « Technicien » et l'accroche « sur le terrain — même sans réseau »
de l'écran de connexion étaient un reste de R4 (mobile alors réservé au Technicien) — devenus
trompeurs pour les autres rôles depuis R6, généricisés.
- Typecheck/tests/lint verts sur les 4 paquets.
**Décisions**
- Aucune — correction directe, aucun changement de comportement voulu au-delà du routage.
**Prochaine étape** : reprendre la recette Demandeur (Accueil, Nouvelle demande filtrée à Tour
Atlas, scan QR) une fois ce correctif rechargé sur l'iPhone.
---
## 2026-08-02 — Pr. Daaif (+ Claude) — R6.7 : assignation à l'approbation (mobile)
**Contexte** — en recette, le référent a signalé que le Gestionnaire doit pouvoir affecter un OT