Correctifs UI workflow — specs + implémentation (8 chantiers) (#6)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Suite à l'arrivée des workflows, correction des régressions UI et améliorations UX mail/modales (reviews Lucile Schnödt, Tristan Schnödtin). **Specs & décisions :** `docs/superpowers/specs/2026-05-20-workflow-ui-fixes-design.md` **Plan d'implémentation :** `docs/superpowers/plans/2026-05-21-workflow-ui-fixes.md` Cette PR contient désormais **les specs ET l'implémentation complète**. ## Chantiers livrés | # | Chantier | Détail | |---|----------|--------| | 2 | Sélecteur de statut filtré par workflow | `statusOptions` dérivé de `project.workflow.statuses`, statut courant conservé s'il est hors workflow | | 1 | Drag & drop « Mes tâches » | handlers `@dragover/@drop` ; résolution par workflow/catégorie (0→refus, 1→PATCH, ≥2→popover `StatusPickerPopover`) | | 4 | Couleurs | (a) migration Doctrine remettant les hex classiques sur le workflow Standard ; (b) entêtes kanban teintées via `STATUS_CATEGORY_COLOR` + contraste auto ; (c) couleur par défaut par catégorie dans `WorkflowDrawer` | | 5 | Suppression du bouton « Lier un mail » | + retrait de `MailPickerModal` et i18n associée | | 6 | Création de tâche depuis un mail | back : `assigneeId` + `statusId` (défaut = 1er statut du workflow), priorité retirée (TDD) ; front : `MailCreateTaskModal` sur `AppModal` + sélecteurs user/statut | | 7 | Modale réutilisable | nouveau `components/ui/AppModal.vue` (footer sticky) ; footer de `TaskModal` sorti du form scrollable | | 3 | Cartes responsive | badges en `flex-wrap` pleine taille (plus aucun débordement) | | 8 | (dette) Sélecteur de catégorie en `MalioSelect` | la lib supporte les valeurs `string` ; note CLAUDE.md corrigée | ## Vérifications - Build frontend OK ; PHPUnit **34 tests verts** (nouveau test fonctionnel TDD sur `create-task`). - Vérif navigateur (Chrome MCP) sur **données prod importées en local** : #2, #3, #4, #5, #6, #7 confirmés. - Revue de code finale : **APPROVED_WITH_NITS**. ## À noter - ⚠️ **#1 (D&D)** : le drag & drop HTML5 natif n'est pas auto-testable → **test manuel requis**. - 🗄️ **#4 (migration)** : `migrations/Version20260521094948.php` s'appliquera en **prod au prochain `make migration-migrate`**. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Matthieu <mtholot19@gmail.com> Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
239
docs/superpowers/specs/2026-05-20-workflow-ui-fixes-design.md
Normal file
239
docs/superpowers/specs/2026-05-20-workflow-ui-fixes-design.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Specs — Correctifs UI suite au système de workflow + UX mail/modales
|
||||
|
||||
> Date : 2026-05-20
|
||||
> Contexte : suite à l'introduction des **workflows** (`docs/superpowers/specs/2026-05-19-project-workflows-design.md`),
|
||||
> plusieurs régressions UI et points UX sont apparus. Reviews faites par Lucile Schnödt et Tristan Schnödtin.
|
||||
> Ce document liste les 7 chantiers à traiter, avec problème, fichiers concernés, solution validée et points ouverts.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
| # | Chantier | Type | Décision |
|
||||
|---|----------|------|----------|
|
||||
| 1 | Drag & drop cassé dans « mes tâches » | régression workflow | Drop = changer de statut (gérer ambiguïté multi-statuts) |
|
||||
| 2 | Sélecteur de statut mélange les workflows | régression workflow | Filtrer par le workflow du projet de la tâche |
|
||||
| 3 | Cartes de tâche non responsive (tags) | UI | Refonte responsive + troncature |
|
||||
| 4 | Couleurs de statut du workflow de base | data/UI | Remettre la palette classique |
|
||||
| 5 | Bouton « lier un mail » dans l'onglet mail d'un ticket | UX mail | Supprimer le bouton |
|
||||
| 6 | Création de ticket depuis un mail | UX mail | + sélecteur user, + sélecteur statut (remplace priorité), modale agrandie |
|
||||
| 7 | Footer collant des modales centrées | UX global | Composant modale réutilisable (header / body scrollable / footer sticky) |
|
||||
| 8 | Sélecteur de catégorie en `<select>` natif (WorkflowDrawer) | UI/dette | Migrer vers `MalioSelect` (la lib supporte les valeurs `string`) |
|
||||
|
||||
## Décisions actées (2026-05-21)
|
||||
|
||||
Suite à la reproduction des bugs sur données prod (import local) et discussion :
|
||||
|
||||
- **#1** — Désambiguïsation au drop : **popover** de choix quand la catégorie cible a ≥2 statuts (0 → drop refusé + feedback ; 1 → PATCH direct ; ≥2 → popover ancré au point de drop). Résolution **par tâche** (workflow de son projet).
|
||||
- **#2** — Source des statuts : `project.workflow.statuses` (déjà embarqué dans `GET /projects` et `task.project.workflow`). Le statut courant est ajouté en tête s'il est hors du workflow (pas de perte à l'enregistrement).
|
||||
- **#3** — Cartes : `min-w-0` partout, titre `line-clamp-2`, badges `truncate`, **2-3 tags max + badge « +N »** (tooltip), hauteur de carte fixe.
|
||||
- **#4** — Deux facettes :
|
||||
- **(a) Statuts** (badges cartes + kanban projet) : dérive **data en prod** (fixtures OK). Correction par **migration Doctrine** idempotente remettant les hex classiques sur le workflow Standard.
|
||||
- **(b) Catégories** (entêtes multi-projets de « Mes tâches », aujourd'hui grises) : nouvelle constante front **`STATUS_CATEGORY_COLOR`** (5 hex classiques) → **bandeau teinté** sur les entêtes, **texte auto noir/blanc** selon la luminance.
|
||||
- **(c)** Couleur par défaut **par catégorie** dans `addStatus()` (au lieu de `#222783` systématique) pour éviter une nouvelle dérive.
|
||||
- **#5** — Suppression du bouton « Lier un mail » + `MailPickerModal` + état/handler + clé i18n (`MailPickerModal` n'est utilisé que par TaskModal — vérifié).
|
||||
- **#6** — + sélecteur **user** (défaut pré-rempli) ; priorité **remplacée** par sélecteur **statut** (filtré workflow, rechargé au changement de projet) ; **`priority`/`priorityId` retiré** du payload et de l'endpoint backend ; statut par défaut = **1er statut du workflow par `position`** ; modale élargie via #7.
|
||||
- **#7** — Composant `frontend/components/ui/AppModal.vue` (header fixe / body `flex-1 min-h-0 overflow-y-auto` / footer `shrink-0` sticky, `max-h-[90vh]`, prop `width`). Migration d'abord : TaskModal puis MailCreateTaskModal.
|
||||
- **#8** — ✅ Fait : `<select>` catégorie → `MalioSelect` (la lib accepte `value: string | number | null` ; note CLAUDE.md corrigée).
|
||||
|
||||
---
|
||||
|
||||
## 1. Drag & drop cassé dans « mes tâches »
|
||||
|
||||
**Problème.** Le drag & drop des cartes entre colonnes ne fonctionne plus depuis l'arrivée des workflows.
|
||||
|
||||
**Fichiers.**
|
||||
- `frontend/pages/my-tasks.vue` — colonnes kanban construites sur les **catégories canoniques** fixes (`CATEGORIES = ['todo','in_progress','blocked','review','done']`, ~l.118-127), template kanban ~l.396-424.
|
||||
- `frontend/components/task/TaskCard.vue` — `@dragstart` / `@dragend` HTML5 natif (~l.154-162), `dataTransfer` = `task.id`.
|
||||
- Pas de librairie externe (HTML5 natif).
|
||||
|
||||
**Cause racine.** Les colonnes sont des **catégories canoniques** (la vue agrège plusieurs projets/workflows, donc on ne peut pas afficher une colonne par statut d'un workflow précis). Or un workflow peut désormais mapper **plusieurs statuts sur une même catégorie** (ex. deux statuts « in_progress »). Au drop dans une colonne, le statut cible devient ambigu — ce qui a cassé / rendu indéterminé le changement de statut.
|
||||
|
||||
**Solution retenue.** Drop dans une colonne ⇒ change le statut de la tâche vers un statut de cette catégorie **dans le workflow du projet de la tâche**. Gestion de l'ambiguïté :
|
||||
- Récupérer les statuts du workflow du projet de la tâche déposée, filtrés par la catégorie de la colonne cible.
|
||||
- **0 statut** dans cette catégorie pour ce workflow → drop refusé (feedback visuel, pas de changement).
|
||||
- **1 statut** → appliquer directement.
|
||||
- **≥ 2 statuts** → afficher un mini-sélecteur (popover/menu) au point de drop pour choisir le statut exact.
|
||||
|
||||
**Points ouverts.**
|
||||
- ⚠️ **À trancher demain** : confirmer la stratégie de désambiguïsation (popover au drop vs. choisir le statut de plus petite `position` dans la catégorie). Le popover est plus sûr mais plus de travail ; le « plus petite position » est transparent mais peut surprendre.
|
||||
- Ajouter les **handlers de drop** sur les colonnes (`@dragover.prevent`, `@drop`) — actuellement absents dans `my-tasks.vue`.
|
||||
- Comme la vue est multi-projets, la résolution du statut cible doit se faire **par tâche** (selon son projet → son workflow), pas globalement.
|
||||
|
||||
---
|
||||
|
||||
## 2. Sélecteur de statut mélange les statuts de tous les workflows
|
||||
|
||||
**Problème.** En ouvrant une tâche (modale), le sélecteur « Statut » liste **tous les statuts globaux**, donc ceux du workflow de base **et** ceux des autres workflows.
|
||||
|
||||
**Fichiers.**
|
||||
- `frontend/components/task/TaskModal.vue` — `<MalioSelect v-model="form.statusId" :options="statusOptions" />` (~l.103-109) ; `statusOptions = props.statuses.map(...)` (~l.674-676).
|
||||
- `frontend/pages/my-tasks.vue` — `:statuses="statuses"` (~l.489), chargés via `statusService.getAll()` (~l.132).
|
||||
- `frontend/services/task-statuses.ts` — `getAll()` → `GET /task_statuses` (tous les statuts).
|
||||
- DTO : `frontend/services/dto/task-status.ts` — le champ `workflow` existe déjà (`{ '@id', id } | string`).
|
||||
|
||||
**Solution retenue.** Le sélecteur ne doit proposer que les statuts du **workflow du projet de la tâche éditée**.
|
||||
- Filtrer `statusOptions` sur `status.workflow` correspondant au workflow du projet de la tâche.
|
||||
- Source du filtre : soit filtrer côté front la liste déjà chargée par `workflow.id`, soit charger les statuts du workflow via le service workflow (`useWorkflowService()` existe mais n'est pas utilisé ici).
|
||||
- S'assurer que le statut courant de la tâche reste affiché même si édge case (ex. statut hérité d'un ancien workflow).
|
||||
|
||||
**Points ouverts.**
|
||||
- Vérifier que la tâche/le projet expose bien l'`@id`/`id` du workflow côté payload pour faire le filtre sans appel supplémentaire.
|
||||
|
||||
---
|
||||
|
||||
## 3. Cartes de tâche non responsive (tags mal placés / trop gros)
|
||||
|
||||
**Problème.** Depuis l'ajout d'éléments dans la carte (statut, priorité, effort, tags, deadline, avatars…), les tags débordent ou se chevauchent quand les textes sont longs ; la carte n'est plus responsive.
|
||||
|
||||
**Fichiers.**
|
||||
- `frontend/components/task/TaskCard.vue` — badges statut (~l.43-49) et tags (~l.58-64) : `rounded-full px-2 py-0.5 text-xs font-semibold text-white`, **sans `truncate`, sans `max-w`, sans `line-clamp`** ; conteneur `flex` sans contrainte de largeur (~l.42-106).
|
||||
|
||||
**Solution retenue.** Refonte du layout de la carte pour rester contenue quelle que soit la longueur des textes :
|
||||
- Conteneur des badges en `flex flex-wrap gap-1` (retour à la ligne propre).
|
||||
- Tags : `max-w-[...] truncate` (ou `line-clamp-1`) + `title`/tooltip pour le texte complet au survol.
|
||||
- Hiérarchiser l'info : titre prioritaire (`line-clamp-2`), badges secondaires qui passent à la ligne ou se condensent.
|
||||
- Option : limiter le nombre de tags affichés (ex. 2-3 + « +N »).
|
||||
|
||||
**Points ouverts.**
|
||||
- ⚠️ Choix d'UX à valider : `flex-wrap` (tous les tags visibles, carte plus haute) vs. troncature « +N » (hauteur fixe). Décision visuelle à prendre demain (éventuellement via mockup).
|
||||
|
||||
---
|
||||
|
||||
## 4. Couleurs de statut du workflow de base à remettre
|
||||
|
||||
**Problème.** Les couleurs « classiques » des statuts du workflow de base ont changé ; il faut remettre les couleurs d'origine.
|
||||
|
||||
**Investigation faite.** Le commit `4775cbf` (palette élargie 9→18 teintes + couleur perso) ne touche **que** `ColorPicker.vue` et `ProjectDrawer.vue` — il n'a pas modifié les couleurs des statuts. Les couleurs d'origine du **workflow Standard** sont dans les fixtures :
|
||||
|
||||
| Catégorie | Statut | Hex classique |
|
||||
|---|---|---|
|
||||
| todo | À faire | `#222783` |
|
||||
| in_progress | En cours | `#4A90D9` |
|
||||
| blocked | Bloqué | `#C62828` |
|
||||
| review | En attente de validation | `#FF8F00` |
|
||||
| done | Terminé | `#26A69A` |
|
||||
|
||||
Source : `src/DataFixtures/AppFixtures.php:101-105` (statuts rattachés au `$standardWorkflow`, ~l.93-116).
|
||||
Migration de rattachement : `migrations/Version20260519175114.php` (attache les statuts existants au workflow Standard).
|
||||
|
||||
**Solution retenue.** Remettre ces 5 couleurs sur les statuts du **workflow Standard/de base**.
|
||||
- Vérifier en prod (et en base de dev si dérive) que les statuts du workflow Standard portent bien ces hex ; corriger ceux qui ont dérivé (via l'UI d'admin des statuts, ou un script/migration de correction des couleurs).
|
||||
- Ne **pas** toucher aux couleurs des statuts des autres workflows.
|
||||
|
||||
**Points ouverts.**
|
||||
- Confirmer où la dérive a eu lieu (prod vs. nouveaux workflows créés via l'UI avec d'autres couleurs). Si c'est un workflow Standard en prod avec des couleurs erronées → correction data ; si c'est par défaut à la création d'un workflow → ajuster les couleurs proposées par défaut.
|
||||
|
||||
---
|
||||
|
||||
## 5. Supprimer le bouton « lier un mail » dans l'onglet mail d'un ticket
|
||||
|
||||
**Problème.** En ouvrant un ticket, l'onglet mail propose un bouton « lier un mail » qui n'a pas d'utilité de ce côté.
|
||||
|
||||
**Fichiers.**
|
||||
- `frontend/components/task/TaskModal.vue` — bouton « lier un mail » (~l.486-494, `mail.taskTab.linkButton`, ouvre `showMailPickerModal`) ; `<MailPickerModal>` (~l.498-503), visible si `isEditing && isMailUser`, onglet `mails`.
|
||||
- Composant lié : `frontend/components/mail/MailPickerModal.vue`.
|
||||
|
||||
**Solution retenue.** Supprimer le bouton « lier un mail » de l'onglet mail du ticket.
|
||||
- Retirer le bouton et, si plus aucun usage, le `MailPickerModal` associé + l'état `showMailPickerModal` + `handleMailLinked`.
|
||||
- Nettoyer la clé i18n `mail.taskTab.linkButton` si elle n'est plus utilisée ailleurs.
|
||||
|
||||
**Points ouverts.**
|
||||
- Vérifier que `MailPickerModal` n'est pas réutilisé ailleurs avant de le supprimer (sinon ne retirer que le bouton).
|
||||
|
||||
---
|
||||
|
||||
## 6. Création d'un ticket depuis un mail
|
||||
|
||||
**Problème.** Le formulaire de création depuis un mail est incomplet et la modale dépasse de l'écran.
|
||||
|
||||
**Fichiers.**
|
||||
- `frontend/components/mail/MailCreateTaskModal.vue` — champs actuels (~l.119-224) : info source (lecture seule), Projet (requis), Groupe (optionnel), **Priorité** (optionnelle). Footer non sticky (~l.210-224).
|
||||
- `frontend/services/mail.ts` — `createTaskFromMail()` (~l.184-192) → `POST /mail/messages/{id}/create-task` avec `{ projectId, taskGroupId, priority }`.
|
||||
|
||||
**Solution retenue.**
|
||||
1. **Sélecteur de user (assigné).** Ajouter un sélecteur d'utilisateur. Un user par défaut est déjà appliqué ; le sélecteur permet d'en choisir un autre. (Charger la liste via le service users.)
|
||||
2. **Statut à la place de la priorité.** **Retirer** le sélecteur de priorité, le **remplacer** par un sélecteur de statut. Le statut proposé doit respecter le workflow du projet sélectionné (cf. chantier #2 — réutiliser la même logique de filtrage par workflow). Au changement de projet, recharger les statuts du workflow correspondant.
|
||||
3. **Agrandir la modale.** Élargir la largeur (et gérer la hauteur via body scrollable + footer sticky, cf. chantier #7) pour qu'elle ne dépasse plus.
|
||||
4. **Backend.** Adapter le payload / l'endpoint `create-task` : accepter `assigneeId` (ou IRI user) et `statusId` (ou IRI statut) ; retirer/garder `priority` selon décision (ici : remplacé par statut côté UI — décider si on garde la priorité par défaut côté backend ou non).
|
||||
|
||||
**Points ouverts.**
|
||||
- Confirmer le statut **par défaut** présélectionné (statut initial du workflow du projet).
|
||||
- Décider si la priorité disparaît totalement du payload ou reste à une valeur par défaut côté backend.
|
||||
|
||||
---
|
||||
|
||||
## 7. Footer collant pour les modales centrées
|
||||
|
||||
**Problème.** Les modales qui s'ouvrent au milieu de l'écran ont leurs boutons d'action en bas, qui défilent avec le contenu et finissent hors écran. On veut un footer **toujours visible**.
|
||||
|
||||
**Constat.** Il n'existe **aucun composant modale réutilisable** (`MalioModal`/`AppModal`). Chaque modale réimplémente `Teleport` + `Transition` + header/body/footer. Footers actuels en `border-t` mais **non sticky** :
|
||||
- `frontend/components/task/TaskModal.vue` (footer ~l.507-549)
|
||||
- `frontend/components/mail/MailCreateTaskModal.vue` (~l.210-224)
|
||||
- `frontend/components/mail/MailLinkTaskModal.vue` (~l.226-239)
|
||||
- `frontend/components/mail/MailPickerModal.vue` (~l.187-201)
|
||||
- `frontend/components/ui/ConfirmDeleteTaskModal.vue` (~l.11-24)
|
||||
- `frontend/components/project/ProjectWorkflowSwitchModal.vue` (~l.63-76)
|
||||
|
||||
**Solution retenue (décision : composant réutilisable).** Créer un composant modale réutilisable dans `frontend/components/ui/` (ex. `AppModal.vue` / `MalioModal.vue` local) :
|
||||
- Structure : `Teleport to="body"` + `Transition`, overlay `fixed inset-0`, conteneur avec **hauteur max** (`max-h-[90vh]`), en flex-col :
|
||||
- **header** (titre + fermeture) fixe en haut,
|
||||
- **body** `flex-1 overflow-y-auto`,
|
||||
- **footer** sticky en bas (`shrink-0 border-t`), via slot `#footer`.
|
||||
- Props : largeur (`sm`/`md`/`lg`/`xl`), titre, `v-model` ouverture ; slots `default` (body) et `footer`.
|
||||
- **Migration progressive** des modales existantes vers ce composant (commencer par celles citées par les reviews : TaskModal, MailCreateTaskModal). Ne pas tout migrer d'un coup.
|
||||
|
||||
**Points ouverts.**
|
||||
- Nom et emplacement définitifs du composant.
|
||||
- Ordre de migration (au minimum : MailCreateTaskModal #6 et TaskModal #2/#5, qui sont déjà touchées par les autres chantiers).
|
||||
|
||||
---
|
||||
|
||||
## 8. Sélecteur de catégorie en `<select>` natif (WorkflowDrawer) → MalioSelect
|
||||
|
||||
**Problème.** Dans l'éditeur de statut d'un workflow, le champ « Catégorie » est un `<select>` HTML natif, visuellement incohérent avec le reste des formulaires (qui utilisent `MalioSelect`).
|
||||
|
||||
**Investigation faite (2026-05-21).** La note de `Lesstime/CLAUDE.md` affirmait que `MalioSelect` n'accepte que des `value: number | null` et qu'il fallait un `<select>` natif pour les enums string. **C'est faux.** Vérifié dans la source `@malio/layer-ui` (v1.4.8 installée **et** repo dev `malio-layer-ui`) :
|
||||
|
||||
```ts
|
||||
// app/components/malio/select/Select.vue
|
||||
type Option = { label: string; value: string | number | null } // string supporté
|
||||
const props = withDefaults(defineProps<{ modelValue: string | number | null; ... }>(), ...)
|
||||
emit('update:modelValue', v: string | number | null)
|
||||
// normalizedOptions n'ajoute l'option vide {value:null} QUE si empty-option-label est passé
|
||||
```
|
||||
|
||||
La comparaison interne utilise `===`, donc les valeurs `string` (ex. l'enum `StatusCategory` : `todo | in_progress | blocked | review | done`) fonctionnent nativement.
|
||||
|
||||
**Fichiers.**
|
||||
- `frontend/components/admin/WorkflowDrawer.vue` — `<select v-model="s.category">` (~l.49-57), `categoryOptions: { value: StatusCategory, label }[]` (~l.145-151).
|
||||
- Note corrigée : `Lesstime/CLAUDE.md` (section « Frontend »).
|
||||
|
||||
**Solution retenue (faite).** Remplacer le `<select>` natif par :
|
||||
|
||||
```vue
|
||||
<MalioSelect
|
||||
v-model="s.category"
|
||||
:options="categoryOptions"
|
||||
label="Catégorie"
|
||||
min-width="w-44"
|
||||
group-class="shrink-0"
|
||||
/>
|
||||
```
|
||||
|
||||
- Pas d'`empty-option-label` (catégorie requise → pas d'option « Aucune » `null`).
|
||||
- `min-width="w-44"` pour rester compact dans la ligne `flex` (sinon défaut `w-96`).
|
||||
|
||||
**Points ouverts / suite possible.**
|
||||
- D'autres `<select>` natifs subsistent pour des enums string et pourraient être migrés de la même façon (candidats : `AdminClientTicketTab.vue`, `AdminMailTab.vue`, `ProjectClientTickets.vue`, `ProjectWorkflowSwitchModal.vue`, `TaskModal.vue`). À traiter au cas par cas, hors scope immédiat.
|
||||
|
||||
---
|
||||
|
||||
## Découpage suggéré pour l'implémentation
|
||||
|
||||
Regrouper par zone pour limiter les conflits :
|
||||
|
||||
- **Lot A — Workflow / statuts** : #2 (filtrage statut) → réutilisé par #1 (D&D) et #6 (statut à la création mail).
|
||||
- **Lot B — Cartes** : #3 (responsive) + #4 (couleurs classiques).
|
||||
- **Lot C — Mail** : #5 (suppr. bouton) + #6 (form création) — dépend de #2 et #7.
|
||||
- **Lot D — Modale réutilisable** : #7 (composant) puis migration de TaskModal et MailCreateTaskModal.
|
||||
|
||||
Ordre conseillé : **#7 (composant modale)** et **#2 (filtrage statut)** d'abord (briques réutilisées), puis #1, #6, #5, puis #3/#4.
|
||||
Reference in New Issue
Block a user