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>
137 lines
11 KiB
Markdown
137 lines
11 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/`.
|