Aller au contenu

🔄 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

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

  1. Page d'accueil (/)
  2. Formulaire email/mot de passe
  3. Boutons OAuth (LinkedIn, Google, Microsoft)
  4. Boutons d'inscription (Trainer, Organization)

  5. Validation des identifiants

  6. API : POST /security/login
  7. Gestion des erreurs (401, 500, etc.)

  8. Récupération des données utilisateur

  9. API : GET /me
  10. Vérification de finishedOnBoarding

  11. Redirection intelligente

  12. Si finishedOnBoarding: true → Dashboard approprié
  13. 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

  1. Sélection du profil (/onboarding/trainer)
  2. Choix du niveau d'expérience
  3. Sauvegarde : onboardingStorage.saveSection('trainerExperience', {...})
  4. Bouton "Continuer"

  5. Inscription (/onboarding/trainer/signup)

  6. Formulaire email/password
  7. Boutons OAuth (LinkedIn, Google, Microsoft)
  8. API : POST /security/trainer/register
  9. Sauvegarde : onboardingStorage.saveSection('signup', {...})

  10. Redirection vers onboarding

  11. Token JWT reçu
  12. 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

  1. Inscription (/onboarding/organization/signup)
  2. Formulaire email/password
  3. Boutons OAuth (LinkedIn, Google, Microsoft)
  4. API : POST /onboarding/user-exist/check
  5. Sauvegarde : onboardingStorage.saveSection('signup', {...})

  6. Validation email (/onboarding/organization/email-validation)

  7. Envoi automatique du code
  8. API : POST /security/auth-code/send
  9. Saisie du code à 6 chiffres
  10. API : POST /security/auth-code/verify

  11. Création du compte

  12. API : POST /security/organization/register
  13. Token JWT reçu
  14. 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

  1. Informations personnelles (/onboarding/trainer/personal-info)
  2. Prénom, nom
  3. Mot de passe (si pas OAuth/Resume)
  4. Acceptation CGV
  5. API : PUT /trainer/preferences
  6. Sauvegarde : onboardingStorage.saveSection('personalInfo', {...})

  7. Options de profil (/onboarding/trainer/profile-options)

  8. Configuration du profil
  9. Sauvegarde : onboardingStorage.saveSection('profileOptions', {...})
  10. Bouton "Continuer"

  11. Édition des expériences (/onboarding/trainer/experiences-edit)

  12. Import depuis CV (si applicable)
  13. Ajout/modification d'expériences
  14. API : POST /trainer/career
  15. Sauvegarde : onboardingStorage.saveSection('experiences', {...})

  16. Finalisation

  17. Depuis Options de profil (« Continuer plus tard ») : POST /user/onboarding/validate (finishOnboarding) puis onboardingStore.fetchProgress() (GET /me) pour synchroniser la progression onboarding et sampleData.
  18. Depuis Édition des expériences (après POST /trainer/career) : fetchProgress() avant redirection pour la même mise à jour.
  19. Nettoyage localStorage (parcours expériences)
  20. 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

  1. Informations personnelles (/onboarding/organization/personal-info)
  2. Prénom, nom
  3. Numéro de téléphone (optionnel)
  4. Acceptation CGV et opt-in
  5. API : PUT /organization/preferences
  6. Sauvegarde : onboardingStorage.saveSection('personalInfo', {...})

  7. Paramètres organisation (/onboarding/organization/settings)

  8. Nom de l'organisation
  9. Service au sein de l'organisation
  10. Type d'organisation
  11. API : PATCH /organization/info
  12. Sauvegarde : onboardingStorage.saveSection('settings', {...})

  13. Finalisation

  14. API : GET /me (mise à jour finishedOnBoarding)
  15. 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

  1. Détection automatique
  2. Vérification finishedOnBoarding: false
  3. Analyse du localStorage
  4. Affichage bannière de reprise

  5. Mécanisme de reprise

  6. Reprise pilotée par les données persistées (signupMethod: 'resume')
  7. Redirection vers l'étape connectée attendue
  8. Préremplissage des données déjà connues

  9. Mode reprise

  10. Pré-remplissage des champs
  11. Masquage des étapes inutiles
  12. Sauvegarde des données existantes

  13. Gestion des données

  14. Section signup avec signupMethod: 'resume'
  15. Données utilisateur pré-remplies
  16. 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

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, role status) : visible si onboardingProgress.sampleData.active === true et pas en fenetre pre-avert J-2 ; action "Supprimer les exemples" => POST /trainer/onboarding/sample-data/dismiss via onboardingStore.dismissSampleData().
  • Pre-avert J-2 (PreAvertBanner, role alert) : visible si sample actif et expiresAt <= now + 2 jours ; CTA "Partager mon profil" vers /trainer/getting-started.
  • Première vraie demande (FirstRealRequestBanner, vert maquette 🎉, role status) : visible hors mode sample si GET /trainer/dashboard/recent-requests retourne au moins un item avec isFirst: true (backend : lot affiché dans « À traiter » est le plus ancien lot du formateur en base — première demande encore en attente) et flag localStorage teadle_disc016_first_request_seen absent ; sous-titre depuis org / module / créneaux ; CTA « Voir la demande » vers /trainer/requests et pose du flag.

