# 🚀 Optimisation BTPXpress - Meilleures Pratiques PrimeFaces ## 📋 Table des Matières 1. [Analyse du Projet Actuel](#analyse-du-projet-actuel) 2. [Optimisations DataTable & Lazy Loading](#optimisations-datatable--lazy-loading) 3. [Performance Ajax & Partial Rendering](#performance-ajax--partial-rendering) 4. [Composants Réutilisables](#composants-réutilisables) 5. [Gestion d'État & ViewScoped](#gestion-détat--viewscoped) 6. [Validation & Messages](#validation--messages) 7. [Plan d'Implémentation](#plan-dimplémentation) --- ## 🔍 Analyse du Projet Actuel ### Points Forts ✅ - ✅ Utilisation de `@ViewScoped` pour les beans (bonne pratique) - ✅ Architecture BaseListView pour la réutilisation du code - ✅ Séparation des concerns (Service, View, Model) - ✅ Utilisation de composants réutilisables (`liste-table.xhtml`, `liste-filters.xhtml`) ### Points à Améliorer 🔧 - ⚠️ **Pas de Lazy Loading** : Toutes les données sont chargées en mémoire - ⚠️ **Filtrage côté client** : Le filtrage se fait en Java après récupération complète - ⚠️ **Pas de cache** : Rechargement complet à chaque fois - ⚠️ **Updates Ajax trop larges** : Risque de re-rendering inutile - ⚠️ **Pas de composants composites Freya** : Utilisation directe de PrimeFaces --- ## 📊 Optimisations DataTable & Lazy Loading ### 1. Implémenter LazyDataModel **Problème actuel** : Dans `FactureView.java`, toutes les factures sont chargées : ```java List> facturesData = factureService.getAllFactures(); ``` **Solution** : Utiliser `LazyDataModel` de PrimeFaces #### Créer un LazyDataModel personnalisé ```java package dev.lions.btpxpress.view.model; import org.primefaces.model.FilterMeta; import org.primefaces.model.LazyDataModel; import org.primefaces.model.SortMeta; import dev.lions.btpxpress.service.FactureService; import dev.lions.btpxpress.view.FactureView.Facture; import java.util.*; public class FactureLazyDataModel extends LazyDataModel { private final FactureService factureService; public FactureLazyDataModel(FactureService factureService) { this.factureService = factureService; } @Override public int count(Map filterBy) { // Appel API pour compter le nombre total avec filtres return factureService.countFactures(buildFilterParams(filterBy)); } @Override public List load(int first, int pageSize, Map sortBy, Map filterBy) { // Appel API avec pagination, tri et filtres Map params = new HashMap<>(); params.put("offset", first); params.put("limit", pageSize); params.putAll(buildSortParams(sortBy)); params.putAll(buildFilterParams(filterBy)); return factureService.getFacturesLazy(params); } private Map buildSortParams(Map sortBy) { Map params = new HashMap<>(); if (sortBy != null && !sortBy.isEmpty()) { SortMeta sort = sortBy.values().iterator().next(); params.put("sortField", sort.getField()); params.put("sortOrder", sort.getOrder().name()); } return params; } private Map buildFilterParams(Map filterBy) { Map params = new HashMap<>(); if (filterBy != null) { filterBy.forEach((key, filter) -> { if (filter.getFilterValue() != null) { params.put("filter_" + key, filter.getFilterValue()); } }); } return params; } } ``` #### Modifier FactureView pour utiliser LazyDataModel ```java @Named("factureView") @ViewScoped public class FactureView implements Serializable { @Inject FactureService factureService; private LazyDataModel lazyModel; @PostConstruct public void init() { lazyModel = new FactureLazyDataModel(factureService); } public LazyDataModel getLazyModel() { return lazyModel; } } ``` #### Modifier factures.xhtml pour utiliser lazy loading ```xml ``` ### 2. Optimiser le Service Backend **Ajouter des endpoints paginés dans BtpXpressApiClient** : ```java @Path("/api/factures") public interface BtpXpressApiClient { @GET @Path("/lazy") @Produces(MediaType.APPLICATION_JSON) Response getFacturesLazy( @QueryParam("offset") int offset, @QueryParam("limit") int limit, @QueryParam("sortField") String sortField, @QueryParam("sortOrder") String sortOrder, @QueryParam("filter_numero") String filtreNumero, @QueryParam("filter_client") String filtreClient, @QueryParam("filter_statut") String filtreStatut ); @GET @Path("/count") @Produces(MediaType.APPLICATION_JSON) int countFactures( @QueryParam("filter_numero") String filtreNumero, @QueryParam("filter_client") String filtreClient, @QueryParam("filter_statut") String filtreStatut ); } ``` --- ## ⚡ Performance Ajax & Partial Rendering ### 1. Optimiser les Updates Ajax **Problème** : Updates trop larges qui re-rendent des composants inutilement **Mauvaise pratique** ❌ : ```xml ``` **Bonne pratique** ✅ : ```xml ``` ### 2. Utiliser process et update de manière ciblée **Règles d'or** : - `process` : Spécifie quels composants doivent être traités (validation, conversion, update model) - `update` : Spécifie quels composants doivent être re-rendus - Toujours utiliser les IDs spécifiques plutôt que `@form` ou `@all` **Exemple optimisé** : ```xml ``` ### 3. Utiliser p:ajax pour les événements **Pour les changements de filtres en temps réel** : ```xml ``` ### 4. Désactiver les auto-updates inutiles **Éviter** : ```xml ``` **Préférer** : ```xml ``` --- ## 🧩 Composants Réutilisables ### 1. Migrer vers Freya Extension **Avantages** : - Composants pré-stylés cohérents - Moins de code boilerplate - Meilleure maintenabilité #### Exemple : Remplacer p:dataTable par fr:dataTable **Avant** : ```xml ``` **Après** : ```xml ``` ### 2. Créer des Composants Composites Métier #### Créer un composant pour les badges de statut **Fichier** : `/WEB-INF/components/facture-statut-badge.xhtml` ```xml ``` **Utilisation** : ```xml ``` #### Créer un composant pour les montants **Fichier** : `/WEB-INF/components/montant-display.xhtml` ```xml ``` **Utilisation** : ```xml ``` ### 3. Créer un Composant de Filtre Réutilisable **Fichier** : `/WEB-INF/components/search-filter-panel.xhtml` ```xml

