gigafibre-fsm/docs/UI_AND_OPTIMIZATION.md
louispaulb 5ab14cac44 feat(fsm): platform build — comms UI, F→ERPNext sync/billing, roster, campaigns, network, reports
Accumulated work on the dispatch/legacy-writeback branch:
- Communications UI: CommunicationsPage, ConversationFullPage, DepartmentBoard,
  PipelineBoard, ReaderStack, Orchestrator/NewTicket/ServiceStatus/Outbox dialogs;
  hub gmail.js, ticket-collab.js, outbox.js, coupon-triage.js, client-diag.js.
- Billing/sync mirror (F→ERPNext): legacy-payments.js, legacy-sync.js,
  sync-orchestrator.js, supplier-invoices.js, municipality.js + incremental
  migration scripts; LegacySyncPage, SupplierInvoices + negative-billing /
  terminated-active reports.
- Roster/campaigns/network/voice: roster + roster-assistant, campaigns, giftbit,
  olt-snmp, traccar, twilio, vision, tech-absence-sms, ai/agent/config/helpers,
  legacy-dispatch-sync; ops PlanificationPage, RapportsPage, Settings, Tickets,
  ClientDetail updates.
- docs/ PLATFORM_GUIDE + UI_AND_OPTIMIZATION; .gitignore __pycache__.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:12:12 -04:00

137 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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/`.