Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré / juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts, sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle week-end + placeholder « en attente du quart », clustering + lasso + filtre-date sur la carte, accès rapide « À assigner » (badge). Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive + timeout fetch global + authFetch durci → fin des rechargements manuels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
6.3 KiB
Markdown
81 lines
6.3 KiB
Markdown
# Cohérence de l'interface Communications — cap « feeling Missive »
|
|
|
|
> Statut : **PLAN** (2026-06-18). Refactor d'AFFICHAGE + threading — ne change pas les données stockées.
|
|
> Objectif : que notre Boîte soit aussi cohérente et intuitive que Missive.
|
|
|
|
## Diagnostic : une seule cause méta
|
|
|
|
Presque toutes les incohérences viennent du même défaut : **le même concept est calculé à
|
|
plusieurs endroits avec des logiques différentes** (liste vs détail vs ingestion). Missive est
|
|
cohérent parce que chaque concept a **une source de vérité**.
|
|
→ **Principe directeur : un *resolver* unique par concept, importé partout.**
|
|
|
|
## Inventaire (causes racines, vérifiées dans le code)
|
|
|
|
### 1. « à moi » au lieu de « à support » (attribution du destinataire)
|
|
- Rendu : `ConversationPanel.vue:126` (entête, `convTo`) + `:414` (`msgTo` → `msgRecipientList`, :1163-1186).
|
|
- Cause : `meAddr = msg.toEmail || convTo` puis `if addr === meAddr → 'moi'`. « moi » = **la boîte qui a REÇU** le courriel, PAS l'utilisateur connecté (`meEmail`, :1395, jamais consulté ici). Aucune table adresse-de-groupe → nom d'équipe.
|
|
- Correctif : table `support@→Support`, `facturation@→Facturation`… (depuis `SENDER_IDENTITIES` / `queueLabels`) ; « moi » seulement si l'adresse == `meEmail`.
|
|
|
|
### 2. Nom d'expéditeur vide / incohérent
|
|
- Rendu : liste `MessageList.vue:104` (finit par « Inconnu », OK) ; détail `msgWho` (:1577-1582) + avatar (:1569-1570).
|
|
- Cause : `fromName=''` si le From n'a pas de display-name → retombe sur `activeDiscussion.customerName` (peut être un AUTRE expéditeur dans un fil multi-personnes) ; avatar `staffInitials('')` → vide. Et `shortAgent` (local part minuscule, ex. « gilles.drolet ») vs `noteAuthorName` (Capitalisé) = **même personne affichée de 2 façons**.
|
|
- Correctif : un resolver `identity()` partagé, jamais vide, agents toujours capitalisés (réutiliser `noteAuthorName`).
|
|
|
|
### 3. Sur-regroupement (fils fusionnés à tort)
|
|
- Lieux : ingestion `conversation.js:722-734` + re-bucketing liste `listConversations` (:435-461).
|
|
- Causes : (a) courriel **sans sujet** → `findConversationByEmail:681` fusionne par email seul (garde sautée si un sujet est vide) ; (b) clé `:active` (:444) fusionne TOUS les fils actifs d'un même `baseKey` **sans borne de date** ; (c) SMS/téléphone fusionne par numéro **pour toujours** (sans sujet ni fenêtre) ; (d) `thread_id` Gmail pris tel quel (réutilisation de sujet → vieux fil) ; (e) **In-Reply-To/References stockés mais PAS utilisés** pour regrouper.
|
|
- Correctif : exiger un **signal de fil positif** (thread_id OU match References) avant de fusionner ; sujet vide/auto = non-concordant ; **fenêtre de récence** sur la clé `:active`.
|
|
|
|
### 4. Canal incohérent
|
|
- `conv.channel` (conversation) vs `msg.via` (message) — vocabulaires différents ; le **courriel n'a AUCUN badge** (`MessageList.vue:102` le masque) ; pas de badge par message.
|
|
- Correctif : `msg.via` = canal par message (vocabulaire normalisé) ; badge cohérent sur CHAQUE ligne/message, courriel inclus.
|
|
|
|
### 5. FILE + identité éclatées
|
|
- FILE : entête `activeConv.queue` (chargé async) vs liste `d.queue` → décalage bref. Identité : titre/chip/message utilisent **3 champs différents** (`customerName` / `coordAccountName` / `email`) → 3 noms pour le même fil.
|
|
- Correctif : FILE = une source (réconcilier une fois `activeConv` arrivé) ; « qui » = un seul computed.
|
|
|
|
### 6. Finitions incohérentes
|
|
- **Deux `formatTime`/`formatDate`** : local `ConversationPanel:2070-2087` (≠ canonique `useFormatters`) → liste en « DD/MM » (relTime) vs entête de message « mois jour HH:MM » ; le local **n'a pas le garde `isNaN`** → « Invalid Date ».
|
|
- Tri : message `emailDate||ts` vs liste/`lastTs` = `ts` → ordre divergent sur fils re-synchronisés.
|
|
- Non-lu / « en train d'écrire » pris du `latest` seul, pas du fil fusionné entier.
|
|
- Couleurs d'avatar : clés de hash différentes liste vs détail → même personne, couleurs différentes.
|
|
|
|
## Les resolvers (colonne vertébrale du correctif)
|
|
|
|
Un module partagé (ex. `composables/useConversationDisplay.js`) exporte **un resolver par concept**,
|
|
utilisé par la liste ET le détail (et côté hub, l'ingestion) :
|
|
|
|
| Concept | Resolver | Règle |
|
|
|---|---|---|
|
|
| Qui | `identity(p)` | nom → local part → tél → « Inconnu » ; agents capitalisés ; avatar/couleur/initiales dérivés du MÊME id |
|
|
| Destinataire | `recipientLabel(addr)` | adresse de groupe → nom d'équipe ; « moi » seulement si == utilisateur ; sinon nom/adresse |
|
|
| Canal | `channelOf(msg)` | depuis `via` normalisé → badge |
|
|
| Fil | `threadKey(msg)` | signal positif requis ; sujet vide = non-match ; fenêtre de récence |
|
|
| File | `fileOf(conv)` | une seule source |
|
|
| Temps | `useFormatters` partout | supprimer les copies locales |
|
|
|
|
## Patterns Missive à adopter (extraits du tour vidéo)
|
|
|
|
- **Ligne uniforme** : avatar + **badge de canal** + nom + heure + sujet + extrait + chips.
|
|
- **1 fil = 1 sujet** (titre visible → toute fausse fusion saute aux yeux).
|
|
- **Fil unifié** : messages client (blocs pleine largeur) + **notes internes (bulles)** + **tâches inline**, avec **2 composeurs** (répondre au client / note d'équipe).
|
|
- **Assign / Snooze / Close** en actions de barre supérieure (dropdowns riches).
|
|
- **Toasts Undo** sur actions réversibles.
|
|
|
|
## Plan par phases
|
|
|
|
| Phase | Contenu | Risque |
|
|
|---|---|---|
|
|
| **P1 — Cohérence** | resolvers `identity` + `recipientLabel` (« à support ») + temps unifié (tuer les `formatTime` locaux) + `fileOf` une source | **faible** (affichage) |
|
|
| **P2 — Threading strict** | signal de fil positif, sujet vide = non-match, fenêtre de récence, In-Reply-To/References utilisés | moyen (logique de fil → tester sur fils réels) |
|
|
| **P3 — Canal** | `channelOf` + badge partout (Messenger/courriel/SMS) | faible |
|
|
| **P4 — UX Missive** | fil unifié + 2 composeurs + assign/snooze + undo | moyen (UI) |
|
|
|
|
## Garde-fous
|
|
|
|
- Refactor d'**affichage** d'abord — ne touche pas aux données stockées.
|
|
- Threading (P2) : **préférer sur-séparer que sur-fusionner** — un faux split est bénin ; une fuite de conversation entre deux clients ne l'est pas.
|
|
- Tester P2 sur de vrais fils : multi-expéditeur, SMS long, adresse de groupe, sujet réutilisé.
|
|
- Chaque resolver = testable isolément (entrées → libellé attendu) avant branchement UI.
|