🔄 Workflows Utilisateur - Teadle
Ce document décrit tous les parcours utilisateur de l'application Teadle, de la connexion jusqu'à l'utilisation des dashboards, incluant les fonctionnalités de reprise d'onboarding.
📋 Table des matières
- Connexion
- Inscription Trainer
- Inscription Organization
- Onboarding Trainer
- Onboarding Organization
- Reprise d'Onboarding
- Dashboard Trainer
- Dashboard Organization
- Facturation formateur
- Notifications formateur
- Gestion des disponibilités
- Gestion des erreurs
- Tests automatisés
🔐 Connexion
Flux de connexion standard
graph TD
A[Page d'accueil] --> B[Formulaire de connexion]
B --> C{Identifiants valides?}
C -->|Oui| D[Appel API /security/login]
C -->|Non| E[Affichage erreur]
D --> F[Appel API /me]
F --> G{finishedOnBoarding?}
G -->|true| H[Redirection dashboard]
G -->|false| I[Vérification données onboarding]
I --> J{Données onboarding existantes?}
J -->|Oui| K[Reprise d'onboarding]
J -->|Non| L[Redirection onboarding initial]
E --> B
Connexion OAuth
graph TD
A[Clique sur OAuth] --> B[Ouverture popup]
B --> C[Authentification provider]
C --> D[Callback avec code]
D --> E[API /security/user/{provider}/oauth]
E --> F{finishedOnBoarding?}
F -->|true| G[Redirection dashboard]
F -->|false| H[Nettoyage localStorage onboarding]
H --> I[Redirection onboarding approprié]
Étapes détaillées
- Page d'accueil (
/) - Formulaire email/mot de passe
- Boutons OAuth (LinkedIn, Google, Microsoft)
-
Boutons d'inscription (Trainer, Organization)
-
Validation des identifiants
- API :
POST /security/login -
Gestion des erreurs (401, 500, etc.)
-
Récupération des données utilisateur
- API :
GET /me -
Vérification de
finishedOnBoarding -
Redirection intelligente
- Si
finishedOnBoarding: true→ Dashboard approprié - Si
finishedOnBoarding: false→ Vérification reprise d'onboarding
👨🏫 Inscription Trainer
Flux d'inscription par formulaire
graph TD
A[Page d'accueil] --> B[Clique "Devenir formateur"]
B --> C[Page sélection expérience]
C --> D[Choix niveau d'expérience]
D --> E[Page inscription]
E --> F[Formulaire email/password]
F --> G[API /security/trainer/register]
G --> H[Redirection onboarding]
Flux d'inscription OAuth
graph TD
A[Page inscription] --> B[Clique OAuth]
B --> C[Authentification provider]
C --> D[Callback OAuth]
D --> E[API /security/user/{provider}/oauth]
E --> F[Sauvegarde données onboarding]
F --> G[Redirection onboarding]
Étapes détaillées
- Sélection du profil (
/onboarding/trainer) - Choix du niveau d'expérience
- Sauvegarde :
onboardingStorage.saveSection('trainerExperience', {...}) -
Bouton "Continuer"
-
Inscription (
/onboarding/trainer/signup) - Formulaire email/password
- Boutons OAuth (LinkedIn, Google, Microsoft)
- API :
POST /security/trainer/register -
Sauvegarde :
onboardingStorage.saveSection('signup', {...}) -
Redirection vers onboarding
- Token JWT reçu
finishedOnBoarding: false
🏢 Inscription Organization
Flux d'inscription par formulaire
graph TD
A[Page d'accueil] --> B[Clique "Devenir organisation"]
B --> C[Page inscription]
C --> D[Formulaire email/password]
D --> E[API /onboarding/user-exist/check]
E --> F{Email disponible?}
F -->|Oui| G[Page validation email]
F -->|Non| H[Erreur email existant]
G --> I[Saisie code validation]
I --> J[API /security/auth-code/verify]
J --> K[API /security/organization/register]
K --> L[Redirection onboarding]
Flux d'inscription OAuth
graph TD
A[Page inscription] --> B[Clique OAuth]
B --> C[Authentification provider]
C --> D[Callback OAuth]
D --> E[API /security/user/{provider}/oauth]
E --> F[Sauvegarde données onboarding]
F --> G[Redirection onboarding]
Étapes détaillées
- Inscription (
/onboarding/organization/signup) - Formulaire email/password
- Boutons OAuth (LinkedIn, Google, Microsoft)
- API :
POST /onboarding/user-exist/check -
Sauvegarde :
onboardingStorage.saveSection('signup', {...}) -
Validation email (
/onboarding/organization/email-validation) - Envoi automatique du code
- API :
POST /security/auth-code/send - Saisie du code à 6 chiffres
-
API :
POST /security/auth-code/verify -
Création du compte
- API :
POST /security/organization/register - Token JWT reçu
finishedOnBoarding: false
🎯 Onboarding Trainer
Flux complet
graph TD
A[Inscription réussie] --> B[Informations personnelles]
B --> C[Options de profil]
C --> D[Édition expériences]
D --> E[API /trainer/career]
E --> F[Finalisation]
F --> G[Dashboard trainer]
B --> B1{signupMethod?}
B1 -->|OAuth| B2[Pré-remplissage OAuth]
B1 -->|Email| B3[Formulaire complet]
B1 -->|Resume| B4[Pré-remplissage utilisateur]
Étapes détaillées
- Informations personnelles (
/onboarding/trainer/personal-info) - Prénom, nom
- Mot de passe (si pas OAuth/Resume)
- Acceptation CGV
- API :
PUT /trainer/preferences -
Sauvegarde :
onboardingStorage.saveSection('personalInfo', {...}) -
Options de profil (
/onboarding/trainer/profile-options) - Configuration du profil
- Sauvegarde :
onboardingStorage.saveSection('profileOptions', {...}) -
Bouton "Continuer"
-
Édition des expériences (
/onboarding/trainer/experiences-edit) - Import depuis CV (si applicable)
- Ajout/modification d'expériences
- API :
POST /trainer/career -
Sauvegarde :
onboardingStorage.saveSection('experiences', {...}) -
Finalisation
- Depuis Options de profil (« Continuer plus tard ») :
POST /user/onboarding/validate(finishOnboarding) puisonboardingStore.fetchProgress()(GET /me) pour synchroniser la progression onboarding etsampleData. - Depuis Édition des expériences (après
POST /trainer/career) :fetchProgress()avant redirection pour la même mise à jour. - Nettoyage localStorage (parcours expériences)
- Redirection : dashboard formateur
🏢 Onboarding Organization
Flux complet
graph TD
A[Inscription réussie] --> B[Informations personnelles]
B --> C[Paramètres organisation]
C --> D[API /organization/info]
D --> E[Finalisation]
E --> F[Dashboard organization]
B --> B1{signupMethod?}
B1 -->|OAuth| B2[Pré-remplissage OAuth]
B1 -->|Email| B3[Formulaire complet]
B1 -->|Resume| B4[Pré-remplissage utilisateur]
Étapes détaillées
- Informations personnelles (
/onboarding/organization/personal-info) - Prénom, nom
- Numéro de téléphone (optionnel)
- Acceptation CGV et opt-in
- API :
PUT /organization/preferences -
Sauvegarde :
onboardingStorage.saveSection('personalInfo', {...}) -
Paramètres organisation (
/onboarding/organization/settings) - Nom de l'organisation
- Service au sein de l'organisation
- Type d'organisation
- API :
PATCH /organization/info -
Sauvegarde :
onboardingStorage.saveSection('settings', {...}) -
Finalisation
- API :
GET /me(mise à jourfinishedOnBoarding) - Redirection :
/dashboard/organization
🔄 Reprise d'Onboarding
Détection automatique
graph TD
A[Connexion utilisateur] --> B{finishedOnBoarding?}
B -->|false| C[Vérification localStorage]
C --> D{Données onboarding existantes?}
D -->|Oui| E[Affichage bannière reprise]
D -->|Non| F[Onboarding initial]
E --> G[Utilisateur clique "Reprendre"]
G --> H[Redirection étape appropriée]
H --> I[Mode reprise activé]
Gestion du localStorage
graph TD
A[Stockage des données] --> B[Section par étape]
B --> C[trainerExperience]
B --> D[signup]
B --> E[personalInfo]
B --> F[profileOptions]
B --> G[experiences]
B --> H[settings]
I[Reprise détectée] --> J[Création données minimales]
J --> K[signup: {signupMethod: 'resume', ...}]
K --> L[Redirection étape appropriée]
Fonctionnalités de reprise
- Détection automatique
- Vérification
finishedOnBoarding: false - Analyse du localStorage
-
Affichage bannière de reprise
-
Mécanisme de reprise
- Reprise pilotée par les données persistées (
signupMethod: 'resume') - Redirection vers l'étape connectée attendue
-
Préremplissage des données déjà connues
-
Mode reprise
- Pré-remplissage des champs
- Masquage des étapes inutiles
-
Sauvegarde des données existantes
-
Gestion des données
- Section
signupavecsignupMethod: 'resume' - Données utilisateur pré-remplies
- Conservation du token d'authentification
APIs de reprise
- Vérification utilisateur :
GET /me - Mise à jour préférences :
PUT /trainer/preferences,PUT /organization/preferences - Finalisation :
POST /trainer/career,PATCH /organization/info
📊 Dashboard Trainer
Sidebar d'onboarding (formateur, onboarding incomplet)
Quand l'onboarding n'est pas terminé, un bandeau droit affiche la progression. Comportement :
- Ouvert par défaut à chaque arrivée sur le dashboard (refresh ou changement de page)
- L'utilisateur peut le fermer (« Fermer » ou « Réduire ») ; la préférence n'est pas persistée
- Au prochain chargement de page ou navigation, le bandeau s'ouvre à nouveau
- Masqué définitivement une fois l'onboarding à 100 %
Item « Démarrer » (menu gauche) — FEAT-090
La signalétique onboarding n’est plus un badge sur « Tableau de bord » (ancien pattern DISC-005) : un item dédié Démarrer (icône fusée, badge X/5 corail ou coche verte) apparaît entre « Tableau de bord » et « Mon planning » lorsque useOnboardingStore().showInSidebar est vrai (onboarding en cours, ou terminé dans les 7 jours si la checklist n’est pas masquée). Lien interne /trainer/getting-started ; le menu utilisateur Guide de démarrage pointe vers la même page (plus de lien marketing externe). Bootstrap sans GET /me : hydrateProgressFromAuth() au tout début du setup de TrainerLayout (pas au onMounted, sinon les vues enfant montent avant et ratent fetchSampleDataPayload), puis filet au onMounted et quand auth.user.onboardingProgress devient disponible (watch). Copie si progress === null — d’abord depuis auth.user.onboardingProgress, sinon depuis un snapshot localStorage (onboardingProgressSnapshot.js, clé teadle_onboarding_progress_snapshot, TTL 24 h) pour conserver checklist et sampleData après un F5 avant le retour de /me. Le snapshot est écrit à chaque fetchProgress réussi et après les mutations qui touchent la progression (completeStep, dismiss, restore, dismissSampleData). Rafraîchissement réseau : fetchProgress() (GET /me) toutes les 5 minutes dans TrainerLayout + à chaque visite de la page Getting started uniquement.
La page Getting started (GettingStartedView) appelle à chaque affichage onboardingStore.fetchProgress() au montage (GET /me via authStore.fetchMe(), mise à jour de user.onboardingProgress), puis affiche un titre et sous-titre selon la progression, une barre segmentée à 5 blocs (OnboardingProgressBar), les actions masquer / réafficher la checklist (store dismiss / restore) puis une zone réservée à la checklist (FEAT-092). L’étape recommandée IMPORT_CALENDAR (« Connecter votre première organisation ») : CTA vers trainer-organizations-configure (/trainer/organizations/configure, ajout ou configuration d’organisation).
FEAT-095 : le masquage passe par un délai de grâce de 4 s (toast inline Gmail-style via BaseToast, action « Annuler », pause timer hover/focus) ; le dismiss backend n’est envoyé qu’à l’expiration.
FEAT-096 : section « Aller plus loin » affichée dès completedCount >= 3 (personnalisation facture, messagerie, statistiques, carte « Bientot » disabled + lien aide), sans bloquer la checklist.
À la complétion des 5 étapes, une modale de célébration sobre une fois par navigateur (OnboardingCelebrationModal, flag localStorage teadle_onboarding_celebration_shown) — FEAT-094.
Accès et fonctionnalités
graph TD
A[Connexion réussie] --> B{finishedOnBoarding?}
B -->|true| C[Dashboard trainer]
B -->|false| D[Redirection onboarding/reprise]
C --> E[Vue opportunités]
C --> F[Vue actions]
C --> G[Vue planning]
C --> H[Vue revenus]
Bandeaux onboarding dashboard (FEAT-097)
En tete du dashboard formateur (DashboardView.vue), trois bandeaux conditionnels pilotent le cycle de vie sample data (etats exclusifs) :
- Sample data actif (
SampleDataBanner, rolestatus) : visible sionboardingProgress.sampleData.active === trueet pas en fenetre pre-avert J-2 ; action "Supprimer les exemples" =>POST /trainer/onboarding/sample-data/dismissviaonboardingStore.dismissSampleData(). - Pre-avert J-2 (
PreAvertBanner, rolealert) : visible si sample actif etexpiresAt <= now + 2 jours; CTA "Partager mon profil" vers/trainer/getting-started. - Première vraie demande (
FirstRealRequestBanner, vert maquette 🎉, rolestatus) : visible hors mode sample siGET /trainer/dashboard/recent-requestsretourne au moins un item avecisFirst: true(backend : lot affiché dans « À traiter » est le plus ancien lot du formateur en base — première demande encore en attente) et flag localStorageteadle_disc016_first_request_seenabsent ; sous-titre depuis org / module / créneaux ; CTA « Voir la demande » vers/trainer/requestset pose du flag.
Injection sample dashboard (FEAT-098)
Quand sampleData.active === true, le dashboard passe en mode demonstration :
DashboardView.vuerecupereonboardingStore.sampleDataPayloadviafetchSampleDataPayload()(GET /trainer/onboarding/sample-data) et mappe vers les shapes attendues par les composants (effectiveSummary,effectiveInterventions*,effectiveRecentRequests,effectiveSchools). Le payload est mis en cache localStorage 24 h (onboardingSampleDataCache.js, cleteadle_onboarding_sample_data_cache) tant que le sample reste actif ; invalidation du cache uniquement si GET /me renvoie explicitementonboardingProgress.sampleData.active === false, après Supprimer les exemples (dismissSampleData), ou logout (clearBrowserStorageOnLogout). Pas de purge sur une réponse/mesans champsampleData/active(évite un F5 qui efface le cache). Le snapshotteadle_onboarding_progress_snapshot(voir ci-dessus) évite de perdre le flagsampleData.activeau premier rendu post-F5. SisampleData.active === falsedans la progression, aucun GET/trainer/onboarding/sample-data(le store court-circuite aussi la réhydratation depuis le cache).- Bandeau orange Donnees d'exemple : meme composant
SampleDataBanner.vueque le dashboard (texte maquette ISS-003 / demandes intervenant) sur Mes demandes et Mes organisations lorsque les donnees d'exemple sont affichees. - Les composants KPI/listes reçoivent
isSample(KpiStrip,MyDay,ToProcess,MySchools,MyIndicators,MyStatistics). - Les items sample portent un badge
Exempleet les clics metier sont interceptes viasample-click(pas d'action irreversible immediate). - Demandes / Organisations : au montage,
fetchSampleDataPayload()si sample actif (cache 24 h ci-dessus ; pas de GET /me dédié sur ces pages). Listes :requests.batcheset cartes depuisdashboard.schoolBreakdowns(onboardingSampleData.js). Actions sensibles →SampleModalpour les lignes d'exemple. - FEAT-099 :
sample-clickouvreSampleModal(guardrail) avec CTA "Completer mon profil" vers/trainer/getting-started; fermeture possible via overlay, Escape ou bouton "Fermer". - FEAT-099 + FIX-106 :
EmptyState.vueest utilise sur les empty states dashboard (ToProcess,MyDay,MySchools,MyIndicators,MyStatistics) aveconboardingLinkvers/trainer/getting-started(labels contextuels par bloc). - Quand le sample est desactive (dismiss explicite ou premiere vraie demande), le dashboard rebascule automatiquement sur les donnees reelles.
Sections principales
- Opportunités (
/dashboard/trainer) - Nouvelles missions disponibles
- Filtres par domaine/compétence
-
Bouton "Postuler"
-
Actions à effectuer
- Missions en cours
- Documents à fournir
-
Évaluations à compléter
-
Planning hebdomadaire
- Sessions programmées
- Disponibilités (voir Gestion des disponibilités)
-
Calendrier interactif
-
Revenus
- Statistiques mensuelles
- Factures en attente
- Historique des paiements
🏢 Dashboard Organization
Accès et fonctionnalités
graph TD
A[Connexion réussie] --> B{finishedOnBoarding?}
B -->|true| C[Dashboard organization]
B -->|false| D[Redirection onboarding/reprise]
C --> E[Gestion formateurs]
C --> F[Gestion missions]
C --> G[Gestion facturation]
C --> H[Statistiques]
Sections principales
- Gestion des formateurs
- Liste des formateurs
- Profils et compétences
-
Évaluations et feedback
-
Gestion des missions
- Missions en cours
- Planning des formations
-
Suivi des performances
-
Facturation
- Factures générées
- Paiements reçus
-
Rapports financiers
-
Statistiques
- Performance des formateurs
- Satisfaction clients
- Métriques d'activité
💰 Facturation formateur
Paramètres entreprise (CompanyView)
- Vue :
/trainer/company(ou équivalent) — page « Mon entreprise » / Paramètres formateur. - Section Facturation (après les infos bancaires) : TVA par défaut, Unité par défaut, Délai de paiement, Numérotation des factures (BUG-031), Numérotation des CRA (UX-039), Mentions légales (TVA exonérée, paiement). Données envoyées via
PUT /trainer/billing/settings(etPATCH /trainer/companypour company) ; récupération viaGET /trainer/billing/settingsetGET /trainer/company. - Numérotation factures : préfixe personnalisable avec variables
(annee),(mois),(jour)(conformité art. 289 CGI), numéro de séquence (verrouillé après première facture de l’année), aperçu en temps réel. Variables insérables par chips dans l’interface. - Numérotation CRA (UX-039) : même moteur que factures (préfixe + séquence, variables (annee)/(mois)/(jour)), template par défaut
CRA-(annee)(mois)-, séquence CRA indépendante, section dédiée et aperçu du prochain CRA. - Ces paramètres servent de défauts à la création de facture et sont exposés dans
GET /trainer/invoices/config.
Création de facture (CreateInvoiceView)
- Préremplissage : paramètres et mentions légales chargés depuis la config entreprise (
GET /trainer/invoices/config). - Bloc lecture seule « Paramètres appliqués » : affiche TVA, Unité, Délai appliqués à la facture (valeurs issues de l’entreprise au moment de la création).
- Case à cocher « TVA non applicable, article 293 B du CGI » : affichée lorsque la TVA est à 0 % sur toutes les lignes.
- Bandeau droit : statut, numéro, modèle, actions (Sauvegarder, Finaliser, PDF, Envoyer, etc.) ; champs libres, libellé du délai de paiement (non éditable : même valeur que les paramètres entreprise « défauts nouvelles factures » ; persistée sur la facture en
paymentTerm) et pied de page personnalisé sont dans l’aperçu HTML (Teleport), pas dans la sidebar.useBillingDocumentFormalimente toujoursdocumentData; toute modification metisDirtycomme pour le corps de la facture. - Brouillon : tant que
isDirtyest vrai (création non sauvegardée ou modifications non enregistrées), les boutons Finaliser et Télécharger PDF sont désactivés (infobulle au survol), comme sur le CRA ; après chargement ou sauvegarde sans changement en attente, ils redeviennent actifs si les autres conditions le permettent (invoiceUuid, etc.). - Modale « Envoyer la facture par email » : bloc Pièces jointes mutualisé (
components/Trainer/SendEmailAttachmentsBlock.vue) : une ligne par document (case à gauche), PDF principal obligatoire, puis annexes configurées (GET /trainer/billing/documents, types facture/avoir), puis fichier PDF optionnel à la volée. Les UUIDs cochés partent enattachmentIds[]dans lePOST /trainer/invoices/{uuid}/send(attachmentpour le fichier manuel).
Création / édition de facture — aperçu HTML (CreateInvoiceView)
Analogue à CreateCRAView, la vue CreateInvoiceView affiche un aperçu HTML de la facture en cours d'édition, avec un bandeau droit responsive (flex-col puis lg:flex-row) : pleine largeur sous le breakpoint lg, colonne ~288px (lg:w-72) au-delà.
Layout :
- Zone principale : rendu HTML chargé via POST /trainer/invoices/preview (méthode trainerAPI.previewInvoice(data)), affiché en v-html (flex-1 min-w-0). Le corps inclut sous le pied de modèle (.bp-invoice-legal-stack) les mentions facture (legalMentions dans le POST, alimenté comme à la sauvegarde : chargement API, préremplissage, synchro TVA 293 B) puis les blocs paramètres facturation : pénalités si texte renseigné, confidentialité et texte perso uniquement si activés dans TrainerBillingSettings — affichage lecture seule dans l’aperçu ; pas de carte dédiée sous la preview (édition des blocs structurés : paramètres facturation).
- Bandeau : statut, numéro fictif, sélecteur de modèle, boutons d'action (Sauvegarder, Finaliser, Télécharger PDF, Envoyer, Marquer payée, Supprimer).
Injection via <Teleport> (même pattern que CRA) :
Les champs du formulaire sont injectés directement dans les slots du HTML du modèle :
- #invoice-client-input-slot — sélecteur de client
- #invoice-meta-issue-date-slot / #invoice-meta-due-date-slot — dates d'émission et d'échéance
- #invoice-custom-fields-slot — champs personnalisés (libellés du modèle + inputs ; conteneur inv-custom-fields-wrap avec max-width ~20 rem ; valeurs indexées par libellé dans customFieldValues)
- #invoice-subject-slot — sujet / description (inv-subject-wrap, max-width ~26 rem)
- #invoice-line-items-tbody-spa — lignes de facture interactives (désignation + dropdown produit, date, quantité, type d’unité unité/jour/heure/forfait, prix unitaire HT, TVA, remise % si colonne modèle discount) ; réordonnancement par glisser-déposer sur la poignée à gauche de la désignation ; si le modèle n’a pas de colonne « unité » (data-inv-col="unit"), le sélecteur d’unité est affiché à côté de la quantité
- #invoice-line-items-add-button-slot — bouton « Ajouter une ligne »
- #invoice-totals-slot — même disposition que le HTML non-SPA : conteneur bp-totals avec Détails TVA (tableau Taux / Mont. TVA / Base HT, un ligne par taux, montants cohérents avec le récap) si settings.totals.showVat ; à droite le récap (HT, remise si colonne modèle, lignes TVA par taux, TTC) ; si TVA masquée dans le modèle, récap seul margin-left: auto
- #invoice-payment-delay-slot — texte du délai (aligné sur billing.paymentTerm de la config / facture chargée ; pas de sélecteur)
- #invoice-footer-custom-slot — textarea du pied de page personnalisé (si le modèle utilise un pied « custom ») : le contenu éditable est footerCustomText, prérempli avec settings.footer.text du modèle (watch immediate + réinit au changement de modèle) ; si documentData.footerCustomText est vide côté API, repli sur le texte du modèle (useBillingDocumentForm.applyFromApi)
Rafraîchissement de l'aperçu :
- Déclenché lors du changement de modèle, de client ou de invoiceData.legalMentions (ex. synchro TVA / chargement), en débouncé pour ce dernier.
- Pendant le rafraîchissement, l'HTML courant n'est pas démonté (badge de chargement en coin) pour ne pas casser les <Teleport> actifs.
Totaux : calculés côté client depuis les lignes saisies et injectés via Teleport dans #invoice-totals-slot.
Génération depuis un CRA (?fromCra=) :
- Depuis InvoicesView (action « Générer facture » sur un CRA sans facture liée) : navigation vers trainer-invoices-create?fromCra={uuid}.
- CreateInvoiceView appelle POST /trainer/invoices/generate-from-activity-report/{uuid} puis redirige vers l’édition de la facture créée (trainer-invoices-view).
Liste factures / CRA (InvoicesView)
- Drawer détail facture :
components/trainer/InvoiceDetailDrawer.vue— ouverture au clic ligne ou depuis les liens CRA (UX-073). - Cellule factures liées CRA :
components/trainer/LinkedInvoicesCell.vue— popoverposition: fixedavec flip viewport (pattern linked-entities-cell-popover, UX-074). - Actions CRA : Voir · Renvoyer (statut
SENT) · Générer facture (0 facture liée) · Supprimer (0 facture liée). - Modal G9 : si suppression impossible (CRA lié à des factures), message « Suppression impossible » + bouton « Compris » (focus trap + retour focus sur le déclencheur).
- Toasts feedback (UX-075) :
components/ui/BaseToast.vuemonté en bas-droite (toastref +showToast/ aliasshowInlineToastpour les transitions KPI UX-070). Feedback transitoire sur marquage payé, suppression brouillon/CRA, envoi email CRA, rechargement liste ; erreurs API hors modales (plus de bannièremarkInvoicePaidErrorni modale d’erreur suppression CRA). - Accessibilité WCAG 2.2 AA (UX-076) : skip-link « Aller au contenu principal » dans
TrainerLayout.vue(#main-content) ;prefers-reduced-motionglobal (assets/main.css) ; focus visible sur checkboxes/radios filtres ; cibles tactiles 24×24 sur actions tableau factures ; déclaration publiquefrontend/docs/accessibility/InvoicesView.md. - Skeletons inline / reduced-motion (UX-077) : skeletons décoratifs par classe →
motion-safe:animate-pulse(vues listées dans le ticket : staff/trainer Requests, Dashboard, GettingStarted, Messages, BillingSettings, ConfigureOrganization) ; filetmain.cssinchangé comme garde-fou.
Liste factures & CRA (InvoicesView.vue) et édition CRA (CreateCRAView.vue)
- Aperçu HTML :
CreateCRAViewaffiche un aperçu du document sur la largeur restante à 100 % du conteneur (billing_template_preview.html.twig: plus demax-widthsur.billing-preview-root--cra, anciennement ~48rem centré), carterounded-xlcomme la facture, avec un bandeau droit pleine largeur en mobile et ~288 px à partir delg: statut (Brouillon / Envoyé), code + modèle, puis actions (PDF, Enregistrer, Envoyer, Générer facture, Supprimer le CRA) — le header du layout n’affiche plusCRAHeaderActions(nettoyé au montage). Contenuv-html; pendant un rafraîchissement, l’HTML courant n’est pas masqué (badge en coin). Lignes d’intervention (manuel, iCal) : éditées dans le tableau du modèle (tbody#cra-line-items-tbody-spa, en-têtesdata-cra-col, bouton d’ajout Teleporté sous le tableau). La colonne Classe/Groupe (class_name) est un champ texte libre (comme les notes), persisté en base sur la ligne CRA et renvoyée par l’API ; à l’import Agenda, préremplissage depuiscalendar_configurator_assignment.class_name. Dès l’init (et à chaque rafraîchissement),POST /trainer/activity-reports/previewest appelé :organizationUuidoptionnel — sans client, l’aperçu HTML comporte un bandeau et un en-tête client vide :billingTemplateUuid,lineItems,documentData, etc. Après le chargement initial, le premier aperçu est planifié après deuxnextTickpour laisseruseBillingDocumentFormappliquer l’UUID par défaut ; unwatchinclut aussi la liste des modèles CRA pour re-déclencher l’aperçu dès sa mise à jour. L’HTML d’aperçu CRA côté backend comporte des attributsdata-bp="…"(cibles SPA) en plus dedata-editable(pied de page, champs libres). En création de CRA, le choix d’école (client) et la période sont injectés dans l’en-tête du modèle par<Teleport>:#cra-client-input-slot(SearchableSelecttoujours visible, même logique que surCreateInvoiceView— plus de bouton « Changer de client ») et#cra-period-input-slot; le nom de l’organisation apparaît dans le HTML du modèle (<p class="bp-client-name">dansbilling_template_preview.html.twig, au-dessus de l’adresse) dès l’aperçu avecPOST /activity-reports/preview; l’adresse / contact suivent. AperçuPOST /activity-reports/preview:_craEditorRealDataOnly(pas de merge YAML). Aperçu rafraîchi en débouncé ; « Ajouter une ligne manuelle ». - Modale « Envoyer le CRA par email » : même composant SendEmailAttachmentsBlock que sur la facture (titre de modale aligné). Annexes configurées filtrées sur
documentType === 'cra'. Envoi :POST /trainer/activity-reports/{uuid}/sendavecattachmentIds[]etattachmentoptionnel.
🔔 Notifications formateur (Mon compte)
Liste in-app (cloche, bandeau, popup) — GET /notification/list
- Auth : 401 si l’appel est fait sans JWT valide (le backend ne renvoie plus une liste vide pour un utilisateur anonyme).
- Où :
layouts/TrainerLayout.vuevianotificationAPI.getNotifications(). - Quand : uniquement lorsque la route courante correspond à une entrée du menu latéral (noms listés dans
constants/trainerMenuNotificationRoutes.js: tableau de bord, planning / partage / disponibilités, demandes, messages, organisations, liste des factures, hub paramètres). Les écrans « profonds » (ex. configuration d’organisation, édition de facture, sous-pages paramètres) ne déclenchent pas ce fetch. - Fréquence : au plus une fois toutes les 5 minutes ; l’horodatage du dernier fetch réussi est stocké dans
localStoragesous la cléteadle_notification_list_last_fetch_at(réinitialisée à la déconnexion). Pas de polling périodique.
Préférences de notification (AccountView.vue)
La page Mon compte > Notifications permet au formateur de gérer ses optins email :
- Toggle global « Activer toutes les notifications » : active/désactive tous les optins d'un coup.
- Toggle « Bilan mensuel » : optin
MONTHLY_SUMMARY, canalEMAILuniquement. Quand activé, le formateur reçoit un email récapitulatif le 1er de chaque mois (interventions, revenus, répartition par école du mois précédent). - Le toggle « Bilan mensuel » est intégré dans le mécanisme du toggle global (coché/décoché avec les autres).
Demandes de réservation (formateur) — API & store (FEAT-052 / DISC-012)
trainerAPI.markBatchViewed(uuid):PATCH /trainer/reservation/batch/{uuid}/view(Content-Type: application/merge-patch+json, corps vide). Idempotent côté backend si déjà vu.trainerAPI.markAllBatchesViewed():POST /trainer/reservation/batches/mark-all-as-viewed(corps vide). Action bulk idempotente pour marquer toutes les demandes vues (firstViewAt), réponse 200 alignée surgetReservationRequests()(liste + compteurs) pour resynchroniser le store.dashboardAPI.getSummary():GET /trainer/dashboard/summary— métriques du mois (fuseau formateur côté back). Après chargement,DashboardView.vueappelletrainerDashboardStore.setSummaryActivityPeriod(): le sous-titre « Voici votre activité pour … » dansTrainerLayout.vuelitsummaryActivityPeriod(periodMonth,periodYear) ; tant que le résumé n’est pas chargé, repli sur la date locale du navigateur.dashboardAPI.getRevenueByClient(period):GET /trainer/dashboard/revenue-by-client— source du bloc Mes écoles (MySchools.vue, FEAT-078) avec toggle local de période (quarterpar défaut,year,12_months), re-fetch au changement de pill, affichage CA + tendance + prochaine session + dot de statut ; mapping couleur basé en priorité surorganizationStatus(ok/conflict/error/none) pour rester iso avec/trainer/organizations; clic ligne/carte vers/trainer/invoices?organization={organizationId}.DashboardView.vue(FEAT-077) : refonte en layout scrollable maquette (header +KpiStrip, BLOC 1MyDay, BLOC 2ToProcess, BLOC 3MyIndicators,RevenueEvolutionChart,MyStatistics) ; retrait des composants onboarding du dashboard ;InterventionDrawerunique (hub → drill-down pour les demandes).dashboardAPI.getUpcomingInterventions(limit, scope): support du paramètrescope(today/week) ; utilisé pour peupler BLOC 1 avec deux appels parallèles (10,todayet20,week).dashboardAPI.getRecentRequests(limit=5): limite par défaut alignée à 5 ; utilisé dans BLOC 2 pour les demandes récentes et le delta localStorage (teadle_dashboard_requests_count).dashboardAPI.getRevenueEvolution(period='12_months'): période par défaut alignée FEAT-076 ; consommée parRevenueEvolutionChart.vueavec toggle6m/12m/Année.trainerAPI.getPendingRequestsCount():GET /trainer/dashboard/pending-requests-count— compteurs plats (legacy) ; préférertrainerAPI.getDashboardBadges():GET /trainer/dashboard/badges—{ notifications, requests: { pending, urgent, newUnseen }, unreadInbox: { total, conversations, notifications, critical } }pour le polling sidebar (stores/trainerDashboard.js, 2 min dansTrainerLayout.vue). Toast « nouvelle demande » sirequests.pendingaugmente entre deux cycles.fetchBadges()déduplique les appels simultanés (une seule requête en vol). Throttling : au plus un GET réussi toutes les 2 min (rechargement F5 inclus) vialocalStorage.trainer_dashboard_badges_last_fetch_at; le compteur affiché s’appuie surtrainer_pending_requests_countentre deux appels. Clétrainer_dashboard_badges_last_fetch_atretirée au logout.staffAPI.getDashboardBadges():GET /staff/dashboard/badges— même forme (y comprisunreadInboxagrégé) ;stores/staffDashboard.jsalimente le badge « Mes Demandes » + même toast (polling 2 min dansStaffLayout.vue).fetchPendingRequestsCount()déduplique les appels simultanés (une seule requête en vol). Throttling : au plus un GET réussi toutes les 2 min (rechargement F5 inclus) vialocalStorage.staff_dashboard_badges_last_fetch_at; le compteur affiché s’appuie surstaff_pending_requests_countentre deux appels. Cléstaff_dashboard_badges_last_fetch_atretirée au logout. Le chargement initial staff ne passe plus parauth: uniquementStaffLayout/ vues dashboard.- Messagerie (
MessagesInbox.vue,stores/messages.js) : au montage,fetchBadges()/fetchPendingRequestsCount()sansforce— même plafond 2 min que le layout (pas d’appel systématique à chaque visite de/trainer/messagesou/staff/messages). Après marquage lu,applyUnreadInboxDeltasur le store dashboard ; quandGET /conversationsa chargé toute la liste (items.length === totalItems),reconcileUnreadConversationsFromLoadedListaligneunreadInbox.conversationsettotalsans/badges.refreshInboxBadgesFromAuthaprès les actions ne force plus le GET (throttle respecté). Dès queunreadInbox.notifications(réponse/badges) passe à > 0,TrainerLayout/StaffLayoutappellentsyncUnreadCountFromBadgespuis unGET /notifications/searchsilencieux (fetchNotifications(true, { silent: true })) pour remplir la liste deBellDropdown(badge rouge = même compteur) sans basculer le skeleton de la page Messages. - BellDropdown (FEAT-118 / DISC-019 V6) : la cloche header
components/messages/BellDropdown.vuesuit le pattern DS 2026 (ARIA dynamiquearia-haspopup+aria-labelavec count, fermetureEsc+ retour focus, zonearia-live, séparation visuelle lu/non-lu, empty state rassurant, drawer mobile bottom-sheet). Actions : marquage lu unitaire au clic (POST /notifications/{uuid}/read) et bulk Tout marquer lu (POST /notifications/read-allviamessagesStore.markAllNotificationsAsRead). Catégorie sync calendrier : prise en charge du type backendsync_failure(fallback payloadcategory=calendar_sync_failure) avec deep-link vers/trainer/organizations/{uuid}/configure?tab=connecteurs. trainerAPI.getBatchMessages(uuid):GET /trainer/reservation/batch/{uuid}/messages—{ messages: [{ uuid, senderName, senderType, content, createdAt }] }(FEAT-051).trainerAPI.sendBatchMessage(uuid, content):POST /trainer/reservation/batch/{uuid}/messagesavec corps JSON{ content }— même forme de réponse que le GET (FEAT-051). Utilisé parRequestDrawer.vue(FEAT-055).useRequestsStore: charge les batches viagetReservationRequests, expose filtres / tri / polling 30 s ; les méthodes messages / compteurs peuvent être branchées par les vues ou issues ultérieures selon besoin.- Acceptation (BUG-042) :
AcceptModal.vuedésactive le bouton « Confirmer l'acceptation » et affiche un indicateur de chargement pendant l’appel API ; en cas d’erreur serveur,RequestsView.vueferme la modale (évite un état bloquant si la demande a toutefois été acceptée côté serveur) — l’utilisateur peut réessayer depuis la carte.
📅 Mon planning (PlanningView) — FEAT-135 / DISC-023 Phase 1
- Route :
/trainer/planning— coquilleh-screen flex flex-col overflow-hidden; seule la zone sous la toolbar défile (flex-1 overflow-y-auto).TrainerLayout:overflow-hiddensur cette route. - Header (FEAT-137) : en-tête intégré dans la vue (
hidden lg:flex) ;TrainerLayoutmasque sa barre titre en desktop (isPlanningSettingsChromeRouteinclut/trainer/planning). Actions : Configurer, compteur partages, Partager +BellDropdown. Mobile : titre dans le layout, actions dans une barre compacte sous le header layout. - Drawer partages (FEAT-141) :
Teleport+ slide-in (sm:w-96/ bottom-sheet mobile) ; liste compacte (loadPartages+normalizePlanningSharesResponse) ; liens Gérer tous les partages →trainer-planning-share; Nouveau partage →trainer-planning-share?new=1(ouvre modale FEAT-140) ;useFocusTrap+ Escape. - Deep-link entrant (FEAT-143) :
?highlight=<eventId>(semaine + scroll +.is-highlightedviauseCrossPageHighlight) ;?date=YYYY-MM-DD(semaine ciblée). Redirections legacy/trainer/planning/shareet/trainer/planning/availability→ routes settings (router). - Drawer évènement (FEAT-142/144) : clic sur un bloc → slide-over consultation ; Supprimer l'évènement si
isRemovable+ règle >48h (eventAPI.deleteEvent) ; warning CA dans modale ; Annuler l'intervention (modale shell, distinct de la suppression). - Toolbar :
CalendarToolbar.vue— chevrons période, labelaria-live, « Aujourd'hui », switcher Jour / Semaine / Mois (role="group",aria-current). - Filtres :
CalendarFilterBar.vue(checkbox par calendrier/org, compteur, « + Ajouter un calendrier » → stub log). - Colonne gauche :
BaseAlertbanner perso (dismiss →localStorage),PlanningMiniCalendar(:ref-date,:view,@select-day),CalendarLegend. - Grilles DS (plus de FullCalendar sur cette route) : semaine =
CalendarWeekGrid+CalendarEventBlock(lanes) ; mois =CalendarMonthGrid; jour =CalendarDayAgenda. Clic événement → log (drawer FEAT-142). - API :
GETplanning viatrainerAPI.getPlanning({ dateStart, dateEnd })et fenêtre 5 moisgetFiveMonthIsoRange(refDate); re-fetch au changement de mois (watch refDate). Calendriers dérivés des événements (syncCalendarsFromEvents, id perso__personal_calendar__). Les demandes en attente apparaissent comme événements org (status: pending, libellé « Demande — {module} »,data.requestId→ CTA drawer Répondre vers Mes demandes). - États : skeleton
motion-safe:animate-pulse; erreur + Réessayer ; vide si aucun événement filtré sur la période visible (showEmptyPeriod+getPlanningEmptyPeriodTitle(view)). - Compteur partages (header) : reporté FEAT-137 (anciennement
GET /trainer/planning/share+PlanningHeaderActions). - API dashboard formateur :
GET /trainer/dashboard/badgesau montage duTrainerLayout(polling 2 min, badges menu).GET /trainer/dashboard/summaryet les autres routesdashboardAPI(/revenue-evolution,/upcoming-interventions, etc.) sont déclenchés uniquement parDashboardView(lazy route) ; les requêtes en vol sont ignorées si l’utilisateur quitte la page.GET /trainer/organization/dashboard(sync calendriers) n’est plus bootstrapé depuis le layout sur Planning — seulement sur routes organisations / configuration (trainerSyncBootstrapRoutes.js).
🗓️ Gestion des disponibilités
Vue Disponibilités (AvailabilityView)
- Route :
/trainer/settings/planning/availability(ongletshoraires,feries,absences,personal). - Onglet Horaires hebdo (FEAT-138) : 2 niveaux — liste des plannings (cards + menu 3-points : renommer, dupliquer, défaut, supprimer) puis éditeur (nom, 7 jours, N plages
startTime/endTimepar jour, copier-vers, planning par défaut). Modèle directrules:[{day, slots}](sansavailabilityWeekDays.js). Temps de trajet minimum uniquement dans Réglages avancés (collapse) →PUT /trainer/availability/configuration(minTravelTime, plus envoyé par plan). Suppression :BaseModal; bloquée si partages actifs (MVP, pas dereplaceByAPI). Tabs :BaseTabs(APG clavier). - Onglets : Jours fériés et Absences via
PUT /trainer/availability/configuration; Agenda personnel (OAuth Google/Outlook ou lien / fichier iCal —TrainerPersonalCalendarConnector.vue→TrainerCalendarConnectorHubflow="personal"). - Absences (FEAT-139) : chaque entrée
vacationSlots[]acceptestart,endet un motif optionnellabel; formulaire avec toggle Journée entière (défaut,T00:00/T23:59) ou Plage horaire (BaseInput type="time"). Métadonnée UIallDaylocale (non envoyée au backend) + heuristiqueT00:00/T23:59au chargement pour rétrocompat. - Fériés (FEAT-139) : callout
BaseAlert, togglesBaseTogglepar jour férié FR. - Agenda (FEAT-139) : callout
BaseAlert+ icôneShieldCheck;TrainerPersonalCalendarConnectorinchangé (OAuth/sync prod).
Page Partages (ShareCalendarView, FEAT-140)
- Route :
/trainer/settings/planning/shares— hub Paramètres → tuile Partages. - Liste :
PlanningShareEntityCard(dot activitélastViewedAt, badges type, actions Copier/Configurer/Révoquer). - Modale unifiée :
PlanningShareModal— 3 modes (lien public / contact d'org / email libre), combobox contacts, options avancées repliables, terminologie orientée résultat (visibilité, permission). - Révocation :
PlanningShareRevokeModal(wrapperBaseModal) ; retraitROLE_AGENDAsi type contact. - Sidebar consultative :
PlanningContactSidebar(distinct deContactSidebar.vuepage Organisations) — toggle agenda + lien « Modifier dans Organisations ». - API :
getPlanningShares,createPlanningShare,updatePlanningShare,deletePlanningShare,getOrganizationContacts,updateOrganizationContactRoles.
Validation des créneaux horaires
Les créneaux (startTime / endTime) sont validés à trois niveaux :
- Frontend (UX) —
AvailabilityView.vue(éditeur N slots, FEAT-138) : - Par plage : fin après début (
isSlotInvalid) ; chevauchement même jour (warning orange,slotOverlaps). -
Bouton « Enregistrer » désactivé si nom vide ou
hasInvalidSlots(fin ≤ début sur une plage active). -
Infrastructure (API) —
TrainerAvailabilitySlotRequest: -
Contrainte
#[Assert\Callback]validateTimeRange(): vérifieendTime > startTime, violation sur le pathendTime. -
Domain (entité) —
TrainerAvailabilitySlot: - Le constructeur lance une
\InvalidArgumentExceptionsiendTime <= startTime.
🔄 Gestion des erreurs
Erreurs de connexion
- 401 - Identifiants incorrects : Message d'erreur explicite
- 429 - Trop de tentatives : Attendre avant nouvelle tentative
- 500 - Erreur serveur : Réessayer plus tard
Erreurs d'inscription
- 409 - Email déjà utilisé : Proposer connexion
- 422 - Données invalides : Validation côté client
- 500 - Erreur serveur : Réessayer plus tard
Erreurs OAuth
- Annulation utilisateur : Retour à la page d'inscription
- Erreur provider : Message d'erreur spécifique
- Erreur callback : Redirection vers page d'erreur
Erreurs de reprise d'onboarding
- Données corrompues : Nettoyage et redémarrage
- Token expiré : Reconnexion requise
- Étape manquante : Redirection vers étape appropriée
Soumission de réservation (Booking Wizard) — anti-double envoi (BUG-041)
- Le CTA "Valider l'envoi" est désactivé pendant la requête (
loading) pour bloquer les doubles clics. submitBookingapplique un garde-fou immédiat (if (loading) return) avant de lancer l’appel API.- Une clé
idempotencyKeyest générée côté front une seule fois par tentative et réutilisée sur les retries de la même tentative. - La clé est envoyée via le header
Idempotency-Keysur : publicAPI.createReservations(...)staffAPI.createReservations(...)- En cas de succès, la clé est réinitialisée ; en cas d’échec, elle est conservée pour permettre un retry idempotent.
🧪 Tests automatisés
Tests Cypress implémentés
- ✅ Tests de connexion (11/11 passés)
- ✅ Tests d'onboarding de base (7/11 passés)
- ✅ Tests de redirection OAuth (3/3 passés)
Couverture des workflows
- ✅ Navigation vers onboarding
- ✅ Flux de base trainer/organization
- ✅ Callbacks OAuth
- ✅ Reprise d'onboarding
- ❌ Initiation OAuth (boutons non trouvés)
Commandes de test
// Compléter l'onboarding trainer
cy.completeTrainerOnboarding({
email: 'trainer@test.com',
password: 'Test123!',
firstName: 'John',
lastName: 'Trainer'
})
// Compléter l'onboarding organisation
cy.completeOrganizationOnboarding({
email: 'org@test.com',
password: 'Test123!',
firstName: 'Jane',
lastName: 'Organization'
})
📝 Notes techniques
APIs principales
- Authentification :
/security/login,/security/user/{provider}/oauth - Inscription :
/security/trainer/register,/security/organization/register - Validation email :
/security/auth-code/send,/security/auth-code/verify - Onboarding :
/trainer/preferences,/organization/preferences,/organization/info - Utilisateur :
/me - Contacts (trainer) :
GET/POST/PUT/DELETE /trainer/organization/{uuid}/contact/...;POST .../contact/{uuid_contact}/invite-teadle(invitation email, 201) - Dashboard organisations (trainer) :
GET /trainer/organization/dashboard— alimenteOrganizationsView.vue(/trainer/organizations, refonte FEAT-040). Layout : titre + sous-titre + CTA dans le header globalTrainerLayout(useTrainerOrganizationsHeader+OrganizationsListHeaderActions) ; zone scrollable avec barre de résumé (compteurs cliquables), recherche fondmushroom-50, tri (nom A→Z / Z→A, CA décroissant, formations = tri surcountUnlinkedAssignments), pills de statut. Ajout : CTA verstrainer-organizations-configure;/trainer/organizations/addredirige (FEAT-048). Avatar liste :getColorForId(uuid || nom)+getInitials(nom)(useAvatarColor) pour cohérence maquette ;calendarColorreste disponible côté données. Par assignation :totalHours/totalRevenue(planifié) ;totalRealizedHours/totalRealizedRevenue; si le backend exposetotalInvoicedRevenue, la ligne « dont X € facturé » l’utilise, sinon repli surtotalRealizedRevenue. Statut organisation :assignment.status(ok/conflict/error/none) est la source de vérité pour badges + filtres (fallback legacy conservé côté front). Cartes : heures « réalisées / planifiées » et « Z % réalisé » ; zone stats vide sitotalHours === 0. Sync :countUnlinkedAssignments,lastImportErrorCode, bandeaux aprèsPOST …/sync+GET /process/{uuid}.providerType: libellé FR viacalendarConnectorProviders.js(ligne métadonnées avec type + contact). Dissociation : menu kebab → modale confirmation (saisie « Supprimer ») →DELETEviaorganizationAPI.unassignTrainerOrganization, puis rechargement dashboard. Recherche : nom, type (libellé FR), fournisseur, contact (plus de filtre sur la ville). - Polling de synchronisation calendrier (DISC-019 / FEAT-108, PERF-115) : factorisé dans
composables/useSyncPolling.js.createSyncPoller(options)sert aux cas multi-process (ex.OrganizationsView.vue, Map d’instances par organisation).useSyncPolling(options)sert aux cas single-process avec auto-cleanup (onUnmounted) côtéConfigureOrganizationView.vue. États terminaux inchangés :TERMINAL_STATES = ['validated', 'failed']. Fréquence dynamique PERF-115 : backoff progressif1s (0-10s) -> 2s (10-30s, nominalPOLL_INTERVAL_MS) -> 5s (30s+), pause automatique quand l’onglet est caché (document.visibilityState === 'hidden'), reprise au retour visible avec tick immédiat et reset du backoff. - Store de synchronisation agrégée (DISC-019 / FEAT-109) :
stores/syncStore.jscentralise l’état multi-orgs (orgStates) et exposesyncingCount,errorCount(>= 2échecs consécutifs),syncGlobalState(error > syncing > stable),calmDotTooltip,lastGlobalSyncFormatted.bootstrap()consommeGET /trainer/organization/dashboardpuis relance les pollers actifs viacurrentSyncProcessUuid. Les vues peuvent notifierstartSync/onSyncCompleteet le layout nettoie viastopAll(). - Composant Calm Dot sidebar (DISC-019 / FEAT-110) :
components/Trainer/SidebarSyncDot.vueconsommeuseSyncStoreet rend 3 états utiles : rien enstable, dot bleu animé ensyncing(compteur si > 1), badge rouge enerror(prioritaire en coexistence). Navigation au clic :trainer-organizations(ou?filter=erroren rouge). A11y :role="status",aria-labeldynamique,titleviacalmDotTooltip, fallbackprefers-reduced-motion. - Toasts synchronisation (DISC-019 / FEAT-111) :
composables/useToast.jsgère un stack singleton max 3 (addToast,dismissToast,clearAllToasts) ;components/ui/ToastContainer.vueaffiche le stack enTeleportbas-droite ;components/ui/SyncToast.vueencapsuleBaseToast: succès (durationMs=5000,pausable=true) et erreur persistante (durationMs=0, CTA optionnel “Voir l’organisation”). - Intégration layout (DISC-019 / FEAT-112) :
layouts/TrainerLayout.vuemonteToastContainerglobalement, ajouteSidebarSyncDotsur l’entrée menu Organisations (mobile + desktop non replié), déclenchesyncStore.bootstrap()auonMountedetsyncStore.stopAll()auonUnmounted. - OrganizationsView (DISC-019 / FEAT-113) : la carte org affiche un 5e badge prioritaire “Sync. en cours” (spinner bleu) qui masque les 4 badges métier pendant la sync locale (
syncingOrganizations). La vue alimenteuseSyncStore(startSyncau dispatch,onSyncCompleteaux terminaux,stopSyncsur erreurs) et émet des toasts globaux viauseToast(succès/erreur + CTA connecteurs). Support deeplink?filter=error: filtre appliqué au mount, scroll vers la première carte erreur, puis nettoyage de l’URL viarouter.replace. - ConfigureOrganizationView (DISC-019 / FEAT-114) : pas de changement UI sur l’onglet Connecteurs (progression/timeline/bannières conservées) ; la vue alimente
useSyncStoreen parallèle du polling local (startSyncau démarrage upload/link/re-sync + callback OAuth?syncProcess=,onSyncCompleteen succès/échec terminal,stopSyncen erreur de suivi). Toast global d’erreur ajouté viauseToastpour les échecs sync OAuth/iCal (pas de toast succès).
États de l'application
- Non connecté : Accès aux pages publiques uniquement
- Connecté, onboarding incomplet : Redirection vers onboarding/reprise
- Connecté, onboarding complet : Accès aux dashboards
Stockage local
- Token JWT : Authentification
- Données onboarding : Progression du parcours (
teadle_onboarding_data) - État OAuth : Gestion des callbacks
Services d'onboarding
onboardingStorage: Gestion du localStorageonboardingGuard: Protection des routesuseOnboardingStore: progression DISC-016 (checklist post-login)
🔧 Services et composants
Services principaux
// Gestion du stockage
import { onboardingStorage } from '@/services/(onboarding)/onboardingStorage'
// Protection des routes
import { onboardingGuard } from '@/services/(onboarding)/onboardingGuard'
Composants d'onboarding
OnboardingLayout.vue: Layout avec étapes et scroll interne (h-screen flex flex-col+ conteneurflex-1 overflow-y-auto) pour éviter un écran bloqué quandbodyest enoverflow: hidden(cas modales/overlays).OnboardingConnectedLayout.vue: même convention via utilitairespage-fullscreen/page-fullscreen-scroll(main.css) — header fixe, fil d’Ariane + slot dans la zone scrollable.SharedPlanningView.vue(views/(unauthenticated)/Planning/) : vue publique pleine page alignée sur la même convention de scroll interne (h-screen flex flex-col+ wrapperflex-1 overflow-y-auto) pour rester scrollable même sibodyest temporairement verrouillé par un overlay.- Auth / pages publiques hors layout (
SigninView,SignupView,PasswordResetView,LoginView,OnboardingView, suivi réservation, invitations,OAuthRedirect) : racinepage-fullscreen+ contenu danspage-fullscreen-scroll;App.vueenh-full flex flex-colcarbody { overflow: hidden }global. GettingStartedView.vue: checklist DISC-016 post-loginOnboardingChecklistItem.vue: item d'étape avec CTAOnboardingCelebrationModal.vue: modale de célébration 5/5
Routes protégées
// Routes avec validation d'onboarding
meta: { requiresOnboardingValidation: true }
// Routes de reprise
'/onboarding/trainer/personal-info'
'/onboarding/organization/personal-info'
Dernière mise à jour : Août 2025