L'action ouvrait déjà le copilote Gemini (staff-agent) mais sans autocomplétion → il fallait taper le client exactement.
Ajout d'un typeahead d'ENTITÉ dans le chat (OrchestratorDialog, prop entity-search) : on tape → suggestions live
(nom · adresse · téléphone · courriel via /collab/customer-search) → le choix INJECTE l'entité RÉSOLUE « client <id>
(<nom> — <adresse>) » dans la demande, que Gemini extrait pour agir (proposer_rdv_client / assign_tech). Activé sur le
copilote STAFF (MainLayout entity-search) ; graine « Allouer un technicien : » (MonAujourdhui). Séparateur propre après « : ».
Vérifié en dev : « tremblay » → 8 suggestions → choix → prompt « Allouer un technicien : client C-SYLVT… (Tremblay…) » ;
endpoint /collab/customer-search OK (12 matches) ; 0 erreur console. Build OK, leak-clean, déployé.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applique « ne pas réutiliser une UX/métrique faible telle quelle » :
- Boîte de réception : remplace le compteur de session 'unread' (démarrait à 0, trompeur) par un VRAI décompte via
/conversations/inbox-tickets — « à trier » (non assignés) + total ouvert. Tickets sur moi = _assign contient mon courriel.
- Interventions aujourd'hui : ajoute l'ÉTAT TERRAIN LIVE (en route / sur place via /roster/geofence-states, pastilles
qui pulsent) en plus du découpage par statut.
- Barre d'ACTIONS réelles : « Allouer un technicien » → ouvre le copilote NL PRÉ-REMPLI (event ops:assistant → MainLayout),
« Nouvelle vente », « Rechercher ⌘K ». Le pont ops:assistant est ajouté dans MainLayout (propre).
- États chargement (« … ») / vide, focus clavier, prefers-reduced-motion.
Vérifié en dev : rendu, 199 à trier (donnée réelle), event allocate + palette OK, 0 erreur console. Build OK, leak-clean, déployé.
Co-Authored-By: Claude Fable 5 <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>
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>
- Comms: sélecteur d'expéditeur « De: » (défaut groupe Support TARGO) via resolveSendFrom + alias vérifiés
- Notifs: prefs de feeds PAR utilisateur (/conversations/notif-prefs) + cloche à bascules ; boot tooltip-ux (clic prioritaire + anti-empilement)
- Courriel: invitation à évaluer = modèle Unlayer éditable (transactional-rating-invite-*) ; test-send via Gmail + expansion {{rating}} ; logo TARGO auto-hébergé sur le magasin d'actifs du hub
- Planif: bloc « sans déplacement » (damier, début de quart, alerte si pas de quart), quart éditable dans l'éditeur de jour, icônes de compétence en vue jour (TV pour télé), clic cellule → éditeur, clic gauche lane → liste / clic droit → menu quart, lien ↗ ticket par job
- Tickets: défaut « Non fermés » + correction du filtre « Mes tickets » (owner)
- Inbox: poll Gmail 1 min + rafraîchir à la demande (poll-now)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inbox identity/grouping (fixes mislabeled threads):
- customerName = real From display name, then a customer matched BY EMAIL,
then the raw address — never the AI's content-guessed name (a gilles@ relay
showed as "Sylvie Juteau").
- never group a thread by one of our own domains (support@targo.ca,
*@targointernet.com): re-ingested outbound was collapsing unrelated threads
into one mislabeled "Guylaine Gagnon" thread.
Refactor: queues/TYPES/QUEUE_OF/CAT centralized in lib/categories.js (were
drifting across conversation.js + inbox-triage.js; telephonie/television had
no matching triage type). Removed dead export findConversationByEmail.
Feat: addMessage stamps msg.agent on outbound replies -> per-agent stats.
Historique (/historique, HistoriquePage.vue): tournées par technicien (ALL
statuses incl. Completed/Cancelled — the board hid them), tech leaderboard,
inbox leaderboard. Hub: /dispatch/history, /dispatch/leaderboard,
/conversations/leaderboard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plus besoin de re-chercher avec un processus complexe : une page liste les adresses de service non
conformes (review/unmatched) avec leur proposition AQ canonique, et permet de RÉSOUDRE une fois (persisté) :
- Approuver : la proposition AQ devient officielle (validated, coords RQA).
- Corriger : recherche AQ locale (rqa_addresses + fibre) → lier la bonne adresse.
- GPS : saisir/coller lat,long (relevé sur map.targointernet.com qui a la géoloc des unités de camping)
+ lien direct « voir sur la carte » par ligne.
- Rejeter : pas d'adresse civique (boîte postale/hors-QC) → 'no_address'.
Tri par type (camping / civique à corriger / à confirmer / non-adresse) + stats + recherche + pagination.
Backend : lib/address-conformity.js (GET stats|list|candidates, POST resolve) sur le Postgres LOCAL,
routé /address/conformity/* (server.js). Front : api/address.js + pages/AddressConformityPage.vue + route
/conformite-adresses + entrée nav « Conformité adresses » (icône MapPinned, requires view_settings).
État courant : validated 15 195 · review 1 366 · unmatched 550 (camping 540 / civique 333 / non-adresse 93).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle page /copilote : chat texte/voix (Web Speech API fr-CA) vers le copilote Gemini Flash
(impact d'absence + propositions de réassignation), + sélecteur de politique de reprise
(réassign/SMS/escalade) persistée. Route + nav (icône Sparkles ; ajout CalendarRange/CalendarClock
manquantes dans la map d'icônes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces ERPNext's Email Queue in Ops (nav « File courriels ») so ops can see
what's queued — important now that mute_emails=1 + scheduler paused mean nothing
flushes — and delete/purge stale entries without the ERPNext desk.
- hub lib/email-queue.js: GET list (by status, recipients read from each row's
full doc since ERPNext ignores fields on child-doctype REST), DELETE :name,
POST /purge {status}. Wired in server.js.
- ops: api/emailQueue.js + EmailQueuePage.vue (status filter, recipients,
reference, error tooltip, per-row delete + « Purger Not Sent »), route + nav.
Verified live: 13 'Not Sent' (old internal test emails, no invoice refs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New /campaigns section in the ops SPA, gated by manage_users (proxy until a
dedicated manage_campaigns capability is added).
Pages (apps/ops/src/modules/campaigns/pages/):
- CampaignsListPage: table of all campaigns with status chip + progress
bar (sent/total, with fail count), "Nouvelle campagne" + "Éditer le
template" buttons. Empty state with onboarding copy.
- CampaignNewPage: 3-step Quasar Stepper wizard.
Step 1 — upload Map CSV + Giftbit CSV, configure params (name, amount,
commitment_months, sender, throttle, multi-email handling).
Step 2 — preview the matched send list from POST /campaigns/parse, with
counters (matched/unmatched/excluded), per-row match-method
chip, and exclude/include toggle. Banner warns when CSVs are
mis-aligned (leftover gifts or contacts).
Step 3 — confirmation recap with estimated send duration, then fire
POST /campaigns + POST /campaigns/:id/send and redirect to the
live detail page.
- CampaignDetailPage: per-recipient table with status chips updated live
via EventSource on the campaign:<id> SSE topic. Counters bar
(envoyés / cliqués / queued / échecs / non envoyés), progress bar,
per-row customer-link badge with deep-link into /clients/<id>.
Auto-subscribes to SSE when status is draft|sending; "Lancer l'envoi"
button for draft campaigns.
- TemplateEditorPage: GrapesJS-based visual editor for the campaign
templates. Three view modes (Visuel / HTML / Aperçu) — the HTML mode
is the fallback for our table-heavy hand-crafted template that
GrapesJS-preset-newsletter may parse imperfectly. Aperçu mode calls
POST /campaigns/templates/:name/preview on the hub for live variable
substitution. Custom GrapesJS blocks under "Variables" category for
drag-drop insertion of {{firstname}}, {{amount}}, {{gift_url}},
{{description}}, {{expiry}}, {{commitment_months}}. Saves via PUT
with hub-side backup of the previous version.
Wiring:
- api/campaigns.js: hubFetch wrapper, exports parseCsvs / createCampaign
/ listCampaigns / getCampaign / updateCampaign / sendCampaign +
campaignSseUrl(id) for EventSource subscription, + listTemplates /
getTemplate / saveTemplate / previewTemplate for the editor.
- router/index.js: three new routes under /campaigns. The
/campaigns/templates/:name? route is positioned ABOVE /campaigns/:id
to prevent the wildcard from catching template paths.
- config/nav.js + layouts/MainLayout.vue: "Campagnes" sidebar entry with
Lucide Gift icon.
- package.json: grapesjs + grapesjs-preset-newsletter dependencies.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Tickets: load 10 initially, "Voir tous les tickets" expands to 500
- Inline editing for ticket status and priority (dblclick → select)
- Search: Enter key triggers immediate search and navigates to result
- Search: Arrow key navigation for result highlighting
- Reset expanded state on customer navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Ollama container running llama3.2-vision:11b on server
- OCR page in ops app: camera/upload → Ollama extracts vendor, date,
amounts, line items → editable form → create Purchase Invoice
- nginx proxies /ollama/ to Ollama API (both ops + field containers)
- Added createDoc to erp.js API layer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ops app (Vue/Quasar PWA) with dispatch V2 integration, tag system,
customer 360, tickets, and dashboard. Served via standalone nginx
container at erp.gigafibre.ca/ops/ with Traefik StripPrefix + Authentik SSO.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>