diff --git a/CLAUDE.md b/CLAUDE.md index 8d8a4f4..ab9ba45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 R0→R5. `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 R0→R5. `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 R0→R5 (v1) + R6 en cours. diff --git a/apps/mobile/app/connexion.tsx b/apps/mobile/app/connexion.tsx index 634005a..5bd7af9 100644 --- a/apps/mobile/app/connexion.tsx +++ b/apps/mobile/app/connexion.tsx @@ -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 ( SIOP - - Technicien - - Vos ordres de travail, sur le terrain — même sans réseau. + Vos interventions et votre suivi, où que vous soyez. @@ -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, diff --git a/apps/mobile/src/auth/session.tsx b/apps/mobile/src/auth/session.tsx index bbaaeef..a42a252 100644 --- a/apps/mobile/src/auth/session.tsx +++ b/apps/mobile/src/auth/session.tsx @@ -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 }, }); } diff --git a/docs/journal/journal.md b/docs/journal/journal.md index 8ed127b..01a45d3 100644 --- a/docs/journal/journal.md +++ b/docs/journal/journal.md @@ -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