gigafibre-fsm/docs/UI_AND_OPTIMIZATION.md
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
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>
2026-07-02 08:49:35 -04:00

13 KiB
Raw Permalink Blame History

OPS — Clean Mobile-First UI & Code Optimization Plan

Companion to PLATFORM_GUIDE.md. This is the design + refactor playbook: what to change, in what order, and at what risk — written so it can be executed safely in small, verifiable batches.

Constraint that shapes everything here: the staff SPA is behind Authentik SSO, so UI changes can't be verified headlessly. Therefore we prefer additive changes (new primitives, new media queries scoped to small viewports) and behavior-neutral refactors (pure-helper consolidation) that the build can validate, and we roll out page-by-page so any regression has a tiny blast radius.


A. Current state (from the 2026-06-13 audit)

  • Shell is responsive, pages are not. MainLayout.vue already does the right thing: <q-drawer :breakpoint="1024"> (overlay below 1024px) + a mobile <q-header v-if="$q.screen.lt.lg"> with hamburger + a desktop bar v-if="$q.screen.gt.md". But $q.screen appears in only 2 files total (this layout + ProjectWizard). Every content page assumes desktop width.
  • Zero q-table grid mode anywhere — the standard Quasar mobile-table fallback is unused, so every table horizontally overflows on a phone.
  • Dialogs hardcode widths (style="width:560px", width:400px, min-width:420px) — only ProjectWizard uses the correct :maximized="$q.screen.lt.sm" pattern; nothing else copies it.
  • Two parallel light token systems: --ops-* (in app.scss) and --sb-* (in dispatch-styles.scss). Dispatch is no longer dark — it's a second light design language. 4 dispatch modals still carry orphaned Quasar dark attributes (visually inconsistent).
  • Helper duplication, adoption stalled: useFormatters (canonical) reaches 17 files, useStatusClasses only 4, while ~25 pages carry private copies of shortAgent (×5), statusColor (×10, all different palettes), date/time formatters (×16), money (×6). ~337 ad-hoc Quasar color-name strings for status.
  • Hardcoded hex instead of tokens: ProjectWizard 326, PlanificationPage 245, NetworkPage 100, EquipmentDetail 84, ClientDetailPage 80, ConversationPanel 61.

B. The clean mobile-first design system

B.1 Principles

  1. One token set. Everything references var(--ops-*). --sb-* becomes an alias layer, then is removed.
  2. Content reflows; dense tools degrade gracefully. List/report/detail pages must be fully usable at 380px. The two genuinely desktop-only tools (Dispatch board, Planification grid) get an explicit "best on desktop" affordance + a read-only mobile summary, rather than a fake reflow.
  3. A handful of primitives, applied everywhere. We don't redesign 30 pages by hand — we build ~6 shared components/utilities and route pages through them.
  4. Touch targets ≥ 44px, single-column forms on mobile, bottom-sheet dialogs.

B.2 Token system (unify --sb-*--ops-*)

  • Step 1 (safe): in dispatch-styles.scss, redefine each --sb-* as var(--ops-*) (alias). No visual change.
  • Step 2 (later): delete --sb-* usages in favor of --ops-*, then remove the aliases.
  • Step 3: remove dark attributes from the 4 dispatch modals (CreateOfferModal, PublishScheduleModal, WoCreateModal, JobEditModal); delete the dead $q.dark branch in TagEditor.vue.

B.3 The responsive primitives to build (additive — zero risk to existing pages)

Primitive What it does Replaces
<ResponsiveDialog> wraps q-dialog with :maximized="$q.screen.lt.sm" + sane max-width:95vw every style="width:NNNpx" dialog
<DataTable> wraps q-table with :grid="$q.screen.lt.md" + a default card slot + column-priority every raw wide q-table
<StatCard> the report stat tile (icon, value, label) using tokens inline style="min-width:130px" cards on 6+ report pages
<StatusBadge :doctype :value> one place that maps status→color/label the ~10 statusColor() maps + 337 color-name strings
<PageHeader> title + actions row that wraps on mobile (col-12 col-sm-auto) fixed-width filter rows (style="width:160px")
<FilterBar> filter inputs that stack on mobile same

