150 lines
11 KiB
Markdown
150 lines
11 KiB
Markdown
# #154 — Refonte UI/UX de la visionneuse & éditeur XLSX (ruban, grille, inspecteur)
|
||
|
||
> **Item de roadmap :** [#154 — Refonte UI/UX tableur](../ROADMAP.md)
|
||
> **Origine :** #152 / #153 (visionneuse XLSX fonctionnelle mais peu conviviale)
|
||
> **Statut :** ✅ **terminé** — Lots 1 → 5 livrés le 2026-09-29 (A1-A5)
|
||
> **Effort estimé :** 6-9 jours (Lot 1 ✅ · Lot 2 ✅ · Lot 3 ✅ · Lot 4 ✅ · Lot 5 ✅)
|
||
> **Règle de maintenance :** la Roadmap porte les cases à cocher (suivi), cette fiche porte
|
||
> l'analyse, l'architecture cible et le plan par lots. **Ne pas dupliquer le détail.**
|
||
|
||
---
|
||
|
||
## 1. Objectif
|
||
|
||
Rendre la vue tableur d'ObsiGate **intuitive, moderne et hautement utilisable** en s'inspirant
|
||
des standards du marché (Excel, Google Sheets, Airtable), **sans renier les contraintes du
|
||
dépôt** : thème sombre, `vanilla JS`, **zéro framework, zéro build npm**
|
||
([`AGENTS.md`](../../AGENTS.md)). La refonte est **organique** : on améliore la coquille
|
||
existante (`frontend/js/viewer.js::renderXlsxViewer`, `frontend/style.css`), on ne réécrit pas
|
||
la grille ni le backend.
|
||
|
||
## 2. Audit UX — les 3 problèmes majeurs
|
||
|
||
| # | Problème | Constat | Résolution |
|
||
|---|---|---|---|
|
||
| **P1** | **Aucune hiérarchie ni regroupement des commandes** | Rangée plate de boutons de poids identique (`viewer.js` toolbar historique) ; « Tableau de bord » *prependé* au runtime ; barre de formule réduite à un `input`. | **Barre de commandes groupée** (Formules · Insertion · Vue · Fichier), bouton **Enregistrer primaire**, état *dirty*. |
|
||
| **P2** | **Grille sans affordances : en-têtes = cellules** | Contraste faible entre `th` et `td`, pas de zébrage, pas de survol lisible, cellule active peu marquée. | **Tokens de grille** + en-têtes plus clairs/interactifs, zébrage, survol, cellule active en bordure accent. |
|
||
| **P3** | **États avancés traités comme du contenu** | Dashboard *inline* qui pousse la grille, troncature/lecture seule/formules non recalculées sans emplacement dédié, `confirm()`/`prompt()` natifs. | **Couche UI dédiée** : bandeaux d'état + **inspecteur droit** (Lot 3) + dialogues thémés (Lot 2). |
|
||
|
||
## 3. Architecture cible de l'écran
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ BARRE APP (globale, existante) │
|
||
├─────────────┬────────────────────────────────────────────────────────────┤
|
||
│ │ A. RUBAN — groupes Formules · Insertion · Vue · Fichier │
|
||
│ EXPLORATEUR│ B. BARRE DE FORMULE — [ A1 ] fx [ … ] │
|
||
│ DE FICHIERS│ C. BANDEAUX D'ÉTAT — lecture seule · formules non recalculées│
|
||
│ (sidebar) ├──────────────────────────────────────────────┬─────────────┤
|
||
│ │ D. GRILLE (en-têtes clairs, zébrage, survol) │ E. INSPECTEUR│
|
||
│ │ │ (dashboard + │
|
||
│ │ │ IA, repliable)│
|
||
│ ├───────────────────────────────────────────────┤ │
|
||
│ │ F. ONGLETS FEUILLES + « + » · 500/522 │ │
|
||
└─────────────┴───────────────────────────────────────────────┴─────────────┘
|
||
```
|
||
|
||
- **A. Ruban** : groupes d'actions avec séparateurs ; actions de style désactivées (styles lus,
|
||
pas écrits). Bouton **Enregistrer** en accent, désactivé si rien de *dirty*.
|
||
- **B. Barre de formule** : zone nom + champ + badge de session `f(x)`.
|
||
- **C. Bandeaux d'état** : empilables, non bloquants ; portent lecture seule et
|
||
« formules non recalculées ».
|
||
- **D. Grille** : rendue côté serveur (`backend/xlsx_reader.py`), habillée et câblée par le front.
|
||
- **E. Inspecteur** : **à venir (Lot 3)** — Tableau de bord + Assistant IA dans un panneau droit
|
||
repliable (réutilise `PaneManager` pour le détachement), au lieu du dashboard *inline* actuel.
|
||
- **F. Onglets feuilles** : permanents (même à une seule feuille) + bouton « + ».
|
||
|
||
## 4. Plan par lots (incréments livrables)
|
||
|
||
### Lot 1 — Coquille : ruban groupé, onglets permanents, badges d'état ✅ *(2026-09-29)*
|
||
|
||
- **A1.1** Barre de commandes groupée (`.xlsx-cmdbar`, `.xlsx-cmd-group`, `.xlsx-cmd-sep`,
|
||
`.xlsx-save-primary`), IDs existants conservés (compatibilité tests JSDOM/E2E).
|
||
- **A1.2** Onglets de feuilles **toujours rendus** (non-CSV) + bouton **`+`** `.xlsx-tab-add`
|
||
→ `sheet_add` (même pipeline `putStructure`).
|
||
- **A1.3** Badges d'état : `.xlsx-status-pill` **lecture seule** (`.xls`/`.ods`) et
|
||
**formules non recalculées** (non-CSV).
|
||
- **A1.4** Tokens de grille (`--grid-bg`, `--grid-header-bg`, `--grid-header-text`,
|
||
`--grid-border`, `--grid-zebra`) déclinés dark/light + affordances (en-têtes clairs,
|
||
zébrage, survol, cellule active solide, cellule *dirty* prioritaire au survol).
|
||
|
||
### Lot 2 — Dialogues thémés & feedback ✅ *(2026-09-29)*
|
||
|
||
- **A2.1** Helpers génériques **`showConfirm()` / `showPrompt()`** (`frontend/js/ui.js`), promise-based,
|
||
réutilisant les classes `.obsigate-modal-*` (fini `window.confirm()` / `window.prompt()`).
|
||
- **A2.2** La visionneuse XLSX utilise ces dialogues pour les actions de structure (ajouter /
|
||
renommer / dupliquer / supprimer feuille, insérer / supprimer ligne et colonne) et pour la
|
||
confirmation de perte (409 `xlsx_lossy_content`).
|
||
- **A2.3** **Conflit de sauvegarde (409 `conflict`)** : bandeau **non bloquant** `.xlsx-banner-conflict`
|
||
avec bouton **Réessayer** — les modifications sont conservées.
|
||
- **A2.4** Indicateur *dirty* sur le bouton **Enregistrer** et sur l'onglet de la feuille concernée.
|
||
|
||
### Lot 3 — Inspecteur droit ✅ *(2026-09-29)*
|
||
|
||
- **A3.1** Le **Tableau de bord** quitte le flux de la grille pour un **panneau droit repliable**
|
||
(`.xlsx-inspector`) : la grille reste visible à côté (`.xlsx-body` = `.xlsx-main` + inspecteur).
|
||
- **A3.2** En-tête d'inspecteur : titre, bouton **Assistant IA** (ouvre le panneau latéral global
|
||
existant) et bouton de fermeture.
|
||
- **A3.3** Responsive : sous 900 px, l'inspecteur passe sous la grille.
|
||
|
||
### Lot 4 — Interactions : undo/redo, défilement, accessibilité ✅ *(2026-09-29)*
|
||
|
||
- **A4.1** **Undo/redo** local (pile de commandes) pour les éditions de cellules : boutons
|
||
**Annuler / Rétablir** dans le ruban + raccourcis `Ctrl+Z`, `Ctrl+Maj+Z`, `Ctrl+Y`.
|
||
- **A4.2** Chargement des fenêtres via **`IntersectionObserver`** (repli sur l'écouteur de
|
||
défilement pour les environnements sans IO).
|
||
- **A4.3** Sémantique **ARIA** : `role="grid"` / `row` / `gridcell` / `columnheader` / `rowheader`.
|
||
|
||
### Lot 5 — Découpage modulaire & finitions ✅ *(2026-09-29)*
|
||
|
||
- **A5.1** Extraction des parties pures/sans état du monolithe `renderXlsxViewer` dans
|
||
`frontend/js/xlsx/` : **`refs.js`** (`parseRef`, `columnName`, `findTd`, `sheetOfRef`,
|
||
`firstCellOfRange`), **`command-bar.js`** (`buildCommandBar` : onglets + ruban + pastilles),
|
||
**`dashboard.js`** (`renderDashboardLoading`, `renderDashboardHtml`). Le noyau **avec état**
|
||
(orchestration DOM, édition, écouteurs) reste dans `viewer.js` : l'extraction est volontairement
|
||
limitée aux unités sans état, à comportement constant et sous couvert des tests.
|
||
- **A5.2** Lien **dashboard → grille** : cliquer une plage nommée sélectionne et révèle sa
|
||
première cellule (change d'onglet si la plage est sur une autre feuille).
|
||
- **A5.3** Inspecteur **redimensionnable** (poignée gauche, largeur 260–640 px, restaurée par
|
||
session via `localStorage`).
|
||
- **A5.4** `SW_VERSION` incrémenté (`v28`) et nouveaux modules ajoutés au pré-cache du service
|
||
worker.
|
||
|
||
> **Hors périmètre (documenté) :** le détachement de l'inspecteur en split view
|
||
> (`PaneManager.splitRight()`) n'est pas retenu — l'inspecteur est intrinsèquement lié à la
|
||
> visionneuse d'un document ; un split générique ouvrirait un second contexte sans le classeur.
|
||
> À réévaluer si un usage concret apparaît.
|
||
|
||
## 5. Recommandations techniques (contrainte « zéro build »)
|
||
|
||
| Option Data Grid | Build | Licence | Verdict |
|
||
|---|---|---|---|
|
||
| AG Grid Community | npm + bundler | MIT | ❌ viole « zéro build », réécrit le DOM, casse les tests |
|
||
| Handsontable | npm + bundler | **commerciale** | ❌ licence non libre |
|
||
| TanStack Table | headless (importable esm.sh) | MIT | ⚠ possible sans build, mais *headless* → gain limité |
|
||
| **Grille maison sur `<table>`** | aucun | — | ✅ **recommandé** (conserve DOM, CSP, i18n, tests) |
|
||
|
||
- **Performance** : ne pas ré-écrire tout le DOM ; réutiliser le pipeline `appendWindow` ;
|
||
`content-visibility:auto; contain:strict` sur les lignes ; garder la pagination serveur
|
||
(500 × 40 = 20 000 cellules/feuille) plutôt qu'une virtualisation client complexe.
|
||
- **CSP** : `main.py` autorise déjà `esm.sh` — une lib *headless* reste possible en Lot 4 si
|
||
un vrai besoin de modèle de colonnes apparaît.
|
||
|
||
## 6. Critères d'acceptation (par lot)
|
||
|
||
- **Lot 1** : une feuille unique affiche son onglet + « + » ; « + » ajoute une feuille via
|
||
`PUT …/xlsx/structure` et re-rend ; `.xls`/`.ods` montrent le badge « lecture seule » (pas de
|
||
« + », pas de structure, pas de dashboard) ; un `.xlsx` montre le badge « formules non
|
||
recalculées », un `.csv` non ; les tests JSDOM/E2E existants restent verts + nouveaux tests.
|
||
- Lots suivants : définis à leur ouverture.
|
||
|
||
## 7. Historique
|
||
|
||
| Date | Événement |
|
||
|---|---|
|
||
| 2026-09-29 | Audit UX (3 problèmes) + architecture cible + plan par lots ; **Lot 1** livré (ruban groupé, onglets permanents + « + », badges d'état, tokens de grille) |
|
||
| 2026-09-29 | **Lot 2** livré : dialogues thémés (`showConfirm`/`showPrompt`) pour la structure et la confirmation de perte, bandeau de conflit 409 non bloquant avec réessai, indicateur *dirty* (bouton + onglet) |
|
||
| 2026-09-29 | **Lot 3** livré : le Tableau de bord passe dans un **inspecteur droit repliable** (grille toujours visible), en-tête d'inspecteur avec entrée **Assistant IA** et fermeture, responsive < 900 px |
|
||
| 2026-09-29 | **Lot 4** livré : **undo/redo** (boutons + `Ctrl+Z`/`Ctrl+Maj+Z`/`Ctrl+Y`), chargement par `IntersectionObserver`, **ARIA** `role="grid"` ; le découpage modulaire est reporté en A5 |
|
||
| 2026-09-29 | **Lot 5** livré (clôture #154) : extraction des unités sans état dans `frontend/js/xlsx/*` (`refs.js`, `command-bar.js`, `dashboard.js`), lien **dashboard → grille**, inspecteur **redimensionnable**, `SW_VERSION` v28 + pré-cache. Split view écarté (documenté) |
|