Filtres de recherche

``` --- ## 🎯 Gestion d'État & ViewScoped ### 1. Optimiser BaseListView **Problème actuel** : Rechargement complet à chaque filtre **Solution** : Ajouter un cache intelligent ```java @Getter @Setter public abstract class BaseListView implements Serializable { protected LazyDataModel lazyModel; protected T selectedItem; protected boolean loading; // Cache pour éviter les rechargements inutiles private transient Map lastFilterParams; @PostConstruct public void init() { initializeLazyModel(); } protected abstract void initializeLazyModel(); public void applyFilters() { Map currentParams = buildFilterParams(); // Vérifier si les filtres ont changé if (!Objects.equals(lastFilterParams, currentParams)) { lastFilterParams = new HashMap<>(currentParams); // Le LazyDataModel se rechargera automatiquement } } protected abstract Map buildFilterParams(); public void resetFilters() { resetFilterFields(); lastFilterParams = null; applyFilters(); } protected abstract void resetFilterFields(); } ``` ### 2. Utiliser @CacheResult pour les données statiques **Pour les listes de référence (statuts, types, etc.)** : ```java @ApplicationScoped public class ReferenceDataService { @CacheResult(cacheName = "statuts-facture") public List getStatutsFacture() { return Arrays.asList( new SelectItem("BROUILLON", "Brouillon"), new SelectItem("EMISE", "Émise"), new SelectItem("PAYEE", "Payée"), // ... ); } } ``` **Utilisation dans le bean** : ```java @Named("factureView") @ViewScoped public class FactureView implements Serializable { @Inject ReferenceDataService refDataService; public List getStatutsFacture() { return refDataService.getStatutsFacture(); // Mis en cache } } ``` --- ## ✅ Validation & Messages ### 1. Validation côté client avec PrimeFaces **Activer la validation client** dans `application.properties` : ```properties primefaces.CLIENT_SIDE_VALIDATION=true primefaces.CSV_ENABLED=true ``` **Exemple de formulaire avec validation** : ```xml ``` ### 2. Messages d'erreur personnalisés **Créer un validateur personnalisé** : ```java @FacesValidator("dateValidator") public class DateValidator implements Validator { @Override public void validate(FacesContext context, UIComponent component, LocalDate value) throws ValidatorException { if (value != null && value.isBefore(LocalDate.now())) { FacesMessage msg = new FacesMessage( FacesMessage.SEVERITY_ERROR, "Date invalide", "La date ne peut pas être dans le passé" ); throw new ValidatorException(msg); } } } ``` ### 3. Utiliser p:growl pour les notifications **Ajouter dans le template** : ```xml ``` **Dans le bean** : ```java public void save() { try { factureService.save(selectedItem); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", "La facture a été enregistrée avec succès")); } catch (Exception e) { FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", "Impossible d'enregistrer la facture: " + e.getMessage())); } } ``` --- ## 📅 Plan d'Implémentation ### Phase 1 : Optimisation des DataTables (Semaine 1) - [ ] Créer `FactureLazyDataModel` - [ ] Modifier `FactureView` pour utiliser LazyDataModel - [ ] Ajouter endpoints paginés dans le backend - [ ] Tester la pagination et le tri - [ ] Répliquer pour Devis, Clients, Chantiers ### Phase 2 : Optimisation Ajax (Semaine 2) - [ ] Auditer tous les `update="@form"` et les remplacer - [ ] Ajouter `process` spécifiques sur tous les commandButton - [ ] Implémenter `p:ajax` pour les filtres en temps réel - [ ] Tester les performances ### Phase 3 : Composants Réutilisables (Semaine 3) - [ ] Créer `facture-statut-badge.xhtml` - [ ] Créer `montant-display.xhtml` - [ ] Créer `search-filter-panel.xhtml` - [ ] Migrer vers `fr:dataTable` pour toutes les tables - [ ] Créer composants métier supplémentaires ### Phase 4 : Validation & UX (Semaine 4) - [ ] Activer validation côté client - [ ] Créer validateurs personnalisés - [ ] Implémenter p:growl pour notifications - [ ] Ajouter confirmations pour actions critiques - [ ] Tests utilisateurs ### Phase 5 : Cache & Performance (Semaine 5) - [ ] Implémenter cache pour données de référence - [ ] Optimiser BaseListView avec cache intelligent - [ ] Profiler et identifier les bottlenecks - [ ] Optimiser les requêtes backend --- ## 📊 Métriques de Succès ### Avant Optimisation - ⏱️ Temps de chargement liste factures : ~2-3s (100+ factures) - 📦 Données transférées : Toutes les factures à chaque fois - 🔄 Re-rendering : Formulaire complet à chaque action - 💾 Mémoire : Toutes les données en mémoire ### Après Optimisation (Objectifs) - ⏱️ Temps de chargement : <500ms (pagination) - 📦 Données transférées : 10-50 factures par page - 🔄 Re-rendering : Composants ciblés uniquement - 💾 Mémoire : Données paginées + cache intelligent - 🎯 Score Lighthouse : >90 --- ## 🔗 Ressources ### Documentation PrimeFaces - [PrimeFaces Showcase](https://www.primefaces.org/showcase/) - [LazyDataModel Guide](https://www.primefaces.org/docs/guide/primefaces_user_guide_14_0_0.pdf) - [Ajax Best Practices](https://www.primefaces.org/showcase/ui/ajax/basic.xhtml) ### Exemples de Code - [PrimeFaces GitHub](https://github.com/primefaces/primefaces) - [PrimeFaces Showcase Source](https://github.com/primefaces/primefaces/tree/master/primefaces-showcase) ### Articles & Tutoriels - [PrimeFaces DataTable Lazy Loading with JPA](https://www.javacodegeeks.com/2014/01/primefaces-datatable-lazy-loading-with-pagination-filtering-and-sorting-using-jpa-criteria-viewscoped.html) - [Optimizing JSF Performance](https://www.baeldung.com/jsf-primefaces-performance) --- ## 🎓 Bonnes Pratiques Générales ### DO ✅ - ✅ Utiliser LazyDataModel pour les grandes listes - ✅ Spécifier `process` et `update` de manière ciblée - ✅ Utiliser `@ViewScoped` pour les beans de vue - ✅ Créer des composants réutilisables - ✅ Valider côté client ET serveur - ✅ Utiliser le cache pour les données statiques - ✅ Tester les performances régulièrement ### DON'T ❌ - ❌ Charger toutes les données en mémoire - ❌ Utiliser `update="@all"` ou `update="@form"` systématiquement - ❌ Oublier `process` sur les commandButton - ❌ Dupliquer le code de composants - ❌ Ignorer la validation côté client - ❌ Recharger les données de référence à chaque fois --- ## 🚀 Prochaines Étapes 1. **Commencer par Phase 1** : Lazy Loading pour Factures 2. **Mesurer les performances** avant/après 3. **Itérer** sur les autres modules 4. **Documenter** les patterns réutilisables 5. **Former l'équipe** aux nouvelles pratiques --- **Créé le** : 2025-12-29 **Auteur** : Équipe BTPXpress **Version** : 1.0 ### 2. Optimiser le Service Backend **Ajouter des endpoints paginés dans BtpXpressApiClient** : ```java @Path("/api/factures") public interface BtpXpressApiClient { @GET @Path("/lazy") @Produces(MediaType.APPLICATION_JSON) Response getFacturesLazy( @QueryParam("offset") int offset, @QueryParam("limit") int limit, @QueryParam("sortField") String sortField, @QueryParam("sortOrder") String sortOrder, @QueryParam("filter_numero") String filtreNumero, @QueryParam("filter_client") String filtreClient, @QueryParam("filter_statut") String filtreStatut ); @GET @Path("/count") @Produces(MediaType.APPLICATION_JSON) int countFactures( @QueryParam("filter_numero") String filtreNumero, @QueryParam("filter_client") String filtreClient, @QueryParam("filter_statut") String filtreStatut ); } ``` --- ## ⚡ Performance Ajax & Partial Rendering ### 1. Optimiser les Updates Ajax **Problème** : Updates trop larges qui re-rendent des composants inutilement **Mauvaise pratique** ❌ : ```xml ``` **Bonne pratique** ✅ : ```xml ``` ### 2. Utiliser process et update de manière ciblée