B.4 Mobile layout rules (apply per page)

  • Filter rows: row q-col-gutter-sm with each control col-12 col-sm-auto (stack on phone, inline on desktop).
  • Two-column detail (col-lg-8 / col-lg-4): keep — it already stacks. But the right "sticky" column must drop its position:sticky/100vh on mobile (it currently eats a full viewport once stacked — see app.scss .convos-sticky). Add @media (max-width: 1023px) { .convos-sticky { position: static; max-height: none } }.
  • Kanban boards (DepartmentBoard, PipelineBoard): already horizontal-scroll with fixed-width columns — acceptable on mobile (swipe). Just shrink column width under sm (flex-basis: 80vw) so one column shows at a time.
  • Dispatch / Planification: gate with a friendly "Best viewed on desktop" banner under lt.md + link to the read-only day summary; don't attempt to reflow the timeline.

C. Per-page responsive strategy

Risk legend: 🟢 trivial/additive · 🟡 moderate (touches layout) · 🔴 large (mega-component).

Page Problem Fix Risk
Report pages (×8) fixed filter widths + wide tables, no grid mode <FilterBar> + <DataTable grid-on-mobile> + <StatCard> 🟢
ClientsPage, EquipePage, EmailQueuePage, TelephonyPage, SupplierInvoicesPage wide tables + fixed dialog widths <DataTable> + <ResponsiveDialog> 🟢
ClientDetailPage sticky right col eats mobile viewport; 560px dialogs media-query unstick + <ResponsiveDialog> 🟡
AddressConformityPage min-width:420px dialogs overflow <ResponsiveDialog> 🟢
CampaignNewPage dense multi-col form single-column under md, step-by-step 🟡
SettingsPage permission matrix wide horizontal-scroll grid keep scroll, add sticky first column + min touch size 🟡
DepartmentBoard / PipelineBoard fixed 270px columns flex-basis: 80vw under sm 🟢
NetworkPage map + topology, 100 hex desktop-gate + token cleanup 🟡
DispatchPage / PlanificationPage 100vh desktop workspaces, can't reflow "best on desktop" banner + read-only mobile day view 🔴 (new view)

D. Code optimization backlog (prioritized)

P0 — behavior-neutral consolidation (safe, build-verifiable) started this session

  1. Finish the helper migration. Make useFormatters.js the single source for: formatMoney, formatDate, formatDateShort, formatDateTime, shortAgent (new), fmtTimeHHhMM (new, 9h05), relTime (new, "today→time, else date"), timeAgo (noteTimeAgo). Then delete the private copies:
    • shortAgent ×5 → import (DepartmentBoard, PipelineBoard, ConversationPanel, InterveneDialog, +tech initials).
    • money ×6 → formatMoney (verify each is byte-equivalent before swapping; the report money/fmtMoney are).
    • date/time ×16 → the canonical set (swap only identical ones; unify the 4 divergent relative formatters into relTime).
    • Risk control: swap only identical implementations; leave divergent ones flagged. Build after each batch.
  2. Add useDebounce and replace the 3 hand-rolled setTimeout debouncers (MainLayout, useUserGroups×2).

P1 — extract shared UI components (additive, then migrate page-by-page)

  1. <StatusBadge> + a single statusColor(doctype, value) module → migrate the ~10 maps + the report pages first.
  2. <StatCard>, <ResponsiveDialog>, <DataTable>, <FilterBar>, <PageHeader> (see §B.3) → migrate report pages first (leaf, low blast radius), then list pages.
  3. Token cleanup pass: replace top-7 hardcoded-hex offenders' brand colors with var(--ops-*).

P2 — split the mega-components (highest effort, do last, one at a time)

The top 5 are ~12k of 38k total .vue lines: PlanificationPage (3,524) · ProjectWizard (2,891) · DispatchPage (2,162) · NetworkPage (1,829) · ClientDetailPage (1,725). Split by section into child components + composables, never a rewrite. These are load-bearing; each split needs its own verify cycle.

