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>
164 lines
13 KiB
Markdown
164 lines
13 KiB
Markdown
# 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.
|