Injection sample dashboard (FEAT-098)

Quand sampleData.active === true, le dashboard passe en mode demonstration :

  • DashboardView.vue recupere onboardingStore.sampleDataPayload via fetchSampleDataPayload() (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, cle teadle_onboarding_sample_data_cache) tant que le sample reste actif ; invalidation du cache uniquement si GET /me renvoie explicitement onboardingProgress.sampleData.active === false, après Supprimer les exemples (dismissSampleData), ou logout (clearBrowserStorageOnLogout). Pas de purge sur une réponse /me sans champ sampleData / active (évite un F5 qui efface le cache). Le snapshot teadle_onboarding_progress_snapshot (voir ci-dessus) évite de perdre le flag sampleData.active au premier rendu post-F5. Si sampleData.active === false dans 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.vue que 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 Exemple et les clics metier sont interceptes via sample-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.batches et cartes depuis dashboard.schoolBreakdowns (onboardingSampleData.js). Actions sensibles → SampleModal pour les lignes d'exemple.
  • FEAT-099 : sample-click ouvre SampleModal (guardrail) avec CTA "Completer mon profil" vers /trainer/getting-started ; fermeture possible via overlay, Escape ou bouton "Fermer".
  • FEAT-099 + FIX-106 : EmptyState.vue est utilise sur les empty states dashboard (ToProcess, MyDay, MySchools, MyIndicators, MyStatistics) avec onboardingLink vers /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

  1. Opportunités (/dashboard/trainer)
  2. Nouvelles missions disponibles
  3. Filtres par domaine/compétence
  4. Bouton "Postuler"

  5. Actions à effectuer

  6. Missions en cours
  7. Documents à fournir
  8. Évaluations à compléter

  9. Planning hebdomadaire

  10. Sessions programmées
  11. Disponibilités (voir Gestion des disponibilités)
  12. Calendrier interactif

  13. Revenus

  14. Statistiques mensuelles
  15. Factures en attente
  16. 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

  1. Gestion des formateurs
  2. Liste des formateurs
  3. Profils et compétences
  4. Évaluations et feedback

  5. Gestion des missions

  6. Missions en cours
  7. Planning des formations
  8. Suivi des performances

  9. Facturation

  10. Factures générées
  11. Paiements reçus
  12. Rapports financiers

  13. Statistiques

  14. Performance des formateurs
  15. Satisfaction clients
  16. 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 (et PATCH /trainer/company pour company) ; récupération via GET /trainer/billing/settings et GET /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. useBillingDocumentForm alimente toujours documentData ; toute modification met isDirty comme pour le corps de la facture.
  • Brouillon : tant que isDirty est 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 en attachmentIds[] dans le POST /trainer/invoices/{uuid}/send (attachment pour 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-slottexte 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 — popover position: fixed avec 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.vue monté en bas-droite (toast ref + showToast / alias showInlineToast pour 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ère markInvoicePaidError ni 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-motion global (assets/main.css) ; focus visible sur checkboxes/radios filtres ; cibles tactiles 24×24 sur actions tableau factures ; déclaration publique frontend/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) ; filet main.css inchangé comme garde-fou.

Liste factures & CRA (InvoicesView.vue) et édition CRA (CreateCRAView.vue)

  • Aperçu HTML : CreateCRAView affiche un aperçu du document sur la largeur restante à 100 % du conteneur (billing_template_preview.html.twig : plus de max-width sur .billing-preview-root--cra, anciennement ~48rem centré), carte rounded-xl comme la facture, avec un bandeau droit pleine largeur en mobile et ~288 px à partir de lg : statut (Brouillon / Envoyé), code + modèle, puis actions (PDF, Enregistrer, Envoyer, Générer facture, Supprimer le CRA) — le header du layout n’affiche plus CRAHeaderActions (nettoyé au montage). Contenu v-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êtes data-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 depuis calendar_configurator_assignment.class_name. Dès l’init (et à chaque rafraîchissement), POST /trainer/activity-reports/preview est appelé : organizationUuid optionnel — 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 deux nextTick pour laisser useBillingDocumentForm appliquer l’UUID par défaut ; un watch inclut 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 attributs data-bp="…" (cibles SPA) en plus de data-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 ( SearchableSelect toujours visible, même logique que sur CreateInvoiceView — 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"> dans billing_template_preview.html.twig, au-dessus de l’adresse) dès l’aperçu avec POST /activity-reports/preview ; l’adresse / contact suivent. Aperçu POST /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}/send avec attachmentIds[] et attachment optionnel.

🔔 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).
  • : layouts/TrainerLayout.vue via notificationAPI.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 localStorage sous 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, canal EMAIL uniquement. 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 sur getReservationRequests() (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.vue appelle trainerDashboardStore.setSummaryActivityPeriod() : le sous-titre « Voici votre activité pour … » dans TrainerLayout.vue lit summaryActivityPeriod (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 (quarter par défaut, year, 12_months), re-fetch au changement de pill, affichage CA + tendance + prochaine session + dot de statut ; mapping couleur basé en priorité sur organizationStatus (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 1 MyDay, BLOC 2 ToProcess, BLOC 3 MyIndicators, RevenueEvolutionChart, MyStatistics) ; retrait des composants onboarding du dashboard ; InterventionDrawer unique (hub → drill-down pour les demandes).
  • dashboardAPI.getUpcomingInterventions(limit, scope) : support du paramètre scope (today / week) ; utilisé pour peupler BLOC 1 avec deux appels parallèles (10,today et 20,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 par RevenueEvolutionChart.vue avec toggle 6m/12m/Année.
  • trainerAPI.getPendingRequestsCount() : GET /trainer/dashboard/pending-requests-count — compteurs plats (legacy) ; préférer trainerAPI.getDashboardBadges() : GET /trainer/dashboard/badges{ notifications, requests: { pending, urgent, newUnseen }, unreadInbox: { total, conversations, notifications, critical } } pour le polling sidebar (stores/trainerDashboard.js, 2 min dans TrainerLayout.vue). Toast « nouvelle demande » si requests.pending augmente 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) via localStorage.trainer_dashboard_badges_last_fetch_at ; le compteur affiché s’appuie sur trainer_pending_requests_count entre deux appels. Clé trainer_dashboard_badges_last_fetch_at retirée au logout.
  • staffAPI.getDashboardBadges() : GET /staff/dashboard/badges — même forme (y compris unreadInbox agrégé) ; stores/staffDashboard.js alimente le badge « Mes Demandes » + même toast (polling 2 min dans StaffLayout.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) via localStorage.staff_dashboard_badges_last_fetch_at ; le compteur affiché s’appuie sur staff_pending_requests_count entre deux appels. Clé staff_dashboard_badges_last_fetch_at retirée au logout. Le chargement initial staff ne passe plus par auth : uniquement StaffLayout / vues dashboard.
  • Messagerie (MessagesInbox.vue, stores/messages.js) : au montage, fetchBadges() / fetchPendingRequestsCount() sans force — même plafond 2 min que le layout (pas d’appel systématique à chaque visite de /trainer/messages ou /staff/messages). Après marquage lu, applyUnreadInboxDelta sur le store dashboard ; quand GET /conversations a chargé toute la liste (items.length === totalItems), reconcileUnreadConversationsFromLoadedList aligne unreadInbox.conversations et total sans /badges. refreshInboxBadgesFromAuth après les actions ne force plus le GET (throttle respecté). Dès que unreadInbox.notifications (réponse /badges) passe à > 0, TrainerLayout / StaffLayout appellent syncUnreadCountFromBadges puis un GET /notifications/search silencieux (fetchNotifications(true, { silent: true })) pour remplir la liste de BellDropdown (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.vue suit le pattern DS 2026 (ARIA dynamique aria-haspopup + aria-label avec count, fermeture Esc + retour focus, zone aria-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-all via messagesStore.markAllNotificationsAsRead). Catégorie sync calendrier : prise en charge du type backend sync_failure (fallback payload category=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}/messages avec corps JSON { content } — même forme de réponse que le GET (FEAT-051). Utilisé par RequestDrawer.vue (FEAT-055).
  • useRequestsStore : charge les batches via getReservationRequests, 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.vue désactive le bouton « Confirmer l'acceptation » et affiche un indicateur de chargement pendant l’appel API ; en cas d’erreur serveur, RequestsView.vue ferme 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 — coquille h-screen flex flex-col overflow-hidden ; seule la zone sous la toolbar défile (flex-1 overflow-y-auto). TrainerLayout : overflow-hidden sur cette route.
  • Header (FEAT-137) : en-tête intégré dans la vue (hidden lg:flex) ; TrainerLayout masque sa barre titre en desktop (isPlanningSettingsChromeRoute inclut /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 partagestrainer-planning-share ; Nouveau partagetrainer-planning-share?new=1 (ouvre modale FEAT-140) ; useFocusTrap + Escape.
  • Deep-link entrant (FEAT-143) : ?highlight=<eventId> (semaine + scroll + .is-highlighted via useCrossPageHighlight) ; ?date=YYYY-MM-DD (semaine ciblée). Redirections legacy /trainer/planning/share et /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, label aria-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 : BaseAlert banner 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 : GET planning via trainerAPI.getPlanning({ dateStart, dateEnd }) et fenêtre 5 mois getFiveMonthIsoRange(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/badges au montage du TrainerLayout (polling 2 min, badges menu). GET /trainer/dashboard/summary et les autres routes dashboardAPI (/revenue-evolution, /upcoming-interventions, etc.) sont déclenchés uniquement par DashboardView (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 (onglets horaires, 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/endTime par jour, copier-vers, planning par défaut). Modèle direct rules:[{day, slots}] (sans availabilityWeekDays.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 de replaceBy API). 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.vueTrainerCalendarConnectorHub flow="personal").
  • Absences (FEAT-139) : chaque entrée vacationSlots[] accepte start, end et un motif optionnel label ; formulaire avec toggle Journée entière (défaut, T00:00/T23:59) ou Plage horaire (BaseInput type="time"). Métadonnée UI allDay locale (non envoyée au backend) + heuristique T00:00/T23:59 au chargement pour rétrocompat.
  • Fériés (FEAT-139) : callout BaseAlert, toggles BaseToggle par jour férié FR.
  • Agenda (FEAT-139) : callout BaseAlert + icône ShieldCheck ; TrainerPersonalCalendarConnector inchangé (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 (wrapper BaseModal) ; retrait ROLE_AGENDA si type contact.
  • Sidebar consultative : PlanningContactSidebar (distinct de ContactSidebar.vue page 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 :

  1. Frontend (UX)AvailabilityView.vue (éditeur N slots, FEAT-138) :
  2. Par plage : fin après début (isSlotInvalid) ; chevauchement même jour (warning orange, slotOverlaps).
  3. Bouton « Enregistrer » désactivé si nom vide ou hasInvalidSlots (fin ≤ début sur une plage active).

  4. Infrastructure (API)TrainerAvailabilitySlotRequest :

  5. Contrainte #[Assert\Callback] validateTimeRange() : vérifie endTime > startTime, violation sur le path endTime.

  6. Domain (entité)TrainerAvailabilitySlot :

  7. Le constructeur lance une \InvalidArgumentException si endTime <= 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.
  • submitBooking applique un garde-fou immédiat (if (loading) return) avant de lancer l’appel API.
  • Une clé idempotencyKey est 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-Key sur :
  • 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 — alimente OrganizationsView.vue (/trainer/organizations, refonte FEAT-040). Layout : titre + sous-titre + CTA dans le header global TrainerLayout (useTrainerOrganizationsHeader + OrganizationsListHeaderActions) ; zone scrollable avec barre de résumé (compteurs cliquables), recherche fond mushroom-50, tri (nom A→Z / Z→A, CA décroissant, formations = tri sur countUnlinkedAssignments), pills de statut. Ajout : CTA vers trainer-organizations-configure ; /trainer/organizations/add redirige (FEAT-048). Avatar liste : getColorForId(uuid || nom) + getInitials(nom) (useAvatarColor) pour cohérence maquette ; calendarColor reste disponible côté données. Par assignation : totalHours / totalRevenue (planifié) ; totalRealizedHours / totalRealizedRevenue ; si le backend expose totalInvoicedRevenue, la ligne « dont X € facturé » l’utilise, sinon repli sur totalRealizedRevenue. 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 si totalHours === 0. Sync : countUnlinkedAssignments, lastImportErrorCode, bandeaux après POST …/sync + GET /process/{uuid}. providerType : libellé FR via calendarConnectorProviders.js (ligne métadonnées avec type + contact). Dissociation : menu kebab → modale confirmation (saisie « Supprimer ») → DELETE via organizationAPI.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 progressif 1s (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.js centralise l’état multi-orgs (orgStates) et expose syncingCount, errorCount (>= 2 échecs consécutifs), syncGlobalState (error > syncing > stable), calmDotTooltip, lastGlobalSyncFormatted. bootstrap() consomme GET /trainer/organization/dashboard puis relance les pollers actifs via currentSyncProcessUuid. Les vues peuvent notifier startSync / onSyncComplete et le layout nettoie via stopAll().
  • Composant Calm Dot sidebar (DISC-019 / FEAT-110) : components/Trainer/SidebarSyncDot.vue consomme useSyncStore et rend 3 états utiles : rien en stable, dot bleu animé en syncing (compteur si > 1), badge rouge en error (prioritaire en coexistence). Navigation au clic : trainer-organizations (ou ?filter=error en rouge). A11y : role="status", aria-label dynamique, title via calmDotTooltip, fallback prefers-reduced-motion.
  • Toasts synchronisation (DISC-019 / FEAT-111) : composables/useToast.js gère un stack singleton max 3 (addToast, dismissToast, clearAllToasts) ; components/ui/ToastContainer.vue affiche le stack en Teleport bas-droite ; components/ui/SyncToast.vue encapsule BaseToast : 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.vue monte ToastContainer globalement, ajoute SidebarSyncDot sur l’entrée menu Organisations (mobile + desktop non replié), déclenche syncStore.bootstrap() au onMounted et syncStore.stopAll() au onUnmounted.
  • 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 alimente useSyncStore (startSync au dispatch, onSyncComplete aux terminaux, stopSync sur erreurs) et émet des toasts globaux via useToast (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 via router.replace.
  • ConfigureOrganizationView (DISC-019 / FEAT-114) : pas de changement UI sur l’onglet Connecteurs (progression/timeline/bannières conservées) ; la vue alimente useSyncStore en parallèle du polling local (startSync au démarrage upload/link/re-sync + callback OAuth ?syncProcess=, onSyncComplete en succès/échec terminal, stopSync en erreur de suivi). Toast global d’erreur ajouté via useToast pour 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 localStorage
  • onboardingGuard : Protection des routes
  • useOnboardingStore : 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 + conteneur flex-1 overflow-y-auto) pour éviter un écran bloqué quand body est en overflow: hidden (cas modales/overlays).
  • OnboardingConnectedLayout.vue : même convention via utilitaires page-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 + wrapper flex-1 overflow-y-auto) pour rester scrollable même si body est temporairement verrouillé par un overlay.
  • Auth / pages publiques hors layout (SigninView, SignupView, PasswordResetView, LoginView, OnboardingView, suivi réservation, invitations, OAuthRedirect) : racine page-fullscreen + contenu dans page-fullscreen-scroll ; App.vue en h-full flex flex-col car body { overflow: hidden } global.
  • GettingStartedView.vue : checklist DISC-016 post-login
  • OnboardingChecklistItem.vue : item d'étape avec CTA
  • OnboardingCelebrationModal.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