Enforcement (so it doesn't regress again)

  • Add an ESLint no-restricted-syntax rule banning local function formatMoney|statusColor|fmtDate|shortAgent definitions outside the canonical modules. (Adoption stalled once already — task #81 was "done" but ~25 pages kept private copies. A lint rule is what makes consolidation stick.)

E. What was actually changed vs. deliberately deferred (2026-06-13)

Done & build-verified, NOT deployed (held for spot-check, because the live app — index.b5007bac.js, the conversations fix + Communications feature — is what's running while you're away and I can't runtime-verify):

  • Extended useFormatters.js with shortAgent, fmtTimeHHhMM, relTime, formatDateTimeShort (all additive).
  • shortAgent consolidated in 4 files: DepartmentBoard, PipelineBoard, ConversationPanel, InterveneDialog (the 5th, shortName, was the same one-liner — aliased on import). 1 left = the tech initials variant (different fn).
  • relTime swapped into DepartmentBoard (ex-fmtTime, identical logic).
  • formatMoney swapped (aliased as money) into ReportNegativeBillingPage + ReportTerminatedActivePage (both were (v==null?0:v).toLocaleString(currency) = equivalent for numeric/null). Left untouched on purpose: SupplierInvoicesPage.fmtMoney (dash-for-empty + multi-currency) and ReportInternetCher.formatMoney (Number() coercion).
  • formatDateTimeShort swapped (aliased as fmtTime/fmt) into ReportNegativeBillingPage, ReportTerminatedActivePage, InterveneDialog (all were toLocaleString({dateStyle:'short',timeStyle:'short'})).
  • Each swap aliases the import to the original local name → zero call-site/template changes. npx quasar build → clean.

Next safe P0 (same pattern, ready to continue): formatDateTimeShort into EquipmentDetail (fmtTs direct; evTime needs an e.created_at wrapper) + RightPanel.fmtThreadDate; add a formatDateTimeMedium for the dateStyle:'medium' copies (EmailQueuePage, CampaignsListPage, GiftsInventoryPage); add useDebounce (MainLayout + useUserGroups×2). Then the statusColor ×10 + <StatusBadge> work moves to P1.

Deliberately deferred (needs your eyes / a verify cycle): everything that changes pixels (responsive primitives, token unification, dialog/table wrappers) and the mega-component splits. The design + order above is ready to execute in safe batches when you're back to glance at each one.

Deploy of P0 refactors is a one-liner once you've glanced: cd apps/ops && npx quasar build && scp -r dist/spa/* root@96.125.196.67:/opt/ops-app/.


F. 2026-06-18 refresh — audit re-run + first batch SHIPPED

Re-ran the audit against current source (32 pages) and unblocked local visual verification (the SSO blocker noted in §A).

Local preview now worksquasar dev (port 9001, .claude/launch.json → "ops"). Two prod-safe localhost guards make it usable without Authentik/hub:

  • src/api/auth.js _reauth() → no reload-loop on localhost (returns the 401).
  • src/composables/usePermissions.js → permissive profile on localhost (so nav + gated content render). Real ERPNext data in dev needs a current VITE_ERP_TOKEN in apps/ops/.env.local (the committed .env token is the 2026-06-16 rotated-out one). Note: $q.screen doesn't track viewport in the headless preview — prefer useMediaQuery (matchMedia) over $q.screen so responsive toggles are both robust and preview-verifiable.

Shipped this batch (the §B.3 primitives now exist + first migration):

  • src/composables/useMediaQuery.jsuseIsPhone() (<600), useIsMobile() (<1024), matchMedia-based.
  • src/components/shared/StatCard.vue, ResponsiveDialog.vue (maximized on phone), DataTable.vue (q-table → grid cards <1024, forwards attrs/slots).
  • ReportRevenuPage.vue migrated as the reference: filter row col-autocol-12/col-6 col-sm-auto (stacks on phone, verified at 375px), summary tiles→StatCard, table→DataTable.
  • app.scss.convos-sticky un-stuck under 1023px (was eating a full mobile viewport once stacked).

Rollout order for the rest (same primitives, page-by-page, leaf-first):

  1. Report pages ×7 remaining (ReportVentes/Taxes/AR/InternetCher/NegativeBilling/TerminatedActive, RapportsPage) → StatCard + DataTable + responsive filter. 🟢
  2. List pages (ClientsPage, EquipePage, EmailQueuePage, TicketsPage, OcrPage, AddressConformityPage) → DataTable. 🟢
  3. Dialog offenders → ResponsiveDialog (TelephonyPage 500/400px, SettingsPage, AddressConformityPage, EvaluationsPage). 🟢
  4. ClientDetailPage (8 tables→DataTable, dialogs→ResponsiveDialog; sticky already fixed). 🟡
  5. Token unification --sb-*--ops-* + the ~800 hardcoded hex (Planif 320, Network 101…) → StatusBadge. 🟡
  6. Mega-component splits (Planif/Dispatch/Network) + "best on desktop" banners. 🔴

⚠️ Data-dependent rendering (table rows, stat values) wasn't visually verified — the dev preview has no live data (token). Verify those after deploy, or drop a fresh token in .env.local for full local verification.