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

164 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)
3. `<StatusBadge>` + a single `statusColor(doctype, value)` module → migrate the ~10 maps + the report pages first.
4. `<StatCard>`, `<ResponsiveDialog>`, `<DataTable>`, `<FilterBar>`, `<PageHeader>` (see §B.3) → migrate report
pages first (leaf, low blast radius), then list pages.
5. 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 works**`quasar 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.js``useIsPhone()` (<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-auto`→`col-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.