- Couleurs restreintes à la charte TARGO (noir/vert/blanc/gris + bleu en haut) sur le
courriel ET la page RSVP : bandeau supérieur bleu #1a86c7, accents/CTA verts, texte
noir/gris, fond blanc, programme en pastilles vert/bleu/gris. Retrait orange/jaune/rose/rouge.
- Admin : le téléphone saisi s'affiche sous le courriel (n'apparaissait pas avant).
- Hub : capture de l'adresse IP du visiteur à l'inscription (x-forwarded-for → socket).
- CSV enrichi = TOUTES les infos : client, n° client, ID compte, nom au dossier, téléphone,
personnes, courriel, allergies, confiance, signaux, langue, IP, créé le, répondu le.
Déployé hub + SPA 2026-07-06. Vérifié prod : palette conforme, téléphone + IP stockés
(819 555-9999 / 96.125.192.22), test envoyé OK. 21/21 tests unitaires.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Titre corrigé partout (courriel, page RSVP, admin) : « Fête Targo 20 ans » →
« Targo fête ses 20 ans » (FR) / « Targo turns 20! » (EN), tagline étoffée.
- Courriel refait façon flyer : bandeau confetti + ballons 🎈 + pastille « 20 ANS »,
programme en cercles colorés (vert/bleu/violet/rose/orange), encart « places limitées »,
ligne « Merci de votre confiance au fil des années 💚 », pied targo.ca.
Déployé hub 2026-07-06 (page RSVP live = « Targo fête ses 20 ans » ; test envoyé OK).
La page RSVP et le nom en admin viennent du hub — pas de rebuild SPA requis (le repli
cosmétique dans EventsPage suivra au prochain déploiement SPA). 21/21 tests unitaires.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suite aux demandes : bouton d'envoi test (courriel par défaut = utilisateur), auto-
rattachement par courriel/nom/téléphone, et confirmation « êtes-vous un client ? » par
recoupement — SANS autosuggest (aucune fuite de PII).
Hub lib/events.js :
- matchAndValidate({token,number,email,phone,name}) : recherche AVEUGLE de candidats par
courriel/téléphone/numéro (en parallèle), le nom corrobore. « confirmed » = ≥2 signaux
concordent sur le MÊME compte ; « probable » = 1 ; « none » = aucun. Jamais bloquant, ne
renvoie jamais de donnée client. Empêche un courriel deviné de mal rattacher.
- Formulaire RSVP : ajoute Nom (requis) + Téléphone (optionnel), n° client devient optionnel.
Message de remerciement adaptatif (« ✓ on vous a reconnu » si confirmed).
- POST /events/:id/send {test:true, test_emails[]} : envoi TEST via Mailjet/Gmail (réutilise
email.sendEmail/gmail.sendMessage + inviteEmail + rsvpLink). Envoi de masse reste gaté (400).
Ops EventsPage : badge de confiance (Confirmé/Probable/À vérifier + signaux en infobulle),
dialogue d'envoi test (canal + courriel défaut = usePermissions().userEmail), CSV enrichi
(téléphone + confiance). Filtres par niveau de confiance.
Vérifié : 21/21 tests unitaires hub (cross-validation + envoi test mailjet/gmail + masse gatée) ;
page Ops rend sans erreur, dialogue s'ouvre avec courriel pré-rempli.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
À la publication (vue Tournées), bouton « Notifier » → aperçu par tech
PUIS envoi. Chaque tech reçoit :
- UN lien token personnel (techFieldLink = app terrain, ses billets du
jour à répondre),
- la liste de ses interventions avec ADRESSE + flags cruciaux (AM/PM ·
URGENT · Appeler avant) détectés du titre/notes/priorité.
Hub (lib/roster.js) : composeDispatchNotifs(dates, techIds) + route
POST /roster/notify-dispatch (preview=compose sans envoi | envoi SMS via
twilio + Email via lib/email.js, best-effort par tech, retourne compte +
erreurs). jobFlags() détecte AM/PM/urgent/appeler-avant.
Client : api roster.notifyDispatch ; dialogue APERÇU (toggles SMS/Courriel
+ liste par tech + corps du message, alerte si pas de tel/courriel) →
« Envoyer ». Aperçu d'abord = pas d'envoi accidentel.
Vérifié : hub node --check OK ; client rend le bouton + dialogue + toggles,
0 erreur. ⚠️ Le PREVIEW/envoi nécessite le déploiement HUB (nouvelle
route) — GATÉ + sortant. Faire un test 1 tech avant usage réel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suggérer peut servir de SIMULATION sur des jobs DÉJÀ dispatchés : nouveau
toggle « Ré-optimiser aussi les jobs déjà assignés » dans la config. Quand
activé, le solveur VRP reçoit AUSSI les noms des jobs assignés de la
fenêtre (assignedNamesForWindow, depuis occByTechDay) en plus du pool →
repropose une répartition de TOUT (existant + nouveaux jobs ajoutés).
Force le solveur (l'heuristique locale n'a que le pool en mémoire).
Aperçu seulement — rien n'est écrit avant « Appliquer » (qui réassigne).
Vérifié : toggle rendu dans la config Suggérer, 0 nouvelle erreur. Le
résultat de ré-optimisation (solveur roster-solver) est à confirmer en
prod (comme les autres runs solveur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cause du « on ne voit qu'une partie du titre, aucun autre détail » :
openJobDetail lisait la forme BLOC (b.customer/skill/detail/legacy_id)
mais reçoit un job BRUT du pool quand ouvert depuis une goutte/carrousel
(customer_name/required_skill/legacy_detail/legacy_ticket_id) → tous les
champs vides sauf le titre. Corrigé : mappe les DEUX formes (client,
compétence, adresse, description, n° ticket, date) + charge le fil du
billet via le bon id → détails + commentaires s'affichent.
- Titre : plus tronqué (ellipsis → retour à la ligne, titre complet).
- Assignation DEPUIS le volet : sélecteur « Assigner à… / Réassigner… »
(jdAssignTech) ; si d'autres tâches à la MÊME adresse/client/jour →
propose de les assigner au même tech (réutilise assignNames +
siblingJobs, comme la grille).
Vérifié live (goutte carrousel, ticket #253649) : titre complet non
tronqué, méta = compétence tv + adresse 44 Carre Morrison + dept + date,
section commentaires + sélecteur d'assignation présents ; 0 erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retours terrain sur les modales de détail job (carte Tournées) :
- Volet détail 1 job : la CARTE (jd-map + tracé GPS) n'est pas requise pour
lire le ticket → repliée par défaut derrière « Voir la carte / tracé
GPS » (init à la demande, pas au chargement). Détails simples d'abord +
champ « Durée estimée » ajustable (comme « Suggérer ») qui persiste
duration_h (+ override de session). Titre déjà complet.
- Popups « liste des jobs à cette adresse » débordaient hors écran sans
scroll : maxWidth 260 + overflow-wrap + max-height 44vh scroll (carte
jour du panneau À assigner ET popup groupée RouteMap).
Vérifié live : volet 1 job → carte masquée par défaut, bouton déplie +
init canvas OK, champ durée = 2.25h ajustable ; 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Détection auto AM/PM : les tickets legacy « Note AM » / « Note PM »
(importés tels quels) sont détectés (detectAmpm : titre + note, tokens
AM/PM / avant-midi / après-midi, null si ambigu) et la contrainte
horaire est PRÉ-APPLIQUÉE dans « Suggérer » (suggestDlg.jobTime seedé à
l'ouverture, marqueur ⚡ auto_awesome sur la puce, modifiable). Le
solveur respecte alors la fenêtre 8-12 / 12-16.
2. Fenêtre RDV « 3 dispos du client » : chaque créneau proposé a un
sélecteur AM/PM (avant-midi/après-midi) qui règle l'heure représentative
(AM=08:00, PM=13:00), heure exacte toujours ajustable. Aligné sur AM/PM
du dispatch.
Vérifié live : Suggérer affiche auto_awesome + puce AM pré-cochée (jobs
« Note AM ») ; RDV → clic PM règle 13:00, AM règle 08:00 ; 0 nouvelle
erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#3 Regroupement par adresse : routeUnassignedPins groupe les jobs non
assignés d'une même adresse en UNE pastille portant les icônes des types
de jobs (distinctes, max 3) + le TEMPS TOTAL + un compteur ; couleur =
priorité la plus haute. Clic (tap mobile) sur une adresse à >1 job →
popup listant chaque job (sujet + durée), chaque ligne ouvre le détail →
plus besoin de cliquer chaque ticket, info visible d'un coup d'œil.
#4 Chevauchement : les gouttes non assignées rejoignent recluster()/fan()
(déjà éprouvé sur les arrêts assignés) → les pastilles proches s'éclatent
en éventail pour rester distinguables/atteignables. (Le regroupement par
adresse réduit déjà le fouillis ; le fan gère les adresses proches ≠.)
Vérifié par injection du markup (impossible de rendre la carte en dev,
jeu sans coordonnées) : pastille groupée = horizontale 92×22, 2 icônes +
compteur + « 45min » visibles ; popup = lignes cliquables. Compile OK,
0 nouvelle erreur. Rendu carte à confirmer en prod (jobs géocodés).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retours terrain :
1. « Ramassage d'équipement » = simple collecte chez le client → job court.
Détection (isPickup : titre/type ~ /ramm?ass|récupération/) → durée
plafonnée à 5 min : jobDur(pickup)=5/60 (maths d'occupation/Suggérer) +
capPickups() sur les 3 chargements du pool (est_min affiché=5). Évite de
surallouer le temps même si l'est_min du ticket dit plus long.
2. Icône de visite GÉNÉRIQUE (type non reconnu) : `bolt` (éclair, peu
parlant) → `handyman` (technicien + outils). Fallback de skillIcon →
s'applique partout (carte markerIcon + listes skillSym).
Vérifié live (données réelles) : ticket « Rammassage Équipements #253602 »
(St-Anicet) affiche ≈ 5min ; carrousel : plus de bolt, handyman présent
pour le job générique ; 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur suggestion de l'user (visuel maison+fibre) : l'icône « installation »
passe de construction/tools-ladder à `router` (le routeur/ONT posé au
domicile) — plus parlant pour une installation fibre. Appliqué PARTOUT via
les 2 fonctions d'icône : markerIcon (carte) + skillSym (carrousel,
kanban, blocs jour/semaine). Ligature material `router` → rend aussi bien
dans le <span material-icons> des marqueurs carte que dans les q-icon.
(Le glyphe custom maison+fibre est un SVG → impossible sur les marqueurs
carte en DOM brut ; router = meilleur équivalent material intégré.)
Vérifié live : carrousel affiche router pour les 8 jobs d'install (à côté
de build/live_tv/bolt), 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les jobs non assignés étaient de simples gouttes oranges SANS symbole,
alors que les assignés ont une pastille horizontale avec icône du type de
job (📞/🔧/⚡) + n°. Uniformisé en RÉUTILISANT le rendu éprouvé des arrêts
assignés (≠ tentative q-icon/SVG précédente qui rendait vertical/vide) :
- RouteMap.renderPins : pastille `.rm-pill` HORIZONTALE + icône via
<span class="material-icons"> (ligature material fiable), couleur =
priorité (orange = non assigné), SANS numéro d'ordre. Fini la goutte.
- routeUnassignedPins fournit `icon: markerIcon(required_skill||service_type)`
(markerIcon = ligatures material, celles qui s'affichent hors q-icon).
Vérifié : le markup exact injecté dans la page rend une pastille
HORIZONTALE 30×22 avec glyphe material 14px visible (police « Material
Icons ») — plus le bug vertical/sans-icône. Rendu carte identique à celui
des pastilles assignées (mêmes classes/mécanisme). À confirmer à l'œil en
prod (dev sans jobs géocodés → 0 marqueur).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Groupes de techs enregistrés (Suggérer) : sélection manuelle de techs
sauvée comme groupe nommé réutilisable (ex. Installateurs, Épisseurs) —
utile quand la compétence déduite du job est imprécise. Persisté par
utilisateur (useUserPrefs 'techGroups', suit l'usager entre appareils).
Chips « Groupes : » (clic = appliquer, ✕ = supprimer) + bouton
« Enregistrer » (nomme la sélection courante) + chip « Tout
désélectionner ». Complète le sélecteur par compétence existant.
2. Vue Tournées — clic sur un chip technicien = ISOLER sa tournée sur la
carte (masquer les autres) pour voir une route précise ; re-clic sur le
tech déjà seul = tout réafficher ; chip « Tout afficher » si masqués.
(Avant : le clic masquait/affichait un tech à la fois — inversé.)
Vérifié live : groupes save→apply→delete + « Tout désélectionner »
(13→0) OK ; 0 nouvelle erreur. (Les chips de tournée n'apparaissent
qu'avec des jobs géocodés — comportement solo vérifié par la logique,
à confirmer à l'œil en prod.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les jobs ASSIGNÉS (marqueurs carte, HTML brut) utilisaient markerIcon =
ligatures material génériques (install→construction, monteur→camion
générique), alors que les jobs NON assignés (carrousel, blocs, gouttes)
utilisent skillSym = icônes SVG custom (échelle+outils, camion-nacelle,
casque). Résultat : même compétence, icône différente selon jour passé
(assigné) vs à venir (non assigné). Unifié sur les SVG custom :
- RouteMap : les marqueurs (arrêts assignés ET gouttes non assignées)
rendent l'icône via un VRAI <q-icon> (h+render) → gère les SVG custom
skillSym comme le reste de l'app ; repli material-icons si échec ;
démontage propre dans clearMarkers/clearPins (pas de fuite).
- PlanificationPage : les arrêts (Tournées + revue Suggérer) passent
skillSym(skill) au lieu de markerIcon ; markerIcon retiré (mort).
⚠️ Rendu carte NON vérifiable en préview (jeu dev sans coordonnées → 0
marqueur). Compile OK, carrousel (25 chips) inchangé, 0 nouvelle erreur.
À confirmer à l'œil en prod (jobs géocodés). Reste : unifier la FORME des
puces (chip/pill/carte) selon contexte — passe design dédiée.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retours terrain :
1. « SLA dépassé » / en-retard se basait sur la CRÉATION du ticket
(jobSlaBadge → creation), ce qui marque en retard des jobs planifiés
plus tard (le client peut demander une visite quelques jours après
l'incident). Désormais l'échéance = la DATE DE VISITE PRÉVUE
(scheduled_date) : badge « En retard » seulement si scheduled_date <
aujourd'hui ; futur/aujourd'hui = à l'heure. Repli SLA-création
uniquement pour les jobs SANS date planifiée (backlog). (Le dispatch
utilisait déjà scheduled_date pour le périmètre — inchangé.)
2. Le carrousel « N à répartir » (Tournées) est plus long à scanner que la
liste par jour du legacy. La pastille « N à répartir » est maintenant
cliquable → ouvre la liste VERTICALE des jobs de ce jour (panneau
« Jobs à assigner » via openDayInPanel, filtré sur le jour) avec
recherche + chips type/compétence + tri par ville. Meilleure lecture,
surtout sur mobile.
Vérifié live (Tournées, 06/07) : clic « à répartir » ouvre le panneau
filtré (25 jobs du jour) ; 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le correctif précédent ne couvrait que l'entrée Tournées. Les jobs en
retard (scheduled_date < fenêtre) ou sans date étaient encore balayés sur
le jour choisi via d'autres chemins (bouton SUGGÉRER hors Tournées,
solveur ⚡ Optimiser). Corrigé À LA SOURCE + au placement :
- suggestJobs() (source unique — utilisée aussi par le solveur qui reçoit
job_names) : exclut désormais les jobs hors fenêtre (retard/sans date/
autre jour) SAUF si l'utilisateur coche « ⏰ en retard » / « Sans date »
ou fait une sélection lasso. Sélectionner un seul jour ne dispatche donc
QUE les jobs dus ce jour-là.
- buildSuggestion() : la branche fourre-tout `else cand = win` ne place
plus les retards/sans-date automatiquement — même garde (lasso ou chips
explicites) en défense.
Vérifié live (Tournées, 06/07) : plan de 25 jobs, tous sur « 07-06 »
(aucune date antérieure), 0 nouvelle erreur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deux retours terrain :
1. Jobs « en retard » (date < jour sélectionné) apparaissaient dans la
répartition auto. Cause : en vue Tournées, suggestWindow suivait les
chips de date du POOL, pas le jour choisi dans la bande → buildSuggestion
balayait les jobs antérieurs sur le jour (branche `else cand = win`).
Fix : en vue Tournées, `suggestWindow = [routesDay]` (la bande de dates
fait autorité) → seuls les jobs DUS ce jour-là sont répartis, jamais les
retards. (Le balayage des retards reste dispo hors Tournées via les chips.)
2. Revue « Répartition suggérée » trop dense sur mobile, lignes rognées
sans défilement. Fix : dialogue MAXIMISÉ <sm + chaque rangée job
(.suggest-entry) défile horizontalement (nowrap + overflow-x, enfants
non rétrécis, sujet borné) → toute la ligne (badges/AM-PM/actions)
accessible au swipe.
Vérifié live 375px : dialogue plein écran ; 25/25 rangées défilent
(scrollW ~698 > clientW 341) ; plan bâti sur le jour choisi ; 0 nouvelle
erreur console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sur la carte Tournées, les jobs NON assignés (props.pins) étaient de
simples gouttes oranges sans icône ni gestion du chevauchement, alors que
les arrêts ASSIGNÉS sont des pastilles round-rect (icône compétence + n°)
qui s'éclatent en éventail au survol quand elles se chevauchent
(recluster/fan). Uniformisé :
- renderPins() rend la MÊME pastille .rm-pill (icône de compétence via
pin.icon = skillSym), variante « non assigné » (.rm-pill-un : bordure
pointillée + halo orange), avec hover onEnter/onLeave.
- recluster() opère désormais sur stopMk.concat(pinMk) → arrêts et jobs
non assignés participent au même éclatement au chevauchement.
- routeUnassignedPins fournit `icon` (skillSym du required_skill/service).
⚠️ Visuel (pastille + éventail) NON vérifiable en préview locale : le jeu
de données dev n'a pas de coordonnées sur les jobs → 0 pin rendu. Code =
miroir fidèle du rendu d'arrêt éprouvé ; map s'initialise, 0 nouvelle
erreur console. À vérifier à l'œil en prod (adresses réelles géocodées).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suite au carrousel : les chips de filtre de la bande « à répartir »
pilotent maintenant AUSSI « Suggérer » (« ce que je filtre = ce que je
répartis »).
- Chips PRIORITÉ (normalisée high/medium/low, via POOL_PRIOS) ajoutés à
côté des secteurs ; filtrent la liste + la carte (routeDayUnassignedView
= jour + compétence + secteur + priorité). Affichés seulement si >1
priorité présente (comme les secteurs).
- suggestJobs() : en vue Tournées, le périmètre = routeDayUnassignedView
EXACTEMENT (corrige l'incohérence : avant, les chips secteur étaient
ignorés par Suggérer alors que l'infobulle disait « du secteur »).
- openSuggest() : pré-sélection des techs par COMPÉTENCE — parmi les
disponibles (quart ce jour), ceux dont t.skills couvre un required_skill
des jobs du périmètre ; repli sur tous les dispos si aucun match.
Toujours modifiable (le dialogue liste les 56 techs, cochables).
Vérifié live (06/07) : secteur Huntingdon → vue 4 jobs → Suggérer affiche
« 4 job » (avant : ignorait le secteur) + 13/56 techs pré-cochés
(compétents+dispos, reste modifiable). 0 nouvelle erreur console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La bande « N à répartir » sous le sélecteur de jour (vue Tournées)
retournait à la ligne sur 2 rangées (max-height 70px, scroll vertical) et
PLAFONNAIT à 16 jobs (« +N de plus » cachait le reste). Passée en
carrousel horizontal 1 rangée (flex nowrap + overflow-x auto, scroll-snap,
barre fine) qui montre TOUS les jobs du jour — compact au-dessus de la
carte, tactile/tablette, aligné avec la bande de dates au-dessus.
Vérifié live (Tournées, 06/07 = 25 à répartir) : 25 chips rendus (fini le
plafond 16), 1 rangée nowrap, scrollWidth 5870 > 1214 = défile bien.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La section Tickets de la fiche utilisait un q-table dont le mode carte
mobile empilait des valeurs non étiquetées (« 0 », n°, sujet, avatars,
date…) = archaïque et peu lisible (cf. capture agent terrain). Nouveau
composant components/customer/TicketCard.vue : sujet en évidence + badges
statut/priorité colorés (édition inline conservée via InlineField) + n°
(legacy ou ISS) + date + avatars ouvreur/assigné empilés ; rangée
responsive (flex-wrap) lisible sur mobile ET desktop. Remplace le
DataTable dans la section Tickets ; imports morts retirés (ticketCols,
ticketStatusClass, priorityClass, staffColor, staffInitials).
Vérifié live (C-LPB4) : 5 cartes, sujet + Open/Medium + #ISS-… + date,
aucun élément > viewport à 375px, rendu propre à 1280px, 0 erreur.
Note : la réutilisation d'icônes de compétences dans les tickets est
reportée — les Issues (surtout legacy) n'ont pas de signal compétence
fiable (0 Dispatch Job correspondant) ; à faire quand un lien
ticket→compétence existe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T7 découpage géants — sort le « lieu de service » (en-tête adresse +
DeviceStrip + abonnements mensuels/annuels drag-drop + tickets) dans
components/customer/LocationCard.vue. Les sélecteurs/actions stateful
(locSubs*/toggle*/onSub*/section*) restent liés aux instances de
composables du parent et sont passés en props-fonctions homonymes → le
template les résout inchangé, comportement identique (mêmes closures,
même invalidation de cache useSubscriptionGroups/useSubscriptionActions).
Helpers purs (formatMoney/isRebate/…) et usePermissions importés dans
l'enfant. Styles loc-*/sub-* copiés (scoped). ClientDetailPage -242 l. ;
imports morts retirés (DeviceStrip/locStatusClass/isRebate/subMainLabel/
sectionTotal/annualPrice/locInlineFields).
Vérifié en local sur hub LIVE (C-LPB4) : rendu pixel-identique (carte
691 rue des hirondelles, DeviceStrip live acs-online, ABONNEMENTS 4 =
INTERNET 3 / RABAIS 1, total 0,00 $/m), toggle de section fonctionne via
la prop (INTERNET 0→3 rangées, chevron expand_more), 0 erreur console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
T7 découpage géants — sort le bandeau d'appareils (chips ONT/routeur +
menu Ajouter) dans components/customer/DeviceStrip.vue. Consomme le
composable singleton useDeviceStatus (couche données ACS/OLT partagée) ;
les helpers de présentation propres aux appareils (signalColor,
formatTimeAgo, doReboot, doRefreshParams) déménagent DANS le composant
(utilisés nulle part ailleurs). Le parent émet open-device + add-*.
ClientDetailPage -108 lignes ; imports/destructures device retirés.
Vérifié en local sur hub LIVE (C-LPB4) : chip vert + point acs-online
réel, tooltip ACS complet (SN TPLGC4160688, OLT oltRem03 1/3/0, En ligne
il y a 4m, Fibre Up, 17 clients WiFi, FW/WAN IP/SSID), 0 warning console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NetworkPage débordait de ~118px à 375px. Deux causes corrigées :
- Barre d'onglets (4 onglets = 435px) non bornée dans une rangée flex → ne
déclenchait pas le défilement natif de Quasar. Ajout global `.q-tabs { max-width:100% }`
en mobile (app.scss) : Quasar réactive son scroll (aucun effet là où déjà borné).
- Arbre GPON : en-têtes OLT/slot/port en flex non-enroulé (nom·IP·adresse·nb
clients·SNMP·slots·éditer) → débordement. `flex-wrap:wrap` + neutralisation du
`margin-left:auto` des compteurs sur mobile.
- Boutons d'action de l'arbre (éditer OLT, ajouter port, éditer lieu) étaient
révélés au survol (opacity:0) → invisibles au tactile. Toujours visibles < 600px.
Vérifié à 375px : docScrollW 493→375, en-têtes enroulées, crayon éditer visible.
Portée strictement mobile (@media max-width:599px) — desktop inchangé.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Premier découpage de la fiche (2102 l) : bloc « Satisfaction » (étoiles/commentaire + « Demander une évaluation »)
→ components/customer/CustomerSatisfaction.vue (props latest/count, émet 'draft'). Vérifié sur la fiche (rend « Aucune
évaluation » à l'identique, 0 erreur). Extraction DÉLIBÉRÉMENT petite + vérifiable ; les gros blocs couplés
(LocationCard/PaymentSection/DeviceStrip — 10+ méthodes parentes : getDevice/combinedStatus/isOnline… + données ACS live
à vérifier) restent à faire en passe dédiée. Build vert, leak 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Worklist de rendez-vous : recherche (job/client/lieu) + chips service_type (dynamiques) + presets ('filter.rdv') ;
`filteredJobs` applique le filtre APRÈS la portée `view` (à planifier/à recontacter/tous), qui reste un toggle séparé.
Client-side, réactif. Build vert, leak 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CustomerHeader : sous-ligne (nom/legacy/type/langue) était `no-wrap` → débordait sa colonne et
chevauchait le bloc statut/liens en mobile. Fix : wrap + colonne `min-width:0` + le bloc statut/ERPNext/Users
passe pleine largeur en dessous (<sm) via `col-12 col-sm-auto` ; titre en ellipsis.
- Ajouter un assistant : q-select brut → <TechSelect> (recherche typeahead) ; jdTeamOptions = nom · compétences
(searchable par nom OU compétence), CAPABLES d'abord (possèdent la compétence du job), value = id ; jdAddAssistant résout le tech par id.
Vérifié préview mobile (375px) : sous-ligne wrap, 0 débordement (scrollW==clientW) ; build vert, leak 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace l'icône terrain/distant par le SYMBOLE de compétence (skillSym : live_tv/build/cable/cell_tower/camion-nacelle…, couleur = getTagColor) — cohérent avec les cartes kanban + blocs de tournée. Vérifié préview : chips = live_tv (TV), build (réparation)…
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch D — regrouper/situer les non-assignés du jour :
- RouteMap : nouvelle prop `pins` (+ renderPins/clearPins/watch, gouttes oranges couleur=priorité, clic → @pin-click) — miroir de `live`, inclus dans le fit bounds
- Planif Tournées : chips SECTEUR (par ville, comptés, triés) qui filtrent SIMULTANÉMENT la liste « à répartir » ET les gouttes sur la carte ; routeUnassignedPins (coords robustes latitude/lat), clic goutte → détail ; « Suggérer » agit sur le secteur si filtré
Vérifié préview : jour 06/07 → 10 secteurs + 21 gouttes ; clic « STE CLOTILDE » → 4 chips + 4 gouttes (« 4 à répartir / 21 »).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch B — le filtre de compétence pilote la carte : dayLivePositions (GPS techs) filtré par visibleTechs
(les tournées l'étaient déjà via visibleTechs) → seuls les techs capables + leurs jobs sur la carte.
Batch C — mise en évidence des non-assignés du jour :
- Badge rouge « dus non assignés » par jour sur la bande (OccupancyStrip.os-unassigned), depuis unassignedByDay
- routeDayUnassigned = jobs dus le jour sélectionné mais non assignés → bande « N à répartir » (chips cliquables → détail) + bouton « Suggérer » ; se met à jour au clic sur la strip
Vérifié préview : positions filtrées par compétence ; badges [1,21,16,15,3,8] sur la bande ; clic 06/07 → bande « 21 à répartir » (14 chips réels).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bouton « Générer » (haut) → « Suggérer » (openSuggest) basé sur le jour de la vue ACTIVE (strip Tournées / Jour kanban / bande mobile). Le solveur de quarts est déplacé dans Outils (« Générer l'horaire (semaine) », doGenerate conservé).
- Fix recherche tech : le X (clearable) mettait search=null → `search.value.trim()` plantait le computed visibleTechs → le champ « ne se vidait pas ». Rendu null-safe.
- Retrait du sélecteur « Équipe » (groupOptions vide : aucun tech n'a de `group` ; le filtre par compétence suffit).
Vérifié préview (desktop 1400px) : X vide le champ sans planter, Suggérer ouvre le dispatch auto, « Équipe » absent, grille rendue.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
routeStripDays passe de 7 → 14 jours (depuis le lundi affiché), données via /roster/capacity
(14 j, découplé de la fenêtre de charge 7 j du grid → barres réelles en semaine 2). Chargé à
l'entrée de la vue Tournées + au changement de semaine (watch [boardView, start]). Bande pleine
largeur + défilement horizontal ; jour courant marqué.
Vérifié préview : 14 cellules (29/06→12/07), today marqué, scroll.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Tableau de bord : 2 bandes d'occupation (« Cette semaine » / « Semaine prochaine », 14 j depuis lundi), jour courant marqué (liseré indigo dans OccupancyStrip). Clic jour → /planification?day=ISO
- Planif : deep-link `?day=` (cadre la semaine + sélectionne le jour grille/mobile/tournées) & `?skill=` (filtre compétence, match casse-insensible vs allSkills) via focusDay() lu au montage
- Slot-finder (SuggestSlotsDialog) : état « Aucun créneau » → bouton « Planifier des quarts pour « X » » → émet plan-shifts → Planif ouvre la timeline (vue grille) au jour cherché, filtrée sur la compétence, + toast « crée un quart pour ouvrir des créneaux »
Vérifié préview bout-en-bout : 2×7 jours + today, clic → ?day=2026-07-09 cadre 06→12/07, recherche vide → bouton → dialog fermé + grille + toast compétence. Build vert + leak-check 0. Aucun changement hub.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch 1 de l'audit d'optimisation. Les libs lourdes étaient statiquement importées
donc livrées même inutilisées → passées en import dynamique / defineAsyncComponent :
- MainLayout : ConversationPanel/PhoneModal/FlowEditor/NewTicket/Orchestrator/
ServiceStatus/OutboxPanel → async (chunk shell 312 KB → 56 KB, chargé sur CHAQUE page)
- TaskGraphPage : TaskGantt (hy-vue-gantt) → async (2.6 MB → 20 KB ; le Gantt ne charge
que dans la vue Gantt)
- NetworkPage : cytoscape → import() dans loadNetworkMap (484 KB → 52 KB)
- ReportAR/Revenu/Taxes : chart.js/auto → import() dans renderChart (async) (~150→~10 KB/page)
Total dist inchangé (~8 M) — le code est DÉFÉRÉ en chunks à la demande, pas supprimé.
Gain réel = ce que l'utilisateur télécharge pour VOIR une page. Build OK, leak-check propre,
shell + route rapport vérifiés (aucune erreur d'import).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le hub compare la position GPS live de chaque tech (Traccar) aux jobs du jour →
détecte les transitions et les journalise par job. Le statut « live » du job est
DÉRIVÉ du journal (compute-on-read, aucune écriture ERPNext — pas de valeur « en
cours » côté doctype). Fiche job : timeline horizontale Assigné → En route → Arrivé
(HH:MM) → Reparti (HH:MM).
- lib/geofence.js : runScan() (rayon 150 m, sortie 230 m hystérésis, séjour 3 min),
store data/geofence.json par job, machine à états ; startScan() scheduler 90 s
(GEOFENCE_SCAN=off pour couper). Vérifié prod : 12 jobs × 14 positions, transitions OK.
- server.js : démarre le scan au boot.
- roster.js : GET /roster/job/:name/geofence (timeline) + /geofence-states + /geofence-scan (test).
- api/roster.js : jobGeofence(), geofenceStates().
- PlanificationPage : openJobDetail charge la timeline ; stepper .jd-geo (scoped).
Suite possible : écrire le vrai statut ERPNext à l'arrivée (nécessite d'ajouter une
valeur « En cours » au doctype Dispatch Job) — non fait pour éviter le risque schéma.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Onglet Tournées : marqueurs « live » de chaque tech (appareil Traccar associé),
rafraîchis ~25 s tant que l'onglet est ouvert. Marqueur = pastille ronde à initiales,
couleur = celle de la tournée du tech, halo pulsé si position fraîche (grisé sans halo
si > 10 min). Tooltip : nom · « il y a X min » · vitesse (km/h) ou « arrêté ».
Bouton « GPS live (N) » pour afficher/masquer. Respecte les chips techs masqués.
- hub roster.js : GET /roster/tech-positions → {positions:[{techId,techName,lat,lon,time,speed}]}
(batch getPositions par appareil). Vérifié prod : 14 positions.
- api/roster.js : techPositions().
- RouteMap.vue : prop `live` + marqueurs .rm-live (halo pulsé, staleness), watch dédié.
- PlanificationPage : polling 25 s (démarré sur l'onglet Tournées, arrêté sinon +
onUnmounted), couleur mappée depuis dayRoutes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sélectionner le 7 juillet ne devait pas tirer les jobs en retard/sans date (souvent
faits mais pas fermés) dans la simulation. Par défaut (aucun chip de date coché),
suggestJobs ne garde que les jobs DATÉS sur le(s) jour(s) de la fenêtre — plus de
pollution par les retards/autres jours. Pour dispatcher les retards : cocher le chip
« ⏰ en retard » (comportement inchangé). Le job_names envoyé au solveur hub ne contient
alors que les jobs du jour → même résultat côté Optimiser.
Vérifié preview : jour 07-07 → 15 jobs, plan = 15 arrêts tous le 07-07, 0 en retard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Étend le filtre du dispatch auto au type affiché (« ce que je vois = ce que
je répartis ») : suggestJobs part désormais de assignJobsFiltered (type + date)
au lieu des seules dates. Défaut inchangé (aucun filtre = tout le pool part).
Levier concret : décocher « téléphonie » → non réparti aux techs terrain.
- suggestJobs : base = assignJobsFiltered (type ∩ date) hors On Hold ; lasso prioritaire.
- suggestFiltered + suggestScope : bouton « Suggérer (N) » et dialogue
« N job(s) des jours + types cochés → jours » quels que soient les filtres actifs.
- Aucun changement hub : optimize-plan filtre déjà par job_names (déployé).
Vérifié preview : réparation seul → 18 ; réparation + 07-06 → 5 arrêts,
tous réparation, tous le 07-06 (heuristique locale).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>