- Add usePermissions composable (isAdmin, canEdit, canView) - Password-protected profile login with modal on profiles page - Disable all form fields for ROLE_VIEWER across edit/create pages - Show navigation buttons (Modifier/Consulter) for all roles, hide delete for viewers - Add readonly prop to ModelTypeForm for category pages - Disable modal fields (sites, constructeurs) for viewers - Guard /admin routes in middleware Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
230 lines
10 KiB
Markdown
230 lines
10 KiB
Markdown
# Plan de migration — Réduction de code frontend
|
||
|
||
> Objectif : réduire ~5 700 LOC sans modifier le fonctionnel.
|
||
> Branche : à partir de `refacto/F1-decoupage-mega-composants`
|
||
> Statut global : **EN ATTENTE**
|
||
|
||
---
|
||
|
||
## Phase 1 — Pages catalogue (3 pages, ~1 200 LOC → ~350 LOC)
|
||
|
||
### M1.1 · Composant générique `CatalogPage.vue`
|
||
|
||
- **Motif** : `component-catalog.vue` (348 LOC), `pieces-catalog.vue` (463 LOC) et `product-catalog.vue` (408 LOC) partagent 95 % de structure (recherche, tri, pagination, tableau, suppression, états vides/loading).
|
||
- **Différences isolées** : colonnes du tableau, garde de suppression, extraction fournisseur.
|
||
- **Plan** :
|
||
1. Créer `app/components/common/CatalogPage.vue` acceptant :
|
||
- `columns: ColumnDef[]` (nom, clé, slot optionnel)
|
||
- `fetchFn: (params) => Promise<PaginatedResult>`
|
||
- `deleteFn: (id) => Promise<Result>`
|
||
- `deleteGuard?: (item) => string | null` (message bloquant ou null)
|
||
- `entityLabel: string`, `createRoute: string`
|
||
- Slots nommés pour colonnes custom (`#col-supplier`, etc.)
|
||
2. Extraire `supplierDisplayUtils.ts` (pattern `MAX_VISIBLE_SUPPLIERS` dupliqué dans pieces-catalog et product-catalog).
|
||
3. Réduire chaque page catalogue à ~80 LOC (config + slots custom).
|
||
- **Gain estimé** : ~850 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Phase 2 — Composables CRUD génériques (~1 170 LOC → ~400 LOC)
|
||
|
||
### M2.1 · Factory `useEntityCRUD<T>(config)`
|
||
|
||
- **Motif** : `usePieces.ts` (240), `useProducts.ts` (305), `useComposants.ts` (231), `useSites.ts` (124) suivent le même pattern CRUD : refs `loading/loaded/error`, `loadItems()` paginé, `create/update/delete` avec mise à jour cache + toast.
|
||
- **Différences isolées** : endpoint, normaliseur, enrichissement constructeurs, champs de tri.
|
||
- **Plan** :
|
||
1. Créer `app/composables/useEntityCRUD.ts` :
|
||
```ts
|
||
interface EntityCRUDConfig {
|
||
endpoint: string
|
||
label: string
|
||
normalizer?: (item: any) => any
|
||
enricher?: (item: any) => Promise<any>
|
||
defaultSort?: { field: string; dir: 'asc' | 'desc' }
|
||
}
|
||
export function useEntityCRUD(config: EntityCRUDConfig)
|
||
```
|
||
2. Extraire `extractTotal()` dans `apiHelpers.ts` (dupliqué 3×, ~10 LOC chacun).
|
||
3. Extraire `buildPaginatedQuery(options)` dans `apiHelpers.ts` (dupliqué 3×, ~15 LOC chacun).
|
||
4. Extraire pattern `withResolvedConstructeurs()` dans `useEntityEnricher.ts` (dupliqué 3× dans pieces/products/composants, ~50 LOC chacun).
|
||
5. Réduire chaque composable à un appel de factory + méthodes spécifiques.
|
||
6. Garder `useMachines.ts` séparé (méthodes spéciales : `reconfigureSkeleton`, `createMachineFromType`).
|
||
- **Gain estimé** : ~770 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M2.2 · Helper `withLoadingState()`
|
||
|
||
- **Motif** : pattern `loading.value = true; try { ... } finally { loading.value = false }` répété 10+ fois dans les composables CRUD.
|
||
- **Plan** : créer `app/composables/useLoadingHelper.ts` exportant :
|
||
```ts
|
||
async function withLoadingState<T>(loading: Ref<boolean>, fn: () => Promise<T>): Promise<T>
|
||
```
|
||
- **Gain estimé** : ~100 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M2.3 · Fusion `usePersistedValue` + `usePersistedSort`
|
||
|
||
- **Motif** : même pattern `useCookie()` + `watch()` + JSON parse/stringify.
|
||
- **Plan** : fusionner en `usePersistedState<T>(key, fallback, prefix?)`.
|
||
- **Gain estimé** : ~30 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Phase 3 — Pages edit entités (~2 750 LOC → ~1 200 LOC)
|
||
|
||
### M3.1 · Composant `HistorySection.vue`
|
||
|
||
- **Motif** : bloc historique identique (loading/error/empty + itération entries) dans `component/[id]/edit.vue` (L437-503), `pieces/[id]/edit.vue` (L384-450), `product/[id]/edit.vue` (L304-370) — ~67 LOC × 3.
|
||
- **Plan** : créer `app/components/common/HistorySection.vue` avec props `entries`, `loading`, `error`.
|
||
- **Gain estimé** : ~130 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M3.2 · Composant `DocumentsSection.vue`
|
||
|
||
- **Motif** : bloc document (upload, liste, preview, download, delete) dupliqué dans les 3 pages edit + `MachineDocumentsCard.vue` + `SiteEditModal.vue` — ~70-180 LOC × 5.
|
||
- **Plan** : créer `app/components/common/DocumentsSection.vue` avec props `documents`, `entityId`, `entityType` et events `upload`, `delete`, `preview`.
|
||
- **Gain estimé** : ~400 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M3.3 · Composable `useEntityEditForm(config)`
|
||
|
||
- **Motif** : les 3 pages edit partagent : chargement entité + types + constructeurs, gestion champs custom, normalisation payload, sauvegarde, gestion erreur.
|
||
- **Différences** : component a structure display, piece a product selection, product est plus simple.
|
||
- **Plan** :
|
||
1. Créer `app/composables/useEntityEditForm.ts` gérant le cycle de vie commun (load, save, custom fields sync).
|
||
2. Chaque page edit ne garde que ses spécificités.
|
||
- **Gain estimé** : ~500 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M3.4 · Réutilisation `customFieldFormUtils.ts` dans `component/create.vue`
|
||
|
||
- **Motif** : `component/create.vue` (1 266 LOC) réimplémente `resolveFieldName`, `resolveFieldType`, `resolveDefaultValue` déjà dans `customFieldFormUtils.ts`. Aussi 3 fonctions `resolveXxxLabel` quasi-identiques (~18 LOC × 3).
|
||
- **Plan** :
|
||
1. Remplacer les fonctions locales par les imports de `customFieldFormUtils.ts`.
|
||
2. Créer `resolveTypeLabel(entity, typeField, labelField, fallback)` générique.
|
||
- **Gain estimé** : ~120 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Phase 4 — Décomposition `useMachineDetailData.ts` (1 410 LOC → ~500 LOC)
|
||
|
||
### M4.1 · Extraire `useMachineDocuments.ts`
|
||
|
||
- **Motif** : gestion documents (upload, delete, preview, refresh) = ~200 LOC dans le composable monolithique.
|
||
- **Gain estimé** : ~150 LOC (après factorisation avec DocumentsSection)
|
||
- **Statut** : `[ ]`
|
||
|
||
### M4.2 · Extraire `useMachineConstructeurs.ts`
|
||
|
||
- **Motif** : résolution constructeurs avec chaînes de fallback 4 niveaux, `uniqueConstructeurIds`, `resolveConstructeurs` = ~80 LOC.
|
||
- **Gain estimé** : ~60 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M4.3 · Fusionner `transformCustomFields` et `transformComponentCustomFields`
|
||
|
||
- **Motif** : L303-405 et L407-514 — logique quasi-identique de transformation des champs custom, seule la source (machine vs composant) diffère.
|
||
- **Plan** : créer `transformEntityCustomFields(entity, fieldSource, config)` paramétrable.
|
||
- **Gain estimé** : ~100 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M4.4 · Extraire groupement de requirements
|
||
|
||
- **Motif** : `componentRequirementGroups`, `pieceRequirementGroups` = computed complexes avec construction de maps et filtres répétitifs.
|
||
- **Gain estimé** : ~80 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Phase 5 — `StructureNodeEditor.vue` (1 167 LOC → ~600 LOC)
|
||
|
||
### M5.1 · Composable `useDragDrop.ts`
|
||
|
||
- **Motif** : 4 handlers drag-drop quasi-identiques (custom fields, pièces, produits, sous-composants) avec chacun `draggingIndex`, `dropTargetIndex`, `reorderClass()`, `handleDragStart/Over/End`.
|
||
- **Plan** : créer `useDragDrop<T>(items: Ref<T[]>)` retournant `{ dragging, target, reorderClass, onDragStart, onDragOver, onDragEnd, onDrop }`.
|
||
- **Gain estimé** : ~350 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M5.2 · Extraire validation noeud
|
||
|
||
- **Motif** : `isAssignmentNodeComplete` + logique de validation dispersée.
|
||
- **Plan** : déplacer vers `app/shared/utils/structureValidation.ts`.
|
||
- **Gain estimé** : ~40 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Phase 6 — Micro-duplications restantes (du `micro-dup-report.md`)
|
||
|
||
### M6.1 · `useControlledModel.ts` (MDUP-004)
|
||
|
||
- **Motif** : `computed({ get, set })` pour transiter `v-model` entre props et emits — dupliqué dans 6 composants.
|
||
- **Gain estimé** : ~60 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M6.2 · `ModalShell.vue` (MDUP-008) + `ModalActions.vue` (MDUP-007)
|
||
|
||
- **Motif** : squelette de modale DaisyUI (`.modal` + `.modal-box` + titre + footer) dupliqué dans 4+ composants. Pieds de modale « Annuler + Primaire + spinner » dupliqués 5×.
|
||
- **Gain estimé** : ~120 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M6.3 · `LoadingButton.vue` (MDUP-010) + `FieldText.vue` (MDUP-009)
|
||
|
||
- **Motif** : bouton primaire avec spinner (3 occurrences), champ texte simple label+input (5 occurrences).
|
||
- **Gain estimé** : ~80 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
### M6.4 · `createRequirementDefaults` + `useEnsureOptionsLoaded` (MDUP-005, MDUP-006)
|
||
|
||
- **Motif** : factory de requirement par défaut + `onMounted` identiques dans les sections composant/pièce.
|
||
- **Gain estimé** : ~30 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Phase 7 — Consolidation custom fields (~1 150 LOC → ~800 LOC)
|
||
|
||
### M7.1 · Fusionner logique de résolution dans `customFieldUtils.ts`
|
||
|
||
- **Motif** : `customFieldUtils.ts` (440), `entityCustomFieldLogic.ts` (349), `customFieldFormUtils.ts` (367) contiennent des fonctions de résolution de champs qui se chevauchent (`resolveFieldId`, `resolveFieldName`, génération de clé, déduplication).
|
||
- **Plan** : consolider les fonctions dupliquées en gardant la séparation thématique (utils / form / entity) mais en partageant les primitives.
|
||
- **Gain estimé** : ~150 LOC
|
||
- **Statut** : `[ ]`
|
||
|
||
---
|
||
|
||
## Récapitulatif
|
||
|
||
| Phase | Cible | LOC avant | Gain estimé | Priorité |
|
||
|-------|-------|-----------|-------------|----------|
|
||
| **P1** | Pages catalogue | ~1 220 | ~850 | Haute |
|
||
| **P2** | Composables CRUD | ~1 170 | ~900 | Haute |
|
||
| **P3** | Pages edit entités | ~2 750 | ~1 150 | Haute |
|
||
| **P4** | useMachineDetailData | ~1 410 | ~390 | Moyenne |
|
||
| **P5** | StructureNodeEditor | ~1 167 | ~390 | Moyenne |
|
||
| **P6** | Micro-duplications | ~400 | ~290 | Basse |
|
||
| **P7** | Custom fields utils | ~1 150 | ~150 | Basse |
|
||
| | **Total** | | **~4 120 LOC** | |
|
||
|
||
### Ordre recommandé
|
||
|
||
1. **P2** (CRUD generics) — fondation pour P1 et P3
|
||
2. **P1** (catalogues) — dépend de P2 pour les fetch functions
|
||
3. **P3** (pages edit) — plus gros gain absolu, dépend partiellement de P2
|
||
4. **P5** (drag-drop) — indépendant, quick win
|
||
5. **P4** (machine detail) — complexe mais fort impact
|
||
6. **P6** (micro-dup) — petits gains, faible risque
|
||
7. **P7** (custom fields) — délicat, à faire en dernier
|
||
|
||
### Vérification après chaque phase
|
||
|
||
```bash
|
||
cd Inventory_frontend
|
||
npx nuxi typecheck # 0 erreurs
|
||
npm run lint:fix # 0 erreurs
|
||
npm run build # succès
|
||
npx vitest run # 54+ tests pass
|
||
```
|