From be2debc6bf1785c21bc62f2f13d0d42c28c09d4b Mon Sep 17 00:00:00 2001 From: lionsdev Date: Thu, 23 Apr 2026 14:48:46 +0000 Subject: [PATCH] chore(quarkus-327): bump to Quarkus 3.27.3 LTS, rename deprecated config keys --- .dockerignore | 110 +- .gitignore | 116 ++ AUDIT_CONFIGURATION.md | 372 ++-- CONFIGURATION.md | 150 +- Dockerfile | 94 +- Dockerfile.prod | 168 +- EXECUTIVE_SUMMARY_OPTIMIZATIONS.md | 263 +++ FIX_431_ERROR.md | 150 +- FOOTER_CONFIGURATION.md | 75 + IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md | 281 +++ LAZY_LOADING_IMPLEMENTATION_SPEC.md | 221 ++ PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md | 776 +++++++ SECURITE_PRODUCTION.md | 670 +++--- pom.xml | 279 ++- .../btpxpress/converter/FcfaConverter.java | 174 +- .../filter/CharacterEncodingFilter.java | 64 +- .../filter/SecurityHeadersFilter.java | 232 +-- .../btpxpress/service/BtpXpressApiClient.java | 609 +++--- .../btpxpress/service/ChantierService.java | 260 ++- .../btpxpress/service/ClientService.java | 1 + .../btpxpress/service/DashboardService.java | 652 +++--- .../btpxpress/service/MessageService.java | 90 + .../service/NotificationService.java | 90 + .../lions/btpxpress/view/BaseListView.java | 766 +++---- .../lions/btpxpress/view/ChantiersView.java | 670 +++--- .../dev/lions/btpxpress/view/ClientsView.java | 499 +++-- .../lions/btpxpress/view/DashboardView.java | 1822 ++++++++--------- .../dev/lions/btpxpress/view/DevisView.java | 5 +- .../dev/lions/btpxpress/view/EmployeView.java | 3 +- .../dev/lions/btpxpress/view/EquipeView.java | 4 +- .../dev/lions/btpxpress/view/FactureView.java | 4 +- .../btpxpress/view/GuestPreferences.java | 94 - .../dev/lions/btpxpress/view/LoginView.java | 106 +- .../lions/btpxpress/view/MaterielView.java | 3 +- .../dev/lions/btpxpress/view/StockView.java | 3 +- .../lions/btpxpress/view/UserSessionBean.java | 204 -- src/main/resources/META-INF/faces-config.xml | 62 +- .../resources/WEB-INF/btpxpress.taglib.xml | 34 + .../components/facture-statut-badge.xhtml | 31 + .../WEB-INF/components/liste-filters.xhtml | 66 +- .../WEB-INF/components/liste-table.xhtml | 88 +- .../WEB-INF/components/montant-display.xhtml | 36 + .../components/search-filter-panel.xhtml | 62 + .../META-INF/resources/WEB-INF/config.xhtml | 188 +- .../resources/WEB-INF/rightpanel.xhtml | 130 +- .../META-INF/resources/WEB-INF/template.xhtml | 6 +- .../META-INF/resources/WEB-INF/topbar.xhtml | 260 +-- .../META-INF/resources/bon-commande.xhtml | 56 +- .../META-INF/resources/budgets.xhtml | 56 +- .../META-INF/resources/chantiers.xhtml | 206 +- .../resources/chantiers/details.xhtml | 614 +++--- .../resources/chantiers/en-cours.xhtml | 194 +- .../resources/chantiers/nouveau.xhtml | 472 ++--- .../resources/chantiers/planifies.xhtml | 188 +- .../resources/chantiers/termines.xhtml | 188 +- .../META-INF/resources/clients.xhtml | 190 +- .../META-INF/resources/clients/details.xhtml | 170 +- .../META-INF/resources/clients/nouveau.xhtml | 190 +- .../resources/clients/recherche.xhtml | 192 +- .../META-INF/resources/dashboard.xhtml | 976 ++++----- .../resources/META-INF/resources/devis.xhtml | 224 +- .../META-INF/resources/devis/acceptes.xhtml | 54 +- .../META-INF/resources/devis/attente.xhtml | 2 +- .../META-INF/resources/devis/expires.xhtml | 54 +- .../META-INF/resources/devis/nouveau.xhtml | 624 +++--- .../META-INF/resources/documentation.xhtml | 48 +- .../META-INF/resources/documents.xhtml | 56 +- .../META-INF/resources/employes.xhtml | 204 +- .../META-INF/resources/employes/actifs.xhtml | 2 +- .../resources/employes/disponibles.xhtml | 2 +- .../META-INF/resources/employes/nouveau.xhtml | 2 +- .../META-INF/resources/equipes.xhtml | 186 +- .../resources/equipes/disponibles.xhtml | 2 +- .../META-INF/resources/equipes/nouveau.xhtml | 2 +- .../resources/equipes/specialites.xhtml | 2 +- .../META-INF/resources/factures.xhtml | 244 +-- .../resources/factures/impayees.xhtml | 2 +- .../META-INF/resources/factures/nouveau.xhtml | 784 +++---- .../META-INF/resources/factures/payees.xhtml | 2 +- .../META-INF/resources/factures/retard.xhtml | 2 +- .../META-INF/resources/fournisseurs.xhtml | 56 +- .../resources/META-INF/resources/index.xhtml | 58 + .../resources/META-INF/resources/login.xhtml | 170 +- .../META-INF/resources/maintenance.xhtml | 46 +- .../resources/maintenance/corrective.xhtml | 2 +- .../resources/maintenance/nouveau.xhtml | 2 +- .../resources/maintenance/preventive.xhtml | 54 +- .../resources/maintenance/urgente.xhtml | 2 +- .../META-INF/resources/materiels.xhtml | 222 +- .../META-INF/resources/messages.xhtml | 46 +- .../resources/messages/archives.xhtml | 2 +- .../META-INF/resources/messages/envoyes.xhtml | 2 +- .../META-INF/resources/messages/nouveau.xhtml | 2 +- .../META-INF/resources/notifications.xhtml | 46 +- .../resources/notifications/non-lues.xhtml | 2 +- .../resources/notifications/nouveau.xhtml | 2 +- .../resources/notifications/recentes.xhtml | 2 +- .../notifications/statistiques.xhtml | 2 +- .../META-INF/resources/parametres.xhtml | 56 +- .../META-INF/resources/planning.xhtml | 46 +- .../resources/planning/calendrier.xhtml | 2 +- .../META-INF/resources/planning/equipes.xhtml | 2 +- .../resources/planning/materiel.xhtml | 2 +- .../META-INF/resources/planning/nouveau.xhtml | 2 +- .../META-INF/resources/profile.xhtml | 420 ++-- .../META-INF/resources/rapports.xhtml | 46 +- .../META-INF/resources/rapports/ca.xhtml | 2 +- .../META-INF/resources/rapports/clients.xhtml | 2 +- .../META-INF/resources/rapports/equipes.xhtml | 2 +- .../META-INF/resources/rapports/nouveau.xhtml | 2 +- .../resources/rapports/rentabilite.xhtml | 2 +- .../resources/resources/css/custom-topbar.css | 370 ++-- .../resources/META-INF/resources/stock.xhtml | 242 +-- .../META-INF/resources/stock/commandes.xhtml | 2 +- .../META-INF/resources/stock/inventaire.xhtml | 2 +- .../META-INF/resources/stock/nouveau.xhtml | 2 +- .../META-INF/resources/stock/sorties.xhtml | 2 +- .../META-INF/resources/utilisateurs.xhtml | 56 +- src/main/resources/META-INF/web.xml | 252 +-- .../resources/application-prod.properties | 4 +- src/main/resources/application.properties | 10 +- src/main/webapp/WEB-INF/web.xml | 236 +-- 122 files changed, 10918 insertions(+), 8797 deletions(-) create mode 100644 .gitignore create mode 100644 EXECUTIVE_SUMMARY_OPTIMIZATIONS.md create mode 100644 FOOTER_CONFIGURATION.md create mode 100644 IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md create mode 100644 LAZY_LOADING_IMPLEMENTATION_SPEC.md create mode 100644 PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md create mode 100644 src/main/java/dev/lions/btpxpress/service/MessageService.java create mode 100644 src/main/java/dev/lions/btpxpress/service/NotificationService.java delete mode 100644 src/main/java/dev/lions/btpxpress/view/GuestPreferences.java delete mode 100644 src/main/java/dev/lions/btpxpress/view/UserSessionBean.java create mode 100644 src/main/resources/META-INF/resources/WEB-INF/btpxpress.taglib.xml create mode 100644 src/main/resources/META-INF/resources/WEB-INF/components/facture-statut-badge.xhtml create mode 100644 src/main/resources/META-INF/resources/WEB-INF/components/montant-display.xhtml create mode 100644 src/main/resources/META-INF/resources/WEB-INF/components/search-filter-panel.xhtml create mode 100644 src/main/resources/META-INF/resources/index.xhtml diff --git a/.dockerignore b/.dockerignore index 0f38e69..45c9905 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,55 +1,55 @@ -# Docker ignore pour BTP Xpress Client - -# Répertoires de build et target -target/ -.mvn/ -.quarkus/ - -# Fichiers IDE -.idea/ -.vscode/ -*.iml -*.ipr -*.iws -.settings/ -.classpath -.project - -# Documentation (non nécessaire dans l'image) -*.md -!README.md - -# Git -.git/ -.gitignore -.gitattributes - -# Fichiers de configuration locale -.env -.env.local -*.log - -# Tests -src/test/ - -# Fichiers temporaires -*.tmp -*.bak -*.swp -*~ - -# OS -.DS_Store -Thumbs.db - -# Scripts de déploiement (non nécessaires dans l'image) -scripts/ -*.sh -*.ps1 -*.bat - -# Kubernetes (géré séparément) -kubernetes/ -*.yaml -*.yml - +# Docker ignore pour BTP Xpress Client + +# Répertoires de build et target +target/ +.mvn/ +.quarkus/ + +# Fichiers IDE +.idea/ +.vscode/ +*.iml +*.ipr +*.iws +.settings/ +.classpath +.project + +# Documentation (non nécessaire dans l'image) +*.md +!README.md + +# Git +.git/ +.gitignore +.gitattributes + +# Fichiers de configuration locale +.env +.env.local +*.log + +# Tests +src/test/ + +# Fichiers temporaires +*.tmp +*.bak +*.swp +*~ + +# OS +.DS_Store +Thumbs.db + +# Scripts de déploiement (non nécessaires dans l'image) +scripts/ +*.sh +*.ps1 +*.bat + +# Kubernetes (géré séparément) +kubernetes/ +*.yaml +*.yml + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8991c8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,116 @@ +# ============================================ +# BTPXpress Client (Quarkus JSF) .gitignore +# ============================================ + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Quarkus +.quarkus/ +quarkus.log + +# IDE +.idea/ +*.iml +*.ipr +*.iws +.vscode/ +.classpath +.project +.settings/ +.factorypath +.apt_generated/ +.apt_generated_tests/ + +# Eclipse +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.loadpath +.recommenders + +# IntelliJ +out/ +.idea_modules/ + +# Logs +*.log +*.log.* +logs/ + +# OS +.DS_Store +Thumbs.db +*.pid + +# Java +*.class +*.jar +!.mvn/wrapper/maven-wrapper.jar +*.war +*.ear +hs_err_pid* + +# JSF/Faces specific +**/META-INF/resources/.faces-config.xml.jsfdia +**/javax.faces.resource/ + +# PrimeFaces cache +**/primefaces_resource_cache/ + +# Node modules (if using npm/webpack) +node_modules/ +npm-debug.log +yarn-error.log +package-lock.json +yarn.lock + +# Static resources compiled +src/main/resources/META-INF/resources/dist/ +src/main/resources/META-INF/resources/assets/vendor/ + +# Application secrets +*.jks +*.p12 +*.pem +*.key +*-secret.properties +application-local.properties +application-dev-override.properties +*.secret +*secret.txt + +# Docker +docker-compose.override.yml + +# Database +*.db +*.sqlite +*.h2.db + +# Test +test-output/ +.gradle/ +build/ + +# Temporary +.tmp/ +temp/ + +# Keycloak secrets (important!) +keycloak-secret.txt +client-secret.txt diff --git a/AUDIT_CONFIGURATION.md b/AUDIT_CONFIGURATION.md index beb4b5e..d832668 100644 --- a/AUDIT_CONFIGURATION.md +++ b/AUDIT_CONFIGURATION.md @@ -1,186 +1,186 @@ -# Audit de Configuration - BTP Xpress Client ↔ Serveur - -## ✅ Résumé de l'audit effectué - -Date : 2025-11-01 -Portée : Configuration complète du client PrimeFaces et mapping avec le serveur backend - ---- - -## 1. Structure du Projet Client - -### ✅ Structure des fichiers -- **XHTML** : `src/main/resources/META-INF/resources/` (structure Quarkus correcte) -- **Configuration** : `src/main/resources/META-INF/web.xml` et `application.properties` -- **Beans CDI** : `src/main/java/dev/lions/btpxpress/` -- **Services** : `src/main/java/dev/lions/btpxpress/service/` - -### ✅ Fichiers créés/vérifiés -- ✅ `BtpXpressApiClient.java` - Interface REST Client pour communication backend -- ✅ `ChantierService.java` - Service encapsulant les appels API chantiers -- ✅ `application.properties` - Configuration complète OIDC + REST Client -- ✅ `pom.xml` - Dépendances OIDC et JWT ajoutées - ---- - -## 2. Configuration OIDC / Keycloak - -### ✅ Client (PrimeFaces) -```properties -quarkus.oidc.enabled=true -quarkus.oidc.auth-server-url=https://security.lions.dev/realms/btpxpress -quarkus.oidc.client-id=btpxpress-frontend -quarkus.oidc.application-type=web-app -quarkus.oidc.token.issuer=https://security.lions.dev/realms/btpxpress -``` - -### ✅ Serveur (Backend) -```properties -mp.jwt.verify.publickey.location=https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs -mp.jwt.verify.issuer=https://security.lions.dev/realms/btpxpress -quarkus.smallrye-jwt.enabled=true -``` - -### ✅ Vérifications -- ✅ **Même realm** : `btpxpress` -- ✅ **Même serveur Keycloak** : `https://security.lions.dev` -- ✅ **Client ID frontend** : `btpxpress-frontend` (doit exister dans Keycloak) -- ✅ **JWT Validation** : Backend valide les tokens via certificats publics - ---- - -## 3. Communication Client ↔ Serveur - -### ✅ Configuration REST Client -```properties -btpxpress.api.base-url=http://localhost:8080 -quarkus.rest-client."dev.lions.btpxpress.service.BtpXpressApiClient".url=${btpxpress.api.base-url} -``` - -### ✅ Endpoints mappés - -| Client (Interface) | Serveur (Resource) | Endpoint | Status | -|-------------------|-------------------|----------|--------| -| `BtpXpressApiClient.getChantiers()` | `ChantierResource.getAllChantiers()` | `GET /api/v1/chantiers` | ✅ Existe | -| `BtpXpressApiClient.getChantier(id)` | `ChantierResource.getChantierById()` | `GET /api/v1/chantiers/{id}` | ✅ Existe | -| `BtpXpressApiClient.getClients()` | `ClientResource.getAllClients()` | `GET /api/v1/clients` | ✅ Existe | -| `BtpXpressApiClient.getClient(id)` | `ClientResource.getClientById()` | `GET /api/v1/clients/{id}` | ✅ Existe | - -### ✅ CORS Configuration - -**Serveur** (`application.properties`) : -```properties -quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:3000,http://localhost:5173,http://localhost:8081} -``` -✅ **Port 8081 ajouté** aux origines autorisées - -**Client** : -```properties -quarkus.http.cors.origins=http://localhost:8080,https://security.lions.dev -``` - ---- - -## 4. Ports et URLs - -| Service | Port | URL | Description | -|---------|------|-----|-------------| -| Backend | 8080 | http://localhost:8080 | API REST backend | -| Client | 8081 | http://localhost:8081 | Application PrimeFaces | -| Keycloak | - | https://security.lions.dev | Authentification OIDC | - ---- - -## 5. Dépendances Maven - -### ✅ Client (`pom.xml`) -- ✅ `quarkus-oidc` - Authentification OIDC -- ✅ `quarkus-smallrye-jwt` - Support JWT -- ✅ `quarkus-rest-client` - REST Client -- ✅ `quarkus-rest-jackson` - Sérialisation JSON -- ✅ `quarkus-primefaces` - PrimeFaces integration -- ✅ `freya-theme` - Thème PrimeFaces Freya - -### ✅ Serveur (vérifié) -- ✅ `quarkus-smallrye-jwt` - Validation JWT -- ✅ CORS activé avec origine `http://localhost:8081` - ---- - -## 6. Flux d'Authentification - -``` -┌─────────────┐ ┌──────────────┐ ┌─────────────┐ -│ Client │ │ Keycloak │ │ Backend │ -│ Port 8081 │ │security.lions│ │ Port 8080 │ -└─────────────┘ └──────────────┘ └─────────────┘ - │ │ │ - │ 1. Accès page protégée │ - │────────────────────────────────────────────────►│ - │ │ │ - │ │ 2. Redirection OIDC │ - │◄────────────────────────────────────────────────│ - │ │ │ - │ 3. Redirect Keycloak │ │ - │────────────────────────►│ │ - │ │ │ - │ 4. Authentification │ │ - │ │ │ - │ 5. Token JWT │ │ - │◄────────────────────────│ │ - │ │ │ - │ 6. Appel API + Token │ │ - │────────────────────────────────────────────────►│ - │ │ │ - │ │ 7. Validation token │ - │ │◄───────────────────────│ - │ │ │ - │ 8. Réponse API │ │ - │◄────────────────────────────────────────────────│ -``` - ---- - -## 7. Points de Vérification Requis - -### ⚠️ À vérifier dans Keycloak -1. **Client `btpxpress-frontend` existe** dans le realm `btpxpress` -2. **Redirect URIs** incluent `http://localhost:8081/*` -3. **Web Origins** incluent `http://localhost:8081` -4. **Client Secret** configuré si nécessaire (pour confidential client) - -### ⚠️ À tester -1. **Authentification OIDC** : Vérifier la redirection vers Keycloak -2. **Token JWT** : Vérifier l'envoi automatique dans les requêtes REST -3. **CORS** : Vérifier que les requêtes depuis 8081 vers 8080 fonctionnent -4. **Endpoints API** : Tester les appels `GET /api/v1/chantiers` et `/api/v1/clients` - ---- - -## 8. Configuration Complète Validée - -| Composant | Configuration | Status | -|-----------|--------------|--------| -| Structure fichiers | Quarkus standard | ✅ | -| OIDC Client | `btpxpress-frontend` | ✅ | -| OIDC Server | `security.lions.dev` | ✅ | -| REST Client | `BtpXpressApiClient` | ✅ | -| Services | `ChantierService` | ✅ | -| CORS Backend | Port 8081 autorisé | ✅ | -| CORS Client | Port 8080 autorisé | ✅ | -| Endpoints mappés | Tous vérifiés | ✅ | -| Dépendances | Toutes présentes | ✅ | - ---- - -## 🎯 Conclusion - -**✅ La configuration est complète et correcte** : -- Le client PrimeFaces est correctement configuré pour communiquer avec le backend -- L'authentification OIDC est configurée avec Keycloak sur `security.lions.dev` -- Les endpoints REST sont mappés correctement -- Le CORS est configuré pour autoriser la communication bidirectionnelle -- Toutes les dépendances nécessaires sont présentes - -**⚠️ Action requise** : Vérifier dans Keycloak que le client `btpxpress-frontend` existe et est correctement configuré avec les redirect URIs appropriés. - +# Audit de Configuration - BTP Xpress Client ↔ Serveur + +## ✅ Résumé de l'audit effectué + +Date : 2025-11-01 +Portée : Configuration complète du client PrimeFaces et mapping avec le serveur backend + +--- + +## 1. Structure du Projet Client + +### ✅ Structure des fichiers +- **XHTML** : `src/main/resources/META-INF/resources/` (structure Quarkus correcte) +- **Configuration** : `src/main/resources/META-INF/web.xml` et `application.properties` +- **Beans CDI** : `src/main/java/dev/lions/btpxpress/` +- **Services** : `src/main/java/dev/lions/btpxpress/service/` + +### ✅ Fichiers créés/vérifiés +- ✅ `BtpXpressApiClient.java` - Interface REST Client pour communication backend +- ✅ `ChantierService.java` - Service encapsulant les appels API chantiers +- ✅ `application.properties` - Configuration complète OIDC + REST Client +- ✅ `pom.xml` - Dépendances OIDC et JWT ajoutées + +--- + +## 2. Configuration OIDC / Keycloak + +### ✅ Client (PrimeFaces) +```properties +quarkus.oidc.enabled=true +quarkus.oidc.auth-server-url=https://security.lions.dev/realms/btpxpress +quarkus.oidc.client-id=btpxpress-frontend +quarkus.oidc.application-type=web-app +quarkus.oidc.token.issuer=https://security.lions.dev/realms/btpxpress +``` + +### ✅ Serveur (Backend) +```properties +mp.jwt.verify.publickey.location=https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs +mp.jwt.verify.issuer=https://security.lions.dev/realms/btpxpress +quarkus.smallrye-jwt.enabled=true +``` + +### ✅ Vérifications +- ✅ **Même realm** : `btpxpress` +- ✅ **Même serveur Keycloak** : `https://security.lions.dev` +- ✅ **Client ID frontend** : `btpxpress-frontend` (doit exister dans Keycloak) +- ✅ **JWT Validation** : Backend valide les tokens via certificats publics + +--- + +## 3. Communication Client ↔ Serveur + +### ✅ Configuration REST Client +```properties +btpxpress.api.base-url=http://localhost:8080 +quarkus.rest-client."dev.lions.btpxpress.service.BtpXpressApiClient".url=${btpxpress.api.base-url} +``` + +### ✅ Endpoints mappés + +| Client (Interface) | Serveur (Resource) | Endpoint | Status | +|-------------------|-------------------|----------|--------| +| `BtpXpressApiClient.getChantiers()` | `ChantierResource.getAllChantiers()` | `GET /api/v1/chantiers` | ✅ Existe | +| `BtpXpressApiClient.getChantier(id)` | `ChantierResource.getChantierById()` | `GET /api/v1/chantiers/{id}` | ✅ Existe | +| `BtpXpressApiClient.getClients()` | `ClientResource.getAllClients()` | `GET /api/v1/clients` | ✅ Existe | +| `BtpXpressApiClient.getClient(id)` | `ClientResource.getClientById()` | `GET /api/v1/clients/{id}` | ✅ Existe | + +### ✅ CORS Configuration + +**Serveur** (`application.properties`) : +```properties +quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:3000,http://localhost:5173,http://localhost:8081} +``` +✅ **Port 8081 ajouté** aux origines autorisées + +**Client** : +```properties +quarkus.http.cors.origins=http://localhost:8080,https://security.lions.dev +``` + +--- + +## 4. Ports et URLs + +| Service | Port | URL | Description | +|---------|------|-----|-------------| +| Backend | 8080 | http://localhost:8080 | API REST backend | +| Client | 8081 | http://localhost:8081 | Application PrimeFaces | +| Keycloak | - | https://security.lions.dev | Authentification OIDC | + +--- + +## 5. Dépendances Maven + +### ✅ Client (`pom.xml`) +- ✅ `quarkus-oidc` - Authentification OIDC +- ✅ `quarkus-smallrye-jwt` - Support JWT +- ✅ `quarkus-rest-client` - REST Client +- ✅ `quarkus-rest-jackson` - Sérialisation JSON +- ✅ `quarkus-primefaces` - PrimeFaces integration +- ✅ `freya-theme` - Thème PrimeFaces Freya + +### ✅ Serveur (vérifié) +- ✅ `quarkus-smallrye-jwt` - Validation JWT +- ✅ CORS activé avec origine `http://localhost:8081` + +--- + +## 6. Flux d'Authentification + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Client │ │ Keycloak │ │ Backend │ +│ Port 8081 │ │security.lions│ │ Port 8080 │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ 1. Accès page protégée │ + │────────────────────────────────────────────────►│ + │ │ │ + │ │ 2. Redirection OIDC │ + │◄────────────────────────────────────────────────│ + │ │ │ + │ 3. Redirect Keycloak │ │ + │────────────────────────►│ │ + │ │ │ + │ 4. Authentification │ │ + │ │ │ + │ 5. Token JWT │ │ + │◄────────────────────────│ │ + │ │ │ + │ 6. Appel API + Token │ │ + │────────────────────────────────────────────────►│ + │ │ │ + │ │ 7. Validation token │ + │ │◄───────────────────────│ + │ │ │ + │ 8. Réponse API │ │ + │◄────────────────────────────────────────────────│ +``` + +--- + +## 7. Points de Vérification Requis + +### ⚠️ À vérifier dans Keycloak +1. **Client `btpxpress-frontend` existe** dans le realm `btpxpress` +2. **Redirect URIs** incluent `http://localhost:8081/*` +3. **Web Origins** incluent `http://localhost:8081` +4. **Client Secret** configuré si nécessaire (pour confidential client) + +### ⚠️ À tester +1. **Authentification OIDC** : Vérifier la redirection vers Keycloak +2. **Token JWT** : Vérifier l'envoi automatique dans les requêtes REST +3. **CORS** : Vérifier que les requêtes depuis 8081 vers 8080 fonctionnent +4. **Endpoints API** : Tester les appels `GET /api/v1/chantiers` et `/api/v1/clients` + +--- + +## 8. Configuration Complète Validée + +| Composant | Configuration | Status | +|-----------|--------------|--------| +| Structure fichiers | Quarkus standard | ✅ | +| OIDC Client | `btpxpress-frontend` | ✅ | +| OIDC Server | `security.lions.dev` | ✅ | +| REST Client | `BtpXpressApiClient` | ✅ | +| Services | `ChantierService` | ✅ | +| CORS Backend | Port 8081 autorisé | ✅ | +| CORS Client | Port 8080 autorisé | ✅ | +| Endpoints mappés | Tous vérifiés | ✅ | +| Dépendances | Toutes présentes | ✅ | + +--- + +## 🎯 Conclusion + +**✅ La configuration est complète et correcte** : +- Le client PrimeFaces est correctement configuré pour communiquer avec le backend +- L'authentification OIDC est configurée avec Keycloak sur `security.lions.dev` +- Les endpoints REST sont mappés correctement +- Le CORS est configuré pour autoriser la communication bidirectionnelle +- Toutes les dépendances nécessaires sont présentes + +**⚠️ Action requise** : Vérifier dans Keycloak que le client `btpxpress-frontend` existe et est correctement configuré avec les redirect URIs appropriés. + diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 1018b1a..692386c 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -1,75 +1,75 @@ -# Configuration BTP Xpress Client - PrimeFaces Freya - -## ✅ Vérifications effectuées - -### 1. Structure du projet -- ✅ Fichiers XHTML dans `src/main/resources/META-INF/resources/` -- ✅ Configuration dans `src/main/resources/META-INF/web.xml` -- ✅ Beans CDI dans `src/main/java/dev/lions/btpxpress/` - -### 2. Configuration OIDC / Keycloak -- ✅ **Serveur Keycloak** : `https://security.lions.dev/realms/btpxpress` -- ✅ **Client ID** : `btpxpress-frontend` -- ✅ **Type d'application** : `web-app` -- ✅ **Redirection** : `/` (restauration du chemin après authentification) -- ✅ **Cookies** : Configurés pour la session -- ✅ **TLS** : `required` (production) - -### 3. Communication avec le backend -- ✅ **URL Backend** : `http://localhost:8080` -- ✅ **Endpoints API** : `/api/v1/*` -- ✅ **REST Client** : `BtpXpressApiClient` configuré -- ✅ **Service** : `ChantierService` créé pour encapsuler les appels API -- ✅ **CORS Backend** : `http://localhost:8081` ajouté aux origines autorisées - -### 4. Configuration serveur backend -- ✅ **Port** : `8080` -- ✅ **CORS Origins** : `http://localhost:3000,http://localhost:5173,http://localhost:8081` -- ✅ **JWT Validation** : `https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs` -- ✅ **Issuer** : `https://security.lions.dev/realms/btpxpress` - -## 📋 Mapping Client ↔ Serveur - -| Client (PrimeFaces) | Serveur (Quarkus) | Description | -|---------------------|-------------------|-------------| -| `http://localhost:8081` | `http://localhost:8080` | Communication HTTP | -| `BtpXpressApiClient` | `@Path("/api/v1/*")` | Interface REST Client | -| OIDC Client `btpxpress-frontend` | JWT Validation | Authentification | -| `ChantierService` | `ChantierResource` | Service métier chantiers | - -## 🔐 Authentification - -1. **Client accède à une page protégée** → Redirection vers Keycloak -2. **Keycloak (security.lions.dev)** → Authentification utilisateur -3. **Keycloak retourne le token** → Stocké dans la session du client -4. **Client fait appel API** → Token JWT envoyé dans header `Authorization` -5. **Backend valide le token** → Via les certificats Keycloak publics - -## 🚀 Démarrage - -1. **Backend** : - ```bash - cd btpxpress-server - mvn quarkus:dev - ``` - → Accessible sur http://localhost:8080 - -2. **Client** : - ```bash - cd btpxpress-client - mvn quarkus:dev - ``` - → Accessible sur http://localhost:8081 - -3. **Accès** : - - Page d'accueil : http://localhost:8081/ - - Dashboard : http://localhost:8081/dashboard.xhtml - - Login : http://localhost:8081/login.xhtml - -## ⚠️ Points d'attention - -- Le client doit être configuré avec le **même realm Keycloak** que le serveur (`btpxpress`) -- Le client ID `btpxpress-frontend` doit exister dans Keycloak -- Les tokens JWT doivent être envoyés automatiquement via le REST Client -- Le backend doit accepter les requêtes CORS depuis `http://localhost:8081` - +# Configuration BTP Xpress Client - PrimeFaces Freya + +## ✅ Vérifications effectuées + +### 1. Structure du projet +- ✅ Fichiers XHTML dans `src/main/resources/META-INF/resources/` +- ✅ Configuration dans `src/main/resources/META-INF/web.xml` +- ✅ Beans CDI dans `src/main/java/dev/lions/btpxpress/` + +### 2. Configuration OIDC / Keycloak +- ✅ **Serveur Keycloak** : `https://security.lions.dev/realms/btpxpress` +- ✅ **Client ID** : `btpxpress-frontend` +- ✅ **Type d'application** : `web-app` +- ✅ **Redirection** : `/` (restauration du chemin après authentification) +- ✅ **Cookies** : Configurés pour la session +- ✅ **TLS** : `required` (production) + +### 3. Communication avec le backend +- ✅ **URL Backend** : `http://localhost:8080` +- ✅ **Endpoints API** : `/api/v1/*` +- ✅ **REST Client** : `BtpXpressApiClient` configuré +- ✅ **Service** : `ChantierService` créé pour encapsuler les appels API +- ✅ **CORS Backend** : `http://localhost:8081` ajouté aux origines autorisées + +### 4. Configuration serveur backend +- ✅ **Port** : `8080` +- ✅ **CORS Origins** : `http://localhost:3000,http://localhost:5173,http://localhost:8081` +- ✅ **JWT Validation** : `https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs` +- ✅ **Issuer** : `https://security.lions.dev/realms/btpxpress` + +## 📋 Mapping Client ↔ Serveur + +| Client (PrimeFaces) | Serveur (Quarkus) | Description | +|---------------------|-------------------|-------------| +| `http://localhost:8081` | `http://localhost:8080` | Communication HTTP | +| `BtpXpressApiClient` | `@Path("/api/v1/*")` | Interface REST Client | +| OIDC Client `btpxpress-frontend` | JWT Validation | Authentification | +| `ChantierService` | `ChantierResource` | Service métier chantiers | + +## 🔐 Authentification + +1. **Client accède à une page protégée** → Redirection vers Keycloak +2. **Keycloak (security.lions.dev)** → Authentification utilisateur +3. **Keycloak retourne le token** → Stocké dans la session du client +4. **Client fait appel API** → Token JWT envoyé dans header `Authorization` +5. **Backend valide le token** → Via les certificats Keycloak publics + +## 🚀 Démarrage + +1. **Backend** : + ```bash + cd btpxpress-server + mvn quarkus:dev + ``` + → Accessible sur http://localhost:8080 + +2. **Client** : + ```bash + cd btpxpress-client + mvn quarkus:dev + ``` + → Accessible sur http://localhost:8081 + +3. **Accès** : + - Page d'accueil : http://localhost:8081/ + - Dashboard : http://localhost:8081/dashboard.xhtml + - Login : http://localhost:8081/login.xhtml + +## ⚠️ Points d'attention + +- Le client doit être configuré avec le **même realm Keycloak** que le serveur (`btpxpress`) +- Le client ID `btpxpress-frontend` doit exister dans Keycloak +- Les tokens JWT doivent être envoyés automatiquement via le REST Client +- Le backend doit accepter les requêtes CORS depuis `http://localhost:8081` + diff --git a/Dockerfile b/Dockerfile index 1ce271b..ef83430 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,47 +1,47 @@ -#### -# Dockerfile pour BTP Xpress Client (Frontend) - Développement -# Utilisé pour les builds de développement local -#### - -## Stage 1 : Build avec Maven -FROM maven:3.9.6-eclipse-temurin-17 AS build -WORKDIR /build - -# Copier pom.xml et télécharger les dépendances -COPY pom.xml . -RUN mvn dependency:go-offline -B - -# Copier le code source -COPY src ./src - -# Build de l'application (uber-jar pour compatibilité lionsctl) -RUN mvn clean package -DskipTests -B -Dquarkus.package.type=uber-jar - -## Stage 2 : Runtime image -FROM eclipse-temurin:17-jre-alpine - -ENV LANGUAGE='fr_FR:fr' - -# Installer curl pour les health checks -RUN apk add --no-cache curl - -# Créer un utilisateur non-root pour la sécurité -RUN addgroup -g 185 -S appuser && adduser -u 185 -S appuser -G appuser -RUN mkdir -p /deployments && chown -R appuser:appuser /deployments - -# Copier le JAR depuis le build (lionsctl utilise uber-jar) -# Note: Le fichier sera btpxpress-client-1.0.0-runner.jar -COPY --from=build --chown=appuser:appuser /build/target/*-runner.jar /deployments/app.jar - -EXPOSE 8080 -USER appuser - -# Variables d'environnement JVM optimisées -ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -XX:+UseG1GC -XX:MaxRAMPercentage=75.0 -XX:+UseStringDeduplication" - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:8080/q/health/ready || exit 1 - -ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/app.jar"] - +#### +# Dockerfile pour BTP Xpress Client (Frontend) - Développement +# Utilisé pour les builds de développement local +#### + +## Stage 1 : Build avec Maven +FROM maven:3.9.6-eclipse-temurin-17 AS build +WORKDIR /build + +# Copier pom.xml et télécharger les dépendances +COPY pom.xml . +RUN mvn dependency:go-offline -B + +# Copier le code source +COPY src ./src + +# Build de l'application (uber-jar pour compatibilité lionsctl) +RUN mvn clean package -DskipTests -B -Dquarkus.package.type=uber-jar + +## Stage 2 : Runtime image +FROM eclipse-temurin:17-jre-alpine + +ENV LANGUAGE='fr_FR:fr' + +# Installer curl pour les health checks +RUN apk add --no-cache curl + +# Créer un utilisateur non-root pour la sécurité +RUN addgroup -g 185 -S appuser && adduser -u 185 -S appuser -G appuser +RUN mkdir -p /deployments && chown -R appuser:appuser /deployments + +# Copier le JAR depuis le build (lionsctl utilise uber-jar) +# Note: Le fichier sera btpxpress-client-1.0.0-runner.jar +COPY --from=build --chown=appuser:appuser /build/target/*-runner.jar /deployments/app.jar + +EXPOSE 8080 +USER appuser + +# Variables d'environnement JVM optimisées +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -XX:+UseG1GC -XX:MaxRAMPercentage=75.0 -XX:+UseStringDeduplication" + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/q/health/ready || exit 1 + +ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/app.jar"] + diff --git a/Dockerfile.prod b/Dockerfile.prod index 7f62762..9641f90 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,84 +1,84 @@ -#### -# Dockerfile de production pour BTP Xpress Client (Frontend) -# Multi-stage build optimisé avec sécurité renforcée -#### - -## Stage 1 : Build avec Maven -FROM maven:3.9.6-eclipse-temurin-17 AS builder - -WORKDIR /app - -# Copier pom.xml et télécharger les dépendances (cache Docker) -COPY pom.xml . -RUN mvn dependency:go-offline -B - -# Copier le code source -COPY src ./src - -# Build de l'application avec profil production (fast-jar par défaut) -RUN mvn clean package -DskipTests -B - -## Stage 2 : Image de production optimisée et sécurisée -FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 - -ENV LANGUAGE='fr_FR:fr' - -# Variables d'environnement de production -# Ces valeurs peuvent être surchargées via docker-compose ou Kubernetes -ENV QUARKUS_PROFILE=prod -ENV QUARKUS_HTTP_PORT=8080 -ENV QUARKUS_HTTP_HOST=0.0.0.0 - -# Configuration Keycloak/OIDC (production) -ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/btpxpress -ENV QUARKUS_OIDC_CLIENT_ID=btpxpress-frontend -ENV QUARKUS_OIDC_ENABLED=true -ENV QUARKUS_OIDC_TLS_VERIFICATION=required - -# Configuration API Backend -ENV BTPXPRESS_API_BASE_URL=https://api.btpxpress.lions.dev - -# Configuration CORS -ENV QUARKUS_HTTP_CORS_ORIGINS=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev -ENV QUARKUS_HTTP_CORS_ALLOW_CREDENTIALS=true - -# Installer curl pour les health checks -USER root -RUN microdnf install -y curl && \ - microdnf clean all && \ - rm -rf /var/cache/yum - -# Créer les répertoires et permissions pour utilisateur non-root -RUN mkdir -p /deployments /app/logs && \ - chown -R 185:185 /deployments /app/logs - -# Passer à l'utilisateur non-root pour la sécurité -USER 185 - -# Copier l'application depuis le builder (format fast-jar Quarkus) -COPY --from=builder --chown=185 /app/target/quarkus-app/ /deployments/ - -# Exposer le port -EXPOSE 8080 - -# Variables JVM optimisées pour production avec sécurité -ENV JAVA_OPTS="-Xmx768m -Xms256m \ - -XX:+UseG1GC \ - -XX:MaxGCPauseMillis=200 \ - -XX:+UseStringDeduplication \ - -XX:+ParallelRefProcEnabled \ - -XX:+HeapDumpOnOutOfMemoryError \ - -XX:HeapDumpPath=/app/logs/heapdump.hprof \ - -Djava.security.egd=file:/dev/./urandom \ - -Djava.awt.headless=true \ - -Dfile.encoding=UTF-8 \ - -Djava.util.logging.manager=org.jboss.logmanager.LogManager \ - -Dquarkus.profile=${QUARKUS_PROFILE}" - -# Health check avec endpoints Quarkus -HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \ - CMD curl -f http://localhost:8080/q/health/ready || exit 1 - -# Point d'entrée avec profil production (format fast-jar) -ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"] - +#### +# Dockerfile de production pour BTP Xpress Client (Frontend) +# Multi-stage build optimisé avec sécurité renforcée +#### + +## Stage 1 : Build avec Maven +FROM maven:3.9.6-eclipse-temurin-17 AS builder + +WORKDIR /app + +# Copier pom.xml et télécharger les dépendances (cache Docker) +COPY pom.xml . +RUN mvn dependency:go-offline -B + +# Copier le code source +COPY src ./src + +# Build de l'application avec profil production (fast-jar par défaut) +RUN mvn clean package -DskipTests -B + +## Stage 2 : Image de production optimisée et sécurisée +FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 + +ENV LANGUAGE='fr_FR:fr' + +# Variables d'environnement de production +# Ces valeurs peuvent être surchargées via docker-compose ou Kubernetes +ENV QUARKUS_PROFILE=prod +ENV QUARKUS_HTTP_PORT=8080 +ENV QUARKUS_HTTP_HOST=0.0.0.0 + +# Configuration Keycloak/OIDC (production) +ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/btpxpress +ENV QUARKUS_OIDC_CLIENT_ID=btpxpress-frontend +ENV QUARKUS_OIDC_ENABLED=true +ENV QUARKUS_OIDC_TLS_VERIFICATION=required + +# Configuration API Backend +ENV BTPXPRESS_API_BASE_URL=https://api.btpxpress.lions.dev + +# Configuration CORS +ENV QUARKUS_HTTP_CORS_ORIGINS=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev +ENV QUARKUS_HTTP_CORS_ALLOW_CREDENTIALS=true + +# Installer curl pour les health checks +USER root +RUN microdnf install -y curl && \ + microdnf clean all && \ + rm -rf /var/cache/yum + +# Créer les répertoires et permissions pour utilisateur non-root +RUN mkdir -p /deployments /app/logs && \ + chown -R 185:185 /deployments /app/logs + +# Passer à l'utilisateur non-root pour la sécurité +USER 185 + +# Copier l'application depuis le builder (format fast-jar Quarkus) +COPY --from=builder --chown=185 /app/target/quarkus-app/ /deployments/ + +# Exposer le port +EXPOSE 8080 + +# Variables JVM optimisées pour production avec sécurité +ENV JAVA_OPTS="-Xmx768m -Xms256m \ + -XX:+UseG1GC \ + -XX:MaxGCPauseMillis=200 \ + -XX:+UseStringDeduplication \ + -XX:+ParallelRefProcEnabled \ + -XX:+HeapDumpOnOutOfMemoryError \ + -XX:HeapDumpPath=/app/logs/heapdump.hprof \ + -Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -Djava.util.logging.manager=org.jboss.logmanager.LogManager \ + -Dquarkus.profile=${QUARKUS_PROFILE}" + +# Health check avec endpoints Quarkus +HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \ + CMD curl -f http://localhost:8080/q/health/ready || exit 1 + +# Point d'entrée avec profil production (format fast-jar) +ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"] + diff --git a/EXECUTIVE_SUMMARY_OPTIMIZATIONS.md b/EXECUTIVE_SUMMARY_OPTIMIZATIONS.md new file mode 100644 index 0000000..6d0de30 --- /dev/null +++ b/EXECUTIVE_SUMMARY_OPTIMIZATIONS.md @@ -0,0 +1,263 @@ +# 📊 Résumé Exécutif - Optimisations PrimeFaces BTPXpress + +## 🎯 Objectif +Optimiser l'application BTPXpress en appliquant les meilleures pratiques du repository PrimeFaces officiel pour améliorer les performances, l'expérience utilisateur et la maintenabilité. + +--- + +## 📈 Gains Attendus + +### Performance +| Métrique | Avant | Après | Gain | +|----------|-------|-------|------| +| **Temps de chargement liste** | 2-3s | <500ms | **80-85%** ⬇️ | +| **Données transférées** | 100+ factures | 10-50 factures | **50-90%** ⬇️ | +| **Mémoire utilisée** | Toutes les données | Données paginées | **70-80%** ⬇️ | +| **Temps de réponse Ajax** | 500-1000ms | 100-200ms | **70-80%** ⬇️ | + +### Expérience Utilisateur +- ✅ **Chargement instantané** : Pagination côté serveur +- ✅ **Filtrage en temps réel** : Résultats en <500ms +- ✅ **Validation immédiate** : Feedback client-side +- ✅ **Interface cohérente** : Composants Freya standardisés +- ✅ **Notifications claires** : Messages growl informatifs + +### Maintenabilité +- ✅ **Code réutilisable** : Composants composites métier +- ✅ **Moins de duplication** : Patterns standardisés +- ✅ **Debugging facilité** : Logs structurés +- ✅ **Tests simplifiés** : Architecture modulaire + +--- + +## 🔧 Optimisations Principales + +### 1. Lazy Loading avec LazyDataModel +**Impact** : 🔥🔥🔥 Critique + +**Problème actuel** : +```java +// ❌ Charge TOUTES les factures en mémoire +List> facturesData = factureService.getAllFactures(); +``` + +**Solution** : +```java +// ✅ Charge seulement 10-50 factures par page +LazyDataModel lazyModel = new FactureLazyDataModel(factureService); +``` + +**Bénéfices** : +- Réduction de 80% du temps de chargement +- Réduction de 90% de la mémoire utilisée +- Scalabilité pour 10,000+ factures + +--- + +### 2. Ajax Ciblé (process + update) +**Impact** : 🔥🔥 Important + +**Problème actuel** : +```xml + + +``` + +**Solution** : +```xml + + +``` + +**Bénéfices** : +- Réduction de 70% du temps de re-rendering +- Moins de bande passante utilisée +- Interface plus réactive + +--- + +### 3. Composants Réutilisables +**Impact** : 🔥🔥 Important + +**Avant** : Code dupliqué dans chaque page +```xml + + +``` + +**Après** : Composant réutilisable +```xml + + +``` + +**Bénéfices** : +- Réduction de 60% du code XHTML +- Cohérence visuelle garantie +- Maintenance centralisée + +--- + +### 4. Cache Intelligent +**Impact** : 🔥 Modéré + +**Solution** : +```java +@ApplicationScoped +public class ReferenceDataService { + @CacheResult(cacheName = "statuts-facture") + public List getStatutsFacture() { + // Appelé une seule fois, puis mis en cache + } +} +``` + +**Bénéfices** : +- Réduction de 95% des appels API pour données statiques +- Temps de réponse instantané +- Moins de charge sur le backend + +--- + +### 5. Validation Côté Client +**Impact** : 🔥 Modéré + +**Configuration** : +```properties +primefaces.CLIENT_SIDE_VALIDATION=true +primefaces.CSV_ENABLED=true +``` + +**Bénéfices** : +- Feedback immédiat pour l'utilisateur +- Réduction de 50% des appels serveur invalides +- Meilleure UX + +--- + +## 📅 Plan de Déploiement (5 Semaines) + +### Semaine 1 : Lazy Loading +- [ ] Implémenter FactureLazyDataModel +- [ ] Modifier FactureView +- [ ] Ajouter endpoints backend +- [ ] Tests et validation +- **Livrable** : Liste factures optimisée + +### Semaine 2 : Ajax Optimisé +- [ ] Auditer tous les commandButton +- [ ] Ajouter process/update ciblés +- [ ] Implémenter p:ajax pour filtres +- **Livrable** : Interface plus réactive + +### Semaine 3 : Composants Réutilisables +- [ ] Créer composants métier +- [ ] Migrer vers fr:dataTable +- [ ] Standardiser les patterns +- **Livrable** : Code plus maintenable + +### Semaine 4 : Validation & UX +- [ ] Activer validation client +- [ ] Implémenter growl +- [ ] Ajouter confirmations +- **Livrable** : Meilleure UX + +### Semaine 5 : Cache & Performance +- [ ] Implémenter cache +- [ ] Profiler et optimiser +- [ ] Tests de charge +- **Livrable** : Application optimisée + +--- + +## 💰 ROI Estimé + +### Coûts +- **Développement** : 5 semaines × 1 développeur = 5 semaines-homme +- **Tests** : 1 semaine +- **Total** : ~6 semaines-homme + +### Gains +- **Performance** : 80% d'amélioration → Meilleure satisfaction utilisateur +- **Scalabilité** : Support de 10x plus de données +- **Maintenance** : 60% moins de code → 40% de temps de maintenance en moins +- **Bugs** : 50% moins de bugs liés aux performances + +### ROI +- **Court terme** (3 mois) : Satisfaction utilisateur +30% +- **Moyen terme** (6 mois) : Temps de maintenance -40% +- **Long terme** (1 an) : Coûts d'infrastructure -20% (moins de ressources serveur) + +--- + +## 🎓 Apprentissages Clés + +### Meilleures Pratiques PrimeFaces +1. **Toujours utiliser LazyDataModel** pour les listes >50 items +2. **Spécifier process et update** de manière ciblée +3. **Créer des composants réutilisables** pour les patterns récurrents +4. **Valider côté client ET serveur** +5. **Utiliser le cache** pour les données statiques + +### Patterns à Éviter +1. ❌ `update="@form"` ou `update="@all"` +2. ❌ Charger toutes les données en mémoire +3. ❌ Dupliquer le code de composants +4. ❌ Ignorer la validation côté client +5. ❌ Recharger les données de référence + +--- + +## 📚 Ressources + +### Documentation +- [Guide complet d'optimisation](./PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md) +- [Exemple d'implémentation Lazy Loading](./IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md) +- [PrimeFaces Showcase](https://www.primefaces.org/showcase/) + +### Support +- **PrimeFaces GitHub** : https://github.com/primefaces/primefaces +- **Documentation officielle** : https://www.primefaces.org/docs/ +- **Community Forum** : https://github.com/primefaces/primefaces/discussions + +--- + +## ✅ Critères de Succès + +### Techniques +- [ ] Temps de chargement <500ms pour toutes les listes +- [ ] Score Lighthouse >90 +- [ ] Couverture de tests >80% +- [ ] Zéro erreur console JavaScript + +### Fonctionnels +- [ ] Pagination fluide sur toutes les listes +- [ ] Filtrage en temps réel <500ms +- [ ] Validation immédiate sur tous les formulaires +- [ ] Messages clairs pour toutes les actions + +### Qualité +- [ ] Code review approuvé +- [ ] Documentation à jour +- [ ] Tests utilisateurs positifs +- [ ] Zéro régression fonctionnelle + +--- + +## 🚀 Prochaines Actions + +1. **Valider le plan** avec l'équipe technique +2. **Prioriser les modules** à optimiser en premier +3. **Commencer par Phase 1** : Lazy Loading Factures +4. **Mesurer les performances** avant/après +5. **Itérer** sur les autres modules + +--- + +**Date** : 2025-12-29 +**Auteur** : Équipe BTPXpress +**Version** : 1.0 +**Statut** : Proposition diff --git a/FIX_431_ERROR.md b/FIX_431_ERROR.md index edc0a21..f2cabf7 100644 --- a/FIX_431_ERROR.md +++ b/FIX_431_ERROR.md @@ -1,75 +1,75 @@ -# Solution pour l'erreur HTTP 431 "Request Header Fields Too Large" - -## Problème -L'erreur 431 se produit lorsque les en-têtes HTTP (notamment les cookies contenant les tokens OIDC/JWT) dépassent la taille maximale autorisée. - -## Solutions appliquées - -### 1. Configuration Quarkus HTTP -```properties -quarkus.http.max-headers-size=64K -quarkus.vertx.max-headers-size=64K -``` - -### 2. Optimisation OIDC Token Management -```properties -quarkus.oidc.token-state-manager.split-tokens=true -quarkus.oidc.token-state-manager.strategy=id-refresh-tokens -quarkus.oidc.token-state-manager.encryption-required=false -quarkus.oidc.token-state-manager.cookie-max-size=8192 -``` - -Ces configurations : -- **split-tokens** : Divise les tokens en plusieurs cookies pour éviter qu'un seul cookie soit trop volumineux -- **id-refresh-tokens** : Utilise une stratégie optimisée avec refresh tokens -- **encryption-required=false** : Désactive l'encryption pour réduire la taille (développement uniquement) -- **cookie-max-size=8192** : Limite la taille d'un cookie individuel à 8KB - -## Actions à effectuer - -### ⚠️ IMPORTANT : Supprimer les cookies du navigateur - -Les cookies existants peuvent être trop volumineux. Vous devez : - -1. **Ouvrir les outils développeur** (F12) -2. **Onglet Application > Cookies** -3. **Supprimer tous les cookies** pour `http://localhost:8081` -4. **Redémarrer l'application Quarkus** -5. **Recharger la page** - -Ou via la console du navigateur : -```javascript -// Supprimer tous les cookies pour localhost:8081 -document.cookie.split(";").forEach(c => { - document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); -}); -``` - -### Redémarrer l'application - -Après modification de `application.properties`, vous **devez redémarrer** l'application Quarkus : -```bash -# Arrêter l'application (Ctrl+C) -# Puis relancer -mvn quarkus:dev -``` - -## Vérification - -Une fois les cookies supprimés et l'application redémarrée : -1. Accédez à http://localhost:8081/dashboard.xhtml -2. Vous serez redirigé vers Keycloak pour l'authentification -3. Après authentification, les nouveaux cookies (optimisés) seront créés - -## Si le problème persiste - -1. **Augmenter encore la limite** : - ```properties - quarkus.http.max-headers-size=128K - quarkus.vertx.max-headers-size=128K - ``` - -2. **Vérifier dans Keycloak** que le client `btpxpress-frontend` n'a pas trop de claims/roles qui gonflent le token - -3. **Mode navigation privée** : Tester dans une fenêtre de navigation privée pour éviter les cookies existants - +# Solution pour l'erreur HTTP 431 "Request Header Fields Too Large" + +## Problème +L'erreur 431 se produit lorsque les en-têtes HTTP (notamment les cookies contenant les tokens OIDC/JWT) dépassent la taille maximale autorisée. + +## Solutions appliquées + +### 1. Configuration Quarkus HTTP +```properties +quarkus.http.max-headers-size=64K +quarkus.vertx.max-headers-size=64K +``` + +### 2. Optimisation OIDC Token Management +```properties +quarkus.oidc.token-state-manager.split-tokens=true +quarkus.oidc.token-state-manager.strategy=id-refresh-tokens +quarkus.oidc.token-state-manager.encryption-required=false +quarkus.oidc.token-state-manager.cookie-max-size=8192 +``` + +Ces configurations : +- **split-tokens** : Divise les tokens en plusieurs cookies pour éviter qu'un seul cookie soit trop volumineux +- **id-refresh-tokens** : Utilise une stratégie optimisée avec refresh tokens +- **encryption-required=false** : Désactive l'encryption pour réduire la taille (développement uniquement) +- **cookie-max-size=8192** : Limite la taille d'un cookie individuel à 8KB + +## Actions à effectuer + +### ⚠️ IMPORTANT : Supprimer les cookies du navigateur + +Les cookies existants peuvent être trop volumineux. Vous devez : + +1. **Ouvrir les outils développeur** (F12) +2. **Onglet Application > Cookies** +3. **Supprimer tous les cookies** pour `http://localhost:8081` +4. **Redémarrer l'application Quarkus** +5. **Recharger la page** + +Ou via la console du navigateur : +```javascript +// Supprimer tous les cookies pour localhost:8081 +document.cookie.split(";").forEach(c => { + document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); +}); +``` + +### Redémarrer l'application + +Après modification de `application.properties`, vous **devez redémarrer** l'application Quarkus : +```bash +# Arrêter l'application (Ctrl+C) +# Puis relancer +mvn quarkus:dev +``` + +## Vérification + +Une fois les cookies supprimés et l'application redémarrée : +1. Accédez à http://localhost:8081/dashboard.xhtml +2. Vous serez redirigé vers Keycloak pour l'authentification +3. Après authentification, les nouveaux cookies (optimisés) seront créés + +## Si le problème persiste + +1. **Augmenter encore la limite** : + ```properties + quarkus.http.max-headers-size=128K + quarkus.vertx.max-headers-size=128K + ``` + +2. **Vérifier dans Keycloak** que le client `btpxpress-frontend` n'a pas trop de claims/roles qui gonflent le token + +3. **Mode navigation privée** : Tester dans une fenêtre de navigation privée pour éviter les cookies existants + diff --git a/FOOTER_CONFIGURATION.md b/FOOTER_CONFIGURATION.md new file mode 100644 index 0000000..ace4599 --- /dev/null +++ b/FOOTER_CONFIGURATION.md @@ -0,0 +1,75 @@ +# 🔧 Configuration du Footer - BTPXpress + +## ✅ Problème résolu + +Le Footer était affiché sur **toutes les pages** de l'application, ce qui n'est pas logique pour une application métier BTP. + +## 🔧 Solution implémentée + +Le Footer est maintenant **conditionnel** et **désactivé par défaut** dans le template principal. + +### Modification apportée + +**Fichier** : `src/main/resources/META-INF/resources/WEB-INF/template.xhtml` + +**Avant** : +```xhtml + +``` + +**Après** : +```xhtml + + + + + +``` + +## 📋 Comportement + +- ✅ **Par défaut** : Le Footer n'est **PAS affiché** sur aucune page +- ✅ **Sur demande** : Pour afficher le Footer sur une page spécifique, ajouter : + +```xhtml + + + + + + + +``` + +## 🎯 Pages concernées + +Le Footer n'est plus affiché sur : +- ✅ Toutes les pages de gestion (chantiers, clients, devis, factures, etc.) +- ✅ Toutes les pages de formulaire (création, édition) +- ✅ Toutes les pages de détails +- ✅ Toutes les pages de configuration +- ✅ Toutes les pages de rapports +- ✅ Toutes les pages internes de l'application +- ✅ La page dashboard (tableau de bord interne) + +## ✅ Footer activé sur + +Le Footer est maintenant affiché **uniquement** sur : +- ✅ **`index.xhtml`** - Page d'accueil publique (accessible sans authentification) + +Cette page sert de point d'entrée public pour l'application et contient : +- Présentation de BTP Xpress +- Boutons de connexion et "En savoir plus" +- Footer complet avec liens, newsletter, etc. + +## 📝 Page d'accueil publique créée + +**Fichier** : `src/main/resources/META-INF/resources/index.xhtml` + +Cette page a été créée pour servir de page d'accueil publique accessible à tous, avec le Footer activé. + +--- + +**Date de modification** : 2026-01-03 +**Statut** : ✅ Résolu + diff --git a/IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md b/IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md new file mode 100644 index 0000000..143520b --- /dev/null +++ b/IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md @@ -0,0 +1,281 @@ +# 🚀 Exemple d'Implémentation : Lazy Loading pour Factures + +## 📋 Objectif +Transformer la liste des factures pour utiliser le lazy loading avec pagination côté serveur. + +--- + +## 📁 Fichiers à Créer/Modifier + +### 1. Créer FactureLazyDataModel.java + +**Chemin** : `src/main/java/dev/lions/btpxpress/view/model/FactureLazyDataModel.java` + +```java +package dev.lions.btpxpress.view.model; + +import dev.lions.btpxpress.service.FactureService; +import dev.lions.btpxpress.view.FactureView.Facture; +import org.primefaces.model.FilterMeta; +import org.primefaces.model.LazyDataModel; +import org.primefaces.model.SortMeta; +import org.primefaces.model.SortOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.*; + +public class FactureLazyDataModel extends LazyDataModel implements Serializable { + + private static final Logger LOG = LoggerFactory.getLogger(FactureLazyDataModel.class); + private final FactureService factureService; + + public FactureLazyDataModel(FactureService factureService) { + this.factureService = factureService; + } + + @Override + public int count(Map filterBy) { + try { + Map filterParams = buildFilterParams(filterBy); + int count = factureService.countFactures(filterParams); + LOG.debug("Count factures with filters: {}", count); + return count; + } catch (Exception e) { + LOG.error("Erreur lors du comptage des factures", e); + return 0; + } + } + + @Override + public List load(int first, int pageSize, + Map sortBy, + Map filterBy) { + try { + Map params = new HashMap<>(); + params.put("offset", first); + params.put("limit", pageSize); + + // Ajouter les paramètres de tri + if (sortBy != null && !sortBy.isEmpty()) { + SortMeta sortMeta = sortBy.values().iterator().next(); + params.put("sortField", sortMeta.getField()); + params.put("sortOrder", sortMeta.getOrder() == SortOrder.ASCENDING ? "ASC" : "DESC"); + } + + // Ajouter les paramètres de filtrage + params.putAll(buildFilterParams(filterBy)); + + LOG.debug("Loading factures with params: {}", params); + List factures = factureService.getFacturesLazy(params); + LOG.debug("Loaded {} factures", factures.size()); + + return factures; + } catch (Exception e) { + LOG.error("Erreur lors du chargement des factures", e); + return Collections.emptyList(); + } + } + + @Override + public String getRowKey(Facture facture) { + return facture.getId() != null ? facture.getId().toString() : null; + } + + @Override + public Facture getRowData(String rowKey) { + // Cette méthode est appelée pour récupérer une facture par son ID + // Pour l'instant, on retourne null car on ne garde pas de cache local + return null; + } + + private Map buildFilterParams(Map filterBy) { + Map params = new HashMap<>(); + + if (filterBy != null) { + filterBy.forEach((field, filterMeta) -> { + Object filterValue = filterMeta.getFilterValue(); + if (filterValue != null && !filterValue.toString().trim().isEmpty()) { + params.put("filter_" + field, filterValue); + } + }); + } + + return params; + } +} +``` + +--- + +### 2. Modifier FactureService.java + +**Ajouter les méthodes pour le lazy loading** : + +```java +package dev.lions.btpxpress.service; + +import dev.lions.btpxpress.view.FactureView.Facture; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +@ApplicationScoped +public class FactureService { + + private static final Logger LOG = LoggerFactory.getLogger(FactureService.class); + + @Inject + @RestClient + BtpXpressApiClient apiClient; + + /** + * Récupère les factures avec pagination et filtres + */ + public List getFacturesLazy(Map params) { + try { + int offset = (int) params.getOrDefault("offset", 0); + int limit = (int) params.getOrDefault("limit", 10); + String sortField = (String) params.get("sortField"); + String sortOrder = (String) params.get("sortOrder"); + + // Extraire les filtres + String filtreNumero = (String) params.get("filter_numero"); + String filtreClient = (String) params.get("filter_client"); + String filtreStatut = (String) params.get("filter_statut"); + + // Appel API (à adapter selon votre backend) + List> facturesData = apiClient.getFacturesLazy( + offset, limit, sortField, sortOrder, + filtreNumero, filtreClient, filtreStatut + ); + + // Mapper vers les objets Facture + return facturesData.stream() + .map(this::mapToFacture) + .collect(Collectors.toList()); + + } catch (Exception e) { + LOG.error("Erreur lors de la récupération lazy des factures", e); + return Collections.emptyList(); + } + } + + /** + * Compte le nombre total de factures avec filtres + */ + public int countFactures(Map params) { + try { + String filtreNumero = (String) params.get("filter_numero"); + String filtreClient = (String) params.get("filter_client"); + String filtreStatut = (String) params.get("filter_statut"); + + return apiClient.countFactures(filtreNumero, filtreClient, filtreStatut); + + } catch (Exception e) { + LOG.error("Erreur lors du comptage des factures", e); + return 0; + } + } + + private Facture mapToFacture(Map data) { + Facture f = new Facture(); + f.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString()) : null); + f.setNumero((String) data.get("numero")); + f.setObjet((String) data.get("objet")); + + // Mapping du client + Object clientObj = data.get("client"); + if (clientObj instanceof Map) { + Map clientData = (Map) clientObj; + String entreprise = (String) clientData.get("entreprise"); + String nom = (String) clientData.get("nom"); + String prenom = (String) clientData.get("prenom"); + f.setClient(entreprise != null && !entreprise.trim().isEmpty() ? + entreprise : (prenom != null ? prenom + " " : "") + (nom != null ? nom : "")); + } else if (clientObj instanceof String) { + f.setClient((String) clientObj); + } + + // Mapping des dates + if (data.get("dateEmission") != null) { + f.setDateEmission(LocalDate.parse(data.get("dateEmission").toString())); + } + if (data.get("dateEcheance") != null) { + f.setDateEcheance(LocalDate.parse(data.get("dateEcheance").toString())); + } + + // Mapping des montants + f.setStatut((String) data.get("statut")); + f.setMontantHT(data.get("montantHT") != null ? Double.valueOf(data.get("montantHT").toString()) : 0.0); + f.setMontantTTC(data.get("montantTTC") != null ? Double.valueOf(data.get("montantTTC").toString()) : 0.0); + f.setMontantPaye(data.get("montantPaye") != null ? Double.valueOf(data.get("montantPaye").toString()) : 0.0); + + return f; + } +} +``` + +--- + +### 3. Modifier BtpXpressApiClient.java + +**Ajouter les endpoints pour le lazy loading** : + +```java +package dev.lions.btpxpress.service; + +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import java.util.List; +import java.util.Map; + +@Path("/api") +@RegisterRestClient(configKey = "btpxpress-api") +public interface BtpXpressApiClient { + + // ... méthodes existantes ... + + @GET + @Path("/factures/lazy") + @Produces(MediaType.APPLICATION_JSON) + List> 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("/factures/count") + @Produces(MediaType.APPLICATION_JSON) + int countFactures( + @QueryParam("filter_numero") String filtreNumero, + @QueryParam("filter_client") String filtreClient, + @QueryParam("filter_statut") String filtreStatut + ); +} +``` + +--- + +## ✅ Prochaines Étapes + +1. Créer les fichiers ci-dessus +2. Tester avec des données de test +3. Vérifier les logs pour le debugging +4. Adapter le backend si nécessaire +5. Répliquer pour les autres modules (Devis, Clients, etc.) + diff --git a/LAZY_LOADING_IMPLEMENTATION_SPEC.md b/LAZY_LOADING_IMPLEMENTATION_SPEC.md new file mode 100644 index 0000000..60e52ca --- /dev/null +++ b/LAZY_LOADING_IMPLEMENTATION_SPEC.md @@ -0,0 +1,221 @@ +# 📋 Spécification Technique - Implémentation Lazy Loading Production + +## 🎯 Objectif +Implémenter le lazy loading pour les factures avec une solution **production-ready** incluant : +- Gestion d'erreurs robuste +- Logging approprié +- Tests unitaires et d'intégration +- Documentation complète +- Compatibilité backend/frontend + +--- + +## 🏗️ Architecture + +### Composants à Créer/Modifier + +#### Frontend (btpxpress-client) +1. **FactureLazyDataModel** - Nouveau +2. **FactureService** - Modifier (ajouter méthodes lazy) +3. **BtpXpressApiClient** - Modifier (ajouter endpoints lazy) +4. **FactureView** - Modifier (utiliser LazyDataModel) +5. **factures.xhtml** - Modifier (activer lazy loading) + +#### Backend (btpxpress-server) +1. **FactureResource** - Modifier (ajouter endpoints lazy) +2. **FactureService** - Modifier (ajouter méthodes paginées) +3. **FactureRepository** - Modifier (ajouter requêtes paginées) + +--- + +## 📊 Flux de Données + +``` +[factures.xhtml] + ↓ (lazy=true, rows=10) +[FactureView + LazyDataModel] + ↓ (load(first, pageSize, sortBy, filterBy)) +[FactureService] + ↓ (getFacturesLazy(params)) +[BtpXpressApiClient] + ↓ (HTTP GET /api/v1/factures/lazy?offset=0&limit=10&...) +[Backend FactureResource] + ↓ (getFacturesLazy(offset, limit, ...)) +[Backend FactureService] + ↓ (findFacturesLazy(offset, limit, ...)) +[Backend FactureRepository] + ↓ (Panache query with pagination) +[Database] +``` + +--- + +## 🔧 Détails d'Implémentation + +### 1. Backend - FactureRepository + +**Méthodes à ajouter** : +```java +/** + * Compte le nombre total de factures avec filtres optionnels + */ +public long countFactures(String filtreNumero, String filtreClient, String filtreStatut); + +/** + * Récupère les factures avec pagination, tri et filtres + */ +public List findFacturesLazy( + int offset, + int limit, + String sortField, + String sortOrder, + String filtreNumero, + String filtreClient, + String filtreStatut +); +``` + +### 2. Backend - FactureService + +**Méthodes à ajouter** : +```java +/** + * Récupère les factures avec pagination + * @return DTO contenant les factures et le total + */ +public FacturePageDTO getFacturesLazy(FactureFilterDTO filters); + +/** + * Compte les factures avec filtres + */ +public long countFactures(FactureFilterDTO filters); +``` + +### 3. Backend - FactureResource + +**Endpoints à ajouter** : +```java +@GET +@Path("/lazy") +@Produces(MediaType.APPLICATION_JSON) +public Response getFacturesLazy( + @QueryParam("offset") @DefaultValue("0") int offset, + @QueryParam("limit") @DefaultValue("10") int limit, + @QueryParam("sortField") String sortField, + @QueryParam("sortOrder") @DefaultValue("ASC") String sortOrder, + @QueryParam("filter_numero") String filtreNumero, + @QueryParam("filter_client") String filtreClient, + @QueryParam("filter_statut") String filtreStatut +); + +@GET +@Path("/count") +@Produces(MediaType.APPLICATION_JSON) +public Response countFactures( + @QueryParam("filter_numero") String filtreNumero, + @QueryParam("filter_client") String filtreClient, + @QueryParam("filter_statut") String filtreStatut +); +``` + +--- + +## ✅ Critères d'Acceptation + +### Fonctionnels +- [ ] La liste des factures charge seulement 10-50 items par page +- [ ] La pagination fonctionne correctement (première, précédente, suivante, dernière page) +- [ ] Le tri fonctionne sur toutes les colonnes triables +- [ ] Les filtres fonctionnent et sont appliqués côté serveur +- [ ] Le compteur total affiche le bon nombre de factures +- [ ] Les performances sont améliorées (temps de chargement <500ms) + +### Techniques +- [ ] Gestion d'erreurs complète (try-catch, logging) +- [ ] Validation des paramètres (offset >= 0, limit > 0, etc.) +- [ ] Logs appropriés (DEBUG, INFO, WARN, ERROR) +- [ ] Code commenté et documenté +- [ ] Tests unitaires (couverture >80%) +- [ ] Tests d'intégration +- [ ] Pas de régression sur les fonctionnalités existantes + +### Sécurité +- [ ] Validation côté serveur de tous les paramètres +- [ ] Protection contre SQL injection +- [ ] Vérification des droits d'accès +- [ ] Limitation du nombre max d'items par page (ex: 100) + +--- + +## 🧪 Plan de Tests + +### Tests Unitaires + +#### Frontend +- `FactureLazyDataModel.load()` - Cas nominal +- `FactureLazyDataModel.load()` - Avec filtres +- `FactureLazyDataModel.load()` - Avec tri +- `FactureLazyDataModel.count()` - Cas nominal +- `FactureLazyDataModel` - Gestion d'erreurs + +#### Backend +- `FactureRepository.findFacturesLazy()` - Pagination +- `FactureRepository.findFacturesLazy()` - Tri +- `FactureRepository.findFacturesLazy()` - Filtres +- `FactureRepository.countFactures()` - Avec/sans filtres +- `FactureService.getFacturesLazy()` - Cas nominal +- `FactureService.getFacturesLazy()` - Gestion d'erreurs + +### Tests d'Intégration +- Chargement de la première page +- Navigation entre les pages +- Tri par différentes colonnes +- Application de filtres multiples +- Gestion d'erreurs réseau +- Performance (temps de réponse <500ms) + +--- + +## 📝 Checklist d'Implémentation + +### Phase 1 : Backend (Priorité 1) +- [ ] Créer DTOs (FactureFilterDTO, FacturePageDTO) +- [ ] Implémenter FactureRepository.findFacturesLazy() +- [ ] Implémenter FactureRepository.countFactures() +- [ ] Implémenter FactureService.getFacturesLazy() +- [ ] Implémenter FactureResource endpoints +- [ ] Tests unitaires backend +- [ ] Tests d'intégration backend + +### Phase 2 : Frontend (Priorité 2) +- [ ] Créer FactureLazyDataModel +- [ ] Modifier BtpXpressApiClient +- [ ] Modifier FactureService +- [ ] Modifier FactureView +- [ ] Modifier factures.xhtml +- [ ] Tests unitaires frontend + +### Phase 3 : Tests & Documentation (Priorité 3) +- [ ] Tests end-to-end +- [ ] Tests de performance +- [ ] Documentation technique +- [ ] Documentation utilisateur +- [ ] Revue de code + +--- + +## 🚨 Risques et Mitigation + +| Risque | Impact | Probabilité | Mitigation | +|--------|--------|-------------|------------| +| Incompatibilité backend | Élevé | Faible | Vérifier version Panache, tester avec données réelles | +| Performance dégradée | Élevé | Moyen | Indexer colonnes de tri/filtre, optimiser requêtes | +| Régression fonctionnelle | Moyen | Moyen | Tests complets avant déploiement | +| Erreurs réseau | Moyen | Faible | Gestion d'erreurs robuste, retry logic | + +--- + +**Version** : 1.0 +**Date** : 2025-12-29 +**Statut** : Prêt pour implémentation + diff --git a/PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md b/PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md new file mode 100644 index 0000000..cde4f74 --- /dev/null +++ b/PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md @@ -0,0 +1,776 @@ +# 🚀 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 + + diff --git a/SECURITE_PRODUCTION.md b/SECURITE_PRODUCTION.md index 3723838..8f3cc7f 100644 --- a/SECURITE_PRODUCTION.md +++ b/SECURITE_PRODUCTION.md @@ -1,335 +1,335 @@ -# 🔒 Sécurisation Complète de l'Application Frontend BTP Xpress - -**Date** : 2025-01-20 -**Version** : 1.0.0 -**Statut** : ✅ **SÉCURISÉ POUR PRODUCTION** - ---- - -## 📋 Vue d'ensemble - -L'application frontend BTP Xpress est maintenant complètement sécurisée pour la production avec : -- ✅ Headers de sécurité HTTP complets -- ✅ Configuration OIDC/Keycloak sécurisée -- ✅ CORS restreint aux domaines autorisés -- ✅ HTTPS/TLS forcé via Ingress -- ✅ Cookies sécurisés (HttpOnly, Secure, SameSite) -- ✅ Content Security Policy (CSP) stricte -- ✅ Protection contre les attaques courantes - ---- - -## 🔐 1. Headers de Sécurité HTTP - -### Filtre de Sécurité (`SecurityHeadersFilter`) - -Le filtre `SecurityHeadersFilter` ajoute automatiquement les headers suivants à toutes les réponses : - -| Header | Valeur | Protection | -|--------|--------|------------| -| **Strict-Transport-Security** | `max-age=31536000; includeSubDomains; preload` | Force HTTPS pendant 1 an | -| **X-Frame-Options** | `DENY` | Empêche le clickjacking | -| **X-Content-Type-Options** | `nosniff` | Empêche le MIME sniffing | -| **X-XSS-Protection** | `1; mode=block` | Active la protection XSS du navigateur | -| **Referrer-Policy** | `strict-origin-when-cross-origin` | Contrôle les informations de referrer | -| **Content-Security-Policy** | Voir ci-dessous | Politique de sécurité stricte | -| **Permissions-Policy** | Désactive geolocation, microphone, etc. | Limite les fonctionnalités du navigateur | -| **X-Permitted-Cross-Domain-Policies** | `none` | Bloque les politiques Flash/Silverlight | -| **Cross-Origin-Embedder-Policy** | `require-corp` | Protection contre les fuites de données | -| **Cross-Origin-Opener-Policy** | `same-origin` | Isolation des fenêtres | -| **Cross-Origin-Resource-Policy** | `same-origin` | Contrôle des ressources cross-origin | - -### Content Security Policy (CSP) - -```http -default-src 'self'; -script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; -style-src 'self' 'unsafe-inline' https://security.lions.dev; -img-src 'self' data: https: blob:; -font-src 'self' data: https://security.lions.dev; -connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.lions.dev; -frame-src 'self' https://security.lions.dev; -object-src 'none'; -base-uri 'self'; -form-action 'self' https://security.lions.dev; -frame-ancestors 'none'; -upgrade-insecure-requests; -``` - -**Explication** : -- Autorise uniquement les ressources depuis `self` et `security.lions.dev` -- Bloque les iframes externes (sauf Keycloak) -- Force l'upgrade vers HTTPS -- Empêche l'injection de code malveillant - ---- - -## 🌐 2. Configuration OIDC / Keycloak - -### Serveur d'authentification -- **URL** : `https://security.lions.dev/realms/btpxpress` -- **Client ID** : `btpxpress-frontend` -- **Type** : `web-app` (application publique) -- **TLS Verification** : `required` (obligatoire en production) - -### Cookies de session sécurisés -- ✅ **HttpOnly** : `true` (protection XSS) -- ✅ **Secure** : `true` (HTTPS uniquement) -- ✅ **SameSite** : `strict` (protection CSRF) -- ✅ **Path** : `/` -- ✅ **Encryption** : `required` (tokens chiffrés) -- ✅ **Max Size** : `8192 bytes` - -### Gestion des tokens -- ✅ **Split Tokens** : Activé (tokens divisés) -- ✅ **Strategy** : `id-refresh-tokens` (refresh automatique) -- ✅ **Session Age Extension** : `PT30M` (30 minutes) -- ✅ **Restore Path After Redirect** : `true` (navigation fluide) - ---- - -## 🔒 3. Configuration CORS - -### Origines autorisées (Production) -```properties -quarkus.http.cors.origins=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev -``` - -### Méthodes HTTP autorisées -- `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH` - -### Headers autorisés -- `Content-Type` -- `Authorization` -- `X-Requested-With` -- `X-CSRF-Token` - -### Credentials -- ✅ **Access-Control-Allow-Credentials** : `true` -- ✅ **Max Age** : `3600 seconds` (1 heure) - ---- - -## 🛡️ 4. Configuration Ingress Kubernetes - -### TLS/HTTPS -- ✅ **SSL Redirect** : Forcé -- ✅ **Force SSL Redirect** : Activé -- ✅ **Cert Manager** : Let's Encrypt (automatique) -- ✅ **TLS Protocols** : `TLSv1.2`, `TLSv1.3` - -### Headers ajoutés par Nginx Ingress -Les headers suivants sont ajoutés au niveau de l'Ingress : -- `X-Frame-Options: DENY` -- `X-Content-Type-Options: nosniff` -- `X-XSS-Protection: 1; mode=block` -- `Referrer-Policy: strict-origin-when-cross-origin` -- `Permissions-Policy: geolocation=(), microphone=(), camera=()` -- `X-Permitted-Cross-Domain-Policies: none` - -### Configuration HSTS -Le header `Strict-Transport-Security` est ajouté par le filtre Java uniquement pour les connexions HTTPS détectées (via `X-Forwarded-Proto`). - ---- - -## 🔐 5. Permissions et Accès - -### Pages publiques (sans authentification) -Les ressources statiques sont accessibles sans authentification : -- `/*.css`, `/*.js`, `/*.png`, `/*.jpg`, etc. -- `/resources/*` - -### Pages protégées (authentification requise) -Toutes les autres pages nécessitent une authentification OIDC : -- ✅ Redirection automatique vers Keycloak si non authentifié -- ✅ Restauration du chemin après authentification -- ✅ Session maintenue pendant 30 minutes - ---- - -## 🔧 6. Configuration de Production - -### Fichier de configuration -**Fichier** : `src/main/resources/application-prod.properties` - -### Variables d'environnement requises -- `BTPXPRESS_API_BASE_URL` : URL de l'API backend (défaut: `https://api.btpxpress.lions.dev`) - -### Activation en production -Pour activer la configuration de production : -```bash -export QUARKUS_PROFILE=prod -# ou -java -Dquarkus.profile=prod -jar btpxpress-client.jar -``` - ---- - -## ✅ 7. Checklist de Sécurisation - -### Headers de sécurité -- [x] Strict-Transport-Security (HSTS) -- [x] X-Frame-Options -- [x] X-Content-Type-Options -- [x] X-XSS-Protection -- [x] Referrer-Policy -- [x] Content-Security-Policy (CSP) -- [x] Permissions-Policy -- [x] Cross-Origin-Embedder-Policy -- [x] Cross-Origin-Opener-Policy -- [x] Cross-Origin-Resource-Policy - -### Authentification -- [x] OIDC/Keycloak configuré -- [x] TLS verification requis -- [x] Cookies sécurisés (HttpOnly, Secure, SameSite) -- [x] Tokens chiffrés -- [x] Refresh tokens automatique - -### CORS -- [x] Origines restreintes à `btpxpress.lions.dev` -- [x] Credentials autorisés -- [x] Méthodes HTTP limitées - -### Infrastructure -- [x] HTTPS forcé via Ingress -- [x] Certificats TLS automatiques (Let's Encrypt) -- [x] TLS 1.2+ uniquement -- [x] Headers sécurité au niveau Ingress - -### Application -- [x] Filtre de sécurité activé -- [x] Configuration production séparée -- [x] Logs sécurisés (pas de secrets) -- [x] Limites HTTP configurées - ---- - -## 🚀 8. Déploiement - -### Prérequis -1. ✅ Certificat TLS configuré pour `btpxpress.lions.dev` -2. ✅ Keycloak accessible sur `https://security.lions.dev` -3. ✅ Client OIDC `btpxpress-frontend` configuré dans Keycloak -4. ✅ Ingress Kubernetes configuré avec annotations de sécurité - -### Vérification post-déploiement - -#### 1. Vérifier les headers de sécurité -```bash -curl -I https://btpxpress.lions.dev - -# Vérifier la présence de : -# - Strict-Transport-Security -# - X-Frame-Options -# - X-Content-Type-Options -# - Content-Security-Policy -``` - -#### 2. Tester l'authentification -1. Accéder à `https://btpxpress.lions.dev` -2. Vérifier la redirection vers Keycloak -3. S'authentifier -4. Vérifier le retour vers l'application - -#### 3. Vérifier les cookies -Dans les DevTools du navigateur : -- ✅ Cookies avec `HttpOnly` -- ✅ Cookies avec `Secure` -- ✅ Cookies avec `SameSite=Strict` - -#### 4. Tester HTTPS -```bash -# Vérifier que HTTP redirige vers HTTPS -curl -I http://btpxpress.lions.dev -# Attendu : 301 ou 302 vers https:// -``` - -#### 5. Vérifier CSP -Dans la console du navigateur : -- Aucune violation CSP -- Ressources chargées uniquement depuis les origines autorisées - ---- - -## 📊 9. Tests de Sécurité Recommandés - -### Outils en ligne -- [SSL Labs](https://www.ssllabs.com/ssltest/) : Test du certificat TLS -- [Security Headers](https://securityheaders.com/) : Vérification des headers de sécurité -- [Mozilla Observatory](https://observatory.mozilla.org/) : Audit de sécurité complet - -### Commandes locales -```bash -# Test SSL -openssl s_client -connect btpxpress.lions.dev:443 - -# Test headers -curl -I https://btpxpress.lions.dev - -# Test CSP -curl -H "Content-Security-Policy-Report-Only: default-src 'self'" https://btpxpress.lions.dev -``` - ---- - -## 🔍 10. Monitoring et Alertes - -### Headers à surveiller -- Taux de violations CSP (si reporting configuré) -- Échecs d'authentification OIDC -- Erreurs de certificat TLS -- Redirections HTTP → HTTPS - -### Logs à surveiller -- Erreurs d'authentification OIDC -- Violations de sécurité détectées -- Échecs de validation de token -- Tentatives d'accès non autorisées - ---- - -## 📝 11. Maintenance - -### Mise à jour des certificats -Les certificats Let's Encrypt sont renouvelés automatiquement par cert-manager. - -### Mise à jour de la CSP -Si des violations CSP sont détectées en production, ajuster la CSP dans `SecurityHeadersFilter.java`. - -### Mise à jour des dépendances -Maintenir les dépendances à jour pour corriger les vulnérabilités : -```bash -mvn versions:display-dependency-updates -mvn versions:display-plugin-updates -``` - ---- - -## 📚 12. Références - -- [OWASP Top 10](https://owasp.org/www-project-top-ten/) -- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security) -- [Quarkus Security Guide](https://quarkus.io/guides/security) -- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) - ---- - -## ✅ Conclusion - -L'application frontend BTP Xpress est maintenant **complètement sécurisée** pour la production avec : -- ✅ **Headers de sécurité complets** (10+ headers) -- ✅ **OIDC/Keycloak sécurisé** (TLS, cookies sécurisés) -- ✅ **CORS restreint** (btpxpress.lions.dev uniquement) -- ✅ **HTTPS forcé** (TLS 1.2+) -- ✅ **CSP stricte** (protection injection) -- ✅ **Infrastructure Kubernetes sécurisée** - -**Statut** : ✅ **PRÊT POUR PRODUCTION** - ---- - -**Auteur** : Équipe BTP Xpress -**Date de dernière mise à jour** : 2025-01-20 -**Version** : 1.0.0 - +# 🔒 Sécurisation Complète de l'Application Frontend BTP Xpress + +**Date** : 2025-01-20 +**Version** : 1.0.0 +**Statut** : ✅ **SÉCURISÉ POUR PRODUCTION** + +--- + +## 📋 Vue d'ensemble + +L'application frontend BTP Xpress est maintenant complètement sécurisée pour la production avec : +- ✅ Headers de sécurité HTTP complets +- ✅ Configuration OIDC/Keycloak sécurisée +- ✅ CORS restreint aux domaines autorisés +- ✅ HTTPS/TLS forcé via Ingress +- ✅ Cookies sécurisés (HttpOnly, Secure, SameSite) +- ✅ Content Security Policy (CSP) stricte +- ✅ Protection contre les attaques courantes + +--- + +## 🔐 1. Headers de Sécurité HTTP + +### Filtre de Sécurité (`SecurityHeadersFilter`) + +Le filtre `SecurityHeadersFilter` ajoute automatiquement les headers suivants à toutes les réponses : + +| Header | Valeur | Protection | +|--------|--------|------------| +| **Strict-Transport-Security** | `max-age=31536000; includeSubDomains; preload` | Force HTTPS pendant 1 an | +| **X-Frame-Options** | `DENY` | Empêche le clickjacking | +| **X-Content-Type-Options** | `nosniff` | Empêche le MIME sniffing | +| **X-XSS-Protection** | `1; mode=block` | Active la protection XSS du navigateur | +| **Referrer-Policy** | `strict-origin-when-cross-origin` | Contrôle les informations de referrer | +| **Content-Security-Policy** | Voir ci-dessous | Politique de sécurité stricte | +| **Permissions-Policy** | Désactive geolocation, microphone, etc. | Limite les fonctionnalités du navigateur | +| **X-Permitted-Cross-Domain-Policies** | `none` | Bloque les politiques Flash/Silverlight | +| **Cross-Origin-Embedder-Policy** | `require-corp` | Protection contre les fuites de données | +| **Cross-Origin-Opener-Policy** | `same-origin` | Isolation des fenêtres | +| **Cross-Origin-Resource-Policy** | `same-origin` | Contrôle des ressources cross-origin | + +### Content Security Policy (CSP) + +```http +default-src 'self'; +script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; +style-src 'self' 'unsafe-inline' https://security.lions.dev; +img-src 'self' data: https: blob:; +font-src 'self' data: https://security.lions.dev; +connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.lions.dev; +frame-src 'self' https://security.lions.dev; +object-src 'none'; +base-uri 'self'; +form-action 'self' https://security.lions.dev; +frame-ancestors 'none'; +upgrade-insecure-requests; +``` + +**Explication** : +- Autorise uniquement les ressources depuis `self` et `security.lions.dev` +- Bloque les iframes externes (sauf Keycloak) +- Force l'upgrade vers HTTPS +- Empêche l'injection de code malveillant + +--- + +## 🌐 2. Configuration OIDC / Keycloak + +### Serveur d'authentification +- **URL** : `https://security.lions.dev/realms/btpxpress` +- **Client ID** : `btpxpress-frontend` +- **Type** : `web-app` (application publique) +- **TLS Verification** : `required` (obligatoire en production) + +### Cookies de session sécurisés +- ✅ **HttpOnly** : `true` (protection XSS) +- ✅ **Secure** : `true` (HTTPS uniquement) +- ✅ **SameSite** : `strict` (protection CSRF) +- ✅ **Path** : `/` +- ✅ **Encryption** : `required` (tokens chiffrés) +- ✅ **Max Size** : `8192 bytes` + +### Gestion des tokens +- ✅ **Split Tokens** : Activé (tokens divisés) +- ✅ **Strategy** : `id-refresh-tokens` (refresh automatique) +- ✅ **Session Age Extension** : `PT30M` (30 minutes) +- ✅ **Restore Path After Redirect** : `true` (navigation fluide) + +--- + +## 🔒 3. Configuration CORS + +### Origines autorisées (Production) +```properties +quarkus.http.cors.origins=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev +``` + +### Méthodes HTTP autorisées +- `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH` + +### Headers autorisés +- `Content-Type` +- `Authorization` +- `X-Requested-With` +- `X-CSRF-Token` + +### Credentials +- ✅ **Access-Control-Allow-Credentials** : `true` +- ✅ **Max Age** : `3600 seconds` (1 heure) + +--- + +## 🛡️ 4. Configuration Ingress Kubernetes + +### TLS/HTTPS +- ✅ **SSL Redirect** : Forcé +- ✅ **Force SSL Redirect** : Activé +- ✅ **Cert Manager** : Let's Encrypt (automatique) +- ✅ **TLS Protocols** : `TLSv1.2`, `TLSv1.3` + +### Headers ajoutés par Nginx Ingress +Les headers suivants sont ajoutés au niveau de l'Ingress : +- `X-Frame-Options: DENY` +- `X-Content-Type-Options: nosniff` +- `X-XSS-Protection: 1; mode=block` +- `Referrer-Policy: strict-origin-when-cross-origin` +- `Permissions-Policy: geolocation=(), microphone=(), camera=()` +- `X-Permitted-Cross-Domain-Policies: none` + +### Configuration HSTS +Le header `Strict-Transport-Security` est ajouté par le filtre Java uniquement pour les connexions HTTPS détectées (via `X-Forwarded-Proto`). + +--- + +## 🔐 5. Permissions et Accès + +### Pages publiques (sans authentification) +Les ressources statiques sont accessibles sans authentification : +- `/*.css`, `/*.js`, `/*.png`, `/*.jpg`, etc. +- `/resources/*` + +### Pages protégées (authentification requise) +Toutes les autres pages nécessitent une authentification OIDC : +- ✅ Redirection automatique vers Keycloak si non authentifié +- ✅ Restauration du chemin après authentification +- ✅ Session maintenue pendant 30 minutes + +--- + +## 🔧 6. Configuration de Production + +### Fichier de configuration +**Fichier** : `src/main/resources/application-prod.properties` + +### Variables d'environnement requises +- `BTPXPRESS_API_BASE_URL` : URL de l'API backend (défaut: `https://api.btpxpress.lions.dev`) + +### Activation en production +Pour activer la configuration de production : +```bash +export QUARKUS_PROFILE=prod +# ou +java -Dquarkus.profile=prod -jar btpxpress-client.jar +``` + +--- + +## ✅ 7. Checklist de Sécurisation + +### Headers de sécurité +- [x] Strict-Transport-Security (HSTS) +- [x] X-Frame-Options +- [x] X-Content-Type-Options +- [x] X-XSS-Protection +- [x] Referrer-Policy +- [x] Content-Security-Policy (CSP) +- [x] Permissions-Policy +- [x] Cross-Origin-Embedder-Policy +- [x] Cross-Origin-Opener-Policy +- [x] Cross-Origin-Resource-Policy + +### Authentification +- [x] OIDC/Keycloak configuré +- [x] TLS verification requis +- [x] Cookies sécurisés (HttpOnly, Secure, SameSite) +- [x] Tokens chiffrés +- [x] Refresh tokens automatique + +### CORS +- [x] Origines restreintes à `btpxpress.lions.dev` +- [x] Credentials autorisés +- [x] Méthodes HTTP limitées + +### Infrastructure +- [x] HTTPS forcé via Ingress +- [x] Certificats TLS automatiques (Let's Encrypt) +- [x] TLS 1.2+ uniquement +- [x] Headers sécurité au niveau Ingress + +### Application +- [x] Filtre de sécurité activé +- [x] Configuration production séparée +- [x] Logs sécurisés (pas de secrets) +- [x] Limites HTTP configurées + +--- + +## 🚀 8. Déploiement + +### Prérequis +1. ✅ Certificat TLS configuré pour `btpxpress.lions.dev` +2. ✅ Keycloak accessible sur `https://security.lions.dev` +3. ✅ Client OIDC `btpxpress-frontend` configuré dans Keycloak +4. ✅ Ingress Kubernetes configuré avec annotations de sécurité + +### Vérification post-déploiement + +#### 1. Vérifier les headers de sécurité +```bash +curl -I https://btpxpress.lions.dev + +# Vérifier la présence de : +# - Strict-Transport-Security +# - X-Frame-Options +# - X-Content-Type-Options +# - Content-Security-Policy +``` + +#### 2. Tester l'authentification +1. Accéder à `https://btpxpress.lions.dev` +2. Vérifier la redirection vers Keycloak +3. S'authentifier +4. Vérifier le retour vers l'application + +#### 3. Vérifier les cookies +Dans les DevTools du navigateur : +- ✅ Cookies avec `HttpOnly` +- ✅ Cookies avec `Secure` +- ✅ Cookies avec `SameSite=Strict` + +#### 4. Tester HTTPS +```bash +# Vérifier que HTTP redirige vers HTTPS +curl -I http://btpxpress.lions.dev +# Attendu : 301 ou 302 vers https:// +``` + +#### 5. Vérifier CSP +Dans la console du navigateur : +- Aucune violation CSP +- Ressources chargées uniquement depuis les origines autorisées + +--- + +## 📊 9. Tests de Sécurité Recommandés + +### Outils en ligne +- [SSL Labs](https://www.ssllabs.com/ssltest/) : Test du certificat TLS +- [Security Headers](https://securityheaders.com/) : Vérification des headers de sécurité +- [Mozilla Observatory](https://observatory.mozilla.org/) : Audit de sécurité complet + +### Commandes locales +```bash +# Test SSL +openssl s_client -connect btpxpress.lions.dev:443 + +# Test headers +curl -I https://btpxpress.lions.dev + +# Test CSP +curl -H "Content-Security-Policy-Report-Only: default-src 'self'" https://btpxpress.lions.dev +``` + +--- + +## 🔍 10. Monitoring et Alertes + +### Headers à surveiller +- Taux de violations CSP (si reporting configuré) +- Échecs d'authentification OIDC +- Erreurs de certificat TLS +- Redirections HTTP → HTTPS + +### Logs à surveiller +- Erreurs d'authentification OIDC +- Violations de sécurité détectées +- Échecs de validation de token +- Tentatives d'accès non autorisées + +--- + +## 📝 11. Maintenance + +### Mise à jour des certificats +Les certificats Let's Encrypt sont renouvelés automatiquement par cert-manager. + +### Mise à jour de la CSP +Si des violations CSP sont détectées en production, ajuster la CSP dans `SecurityHeadersFilter.java`. + +### Mise à jour des dépendances +Maintenir les dépendances à jour pour corriger les vulnérabilités : +```bash +mvn versions:display-dependency-updates +mvn versions:display-plugin-updates +``` + +--- + +## 📚 12. Références + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security) +- [Quarkus Security Guide](https://quarkus.io/guides/security) +- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) + +--- + +## ✅ Conclusion + +L'application frontend BTP Xpress est maintenant **complètement sécurisée** pour la production avec : +- ✅ **Headers de sécurité complets** (10+ headers) +- ✅ **OIDC/Keycloak sécurisé** (TLS, cookies sécurisés) +- ✅ **CORS restreint** (btpxpress.lions.dev uniquement) +- ✅ **HTTPS forcé** (TLS 1.2+) +- ✅ **CSP stricte** (protection injection) +- ✅ **Infrastructure Kubernetes sécurisée** + +**Statut** : ✅ **PRÊT POUR PRODUCTION** + +--- + +**Auteur** : Équipe BTP Xpress +**Date de dernière mise à jour** : 2025-01-20 +**Version** : 1.0.0 + diff --git a/pom.xml b/pom.xml index 4a350f5..55b4fb4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,153 +1,128 @@ - - - 4.0.0 - dev.lions - btpxpress-client - 1.0.0 - jar - BTP Xpress Client - PrimeFaces Freya - Application cliente BTP Xpress basée sur Quarkus et PrimeFaces Freya - - 3.13.0 - 17 - UTF-8 - UTF-8 - quarkus-bom - io.quarkus.platform - 3.15.1 - false - 5.0.0-jakarta - - - - - lions-maven-repo - Lions Dev Maven Repository - https://git.lions.dev/lionsdev/btpxpress-maven-repo/raw/branch/main - - - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - - - io.quarkus - quarkus-arc - - - io.quarkiverse.primefaces - quarkus-primefaces - 3.15.0-RC2 - - - org.primefaces - freya-theme - ${freya.theme.version} - - - org.primefaces - freya - ${freya.theme.version} - - - jakarta.faces - jakarta.faces-api - 3.0.0 - - - jakarta.servlet - jakarta.servlet-api - - - jakarta.enterprise - jakarta.enterprise.cdi-api - - - jakarta.el - jakarta.el-api - - - org.projectlombok - lombok - 1.18.30 - provided - - - io.quarkus - quarkus-logging-json - - - io.quarkus - quarkus-rest-client - - - io.quarkus - quarkus-rest-jackson - - - - io.quarkus - quarkus-oidc - - - - io.quarkus - quarkus-smallrye-jwt - - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - - - - - - maven-compiler-plugin - ${compiler-plugin.version} - - true - - - - maven-surefire-plugin - 3.5.0 - - - org.jboss.logmanager.LogManager - - - - - + + + 4.0.0 + dev.lions + btpxpress-client + 1.0.0 + jar + BTP Xpress Client - PrimeFaces Freya + Application cliente BTP Xpress basée sur Quarkus et PrimeFaces Freya + + 3.13.0 + 21 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.27.3 + false + + + + + gitea-lionsdev + Lions Dev Gitea Maven + https://git.lions.dev/api/packages/lionsdev/maven + true + true + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-arc + + + + + + + + + + dev.lions + lions-faces-layout + 1.0.0 + + + + org.projectlombok + lombok + 1.18.30 + provided + + + io.quarkus + quarkus-logging-json + + + io.quarkus + quarkus-rest-client + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-smallrye-jwt + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + true + + + + maven-surefire-plugin + 3.5.0 + + + org.jboss.logmanager.LogManager + + + + + \ No newline at end of file diff --git a/src/main/java/dev/lions/btpxpress/converter/FcfaConverter.java b/src/main/java/dev/lions/btpxpress/converter/FcfaConverter.java index 598116b..7be1894 100644 --- a/src/main/java/dev/lions/btpxpress/converter/FcfaConverter.java +++ b/src/main/java/dev/lions/btpxpress/converter/FcfaConverter.java @@ -1,87 +1,87 @@ -package dev.lions.btpxpress.converter; - -import jakarta.faces.component.UIComponent; -import jakarta.faces.context.FacesContext; -import jakarta.faces.convert.Converter; -import jakarta.faces.convert.FacesConverter; - -import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; - -/** - * Converter personnalisé pour formater les montants en Franc CFA (Fcfa). - * - *

Ce converter formate les nombres avec des espaces comme séparateurs de milliers - * au lieu de virgules, conformément au format standard du Franc CFA.

- * - *

Exemple : 1234567 devient "1 234 567 Fcfa"

- * - * @author BTP Xpress Team - * @version 1.0 - */ -@FacesConverter("fcfaConverter") -public class FcfaConverter implements Converter { - - private static final DecimalFormatSymbols SYMBOLS; - - static { - SYMBOLS = new DecimalFormatSymbols(Locale.FRENCH); - SYMBOLS.setGroupingSeparator(' '); - SYMBOLS.setDecimalSeparator(','); - } - - /** - * Convertit une chaîne de caractères en nombre. - * - * @param context Le contexte Faces - * @param component Le composant UI - * @param value La valeur string à convertir - * @return Le nombre converti, ou null si la valeur est vide/null - */ - @Override - public Number getAsObject(FacesContext context, UIComponent component, String value) { - if (value == null || value.trim().isEmpty()) { - return null; - } - - try { - // Retirer les espaces et le préfixe "Fcfa" si présent - String cleanedValue = value.replaceAll("\\s+", "") - .replace("Fcfa", "") - .replace("fcfa", "") - .trim(); - - return new BigDecimal(cleanedValue); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Impossible de convertir '" + value + "' en nombre", e); - } - } - - /** - * Convertit un nombre en chaîne de caractères formatée. - * - * @param context Le contexte Faces - * @param component Le composant UI - * @param value Le nombre à convertir - * @return La chaîne formatée avec espaces comme séparateurs de milliers - */ - @Override - public String getAsString(FacesContext context, UIComponent component, Number value) { - if (value == null) { - return ""; - } - - // Formater avec espaces comme séparateurs de milliers (format Fcfa standard) - // Le pattern "#" avec groupingUsed=true utilise le groupingSeparator défini dans SYMBOLS (espace) - DecimalFormat formatter = new DecimalFormat("#", SYMBOLS); - formatter.setGroupingSize(3); - formatter.setGroupingUsed(true); - formatter.setMaximumFractionDigits(0); - - long amount = value.longValue(); - return formatter.format(amount); - } -} - +package dev.lions.btpxpress.converter; + +import jakarta.faces.component.UIComponent; +import jakarta.faces.context.FacesContext; +import jakarta.faces.convert.Converter; +import jakarta.faces.convert.FacesConverter; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +/** + * Converter personnalisé pour formater les montants en Franc CFA (Fcfa). + * + *

Ce converter formate les nombres avec des espaces comme séparateurs de milliers + * au lieu de virgules, conformément au format standard du Franc CFA.

+ * + *

Exemple : 1234567 devient "1 234 567 Fcfa"

+ * + * @author BTP Xpress Team + * @version 1.0 + */ +@FacesConverter("fcfaConverter") +public class FcfaConverter implements Converter { + + private static final DecimalFormatSymbols SYMBOLS; + + static { + SYMBOLS = new DecimalFormatSymbols(Locale.FRENCH); + SYMBOLS.setGroupingSeparator(' '); + SYMBOLS.setDecimalSeparator(','); + } + + /** + * Convertit une chaîne de caractères en nombre. + * + * @param context Le contexte Faces + * @param component Le composant UI + * @param value La valeur string à convertir + * @return Le nombre converti, ou null si la valeur est vide/null + */ + @Override + public Number getAsObject(FacesContext context, UIComponent component, String value) { + if (value == null || value.trim().isEmpty()) { + return null; + } + + try { + // Retirer les espaces et le préfixe "Fcfa" si présent + String cleanedValue = value.replaceAll("\\s+", "") + .replace("Fcfa", "") + .replace("fcfa", "") + .trim(); + + return new BigDecimal(cleanedValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Impossible de convertir '" + value + "' en nombre", e); + } + } + + /** + * Convertit un nombre en chaîne de caractères formatée. + * + * @param context Le contexte Faces + * @param component Le composant UI + * @param value Le nombre à convertir + * @return La chaîne formatée avec espaces comme séparateurs de milliers + */ + @Override + public String getAsString(FacesContext context, UIComponent component, Number value) { + if (value == null) { + return ""; + } + + // Formater avec espaces comme séparateurs de milliers (format Fcfa standard) + // Le pattern "#" avec groupingUsed=true utilise le groupingSeparator défini dans SYMBOLS (espace) + DecimalFormat formatter = new DecimalFormat("#", SYMBOLS); + formatter.setGroupingSize(3); + formatter.setGroupingUsed(true); + formatter.setMaximumFractionDigits(0); + + long amount = value.longValue(); + return formatter.format(amount); + } +} + diff --git a/src/main/java/dev/lions/btpxpress/filter/CharacterEncodingFilter.java b/src/main/java/dev/lions/btpxpress/filter/CharacterEncodingFilter.java index 76a6c87..63b3c9a 100644 --- a/src/main/java/dev/lions/btpxpress/filter/CharacterEncodingFilter.java +++ b/src/main/java/dev/lions/btpxpress/filter/CharacterEncodingFilter.java @@ -1,32 +1,32 @@ -package dev.lions.btpxpress.filter; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; - -import java.io.IOException; - -public class CharacterEncodingFilter implements Filter { - - private static final String DEFAULT_ENCODING = "UTF-8"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - request.setCharacterEncoding(DEFAULT_ENCODING); - response.setCharacterEncoding(DEFAULT_ENCODING); - response.setContentType("text/html; charset=" + DEFAULT_ENCODING); - chain.doFilter(request, response); - } - - @Override - public void destroy() { - } -} +package dev.lions.btpxpress.filter; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + +public class CharacterEncodingFilter implements Filter { + + private static final String DEFAULT_ENCODING = "UTF-8"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + request.setCharacterEncoding(DEFAULT_ENCODING); + response.setCharacterEncoding(DEFAULT_ENCODING); + response.setContentType("text/html; charset=" + DEFAULT_ENCODING); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } +} diff --git a/src/main/java/dev/lions/btpxpress/filter/SecurityHeadersFilter.java b/src/main/java/dev/lions/btpxpress/filter/SecurityHeadersFilter.java index 747ba74..4b36553 100644 --- a/src/main/java/dev/lions/btpxpress/filter/SecurityHeadersFilter.java +++ b/src/main/java/dev/lions/btpxpress/filter/SecurityHeadersFilter.java @@ -1,116 +1,116 @@ -package dev.lions.btpxpress.filter; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - -/** - * Filtre de sécurité qui ajoute les headers HTTP de sécurité essentiels - * pour protéger l'application contre diverses attaques. - */ -public class SecurityHeadersFilter implements Filter { - - private static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; - private static final String X_FRAME_OPTIONS = "X-Frame-Options"; - private static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; - private static final String X_XSS_PROTECTION = "X-XSS-Protection"; - private static final String REFERRER_POLICY = "Referrer-Policy"; - private static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy"; - private static final String PERMISSIONS_POLICY = "Permissions-Policy"; - - // HSTS - Force HTTPS pendant 1 an, inclut les sous-domaines - private static final String HSTS_VALUE = "max-age=31536000; includeSubDomains; preload"; - - // X-Frame-Options - Empêche le clickjacking - private static final String X_FRAME_OPTIONS_VALUE = "DENY"; - - // X-Content-Type-Options - Empêche le MIME sniffing - private static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff"; - - // X-XSS-Protection - Active la protection XSS du navigateur (legacy mais utile) - private static final String X_XSS_PROTECTION_VALUE = "1; mode=block"; - - // Referrer-Policy - Contrôle les informations de referrer envoyées - private static final String REFERRER_POLICY_VALUE = "strict-origin-when-cross-origin"; - - // Content Security Policy - Politique de sécurité stricte - // Autorise uniquement les ressources depuis le même domaine et security.lions.dev - private static final String CSP_VALUE = - "default-src 'self'; " + - "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; " + - "style-src 'self' 'unsafe-inline' https://security.lions.dev; " + - "img-src 'self' data: https: blob:; " + - "font-src 'self' data: https://security.lions.dev; " + - "connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.lions.dev; " + - "frame-src 'self' https://security.lions.dev; " + - "object-src 'none'; " + - "base-uri 'self'; " + - "form-action 'self' https://security.lions.dev; " + - "frame-ancestors 'none'; " + - "upgrade-insecure-requests;"; - - // Permissions Policy - Désactive les fonctionnalités non nécessaires - private static final String PERMISSIONS_POLICY_VALUE = - "geolocation=(), " + - "microphone=(), " + - "camera=(), " + - "payment=(), " + - "usb=(), " + - "magnetometer=(), " + - "gyroscope=(), " + - "speaker=()"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // Initialisation non nécessaire - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - // Ajouter les headers de sécurité uniquement pour les requêtes HTTPS - if (httpRequest.isSecure() || - "https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Proto")) || - "https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Scheme"))) { - - // Strict Transport Security (HSTS) - httpResponse.setHeader(STRICT_TRANSPORT_SECURITY, HSTS_VALUE); - - // Content Security Policy - httpResponse.setHeader(CONTENT_SECURITY_POLICY, CSP_VALUE); - } - - // Headers de sécurité applicables même en HTTP (développement) - // Ces headers seront toujours présents - httpResponse.setHeader(X_FRAME_OPTIONS, X_FRAME_OPTIONS_VALUE); - httpResponse.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_VALUE); - httpResponse.setHeader(X_XSS_PROTECTION, X_XSS_PROTECTION_VALUE); - httpResponse.setHeader(REFERRER_POLICY, REFERRER_POLICY_VALUE); - httpResponse.setHeader(PERMISSIONS_POLICY, PERMISSIONS_POLICY_VALUE); - - // Headers supplémentaires pour renforcer la sécurité - httpResponse.setHeader("X-Permitted-Cross-Domain-Policies", "none"); - httpResponse.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); - httpResponse.setHeader("Cross-Origin-Opener-Policy", "same-origin"); - httpResponse.setHeader("Cross-Origin-Resource-Policy", "same-origin"); - - chain.doFilter(request, response); - } - - @Override - public void destroy() { - // Nettoyage non nécessaire - } -} - +package dev.lions.btpxpress.filter; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +/** + * Filtre de sécurité qui ajoute les headers HTTP de sécurité essentiels + * pour protéger l'application contre diverses attaques. + */ +public class SecurityHeadersFilter implements Filter { + + private static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + private static final String X_FRAME_OPTIONS = "X-Frame-Options"; + private static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; + private static final String X_XSS_PROTECTION = "X-XSS-Protection"; + private static final String REFERRER_POLICY = "Referrer-Policy"; + private static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy"; + private static final String PERMISSIONS_POLICY = "Permissions-Policy"; + + // HSTS - Force HTTPS pendant 1 an, inclut les sous-domaines + private static final String HSTS_VALUE = "max-age=31536000; includeSubDomains; preload"; + + // X-Frame-Options - Empêche le clickjacking + private static final String X_FRAME_OPTIONS_VALUE = "DENY"; + + // X-Content-Type-Options - Empêche le MIME sniffing + private static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff"; + + // X-XSS-Protection - Active la protection XSS du navigateur (legacy mais utile) + private static final String X_XSS_PROTECTION_VALUE = "1; mode=block"; + + // Referrer-Policy - Contrôle les informations de referrer envoyées + private static final String REFERRER_POLICY_VALUE = "strict-origin-when-cross-origin"; + + // Content Security Policy - Politique de sécurité stricte + // Autorise uniquement les ressources depuis le même domaine et security.lions.dev + private static final String CSP_VALUE = + "default-src 'self'; " + + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; " + + "style-src 'self' 'unsafe-inline' https://security.lions.dev; " + + "img-src 'self' data: https: blob:; " + + "font-src 'self' data: https://security.lions.dev; " + + "connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.lions.dev; " + + "frame-src 'self' https://security.lions.dev; " + + "object-src 'none'; " + + "base-uri 'self'; " + + "form-action 'self' https://security.lions.dev; " + + "frame-ancestors 'none'; " + + "upgrade-insecure-requests;"; + + // Permissions Policy - Désactive les fonctionnalités non nécessaires + private static final String PERMISSIONS_POLICY_VALUE = + "geolocation=(), " + + "microphone=(), " + + "camera=(), " + + "payment=(), " + + "usb=(), " + + "magnetometer=(), " + + "gyroscope=(), " + + "speaker=()"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Initialisation non nécessaire + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + // Ajouter les headers de sécurité uniquement pour les requêtes HTTPS + if (httpRequest.isSecure() || + "https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Proto")) || + "https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Scheme"))) { + + // Strict Transport Security (HSTS) + httpResponse.setHeader(STRICT_TRANSPORT_SECURITY, HSTS_VALUE); + + // Content Security Policy + httpResponse.setHeader(CONTENT_SECURITY_POLICY, CSP_VALUE); + } + + // Headers de sécurité applicables même en HTTP (développement) + // Ces headers seront toujours présents + httpResponse.setHeader(X_FRAME_OPTIONS, X_FRAME_OPTIONS_VALUE); + httpResponse.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_VALUE); + httpResponse.setHeader(X_XSS_PROTECTION, X_XSS_PROTECTION_VALUE); + httpResponse.setHeader(REFERRER_POLICY, REFERRER_POLICY_VALUE); + httpResponse.setHeader(PERMISSIONS_POLICY, PERMISSIONS_POLICY_VALUE); + + // Headers supplémentaires pour renforcer la sécurité + httpResponse.setHeader("X-Permitted-Cross-Domain-Policies", "none"); + httpResponse.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + httpResponse.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + httpResponse.setHeader("Cross-Origin-Resource-Policy", "same-origin"); + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // Nettoyage non nécessaire + } +} + diff --git a/src/main/java/dev/lions/btpxpress/service/BtpXpressApiClient.java b/src/main/java/dev/lions/btpxpress/service/BtpXpressApiClient.java index 7ada8cb..d9502b9 100644 --- a/src/main/java/dev/lions/btpxpress/service/BtpXpressApiClient.java +++ b/src/main/java/dev/lions/btpxpress/service/BtpXpressApiClient.java @@ -1,273 +1,336 @@ -package dev.lions.btpxpress.service; - -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; - -/** - * Interface REST Client pour communiquer avec l'API backend BTP Xpress. - *

- * Ce client permet au frontend PrimeFaces de communiquer avec le backend Quarkus - * en utilisant les endpoints REST exposés sur /api/v1/*. L'authentification - * est gérée automatiquement via les tokens JWT Keycloak. - *

- * - * @author BTP Xpress Development Team - * @version 1.0.0 - * @since 1.0.0 - */ -@RegisterRestClient(configKey = "btpxpress.api") -@RegisterClientHeaders -@Path("/api/v1") -@Produces(MediaType.APPLICATION_JSON) -@Consumes(MediaType.APPLICATION_JSON) -public interface BtpXpressApiClient { - - /** - * Récupère la liste des chantiers. - * Correspond à {@code ChantierResource.getAllChantiers()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des chantiers. - */ - @GET - @Path("/chantiers") - Response getChantiers(); - - /** - * Récupère un chantier par son identifiant. - * Correspond à {@code ChantierResource.getChantierById()} dans le serveur. - * - * @param id L'identifiant du chantier. - * @return Réponse HTTP contenant le chantier. - */ - @GET - @Path("/chantiers/{id}") - Response getChantier(@PathParam("id") Long id); - - /** - * Récupère la liste des clients. - * Correspond à {@code ClientResource.getAllClients()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des clients. - */ - @GET - @Path("/clients") - Response getClients(); - - /** - * Récupère un client par son identifiant. - * Correspond à {@code ClientResource.getClientById()} dans le serveur. - * - * @param id L'identifiant du client. - * @return Réponse HTTP contenant le client. - */ - @GET - @Path("/clients/{id}") - Response getClient(@PathParam("id") Long id); - - // === ENDPOINTS DASHBOARD === - - /** - * Récupère le dashboard principal avec les métriques globales. - * Correspond à {@code DashboardResource.getDashboardPrincipal()} dans le serveur. - * - * @return Réponse HTTP contenant les métriques du dashboard. - */ - @GET - @Path("/dashboard") - Response getDashboardPrincipal(); - - /** - * Récupère le dashboard des chantiers avec métriques détaillées. - * Correspond à {@code DashboardResource.getDashboardChantiers()} dans le serveur. - * - * @return Réponse HTTP contenant les métriques des chantiers. - */ - @GET - @Path("/dashboard/chantiers") - Response getDashboardChantiers(); - - /** - * Récupère les métriques financières. - * Correspond à {@code DashboardResource.getDashboardFinances()} dans le serveur. - * - * @param periode Période en jours (défaut: 30). - * @return Réponse HTTP contenant les métriques financières. - */ - @GET - @Path("/dashboard/finances") - Response getDashboardFinances(@QueryParam("periode") @DefaultValue("30") int periode); - - /** - * Récupère les métriques de maintenance. - * Correspond à {@code DashboardResource.getDashboardMaintenance()} dans le serveur. - * - * @return Réponse HTTP contenant les métriques de maintenance. - */ - @GET - @Path("/dashboard/maintenance") - Response getDashboardMaintenance(); - - /** - * Récupère les métriques des ressources (équipes, employés, matériel). - * Correspond à {@code DashboardResource.getDashboardRessources()} dans le serveur. - * - * @return Réponse HTTP contenant les métriques des ressources. - */ - @GET - @Path("/dashboard/ressources") - Response getDashboardRessources(); - - /** - * Récupère les alertes nécessitant une attention immédiate. - * Correspond à {@code DashboardResource.getAlertes()} dans le serveur. - * - * @return Réponse HTTP contenant les alertes. - */ - @GET - @Path("/dashboard/alertes") - Response getAlertes(); - - /** - * Récupère les KPIs principaux. - * Correspond à {@code DashboardResource.getKPI()} dans le serveur. - * - * @param periode Période en jours (défaut: 30). - * @return Réponse HTTP contenant les KPIs. - */ - @GET - @Path("/dashboard/kpi") - Response getKPI(@QueryParam("periode") @DefaultValue("30") int periode); - - /** - * Récupère les activités récentes. - * Correspond à {@code DashboardResource.getActivitesRecentes()} dans le serveur. - * - * @param limit Nombre d'activités à récupérer (défaut: 10). - * @return Réponse HTTP contenant les activités récentes. - */ - @GET - @Path("/dashboard/activites-recentes") - Response getActivitesRecentes(@QueryParam("limit") @DefaultValue("10") int limit); - - /** - * Récupère le résumé quotidien. - * Correspond à {@code DashboardResource.getResumeQuotidien()} dans le serveur. - * - * @return Réponse HTTP contenant le résumé quotidien. - */ - @GET - @Path("/dashboard/resume-quotidien") - Response getResumeQuotidien(); - - /** - * Récupère la liste des devis. - * Correspond à {@code DevisResource.getAllDevis()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des devis. - */ - @GET - @Path("/devis") - Response getDevis(); - - /** - * Récupère la liste des factures. - * Correspond à {@code FactureResource.getAllFactures()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des factures. - */ - @GET - @Path("/factures") - Response getFactures(); - - // === ENDPOINTS EMPLOYÉS === - - /** - * Récupère la liste des employés. - * Correspond à {@code EmployeResource.getAllEmployes()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des employés. - */ - @GET - @Path("/employes") - Response getEmployes(); - - /** - * Récupère un employé par son identifiant. - * - * @param id L'identifiant de l'employé. - * @return Réponse HTTP contenant l'employé. - */ - @GET - @Path("/employes/{id}") - Response getEmploye(@PathParam("id") String id); - - // === ENDPOINTS ÉQUIPES === - - /** - * Récupère la liste des équipes. - * Correspond à {@code EquipeResource.getAllEquipes()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des équipes. - */ - @GET - @Path("/equipes") - Response getEquipes(); - - /** - * Récupère une équipe par son identifiant. - * - * @param id L'identifiant de l'équipe. - * @return Réponse HTTP contenant l'équipe. - */ - @GET - @Path("/equipes/{id}") - Response getEquipe(@PathParam("id") String id); - - // === ENDPOINTS MATÉRIELS === - - /** - * Récupère la liste des matériels. - * Correspond à {@code MaterielResource.getAllMateriels()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des matériels. - */ - @GET - @Path("/materiels") - Response getMateriels(); - - /** - * Récupère un matériel par son identifiant. - * - * @param id L'identifiant du matériel. - * @return Réponse HTTP contenant le matériel. - */ - @GET - @Path("/materiels/{id}") - Response getMateriel(@PathParam("id") String id); - - // === ENDPOINTS STOCKS === - - /** - * Récupère la liste des stocks. - * Correspond à {@code StockResource.getAllStocks()} dans le serveur. - * - * @return Réponse HTTP contenant la liste des stocks. - */ - @GET - @Path("/stocks") - Response getStocks(); - - /** - * Récupère un stock par son identifiant. - * - * @param id L'identifiant du stock. - * @return Réponse HTTP contenant le stock. - */ - @GET - @Path("/stocks/{id}") - Response getStock(@PathParam("id") String id); -} - +package dev.lions.btpxpress.service; + +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +/** + * Interface REST Client pour communiquer avec l'API backend BTP Xpress. + *

+ * Ce client permet au frontend PrimeFaces de communiquer avec le backend Quarkus + * en utilisant les endpoints REST exposés sur /api/v1/*. L'authentification + * est gérée automatiquement via les tokens JWT Keycloak. + *

+ * + * @author BTP Xpress Development Team + * @version 1.0.0 + * @since 1.0.0 + */ +@RegisterRestClient(configKey = "btpxpress.api") +@RegisterClientHeaders +@Path("/api/v1") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public interface BtpXpressApiClient { + + /** + * Récupère la liste des chantiers. + * Correspond à {@code ChantierResource.getAllChantiers()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des chantiers. + */ + @GET + @Path("/chantiers") + Response getChantiers(); + + /** + * Récupère un chantier par son identifiant. + * Correspond à {@code ChantierResource.getChantierById()} dans le serveur. + * + * @param id L'identifiant du chantier. + * @return Réponse HTTP contenant le chantier. + */ + @GET + @Path("/chantiers/{id}") + Response getChantier(@PathParam("id") Long id); + + /** + * Crée un nouveau chantier. + * Correspond à {@code ChantierResource.createChantier()} dans le serveur. + * + * @param chantierDTO Les données du chantier à créer. + * @return Réponse HTTP contenant le chantier créé. + */ + @POST + @Path("/chantiers") + Response createChantier(Object chantierDTO); + + /** + * Met à jour un chantier existant. + * Correspond à {@code ChantierResource.updateChantier()} dans le serveur. + * + * @param id L'identifiant du chantier. + * @param chantierDTO Les nouvelles données du chantier. + * @return Réponse HTTP contenant le chantier mis à jour. + */ + @PUT + @Path("/chantiers/{id}") + Response updateChantier(@PathParam("id") String id, Object chantierDTO); + + /** + * Supprime un chantier. + * Correspond à {@code ChantierResource.deleteChantier()} dans le serveur. + * + * @param id L'identifiant du chantier. + * @param permanent Si true, suppression définitive, sinon suppression logique. + * @return Réponse HTTP (204 No Content en cas de succès). + */ + @DELETE + @Path("/chantiers/{id}") + Response deleteChantier(@PathParam("id") String id, @QueryParam("permanent") @DefaultValue("false") boolean permanent); + + /** + * Récupère la liste des clients. + * Correspond à {@code ClientResource.getAllClients()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des clients. + */ + @GET + @Path("/clients") + Response getClients(); + + /** + * Récupère un client par son identifiant. + * Correspond à {@code ClientResource.getClientById()} dans le serveur. + * + * @param id L'identifiant du client. + * @return Réponse HTTP contenant le client. + */ + @GET + @Path("/clients/{id}") + Response getClient(@PathParam("id") Long id); + + // === ENDPOINTS DASHBOARD === + + /** + * Récupère le dashboard principal avec les métriques globales. + * Correspond à {@code DashboardResource.getDashboardPrincipal()} dans le serveur. + * + * @return Réponse HTTP contenant les métriques du dashboard. + */ + @GET + @Path("/dashboard") + Response getDashboardPrincipal(); + + /** + * Récupère le dashboard des chantiers avec métriques détaillées. + * Correspond à {@code DashboardResource.getDashboardChantiers()} dans le serveur. + * + * @return Réponse HTTP contenant les métriques des chantiers. + */ + @GET + @Path("/dashboard/chantiers") + Response getDashboardChantiers(); + + /** + * Récupère les métriques financières. + * Correspond à {@code DashboardResource.getDashboardFinances()} dans le serveur. + * + * @param periode Période en jours (défaut: 30). + * @return Réponse HTTP contenant les métriques financières. + */ + @GET + @Path("/dashboard/finances") + Response getDashboardFinances(@QueryParam("periode") @DefaultValue("30") int periode); + + /** + * Récupère les métriques de maintenance. + * Correspond à {@code DashboardResource.getDashboardMaintenance()} dans le serveur. + * + * @return Réponse HTTP contenant les métriques de maintenance. + */ + @GET + @Path("/dashboard/maintenance") + Response getDashboardMaintenance(); + + /** + * Récupère les métriques des ressources (équipes, employés, matériel). + * Correspond à {@code DashboardResource.getDashboardRessources()} dans le serveur. + * + * @return Réponse HTTP contenant les métriques des ressources. + */ + @GET + @Path("/dashboard/ressources") + Response getDashboardRessources(); + + /** + * Récupère les alertes nécessitant une attention immédiate. + * Correspond à {@code DashboardResource.getAlertes()} dans le serveur. + * + * @return Réponse HTTP contenant les alertes. + */ + @GET + @Path("/dashboard/alertes") + Response getAlertes(); + + /** + * Récupère les KPIs principaux. + * Correspond à {@code DashboardResource.getKPI()} dans le serveur. + * + * @param periode Période en jours (défaut: 30). + * @return Réponse HTTP contenant les KPIs. + */ + @GET + @Path("/dashboard/kpi") + Response getKPI(@QueryParam("periode") @DefaultValue("30") int periode); + + /** + * Récupère les activités récentes. + * Correspond à {@code DashboardResource.getActivitesRecentes()} dans le serveur. + * + * @param limit Nombre d'activités à récupérer (défaut: 10). + * @return Réponse HTTP contenant les activités récentes. + */ + @GET + @Path("/dashboard/activites-recentes") + Response getActivitesRecentes(@QueryParam("limit") @DefaultValue("10") int limit); + + /** + * Récupère le résumé quotidien. + * Correspond à {@code DashboardResource.getResumeQuotidien()} dans le serveur. + * + * @return Réponse HTTP contenant le résumé quotidien. + */ + @GET + @Path("/dashboard/resume-quotidien") + Response getResumeQuotidien(); + + /** + * Récupère la liste des devis. + * Correspond à {@code DevisResource.getAllDevis()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des devis. + */ + @GET + @Path("/devis") + Response getDevis(); + + /** + * Récupère la liste des factures. + * Correspond à {@code FactureResource.getAllFactures()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des factures. + */ + @GET + @Path("/factures") + Response getFactures(); + + // === ENDPOINTS EMPLOYÉS === + + /** + * Récupère la liste des employés. + * Correspond à {@code EmployeResource.getAllEmployes()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des employés. + */ + @GET + @Path("/employes") + Response getEmployes(); + + /** + * Récupère un employé par son identifiant. + * + * @param id L'identifiant de l'employé. + * @return Réponse HTTP contenant l'employé. + */ + @GET + @Path("/employes/{id}") + Response getEmploye(@PathParam("id") String id); + + // === ENDPOINTS ÉQUIPES === + + /** + * Récupère la liste des équipes. + * Correspond à {@code EquipeResource.getAllEquipes()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des équipes. + */ + @GET + @Path("/equipes") + Response getEquipes(); + + /** + * Récupère une équipe par son identifiant. + * + * @param id L'identifiant de l'équipe. + * @return Réponse HTTP contenant l'équipe. + */ + @GET + @Path("/equipes/{id}") + Response getEquipe(@PathParam("id") String id); + + // === ENDPOINTS MATÉRIELS === + + /** + * Récupère la liste des matériels. + * Correspond à {@code MaterielResource.getAllMateriels()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des matériels. + */ + @GET + @Path("/materiels") + Response getMateriels(); + + /** + * Récupère un matériel par son identifiant. + * + * @param id L'identifiant du matériel. + * @return Réponse HTTP contenant le matériel. + */ + @GET + @Path("/materiels/{id}") + Response getMateriel(@PathParam("id") String id); + + // === ENDPOINTS STOCKS === + + /** + * Récupère la liste des stocks. + * Correspond à {@code StockResource.getAllStocks()} dans le serveur. + * + * @return Réponse HTTP contenant la liste des stocks. + */ + @GET + @Path("/stocks") + Response getStocks(); + + /** + * Récupère un stock par son identifiant. + * + * @param id L'identifiant du stock. + * @return Réponse HTTP contenant le stock. + */ + @GET + @Path("/stocks/{id}") + Response getStock(@PathParam("id") String id); + + // === ENDPOINTS NOTIFICATIONS === + + /** + * Récupère les notifications non lues pour un utilisateur. + * Correspond à {@code NotificationResource.getAllNotifications()} avec filtre nonLues=true. + * + * @param userId ID de l'utilisateur (UUID en String). + * @return Réponse HTTP contenant la liste des notifications non lues. + */ + @GET + @Path("/notifications") + Response getNotifications( + @QueryParam("userId") String userId, + @QueryParam("nonLues") @DefaultValue("true") boolean nonLues); + + // === ENDPOINTS MESSAGES === + + /** + * Récupère les messages non lus pour un utilisateur. + * Correspond à {@code MessageResource.getMessagesNonLus()} dans le serveur. + * + * @param userId ID de l'utilisateur (UUID en String). + * @return Réponse HTTP contenant la liste des messages non lus. + */ + @GET + @Path("/messages/non-lus/{userId}") + Response getMessagesNonLus(@PathParam("userId") String userId); +} + diff --git a/src/main/java/dev/lions/btpxpress/service/ChantierService.java b/src/main/java/dev/lions/btpxpress/service/ChantierService.java index c5e9a62..2a226d6 100644 --- a/src/main/java/dev/lions/btpxpress/service/ChantierService.java +++ b/src/main/java/dev/lions/btpxpress/service/ChantierService.java @@ -1,83 +1,177 @@ -package dev.lions.btpxpress.service; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Service de gestion des chantiers côté client. - *

- * Ce service encapsule la communication avec l'API backend pour les opérations - * liées aux chantiers. Il utilise le REST Client pour effectuer les appels HTTP - * vers le backend. - *

- * - * @author BTP Xpress Development Team - * @version 1.0.0 - * @since 1.0.0 - */ -@ApplicationScoped -public class ChantierService { - - private static final Logger LOG = LoggerFactory.getLogger(ChantierService.class); - - @Inject - @RestClient - BtpXpressApiClient apiClient; - - /** - * Récupère tous les chantiers depuis l'API backend. - * - * @return Liste des chantiers, ou liste vide en cas d'erreur. - */ - public List> getAllChantiers() { - try { - LOG.debug("Récupération de la liste des chantiers depuis l'API backend."); - Response response = apiClient.getChantiers(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - @SuppressWarnings("unchecked") - List> chantiers = response.readEntity(List.class); - LOG.debug("Chantiers récupérés avec succès : {} élément(s)", chantiers != null ? chantiers.size() : 0); - return chantiers != null ? chantiers : new ArrayList<>(); - } else { - LOG.warn("Erreur lors de la récupération des chantiers. Code HTTP : {}", response.getStatus()); - return new ArrayList<>(); - } - } catch (Exception e) { - LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les chantiers : {}", e.getMessage(), e); - return new ArrayList<>(); - } - } - - /** - * Récupère un chantier par son identifiant depuis l'API backend. - * - * @param id L'identifiant du chantier. - * @return Le chantier sous forme de Map, ou null en cas d'erreur. - */ - public Map getChantierById(Long id) { - try { - LOG.debug("Récupération du chantier avec ID : {}", id); - Response response = apiClient.getChantier(id); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Map chantier = response.readEntity(Map.class); - LOG.debug("Chantier récupéré avec succès."); - return chantier; - } else { - LOG.warn("Erreur lors de la récupération du chantier. Code HTTP : {}", response.getStatus()); - return null; - } - } catch (Exception e) { - LOG.error("Erreur lors de la communication avec l'API backend pour récupérer le chantier : {}", e.getMessage(), e); - return null; - } - } -} - +package dev.lions.btpxpress.service; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Service de gestion des chantiers côté client. + *

+ * Ce service encapsule la communication avec l'API backend pour les opérations + * liées aux chantiers. Il utilise le REST Client pour effectuer les appels HTTP + * vers le backend. + *

+ * + * @author BTP Xpress Development Team + * @version 1.0.0 + * @since 1.0.0 + */ +@ApplicationScoped +public class ChantierService { + + private static final Logger LOG = LoggerFactory.getLogger(ChantierService.class); + + @Inject + @RestClient + BtpXpressApiClient apiClient; + + /** + * Récupère tous les chantiers depuis l'API backend. + * + * @return Liste des chantiers, ou liste vide en cas d'erreur. + */ + public List> getAllChantiers() { + try { + LOG.debug("Récupération de la liste des chantiers depuis l'API backend."); + Response response = apiClient.getChantiers(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + List> chantiers = response.readEntity(List.class); + LOG.debug("Chantiers récupérés avec succès : {} élément(s)", chantiers != null ? chantiers.size() : 0); + return chantiers != null ? chantiers : new ArrayList<>(); + } else { + LOG.warn("Erreur lors de la récupération des chantiers. Code HTTP : {}", response.getStatus()); + return new ArrayList<>(); + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les chantiers : {}", e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * Récupère un chantier par son identifiant depuis l'API backend. + * + * @param id L'identifiant du chantier. + * @return Le chantier sous forme de Map, ou null en cas d'erreur. + */ + public Map getChantierById(Long id) { + try { + LOG.debug("Récupération du chantier avec ID : {}", id); + Response response = apiClient.getChantier(id); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + Map chantier = response.readEntity(Map.class); + LOG.debug("Chantier récupéré avec succès."); + return chantier; + } else { + LOG.warn("Erreur lors de la récupération du chantier. Code HTTP : {}", response.getStatus()); + return null; + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour récupérer le chantier : {}", e.getMessage(), e); + return null; + } + } + + /** + * Crée un nouveau chantier via l'API backend. + * + * @param chantierData Les données du chantier à créer (Map ou DTO). + * @return Le chantier créé sous forme de Map, ou null en cas d'erreur. + */ + public Map createChantier(Map chantierData) { + try { + LOG.debug("Création d'un nouveau chantier : {}", chantierData.get("nom")); + Response response = apiClient.createChantier(chantierData); + if (response.getStatus() == Response.Status.CREATED.getStatusCode() || + response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + Map chantier = response.readEntity(Map.class); + LOG.info("Chantier créé avec succès : {}", chantier.get("id")); + return chantier; + } else { + String errorMessage = response.readEntity(String.class); + LOG.warn("Erreur lors de la création du chantier. Code HTTP : {}, Message : {}", + response.getStatus(), errorMessage); + return null; + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour créer le chantier : {}", e.getMessage(), e); + return null; + } + } + + /** + * Met à jour un chantier existant via l'API backend. + * + * @param id L'identifiant du chantier (UUID en String). + * @param chantierData Les nouvelles données du chantier (Map ou DTO). + * @return Le chantier mis à jour sous forme de Map, ou null en cas d'erreur. + */ + public Map updateChantier(String id, Map chantierData) { + try { + LOG.debug("Mise à jour du chantier avec ID : {}", id); + Response response = apiClient.updateChantier(id, chantierData); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + Map chantier = response.readEntity(Map.class); + LOG.info("Chantier mis à jour avec succès : {}", id); + return chantier; + } else { + String errorMessage = response.readEntity(String.class); + LOG.warn("Erreur lors de la mise à jour du chantier. Code HTTP : {}, Message : {}", + response.getStatus(), errorMessage); + return null; + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour mettre à jour le chantier : {}", e.getMessage(), e); + return null; + } + } + + /** + * Supprime un chantier via l'API backend. + * + * @param id L'identifiant du chantier (UUID en String). + * @param permanent Si true, suppression définitive, sinon suppression logique (défaut: false). + * @return true si la suppression a réussi, false sinon. + */ + public boolean deleteChantier(String id, boolean permanent) { + try { + LOG.debug("Suppression du chantier avec ID : {} (permanent: {})", id, permanent); + Response response = apiClient.deleteChantier(id, permanent); + if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode() || + response.getStatus() == Response.Status.OK.getStatusCode()) { + LOG.info("Chantier supprimé avec succès : {}", id); + return true; + } else { + String errorMessage = response.readEntity(String.class); + LOG.warn("Erreur lors de la suppression du chantier. Code HTTP : {}, Message : {}", + response.getStatus(), errorMessage); + return false; + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour supprimer le chantier : {}", e.getMessage(), e); + return false; + } + } + + /** + * Supprime un chantier via l'API backend (suppression logique par défaut). + * + * @param id L'identifiant du chantier (UUID en String). + * @return true si la suppression a réussi, false sinon. + */ + public boolean deleteChantier(String id) { + return deleteChantier(id, false); + } +} + diff --git a/src/main/java/dev/lions/btpxpress/service/ClientService.java b/src/main/java/dev/lions/btpxpress/service/ClientService.java index 8be6bdc..9c0664e 100644 --- a/src/main/java/dev/lions/btpxpress/service/ClientService.java +++ b/src/main/java/dev/lions/btpxpress/service/ClientService.java @@ -67,6 +67,7 @@ public class ClientService { LOG.debug("Récupération du client avec ID : {}", id); Response response = apiClient.getClient(id); if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") Map client = response.readEntity(Map.class); LOG.debug("Client récupéré avec succès."); return client; diff --git a/src/main/java/dev/lions/btpxpress/service/DashboardService.java b/src/main/java/dev/lions/btpxpress/service/DashboardService.java index d535d92..fc16397 100644 --- a/src/main/java/dev/lions/btpxpress/service/DashboardService.java +++ b/src/main/java/dev/lions/btpxpress/service/DashboardService.java @@ -1,311 +1,341 @@ -package dev.lions.btpxpress.service; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.Map; - -/** - * Service pour récupérer et transformer les données du dashboard depuis l'API backend. - * - *

Ce service encapsule tous les appels à l'API dashboard et transforme - * les réponses JSON en objets Java utilisables par les vues JSF.

- * - * @author BTP Xpress Team - * @version 1.0 - */ -@ApplicationScoped -public class DashboardService { - - private static final Logger logger = LoggerFactory.getLogger(DashboardService.class); - - @Inject - @RestClient - BtpXpressApiClient apiClient; - - private final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * Récupère les métriques du dashboard principal. - * - * @return JsonNode contenant les métriques ou null en cas d'erreur - */ - public JsonNode getDashboardPrincipal() { - try { - Response response = apiClient.getDashboardPrincipal(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - if (entity == null) { - logger.warn("Réponse vide du dashboard principal"); - return null; - } - // REST Client avec Jackson désérialise déjà en Map/Object - return convertToJsonNode(entity); - } else { - logger.error("Erreur API dashboard principal: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération du dashboard principal", e); - return null; - } - } - - /** - * Convertit un objet en JsonNode, quel que soit son type (String, Map, Object, etc.). - */ - private JsonNode convertToJsonNode(Object entity) { - try { - if (entity instanceof String) { - return objectMapper.readTree((String) entity); - } else if (entity instanceof Map || entity instanceof List) { - // Map ou List sont déjà désérialisés par REST Client - return objectMapper.valueToTree(entity); - } else { - // Pour les autres objets, conversion via ObjectMapper - return objectMapper.valueToTree(entity); - } - } catch (Exception e) { - logger.error("Erreur lors de la conversion en JsonNode", e); - return null; - } - } - - /** - * Récupère les métriques des chantiers. - * - * @return JsonNode contenant les métriques des chantiers ou null en cas d'erreur - */ - public JsonNode getDashboardChantiers() { - try { - Response response = apiClient.getDashboardChantiers(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return convertToJsonNode(entity); - } else { - logger.error("Erreur API dashboard chantiers: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération du dashboard chantiers", e); - return null; - } - } - - /** - * Récupère les métriques financières. - * - * @param periode Période en jours (défaut: 30) - * @return JsonNode contenant les métriques financières ou null en cas d'erreur - */ - public JsonNode getDashboardFinances(int periode) { - try { - Response response = apiClient.getDashboardFinances(periode); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API dashboard finances: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération du dashboard finances", e); - return null; - } - } - - /** - * Récupère les métriques de maintenance. - * - * @return JsonNode contenant les métriques de maintenance ou null en cas d'erreur - */ - public JsonNode getDashboardMaintenance() { - try { - Response response = apiClient.getDashboardMaintenance(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API dashboard maintenance: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération du dashboard maintenance", e); - return null; - } - } - - /** - * Récupère les métriques des ressources. - * - * @return JsonNode contenant les métriques des ressources ou null en cas d'erreur - */ - public JsonNode getDashboardRessources() { - try { - Response response = apiClient.getDashboardRessources(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API dashboard ressources: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération du dashboard ressources", e); - return null; - } - } - - /** - * Récupère les alertes. - * - * @return JsonNode contenant les alertes ou null en cas d'erreur - */ - public JsonNode getAlertes() { - try { - Response response = apiClient.getAlertes(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API alertes: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération des alertes", e); - return null; - } - } - - /** - * Récupère les KPIs. - * - * @param periode Période en jours (défaut: 30) - * @return JsonNode contenant les KPIs ou null en cas d'erreur - */ - public JsonNode getKPI(int periode) { - try { - Response response = apiClient.getKPI(periode); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API KPI: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération des KPIs", e); - return null; - } - } - - /** - * Récupère les activités récentes. - * - * @param limit Nombre d'activités à récupérer - * @return JsonNode contenant les activités récentes ou null en cas d'erreur - */ - public JsonNode getActivitesRecentes(int limit) { - try { - Response response = apiClient.getActivitesRecentes(limit); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API activités récentes: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération des activités récentes", e); - return null; - } - } - - /** - * Récupère le résumé quotidien. - * - * @return JsonNode contenant le résumé quotidien ou null en cas d'erreur - */ - public JsonNode getResumeQuotidien() { - try { - Response response = apiClient.getResumeQuotidien(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - Object entity = response.getEntity(); - return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); - } else { - logger.error("Erreur API résumé quotidien: status {}", response.getStatus()); - return null; - } - } catch (Exception e) { - logger.error("Erreur lors de la récupération du résumé quotidien", e); - return null; - } - } - - /** - * Récupère le nombre de clients. - * - * @return Nombre de clients ou 0 en cas d'erreur - */ - public int getNombreClients() { - try { - Response response = apiClient.getClients(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - List clients = (List) response.getEntity(); - return clients != null ? clients.size() : 0; - } - return 0; - } catch (Exception e) { - logger.error("Erreur lors de la récupération du nombre de clients", e); - return 0; - } - } - - /** - * Récupère le nombre de devis en attente. - * - * @return Nombre de devis en attente ou 0 en cas d'erreur - */ - public int getNombreDevisEnAttente() { - try { - Response response = apiClient.getDevis(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - List devis = (List) response.getEntity(); - // TODO: Filtrer par statut EN_ATTENTE si l'API le permet - return devis != null ? devis.size() : 0; - } - return 0; - } catch (Exception e) { - logger.error("Erreur lors de la récupération du nombre de devis", e); - return 0; - } - } - - /** - * Récupère le nombre de factures impayées. - * - * @return Nombre de factures impayées ou 0 en cas d'erreur - */ - public int getNombreFacturesImpayees() { - try { - Response response = apiClient.getFactures(); - if (response.getStatus() == Response.Status.OK.getStatusCode()) { - List factures = (List) response.getEntity(); - // TODO: Filtrer par statut IMPAYEE si l'API le permet - return factures != null ? factures.size() : 0; - } - return 0; - } catch (Exception e) { - logger.error("Erreur lors de la récupération du nombre de factures", e); - return 0; - } - } -} - +package dev.lions.btpxpress.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +/** + * Service pour récupérer et transformer les données du dashboard depuis l'API backend. + * + *

Ce service encapsule tous les appels à l'API dashboard et transforme + * les réponses JSON en objets Java utilisables par les vues JSF.

+ * + * @author BTP Xpress Team + * @version 1.0 + */ +@ApplicationScoped +public class DashboardService { + + private static final Logger logger = LoggerFactory.getLogger(DashboardService.class); + + @Inject + @RestClient + BtpXpressApiClient apiClient; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Récupère les métriques du dashboard principal. + * + * @return JsonNode contenant les métriques ou null en cas d'erreur + */ + public JsonNode getDashboardPrincipal() { + try { + Response response = apiClient.getDashboardPrincipal(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + if (entity == null) { + logger.warn("Réponse vide du dashboard principal"); + return null; + } + // REST Client avec Jackson désérialise déjà en Map/Object + return convertToJsonNode(entity); + } else { + logger.error("Erreur API dashboard principal: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération du dashboard principal", e); + return null; + } + } + + /** + * Convertit un objet en JsonNode, quel que soit son type (String, Map, Object, etc.). + */ + private JsonNode convertToJsonNode(Object entity) { + try { + if (entity instanceof String) { + return objectMapper.readTree((String) entity); + } else if (entity instanceof Map || entity instanceof List) { + // Map ou List sont déjà désérialisés par REST Client + return objectMapper.valueToTree(entity); + } else { + // Pour les autres objets, conversion via ObjectMapper + return objectMapper.valueToTree(entity); + } + } catch (Exception e) { + logger.error("Erreur lors de la conversion en JsonNode", e); + return null; + } + } + + /** + * Récupère les métriques des chantiers. + * + * @return JsonNode contenant les métriques des chantiers ou null en cas d'erreur + */ + public JsonNode getDashboardChantiers() { + try { + Response response = apiClient.getDashboardChantiers(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return convertToJsonNode(entity); + } else { + logger.error("Erreur API dashboard chantiers: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération du dashboard chantiers", e); + return null; + } + } + + /** + * Récupère les métriques financières. + * + * @param periode Période en jours (défaut: 30) + * @return JsonNode contenant les métriques financières ou null en cas d'erreur + */ + public JsonNode getDashboardFinances(int periode) { + try { + Response response = apiClient.getDashboardFinances(periode); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API dashboard finances: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération du dashboard finances", e); + return null; + } + } + + /** + * Récupère les métriques de maintenance. + * + * @return JsonNode contenant les métriques de maintenance ou null en cas d'erreur + */ + public JsonNode getDashboardMaintenance() { + try { + Response response = apiClient.getDashboardMaintenance(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API dashboard maintenance: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération du dashboard maintenance", e); + return null; + } + } + + /** + * Récupère les métriques des ressources. + * + * @return JsonNode contenant les métriques des ressources ou null en cas d'erreur + */ + public JsonNode getDashboardRessources() { + try { + Response response = apiClient.getDashboardRessources(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API dashboard ressources: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération du dashboard ressources", e); + return null; + } + } + + /** + * Récupère les alertes. + * + * @return JsonNode contenant les alertes ou null en cas d'erreur + */ + public JsonNode getAlertes() { + try { + Response response = apiClient.getAlertes(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API alertes: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération des alertes", e); + return null; + } + } + + /** + * Récupère les KPIs. + * + * @param periode Période en jours (défaut: 30) + * @return JsonNode contenant les KPIs ou null en cas d'erreur + */ + public JsonNode getKPI(int periode) { + try { + Response response = apiClient.getKPI(periode); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API KPI: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération des KPIs", e); + return null; + } + } + + /** + * Récupère les activités récentes. + * + * @param limit Nombre d'activités à récupérer + * @return JsonNode contenant les activités récentes ou null en cas d'erreur + */ + public JsonNode getActivitesRecentes(int limit) { + try { + Response response = apiClient.getActivitesRecentes(limit); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API activités récentes: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération des activités récentes", e); + return null; + } + } + + /** + * Récupère le résumé quotidien. + * + * @return JsonNode contenant le résumé quotidien ou null en cas d'erreur + */ + public JsonNode getResumeQuotidien() { + try { + Response response = apiClient.getResumeQuotidien(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + Object entity = response.getEntity(); + return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity); + } else { + logger.error("Erreur API résumé quotidien: status {}", response.getStatus()); + return null; + } + } catch (Exception e) { + logger.error("Erreur lors de la récupération du résumé quotidien", e); + return null; + } + } + + /** + * Récupère le nombre de clients. + * + * @return Nombre de clients ou 0 en cas d'erreur + */ + public int getNombreClients() { + try { + Response response = apiClient.getClients(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + List clients = (List) response.getEntity(); + return clients != null ? clients.size() : 0; + } + return 0; + } catch (Exception e) { + logger.error("Erreur lors de la récupération du nombre de clients", e); + return 0; + } + } + + /** + * Récupère le nombre de devis en attente. + * Filtre côté client les devis avec statut EN_ATTENTE. + * + * @return Nombre de devis en attente ou 0 en cas d'erreur + */ + @SuppressWarnings("unchecked") + public int getNombreDevisEnAttente() { + try { + Response response = apiClient.getDevis(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + List> devis = response.readEntity(List.class); + if (devis == null) { + return 0; + } + // Filtrer par statut EN_ATTENTE côté client + long count = devis.stream() + .filter(d -> { + Object statut = d.get("statut"); + return statut != null && + (statut.toString().equalsIgnoreCase("EN_ATTENTE") || + statut.toString().equalsIgnoreCase("EN ATTENTE")); + }) + .count(); + return (int) count; + } + return 0; + } catch (Exception e) { + logger.error("Erreur lors de la récupération du nombre de devis", e); + return 0; + } + } + + /** + * Récupère le nombre de factures impayées. + * Filtre côté client les factures avec statut IMPAYEE ou EN_RETARD. + * + * @return Nombre de factures impayées ou 0 en cas d'erreur + */ + @SuppressWarnings("unchecked") + public int getNombreFacturesImpayees() { + try { + Response response = apiClient.getFactures(); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + List> factures = response.readEntity(List.class); + if (factures == null) { + return 0; + } + // Filtrer par statut IMPAYEE ou EN_RETARD côté client + long count = factures.stream() + .filter(f -> { + Object statut = f.get("statut"); + if (statut == null) { + return false; + } + String statutStr = statut.toString().toUpperCase(); + return statutStr.equals("IMPAYEE") || + statutStr.equals("EN_RETARD") || + statutStr.equals("EN RETARD"); + }) + .count(); + return (int) count; + } + return 0; + } catch (Exception e) { + logger.error("Erreur lors de la récupération du nombre de factures", e); + return 0; + } + } +} + diff --git a/src/main/java/dev/lions/btpxpress/service/MessageService.java b/src/main/java/dev/lions/btpxpress/service/MessageService.java new file mode 100644 index 0000000..1ef87df --- /dev/null +++ b/src/main/java/dev/lions/btpxpress/service/MessageService.java @@ -0,0 +1,90 @@ +package dev.lions.btpxpress.service; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Service de gestion des messages côté client. + *

+ * Ce service encapsule la communication avec l'API backend pour les opérations + * liées aux messages. + *

+ * + * @author BTP Xpress Development Team + * @version 1.0.0 + * @since 1.0.0 + */ +@ApplicationScoped +public class MessageService { + + private static final Logger LOG = LoggerFactory.getLogger(MessageService.class); + + @Inject + @RestClient + BtpXpressApiClient apiClient; + + /** + * Récupère le nombre de messages non lus pour un utilisateur. + * + * @param userId L'identifiant de l'utilisateur (UUID en String). + * @return Le nombre de messages non lus, ou 0 en cas d'erreur. + */ + public int getUnreadCount(String userId) { + try { + LOG.debug("Récupération du nombre de messages non lus pour l'utilisateur : {}", userId); + Response response = apiClient.getMessagesNonLus(userId); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + List> messages = response.readEntity(List.class); + int count = messages != null ? messages.size() : 0; + LOG.debug("Nombre de messages non lus : {}", count); + return count; + } else { + LOG.warn("Erreur lors de la récupération des messages non lus. Code HTTP : {}", + response.getStatus()); + return 0; + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les messages : {}", + e.getMessage(), e); + return 0; + } + } + + /** + * Récupère tous les messages non lus pour un utilisateur. + * + * @param userId L'identifiant de l'utilisateur (UUID en String). + * @return Liste des messages non lus, ou liste vide en cas d'erreur. + */ + public List> getUnreadMessages(String userId) { + try { + LOG.debug("Récupération des messages non lus pour l'utilisateur : {}", userId); + Response response = apiClient.getMessagesNonLus(userId); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + List> messages = response.readEntity(List.class); + LOG.debug("Messages non lus récupérés : {} élément(s)", + messages != null ? messages.size() : 0); + return messages != null ? messages : new ArrayList<>(); + } else { + LOG.warn("Erreur lors de la récupération des messages. Code HTTP : {}", + response.getStatus()); + return new ArrayList<>(); + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les messages : {}", + e.getMessage(), e); + return new ArrayList<>(); + } + } +} + diff --git a/src/main/java/dev/lions/btpxpress/service/NotificationService.java b/src/main/java/dev/lions/btpxpress/service/NotificationService.java new file mode 100644 index 0000000..9c6e83f --- /dev/null +++ b/src/main/java/dev/lions/btpxpress/service/NotificationService.java @@ -0,0 +1,90 @@ +package dev.lions.btpxpress.service; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Service de gestion des notifications côté client. + *

+ * Ce service encapsule la communication avec l'API backend pour les opérations + * liées aux notifications. + *

+ * + * @author BTP Xpress Development Team + * @version 1.0.0 + * @since 1.0.0 + */ +@ApplicationScoped +public class NotificationService { + + private static final Logger LOG = LoggerFactory.getLogger(NotificationService.class); + + @Inject + @RestClient + BtpXpressApiClient apiClient; + + /** + * Récupère le nombre de notifications non lues pour un utilisateur. + * + * @param userId L'identifiant de l'utilisateur (UUID en String). + * @return Le nombre de notifications non lues, ou 0 en cas d'erreur. + */ + public int getUnreadCount(String userId) { + try { + LOG.debug("Récupération du nombre de notifications non lues pour l'utilisateur : {}", userId); + Response response = apiClient.getNotifications(userId, true); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + List> notifications = response.readEntity(List.class); + int count = notifications != null ? notifications.size() : 0; + LOG.debug("Nombre de notifications non lues : {}", count); + return count; + } else { + LOG.warn("Erreur lors de la récupération des notifications non lues. Code HTTP : {}", + response.getStatus()); + return 0; + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les notifications : {}", + e.getMessage(), e); + return 0; + } + } + + /** + * Récupère toutes les notifications non lues pour un utilisateur. + * + * @param userId L'identifiant de l'utilisateur (UUID en String). + * @return Liste des notifications non lues, ou liste vide en cas d'erreur. + */ + public List> getUnreadNotifications(String userId) { + try { + LOG.debug("Récupération des notifications non lues pour l'utilisateur : {}", userId); + Response response = apiClient.getNotifications(userId, true); + if (response.getStatus() == Response.Status.OK.getStatusCode()) { + @SuppressWarnings("unchecked") + List> notifications = response.readEntity(List.class); + LOG.debug("Notifications non lues récupérées : {} élément(s)", + notifications != null ? notifications.size() : 0); + return notifications != null ? notifications : new ArrayList<>(); + } else { + LOG.warn("Erreur lors de la récupération des notifications. Code HTTP : {}", + response.getStatus()); + return new ArrayList<>(); + } + } catch (Exception e) { + LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les notifications : {}", + e.getMessage(), e); + return new ArrayList<>(); + } + } +} + diff --git a/src/main/java/dev/lions/btpxpress/view/BaseListView.java b/src/main/java/dev/lions/btpxpress/view/BaseListView.java index 154a214..5955cb6 100644 --- a/src/main/java/dev/lions/btpxpress/view/BaseListView.java +++ b/src/main/java/dev/lions/btpxpress/view/BaseListView.java @@ -1,383 +1,383 @@ -package dev.lions.btpxpress.view; - -import jakarta.annotation.PostConstruct; -import jakarta.faces.application.FacesMessage; -import jakarta.faces.context.FacesContext; -import lombok.Getter; -import lombok.Setter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.util.*; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Classe de base pour les vues de type liste/CRUD. - * - * Fonctionnalités: - * - Chargement et affichage de listes - * - Filtrage multi-critères - * - Tri (ascendant/descendant) - * - Pagination - * - CRUD complet (Create, Read, Update, Delete) - * - Sélection simple/multiple - * - Messages utilisateur (succès, erreur, warning) - * - Lazy loading pour grandes listes - * - * Principe DRY: Toute la logique commune des écrans de liste est centralisée ici. - * - * @param Type d'entité - * @param Type de l'identifiant - */ -@Getter -@Setter -public abstract class BaseListView implements Serializable { - - protected static final Logger LOG = LoggerFactory.getLogger(BaseListView.class); - private static final long serialVersionUID = 1L; - - // ========== Données ========== - protected List items = new ArrayList<>(); - protected List filteredItems = new ArrayList<>(); - protected T selectedItem; - protected List selectedItems = new ArrayList<>(); - protected T entity; // Pour les formulaires create/edit - - // ========== États ========== - protected boolean loading = false; - protected boolean editing = false; // Mode édition vs création - protected String globalFilter; // Recherche globale - - // ========== Pagination ========== - protected int first = 0; // Index de départ - protected int pageSize = 10; // Taille de page - protected int totalRecords = 0; // Nombre total d'enregistrements - - // ========== Tri ========== - protected String sortField; // Champ de tri - protected boolean sortAscending = true; // Ordre de tri - - // ========== Sélection ========== - protected String selectionMode = "single"; // single, multiple, checkbox - - /** - * Initialisation du bean au chargement de la page. - */ - @PostConstruct - public void init() { - LOG.debug("Initialisation de {}", getClass().getSimpleName()); - try { - initializeFields(); - loadItems(); - } catch (Exception e) { - LOG.error("Erreur lors de l'initialisation", e); - addErrorMessage("Erreur lors du chargement des données"); - } - } - - /** - * Initialiser les champs spécifiques de la vue. - * Override si nécessaire. - */ - protected void initializeFields() { - // À surcharger dans les classes filles si besoin - } - - /** - * Charger les items depuis la source de données. - * DOIT être implémenté par les classes filles. - */ - public abstract void loadItems(); - - /** - * Recharger les données (alias pour loadItems). - */ - public void refresh() { - LOG.debug("Rafraîchissement des données"); - loadItems(); - } - - // ========== Filtrage ========== - - /** - * Appliquer les filtres à la liste d'items. - */ - protected void applyFilters(List sourceItems, List> filters) { - if (filters == null || filters.isEmpty()) { - filteredItems = new ArrayList<>(sourceItems); - return; - } - - filteredItems = sourceItems.stream() - .filter(filters.stream().reduce(Predicate::and).orElse(x -> true)) - .collect(Collectors.toList()); - } - - /** - * Recherche avec les critères de filtrage actuels. - */ - public void search() { - LOG.debug("Recherche lancée pour {}", getClass().getSimpleName()); - first = 0; // Retour à la première page - loadItems(); - } - - /** - * Réinitialiser tous les filtres. - */ - public void resetFilters() { - LOG.debug("Réinitialisation des filtres pour {}", getClass().getSimpleName()); - globalFilter = null; - sortField = null; - sortAscending = true; - first = 0; - resetFilterFields(); - loadItems(); - } - - /** - * Réinitialiser les champs de filtre spécifiques. - * DOIT être implémenté par les classes filles. - */ - protected abstract void resetFilterFields(); - - // ========== Tri ========== - - /** - * Trier la liste par un champ donné. - */ - public void sort(String field) { - if (field.equals(sortField)) { - sortAscending = !sortAscending; - } else { - sortField = field; - sortAscending = true; - } - LOG.debug("Tri par {} ({})", field, sortAscending ? "ASC" : "DESC"); - loadItems(); - } - - // ========== Navigation ========== - - /** - * Naviguer vers la page de détails d'un item. - */ - public String viewDetails(ID id) { - LOG.debug("Redirection vers détails : {}", id); - return getDetailsPath() + "?id=" + id + "&faces-redirect=true"; - } - - /** - * Naviguer vers la page de détails de l'item sélectionné. - */ - public String viewSelectedDetails() { - if (selectedItem == null) { - addWarningMessage("Aucun élément sélectionné"); - return null; - } - return viewDetails(getEntityId(selectedItem)); - } - - /** - * Obtenir le chemin de la page de détails. - */ - protected abstract String getDetailsPath(); - - /** - * Naviguer vers la page de création. - */ - public String createNew() { - LOG.debug("Redirection vers création"); - return getCreatePath() + "?faces-redirect=true"; - } - - /** - * Obtenir le chemin de la page de création. - */ - protected abstract String getCreatePath(); - - // ========== CRUD ========== - - /** - * Préparer un nouvel item pour création. - */ - public void prepareNew() { - LOG.debug("Préparation nouvelle entité"); - entity = createNewEntity(); - editing = false; - } - - /** - * Créer une nouvelle instance de l'entité. - * DOIT être implémenté par les classes filles. - */ - protected abstract T createNewEntity(); - - /** - * Préparer un item pour édition. - */ - public void prepareEdit(T item) { - LOG.debug("Préparation édition : {}", item); - entity = item; - editing = true; - } - - /** - * Sauvegarder l'entité (création ou modification). - */ - public void save() { - try { - loading = true; - - if (editing) { - performUpdate(); - addSuccessMessage("Modification réussie"); - } else { - performCreate(); - addSuccessMessage("Création réussie"); - } - - loadItems(); - entity = null; - editing = false; - - } catch (Exception e) { - LOG.error("Erreur lors de la sauvegarde", e); - addErrorMessage("Erreur lors de la sauvegarde : " + e.getMessage()); - } finally { - loading = false; - } - } - - /** - * Créer une nouvelle entité. - * DOIT être implémenté par les classes filles. - */ - protected abstract void performCreate(); - - /** - * Mettre à jour une entité existante. - * DOIT être implémenté par les classes filles. - */ - protected abstract void performUpdate(); - - /** - * Supprimer l'item sélectionné. - */ - public void delete() { - if (selectedItem == null) { - addWarningMessage("Aucun élément sélectionné"); - return; - } - - try { - loading = true; - LOG.info("Suppression : {}", selectedItem); - performDelete(); - items.remove(selectedItem); - selectedItem = null; - addSuccessMessage("Suppression réussie"); - loadItems(); - } catch (Exception e) { - LOG.error("Erreur lors de la suppression", e); - addErrorMessage("Erreur lors de la suppression : " + e.getMessage()); - } finally { - loading = false; - } - } - - /** - * Supprimer les items sélectionnés (sélection multiple). - */ - public void deleteSelected() { - if (selectedItems == null || selectedItems.isEmpty()) { - addWarningMessage("Aucun élément sélectionné"); - return; - } - - try { - loading = true; - int count = selectedItems.size(); - for (T item : selectedItems) { - selectedItem = item; - performDelete(); - } - loadItems(); - selectedItems.clear(); - selectedItem = null; - addSuccessMessage(count + " élément(s) supprimé(s)"); - } catch (Exception e) { - LOG.error("Erreur lors de la suppression multiple", e); - addErrorMessage("Erreur lors de la suppression"); - } finally { - loading = false; - } - } - - /** - * Effectuer la suppression réelle. - * DOIT être implémenté par les classes filles. - */ - protected abstract void performDelete(); - - /** - * Obtenir l'ID d'une entité. - * DOIT être implémenté par les classes filles. - */ - protected abstract ID getEntityId(T entity); - - // ========== Messages utilisateur ========== - - protected void addSuccessMessage(String message) { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message)); - } - - protected void addErrorMessage(String message) { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message)); - } - - protected void addWarningMessage(String message) { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", message)); - } - - protected void addInfoMessage(String message) { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message)); - } - - // ========== Utilitaires ========== - - /** - * Vérifier si la liste est vide. - */ - public boolean isEmpty() { - return items == null || items.isEmpty(); - } - - /** - * Obtenir le nombre d'items. - */ - public int getItemCount() { - return items == null ? 0 : items.size(); - } - - /** - * Vérifier si un item est sélectionné. - */ - public boolean hasSelection() { - return selectedItem != null; - } - - /** - * Vérifier si plusieurs items sont sélectionnés. - */ - public boolean hasMultipleSelection() { - return selectedItems != null && !selectedItems.isEmpty(); - } -} - +package dev.lions.btpxpress.view; + +import jakarta.annotation.PostConstruct; +import jakarta.faces.application.FacesMessage; +import jakarta.faces.context.FacesContext; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Classe de base pour les vues de type liste/CRUD. + * + * Fonctionnalités: + * - Chargement et affichage de listes + * - Filtrage multi-critères + * - Tri (ascendant/descendant) + * - Pagination + * - CRUD complet (Create, Read, Update, Delete) + * - Sélection simple/multiple + * - Messages utilisateur (succès, erreur, warning) + * - Lazy loading pour grandes listes + * + * Principe DRY: Toute la logique commune des écrans de liste est centralisée ici. + * + * @param Type d'entité + * @param Type de l'identifiant + */ +@Getter +@Setter +public abstract class BaseListView implements Serializable { + + protected static final Logger LOG = LoggerFactory.getLogger(BaseListView.class); + private static final long serialVersionUID = 1L; + + // ========== Données ========== + protected List items = new ArrayList<>(); + protected List filteredItems = new ArrayList<>(); + protected T selectedItem; + protected List selectedItems = new ArrayList<>(); + protected T entity; // Pour les formulaires create/edit + + // ========== États ========== + protected boolean loading = false; + protected boolean editing = false; // Mode édition vs création + protected String globalFilter; // Recherche globale + + // ========== Pagination ========== + protected int first = 0; // Index de départ + protected int pageSize = 10; // Taille de page + protected int totalRecords = 0; // Nombre total d'enregistrements + + // ========== Tri ========== + protected String sortField; // Champ de tri + protected boolean sortAscending = true; // Ordre de tri + + // ========== Sélection ========== + protected String selectionMode = "single"; // single, multiple, checkbox + + /** + * Initialisation du bean au chargement de la page. + */ + @PostConstruct + public void init() { + LOG.debug("Initialisation de {}", getClass().getSimpleName()); + try { + initializeFields(); + loadItems(); + } catch (Exception e) { + LOG.error("Erreur lors de l'initialisation", e); + addErrorMessage("Erreur lors du chargement des données"); + } + } + + /** + * Initialiser les champs spécifiques de la vue. + * Override si nécessaire. + */ + protected void initializeFields() { + // À surcharger dans les classes filles si besoin + } + + /** + * Charger les items depuis la source de données. + * DOIT être implémenté par les classes filles. + */ + public abstract void loadItems(); + + /** + * Recharger les données (alias pour loadItems). + */ + public void refresh() { + LOG.debug("Rafraîchissement des données"); + loadItems(); + } + + // ========== Filtrage ========== + + /** + * Appliquer les filtres à la liste d'items. + */ + protected void applyFilters(List sourceItems, List> filters) { + if (filters == null || filters.isEmpty()) { + filteredItems = new ArrayList<>(sourceItems); + return; + } + + filteredItems = sourceItems.stream() + .filter(filters.stream().reduce(Predicate::and).orElse(x -> true)) + .collect(Collectors.toList()); + } + + /** + * Recherche avec les critères de filtrage actuels. + */ + public void search() { + LOG.debug("Recherche lancée pour {}", getClass().getSimpleName()); + first = 0; // Retour à la première page + loadItems(); + } + + /** + * Réinitialiser tous les filtres. + */ + public void resetFilters() { + LOG.debug("Réinitialisation des filtres pour {}", getClass().getSimpleName()); + globalFilter = null; + sortField = null; + sortAscending = true; + first = 0; + resetFilterFields(); + loadItems(); + } + + /** + * Réinitialiser les champs de filtre spécifiques. + * DOIT être implémenté par les classes filles. + */ + protected abstract void resetFilterFields(); + + // ========== Tri ========== + + /** + * Trier la liste par un champ donné. + */ + public void sort(String field) { + if (field.equals(sortField)) { + sortAscending = !sortAscending; + } else { + sortField = field; + sortAscending = true; + } + LOG.debug("Tri par {} ({})", field, sortAscending ? "ASC" : "DESC"); + loadItems(); + } + + // ========== Navigation ========== + + /** + * Naviguer vers la page de détails d'un item. + */ + public String viewDetails(ID id) { + LOG.debug("Redirection vers détails : {}", id); + return getDetailsPath() + "?id=" + id + "&faces-redirect=true"; + } + + /** + * Naviguer vers la page de détails de l'item sélectionné. + */ + public String viewSelectedDetails() { + if (selectedItem == null) { + addWarningMessage("Aucun élément sélectionné"); + return null; + } + return viewDetails(getEntityId(selectedItem)); + } + + /** + * Obtenir le chemin de la page de détails. + */ + protected abstract String getDetailsPath(); + + /** + * Naviguer vers la page de création. + */ + public String createNew() { + LOG.debug("Redirection vers création"); + return getCreatePath() + "?faces-redirect=true"; + } + + /** + * Obtenir le chemin de la page de création. + */ + protected abstract String getCreatePath(); + + // ========== CRUD ========== + + /** + * Préparer un nouvel item pour création. + */ + public void prepareNew() { + LOG.debug("Préparation nouvelle entité"); + entity = createNewEntity(); + editing = false; + } + + /** + * Créer une nouvelle instance de l'entité. + * DOIT être implémenté par les classes filles. + */ + protected abstract T createNewEntity(); + + /** + * Préparer un item pour édition. + */ + public void prepareEdit(T item) { + LOG.debug("Préparation édition : {}", item); + entity = item; + editing = true; + } + + /** + * Sauvegarder l'entité (création ou modification). + */ + public void save() { + try { + loading = true; + + if (editing) { + performUpdate(); + addSuccessMessage("Modification réussie"); + } else { + performCreate(); + addSuccessMessage("Création réussie"); + } + + loadItems(); + entity = null; + editing = false; + + } catch (Exception e) { + LOG.error("Erreur lors de la sauvegarde", e); + addErrorMessage("Erreur lors de la sauvegarde : " + e.getMessage()); + } finally { + loading = false; + } + } + + /** + * Créer une nouvelle entité. + * DOIT être implémenté par les classes filles. + */ + protected abstract void performCreate(); + + /** + * Mettre à jour une entité existante. + * DOIT être implémenté par les classes filles. + */ + protected abstract void performUpdate(); + + /** + * Supprimer l'item sélectionné. + */ + public void delete() { + if (selectedItem == null) { + addWarningMessage("Aucun élément sélectionné"); + return; + } + + try { + loading = true; + LOG.info("Suppression : {}", selectedItem); + performDelete(); + items.remove(selectedItem); + selectedItem = null; + addSuccessMessage("Suppression réussie"); + loadItems(); + } catch (Exception e) { + LOG.error("Erreur lors de la suppression", e); + addErrorMessage("Erreur lors de la suppression : " + e.getMessage()); + } finally { + loading = false; + } + } + + /** + * Supprimer les items sélectionnés (sélection multiple). + */ + public void deleteSelected() { + if (selectedItems == null || selectedItems.isEmpty()) { + addWarningMessage("Aucun élément sélectionné"); + return; + } + + try { + loading = true; + int count = selectedItems.size(); + for (T item : selectedItems) { + selectedItem = item; + performDelete(); + } + loadItems(); + selectedItems.clear(); + selectedItem = null; + addSuccessMessage(count + " élément(s) supprimé(s)"); + } catch (Exception e) { + LOG.error("Erreur lors de la suppression multiple", e); + addErrorMessage("Erreur lors de la suppression"); + } finally { + loading = false; + } + } + + /** + * Effectuer la suppression réelle. + * DOIT être implémenté par les classes filles. + */ + protected abstract void performDelete(); + + /** + * Obtenir l'ID d'une entité. + * DOIT être implémenté par les classes filles. + */ + protected abstract ID getEntityId(T entity); + + // ========== Messages utilisateur ========== + + protected void addSuccessMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message)); + } + + protected void addErrorMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message)); + } + + protected void addWarningMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", message)); + } + + protected void addInfoMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message)); + } + + // ========== Utilitaires ========== + + /** + * Vérifier si la liste est vide. + */ + public boolean isEmpty() { + return items == null || items.isEmpty(); + } + + /** + * Obtenir le nombre d'items. + */ + public int getItemCount() { + return items == null ? 0 : items.size(); + } + + /** + * Vérifier si un item est sélectionné. + */ + public boolean hasSelection() { + return selectedItem != null; + } + + /** + * Vérifier si plusieurs items sont sélectionnés. + */ + public boolean hasMultipleSelection() { + return selectedItems != null && !selectedItems.isEmpty(); + } +} + diff --git a/src/main/java/dev/lions/btpxpress/view/ChantiersView.java b/src/main/java/dev/lions/btpxpress/view/ChantiersView.java index 773a4df..29801c0 100644 --- a/src/main/java/dev/lions/btpxpress/view/ChantiersView.java +++ b/src/main/java/dev/lions/btpxpress/view/ChantiersView.java @@ -1,273 +1,397 @@ -package dev.lions.btpxpress.view; - -import dev.lions.btpxpress.service.ChantierService; -import jakarta.annotation.PostConstruct; -import jakarta.faces.view.ViewScoped; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import lombok.Getter; -import lombok.Setter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -@Named("chantiersView") -@ViewScoped -@Getter -@Setter -public class ChantiersView extends BaseListView implements Serializable { - - private static final Logger LOG = LoggerFactory.getLogger(ChantiersView.class); - - @Inject - ChantierService chantierService; - - private String filtreNom; - private String filtreClient; - private String filtreStatut; - private Long chantierId; - - @PostConstruct - public void init() { - if (filtreStatut == null) { - filtreStatut = "TOUS"; - } - loadItems(); - } - - /** - * Définit le filtre de statut (utilisé depuis les pages filtrées). - */ - public void setFiltreStatut(String statut) { - this.filtreStatut = statut; - } - - @Override - public void loadItems() { - loading = true; - try { - items = new ArrayList<>(); - - // Récupération depuis l'API backend - List> chantiersData = chantierService.getAllChantiers(); - - for (Map data : chantiersData) { - Chantier c = new Chantier(); - - // Mapping des données de l'API vers l'objet Chantier - c.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null); - c.setNom((String) data.get("nom")); - - // Le client peut être un objet ou une chaîne - Object clientObj = data.get("client"); - if (clientObj instanceof Map) { - Map clientData = (Map) clientObj; - c.setClient((String) clientData.get("raisonSociale")); - } else if (clientObj instanceof String) { - c.setClient((String) clientObj); - } else { - c.setClient("N/A"); - } - - c.setAdresse((String) data.get("adresse")); - - // Conversion des dates - if (data.get("dateDebut") != null) { - c.setDateDebut(LocalDate.parse(data.get("dateDebut").toString())); - } - if (data.get("dateFinPrevue") != null) { - c.setDateFinPrevue(LocalDate.parse(data.get("dateFinPrevue").toString())); - } - - c.setStatut((String) data.get("statut")); - - // Avancement en pourcentage - Object avancementObj = data.get("avancement"); - if (avancementObj != null) { - c.setAvancement(avancementObj instanceof Integer ? - (Integer) avancementObj : - Integer.parseInt(avancementObj.toString())); - } else { - c.setAvancement(0); - } - - // Budget et coût réel - Object montantObj = data.get("montant"); - if (montantObj != null) { - c.setBudget(montantObj instanceof Number ? - ((Number) montantObj).doubleValue() : - Double.parseDouble(montantObj.toString())); - } else { - c.setBudget(0.0); - } - - Object coutReelObj = data.get("coutReel"); - if (coutReelObj != null) { - c.setCoutReel(coutReelObj instanceof Number ? - ((Number) coutReelObj).doubleValue() : - Double.parseDouble(coutReelObj.toString())); - } else { - c.setCoutReel(0.0); - } - - items.add(c); - } - - LOG.info("Chantiers chargés depuis l'API : {} élément(s)", items.size()); - applyFilters(items, buildFilters()); - } catch (Exception e) { - LOG.error("Erreur chargement chantiers depuis l'API", e); - // En cas d'erreur, on garde une liste vide - items = new ArrayList<>(); - } finally { - loading = false; - } - } - - private List> buildFilters() { - List> filters = new ArrayList<>(); - if (filtreNom != null && !filtreNom.trim().isEmpty()) { - filters.add(c -> c.getNom().toLowerCase().contains(filtreNom.toLowerCase())); - } - if (filtreClient != null && !filtreClient.trim().isEmpty()) { - filters.add(c -> c.getClient().toLowerCase().contains(filtreClient.toLowerCase())); - } - if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) { - filters.add(c -> c.getStatut().equals(filtreStatut)); - } - return filters; - } - - @Override - protected void resetFilterFields() { - filtreNom = null; - filtreClient = null; - filtreStatut = "TOUS"; - } - - @Override - protected String getDetailsPath() { - return "/chantiers/"; - } - - @Override - protected String getCreatePath() { - return "/chantiers/nouveau"; - } - - @Override - protected void performDelete() { - LOG.info("Suppression chantier : {}", selectedItem.getId()); - // TODO: Appeler chantierService.delete(selectedItem.getId()) - } - - @Override - protected Chantier createNewEntity() { - Chantier c = new Chantier(); - c.setStatut("PLANIFIE"); - c.setAvancement(0); - c.setDateDebut(LocalDate.now()); - c.setDateCreation(LocalDateTime.now()); - return c; - } - - @Override - protected void performCreate() { - entity.setId(System.currentTimeMillis()); // Simulation ID - entity.setDateCreation(LocalDateTime.now()); - entity.setDateModification(LocalDateTime.now()); - items.add(entity); - LOG.info("Nouveau chantier créé : {}", entity.getNom()); - // TODO: Appeler chantierService.create(entity) - } - - @Override - protected void performUpdate() { - entity.setDateModification(LocalDateTime.now()); - LOG.info("Chantier modifié : {}", entity.getNom()); - // TODO: Appeler chantierService.update(entity) - } - - @Override - protected Long getEntityId(Chantier chantier) { - return chantier.getId(); - } - - /** - * Initialise un nouveau chantier pour la création. - */ - @Override - public String createNew() { - prepareNew(); - return getCreatePath() + "?faces-redirect=true"; - } - - /** - * Sauvegarde un nouveau chantier. - */ - public String saveNew() { - if (selectedItem == null) { - selectedItem = new Chantier(); - } - selectedItem.setId(System.currentTimeMillis()); // Simulation ID - selectedItem.setDateCreation(LocalDateTime.now()); - selectedItem.setDateModification(LocalDateTime.now()); - items.add(selectedItem); - LOG.info("Nouveau chantier créé : {}", selectedItem.getNom()); - return "/chantiers?faces-redirect=true"; - } - - /** - * Charge un chantier par son ID depuis les paramètres de la requête. - */ - public void loadChantierById() { - if (chantierId != null) { - loadItems(); // S'assurer que les items sont chargés - selectedItem = items.stream() - .filter(c -> c.getId().equals(chantierId)) - .findFirst() - .orElse(null); - if (selectedItem == null) { - LOG.warn("Chantier avec ID {} non trouvé", chantierId); - } - } - } - - /** - * Affiche les détails d'un chantier. - */ - public String viewDetails(Long id) { - selectedItem = items.stream() - .filter(c -> c.getId().equals(id)) - .findFirst() - .orElse(null); - if (selectedItem != null) { - return getDetailsPath() + "details?id=" + id + "&faces-redirect=true"; - } - return "/chantiers?faces-redirect=true"; - } - - @lombok.Getter - @lombok.Setter - public static class Chantier { - private Long id; - private String nom; - private String client; - private String adresse; - private LocalDate dateDebut; - private LocalDate dateFinPrevue; - private String statut; - private int avancement; - private double budget; - private double coutReel; - private LocalDateTime dateCreation; - private LocalDateTime dateModification; - } -} +package dev.lions.btpxpress.view; + +import dev.lions.btpxpress.service.ChantierService; +import jakarta.annotation.PostConstruct; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +@Named("chantiersView") +@ViewScoped +@Getter +@Setter +public class ChantiersView extends BaseListView { + + private static final Logger LOG = LoggerFactory.getLogger(ChantiersView.class); + + @Inject + ChantierService chantierService; + + private String filtreNom; + private String filtreClient; + private String filtreStatut; + private Long chantierId; + + @PostConstruct + public void init() { + if (filtreStatut == null) { + filtreStatut = "TOUS"; + } + loadItems(); + } + + /** + * Définit le filtre de statut (utilisé depuis les pages filtrées). + */ + public void setFiltreStatut(String statut) { + this.filtreStatut = statut; + } + + @Override + public void loadItems() { + loading = true; + try { + items = new ArrayList<>(); + + // Récupération depuis l'API backend + List> chantiersData = chantierService.getAllChantiers(); + + for (Map data : chantiersData) { + Chantier c = new Chantier(); + + // Mapping des données de l'API vers l'objet Chantier + // Stocker l'UUID original comme String pour les opérations CRUD + Object idObj = data.get("id"); + if (idObj != null) { + String idString = idObj.toString(); + // Stocker l'UUID comme String dans un champ caché, et utiliser hashCode pour l'affichage + c.setId(Long.valueOf(idString.hashCode())); // Pour compatibilité avec l'interface existante + c.setUuidOriginal(idString); // Stocker l'UUID original + } + c.setNom((String) data.get("nom")); + + // Le client peut être un objet ou une chaîne + Object clientObj = data.get("client"); + if (clientObj instanceof Map) { + @SuppressWarnings("unchecked") + Map clientData = (Map) clientObj; + c.setClient((String) clientData.get("raisonSociale")); + // Extraire l'ID du client si disponible + Object clientIdObj = clientData.get("id"); + if (clientIdObj != null) { + c.setClientId(clientIdObj.toString()); + } + } else if (clientObj instanceof String) { + c.setClient((String) clientObj); + } else { + c.setClient("N/A"); + } + + // Vérifier aussi si clientId est directement dans les données + Object clientIdDirect = data.get("clientId"); + if (clientIdDirect != null && c.getClientId() == null) { + c.setClientId(clientIdDirect.toString()); + } + + c.setAdresse((String) data.get("adresse")); + + // Conversion des dates + if (data.get("dateDebut") != null) { + c.setDateDebut(LocalDate.parse(data.get("dateDebut").toString())); + } + if (data.get("dateFinPrevue") != null) { + c.setDateFinPrevue(LocalDate.parse(data.get("dateFinPrevue").toString())); + } + + c.setStatut((String) data.get("statut")); + + // Avancement en pourcentage + Object avancementObj = data.get("avancement"); + if (avancementObj != null) { + c.setAvancement(avancementObj instanceof Integer ? + (Integer) avancementObj : + Integer.parseInt(avancementObj.toString())); + } else { + c.setAvancement(0); + } + + // Budget et coût réel + Object montantObj = data.get("montant"); + if (montantObj != null) { + c.setBudget(montantObj instanceof Number ? + ((Number) montantObj).doubleValue() : + Double.parseDouble(montantObj.toString())); + } else { + c.setBudget(0.0); + } + + Object coutReelObj = data.get("coutReel"); + if (coutReelObj != null) { + c.setCoutReel(coutReelObj instanceof Number ? + ((Number) coutReelObj).doubleValue() : + Double.parseDouble(coutReelObj.toString())); + } else { + c.setCoutReel(0.0); + } + + items.add(c); + } + + LOG.info("Chantiers chargés depuis l'API : {} élément(s)", items.size()); + applyFilters(items, buildFilters()); + } catch (Exception e) { + LOG.error("Erreur chargement chantiers depuis l'API", e); + // En cas d'erreur, on garde une liste vide + items = new ArrayList<>(); + } finally { + loading = false; + } + } + + private List> buildFilters() { + List> filters = new ArrayList<>(); + if (filtreNom != null && !filtreNom.trim().isEmpty()) { + filters.add(c -> c.getNom().toLowerCase().contains(filtreNom.toLowerCase())); + } + if (filtreClient != null && !filtreClient.trim().isEmpty()) { + filters.add(c -> c.getClient().toLowerCase().contains(filtreClient.toLowerCase())); + } + if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) { + filters.add(c -> c.getStatut().equals(filtreStatut)); + } + return filters; + } + + @Override + protected void resetFilterFields() { + filtreNom = null; + filtreClient = null; + filtreStatut = "TOUS"; + } + + @Override + protected String getDetailsPath() { + return "/chantiers/"; + } + + @Override + protected String getCreatePath() { + return "/chantiers/nouveau"; + } + + @Override + protected void performDelete() { + if (selectedItem == null || selectedItem.getId() == null) { + LOG.warn("Aucun chantier sélectionné pour la suppression"); + return; + } + + LOG.info("Suppression chantier : {}", selectedItem.getId()); + + // Convertir l'ID Long en String UUID (le backend utilise UUID) + String idString = convertIdToString(selectedItem); + + boolean success = chantierService.deleteChantier(idString, false); + if (success) { + LOG.info("Chantier supprimé avec succès : {}", idString); + // Recharger la liste après suppression + loadItems(); + } else { + LOG.error("Échec de la suppression du chantier : {}", idString); + } + } + + @Override + protected Chantier createNewEntity() { + Chantier c = new Chantier(); + c.setStatut("PLANIFIE"); + c.setAvancement(0); + c.setDateDebut(LocalDate.now()); + c.setDateCreation(LocalDateTime.now()); + return c; + } + + @Override + protected void performCreate() { + if (entity == null) { + LOG.warn("Aucune entité à créer"); + return; + } + + LOG.info("Création d'un nouveau chantier : {}", entity.getNom()); + + // Convertir l'entité Chantier en Map pour l'API + Map chantierData = convertChantierToMap(entity); + + Map createdChantier = chantierService.createChantier(chantierData); + if (createdChantier != null) { + LOG.info("Chantier créé avec succès : {}", createdChantier.get("id")); + // Recharger la liste après création + loadItems(); + } else { + LOG.error("Échec de la création du chantier : {}", entity.getNom()); + } + } + + @Override + protected void performUpdate() { + if (entity == null || entity.getId() == null) { + LOG.warn("Aucune entité à mettre à jour ou ID manquant"); + return; + } + + LOG.info("Mise à jour du chantier : {} (ID: {})", entity.getNom(), entity.getId()); + + // Convertir l'ID Long en String UUID + String idString = convertIdToString(entity); + + // Convertir l'entité Chantier en Map pour l'API + Map chantierData = convertChantierToMap(entity); + + Map updatedChantier = chantierService.updateChantier(idString, chantierData); + if (updatedChantier != null) { + LOG.info("Chantier mis à jour avec succès : {}", idString); + // Recharger la liste après mise à jour + loadItems(); + } else { + LOG.error("Échec de la mise à jour du chantier : {}", idString); + } + } + + @Override + protected Long getEntityId(Chantier chantier) { + return chantier.getId(); + } + + /** + * Initialise un nouveau chantier pour la création. + */ + @Override + public String createNew() { + prepareNew(); + return getCreatePath() + "?faces-redirect=true"; + } + + /** + * Sauvegarde un nouveau chantier. + */ + public String saveNew() { + if (selectedItem == null) { + selectedItem = new Chantier(); + } + selectedItem.setId(System.currentTimeMillis()); // Simulation ID + selectedItem.setDateCreation(LocalDateTime.now()); + selectedItem.setDateModification(LocalDateTime.now()); + items.add(selectedItem); + LOG.info("Nouveau chantier créé : {}", selectedItem.getNom()); + return "/chantiers?faces-redirect=true"; + } + + /** + * Charge un chantier par son ID depuis les paramètres de la requête. + */ + public void loadChantierById() { + if (chantierId != null) { + loadItems(); // S'assurer que les items sont chargés + selectedItem = items.stream() + .filter(c -> c.getId().equals(chantierId)) + .findFirst() + .orElse(null); + if (selectedItem == null) { + LOG.warn("Chantier avec ID {} non trouvé", chantierId); + } + } + } + + /** + * Affiche les détails d'un chantier. + */ + public String viewDetails(Long id) { + selectedItem = items.stream() + .filter(c -> c.getId().equals(id)) + .findFirst() + .orElse(null); + if (selectedItem != null) { + return getDetailsPath() + "details?id=" + id + "&faces-redirect=true"; + } + return "/chantiers?faces-redirect=true"; + } + + /** + * Convertit un Chantier en Map pour l'API backend. + * Le backend attend un ChantierCreateDTO avec des champs spécifiques. + */ + private Map convertChantierToMap(Chantier chantier) { + Map map = new HashMap<>(); + + if (chantier.getNom() != null) { + map.put("nom", chantier.getNom()); + } + if (chantier.getAdresse() != null) { + map.put("adresse", chantier.getAdresse()); + } + if (chantier.getDateDebut() != null) { + map.put("dateDebut", chantier.getDateDebut().toString()); + } + if (chantier.getDateFinPrevue() != null) { + map.put("dateFinPrevue", chantier.getDateFinPrevue().toString()); + } + if (chantier.getStatut() != null) { + map.put("statut", chantier.getStatut()); + } + if (chantier.getBudget() > 0) { + map.put("montantPrevu", chantier.getBudget()); + } + if (chantier.getCoutReel() > 0) { + map.put("montantReel", chantier.getCoutReel()); + } + + // Ajouter le clientId si disponible + if (chantier.getClientId() != null && !chantier.getClientId().trim().isEmpty()) { + map.put("clientId", chantier.getClientId()); + } + + return map; + } + + /** + * Convertit un ID Long en String UUID. + * Utilise l'UUID original stocké dans l'entité si disponible. + */ + private String convertIdToString(Chantier chantier) { + if (chantier == null) { + return null; + } + // Utiliser l'UUID original si disponible + if (chantier.getUuidOriginal() != null) { + return chantier.getUuidOriginal(); + } + // Sinon, essayer de convertir depuis l'ID Long + // Note: Cette conversion n'est pas idéale, mais nécessaire pour compatibilité + if (chantier.getId() != null) { + return chantier.getId().toString(); + } + return null; + } + + @lombok.Getter + @lombok.Setter + public static class Chantier { + private Long id; // ID pour affichage (hashCode de l'UUID) + private String uuidOriginal; // UUID original depuis l'API (pour les opérations CRUD) + private String nom; + private String client; // Raison sociale du client + private String clientId; // UUID du client (pour les opérations CRUD) + private String adresse; + private LocalDate dateDebut; + private LocalDate dateFinPrevue; + private String statut; + private int avancement; + private double budget; + private double coutReel; + private LocalDateTime dateCreation; + private LocalDateTime dateModification; + } +} diff --git a/src/main/java/dev/lions/btpxpress/view/ClientsView.java b/src/main/java/dev/lions/btpxpress/view/ClientsView.java index 379f68c..807020b 100644 --- a/src/main/java/dev/lions/btpxpress/view/ClientsView.java +++ b/src/main/java/dev/lions/btpxpress/view/ClientsView.java @@ -1,250 +1,249 @@ -package dev.lions.btpxpress.view; - -import dev.lions.btpxpress.service.ClientService; -import jakarta.annotation.PostConstruct; -import jakarta.faces.view.ViewScoped; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import lombok.Getter; -import lombok.Setter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -@Named("clientsView") -@ViewScoped -@Getter -@Setter -public class ClientsView extends BaseListView implements Serializable { - - private static final Logger LOG = LoggerFactory.getLogger(ClientsView.class); - - @Inject - ClientService clientService; - - private String filtreNom; - private String filtreEmail; - private String filtreVille; - private Long clientId; - - @PostConstruct - public void init() { - loadItems(); - } - - @Override - public void loadItems() { - loading = true; - try { - items = new ArrayList<>(); - - // Récupération depuis l'API backend - List> clientsData = clientService.getAllClients(); - - for (Map data : clientsData) { - Client c = new Client(); - - // Mapping des données de l'API vers l'objet Client - c.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null); - - // Raison sociale : entreprise ou "Particulier" si vide - String entreprise = (String) data.get("entreprise"); - c.setRaisonSociale(entreprise != null && !entreprise.trim().isEmpty() ? - entreprise : "Particulier"); - - // Nom complet du contact : prénom + nom - String prenom = (String) data.get("prenom"); - String nom = (String) data.get("nom"); - c.setNomContact((prenom != null ? prenom + " " : "") + (nom != null ? nom : "")); - - c.setEmail((String) data.get("email")); - c.setTelephone((String) data.get("telephone")); - c.setAdresse((String) data.get("adresse")); - c.setVille((String) data.get("ville")); - c.setCodePostal((String) data.get("codePostal")); - - // Nombre de chantiers (relation) - Object chantiersObj = data.get("chantiers"); - if (chantiersObj instanceof List) { - c.setNombreChantiers(((List) chantiersObj).size()); - } else { - c.setNombreChantiers(0); - } - - // Chiffre d'affaires total (à calculer ou récupérer) - // Pour l'instant, on met 0 car cette donnée n'est pas dans l'API - c.setChiffreAffairesTotal(0.0); - - // Date de création - if (data.get("dateCreation") != null) { - c.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString())); - } - - // Date de modification - if (data.get("dateModification") != null) { - c.setDateModification(LocalDateTime.parse(data.get("dateModification").toString())); - } - - items.add(c); - } - - LOG.info("Clients chargés depuis l'API : {} élément(s)", items.size()); - applyFilters(items, buildFilters()); - } catch (Exception e) { - LOG.error("Erreur chargement clients depuis l'API", e); - // En cas d'erreur, on garde une liste vide - items = new ArrayList<>(); - } finally { - loading = false; - } - } - - private List> buildFilters() { - List> filters = new ArrayList<>(); - if (filtreNom != null && !filtreNom.trim().isEmpty()) { - filters.add(c -> c.getRaisonSociale().toLowerCase().contains(filtreNom.toLowerCase()) || - c.getNomContact().toLowerCase().contains(filtreNom.toLowerCase())); - } - if (filtreEmail != null && !filtreEmail.trim().isEmpty()) { - filters.add(c -> c.getEmail().toLowerCase().contains(filtreEmail.toLowerCase())); - } - if (filtreVille != null && !filtreVille.trim().isEmpty()) { - filters.add(c -> c.getVille().toLowerCase().contains(filtreVille.toLowerCase())); - } - return filters; - } - - @Override - protected void resetFilterFields() { - filtreNom = null; - filtreEmail = null; - filtreVille = null; - } - - @Override - protected String getDetailsPath() { - return "/clients/"; - } - - @Override - protected String getCreatePath() { - return "/clients/nouveau"; - } - - @Override - protected void performDelete() { - LOG.info("Suppression client : {}", selectedItem.getId()); - } - - @Override - protected Client createNewEntity() { - Client client = new Client(); - client.setDateCreation(LocalDateTime.now()); - client.setDateModification(LocalDateTime.now()); - client.setNombreChantiers(0); - client.setChiffreAffairesTotal(0.0); - return client; - } - - @Override - protected void performCreate() { - if (selectedItem.getId() == null) { - selectedItem.setId(System.currentTimeMillis()); - } - selectedItem.setDateCreation(LocalDateTime.now()); - selectedItem.setDateModification(LocalDateTime.now()); - items.add(selectedItem); - LOG.info("Created: {}", selectedItem); - } - - @Override - protected void performUpdate() { - selectedItem.setDateModification(LocalDateTime.now()); - LOG.info("Updated: {}", selectedItem); - } - - @Override - protected Long getEntityId(Client entity) { - return entity.getId(); - } - - /** - * Initialise un nouveau client pour la création. - */ - @Override - public String createNew() { - selectedItem = new Client(); - return getCreatePath() + "?faces-redirect=true"; - } - - /** - * Sauvegarde un nouveau client. - */ - public String saveNew() { - if (selectedItem == null) { - selectedItem = new Client(); - } - selectedItem.setId(System.currentTimeMillis()); - selectedItem.setDateCreation(LocalDateTime.now()); - selectedItem.setDateModification(LocalDateTime.now()); - selectedItem.setNombreChantiers(0); - selectedItem.setChiffreAffairesTotal(0.0); - items.add(selectedItem); - LOG.info("Nouveau client créé : {}", selectedItem.getRaisonSociale()); - return "/clients?faces-redirect=true"; - } - - /** - * Affiche les détails d'un client. - */ - public String viewDetails(Long id) { - selectedItem = items.stream() - .filter(c -> c.getId().equals(id)) - .findFirst() - .orElse(null); - if (selectedItem != null) { - return getDetailsPath() + "details?id=" + id + "&faces-redirect=true"; - } - return "/clients?faces-redirect=true"; - } - - /** - * Charge un client par son ID depuis les paramètres de la requête. - */ - public void loadClientById() { - if (clientId != null) { - loadItems(); // S'assurer que les items sont chargés - selectedItem = items.stream() - .filter(c -> c.getId().equals(clientId)) - .findFirst() - .orElse(null); - if (selectedItem == null) { - LOG.warn("Client avec ID {} non trouvé", clientId); - } - } - } - - @lombok.Getter - @lombok.Setter - public static class Client { - private Long id; - private String raisonSociale; - private String nomContact; - private String email; - private String telephone; - private String adresse; - private String ville; - private String codePostal; - private String pays; - private int nombreChantiers; - private double chiffreAffairesTotal; - private LocalDateTime dateCreation; - private LocalDateTime dateModification; - } -} +package dev.lions.btpxpress.view; + +import dev.lions.btpxpress.service.ClientService; +import jakarta.annotation.PostConstruct; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +@Named("clientsView") +@ViewScoped +@Getter +@Setter +public class ClientsView extends BaseListView { + + private static final Logger LOG = LoggerFactory.getLogger(ClientsView.class); + + @Inject + ClientService clientService; + + private String filtreNom; + private String filtreEmail; + private String filtreVille; + private Long clientId; + + @PostConstruct + public void init() { + loadItems(); + } + + @Override + public void loadItems() { + loading = true; + try { + items = new ArrayList<>(); + + // Récupération depuis l'API backend + List> clientsData = clientService.getAllClients(); + + for (Map data : clientsData) { + Client c = new Client(); + + // Mapping des données de l'API vers l'objet Client + c.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null); + + // Raison sociale : entreprise ou "Particulier" si vide + String entreprise = (String) data.get("entreprise"); + c.setRaisonSociale(entreprise != null && !entreprise.trim().isEmpty() ? + entreprise : "Particulier"); + + // Nom complet du contact : prénom + nom + String prenom = (String) data.get("prenom"); + String nom = (String) data.get("nom"); + c.setNomContact((prenom != null ? prenom + " " : "") + (nom != null ? nom : "")); + + c.setEmail((String) data.get("email")); + c.setTelephone((String) data.get("telephone")); + c.setAdresse((String) data.get("adresse")); + c.setVille((String) data.get("ville")); + c.setCodePostal((String) data.get("codePostal")); + + // Nombre de chantiers (relation) + Object chantiersObj = data.get("chantiers"); + if (chantiersObj instanceof List) { + c.setNombreChantiers(((List) chantiersObj).size()); + } else { + c.setNombreChantiers(0); + } + + // Chiffre d'affaires total (à calculer ou récupérer) + // Pour l'instant, on met 0 car cette donnée n'est pas dans l'API + c.setChiffreAffairesTotal(0.0); + + // Date de création + if (data.get("dateCreation") != null) { + c.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString())); + } + + // Date de modification + if (data.get("dateModification") != null) { + c.setDateModification(LocalDateTime.parse(data.get("dateModification").toString())); + } + + items.add(c); + } + + LOG.info("Clients chargés depuis l'API : {} élément(s)", items.size()); + applyFilters(items, buildFilters()); + } catch (Exception e) { + LOG.error("Erreur chargement clients depuis l'API", e); + // En cas d'erreur, on garde une liste vide + items = new ArrayList<>(); + } finally { + loading = false; + } + } + + private List> buildFilters() { + List> filters = new ArrayList<>(); + if (filtreNom != null && !filtreNom.trim().isEmpty()) { + filters.add(c -> c.getRaisonSociale().toLowerCase().contains(filtreNom.toLowerCase()) || + c.getNomContact().toLowerCase().contains(filtreNom.toLowerCase())); + } + if (filtreEmail != null && !filtreEmail.trim().isEmpty()) { + filters.add(c -> c.getEmail().toLowerCase().contains(filtreEmail.toLowerCase())); + } + if (filtreVille != null && !filtreVille.trim().isEmpty()) { + filters.add(c -> c.getVille().toLowerCase().contains(filtreVille.toLowerCase())); + } + return filters; + } + + @Override + protected void resetFilterFields() { + filtreNom = null; + filtreEmail = null; + filtreVille = null; + } + + @Override + protected String getDetailsPath() { + return "/clients/"; + } + + @Override + protected String getCreatePath() { + return "/clients/nouveau"; + } + + @Override + protected void performDelete() { + LOG.info("Suppression client : {}", selectedItem.getId()); + } + + @Override + protected Client createNewEntity() { + Client client = new Client(); + client.setDateCreation(LocalDateTime.now()); + client.setDateModification(LocalDateTime.now()); + client.setNombreChantiers(0); + client.setChiffreAffairesTotal(0.0); + return client; + } + + @Override + protected void performCreate() { + if (selectedItem.getId() == null) { + selectedItem.setId(System.currentTimeMillis()); + } + selectedItem.setDateCreation(LocalDateTime.now()); + selectedItem.setDateModification(LocalDateTime.now()); + items.add(selectedItem); + LOG.info("Created: {}", selectedItem); + } + + @Override + protected void performUpdate() { + selectedItem.setDateModification(LocalDateTime.now()); + LOG.info("Updated: {}", selectedItem); + } + + @Override + protected Long getEntityId(Client entity) { + return entity.getId(); + } + + /** + * Initialise un nouveau client pour la création. + */ + @Override + public String createNew() { + selectedItem = new Client(); + return getCreatePath() + "?faces-redirect=true"; + } + + /** + * Sauvegarde un nouveau client. + */ + public String saveNew() { + if (selectedItem == null) { + selectedItem = new Client(); + } + selectedItem.setId(System.currentTimeMillis()); + selectedItem.setDateCreation(LocalDateTime.now()); + selectedItem.setDateModification(LocalDateTime.now()); + selectedItem.setNombreChantiers(0); + selectedItem.setChiffreAffairesTotal(0.0); + items.add(selectedItem); + LOG.info("Nouveau client créé : {}", selectedItem.getRaisonSociale()); + return "/clients?faces-redirect=true"; + } + + /** + * Affiche les détails d'un client. + */ + public String viewDetails(Long id) { + selectedItem = items.stream() + .filter(c -> c.getId().equals(id)) + .findFirst() + .orElse(null); + if (selectedItem != null) { + return getDetailsPath() + "details?id=" + id + "&faces-redirect=true"; + } + return "/clients?faces-redirect=true"; + } + + /** + * Charge un client par son ID depuis les paramètres de la requête. + */ + public void loadClientById() { + if (clientId != null) { + loadItems(); // S'assurer que les items sont chargés + selectedItem = items.stream() + .filter(c -> c.getId().equals(clientId)) + .findFirst() + .orElse(null); + if (selectedItem == null) { + LOG.warn("Client avec ID {} non trouvé", clientId); + } + } + } + + @lombok.Getter + @lombok.Setter + public static class Client { + private Long id; + private String raisonSociale; + private String nomContact; + private String email; + private String telephone; + private String adresse; + private String ville; + private String codePostal; + private String pays; + private int nombreChantiers; + private double chiffreAffairesTotal; + private LocalDateTime dateCreation; + private LocalDateTime dateModification; + } +} diff --git a/src/main/java/dev/lions/btpxpress/view/DashboardView.java b/src/main/java/dev/lions/btpxpress/view/DashboardView.java index b439699..210bc9e 100644 --- a/src/main/java/dev/lions/btpxpress/view/DashboardView.java +++ b/src/main/java/dev/lions/btpxpress/view/DashboardView.java @@ -1,911 +1,911 @@ -package dev.lions.btpxpress.view; - -import com.fasterxml.jackson.databind.JsonNode; -import jakarta.annotation.PostConstruct; -import jakarta.faces.view.ViewScoped; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import dev.lions.btpxpress.service.DashboardService; -import lombok.Getter; -import lombok.Setter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * Bean de vue pour le tableau de bord principal optimisé BTP Xpress. - * - *

Architecture 2025 - Dashboard Complet

- * - *

Ce bean orchestre l'affichage du tableau de bord professionnel couvrant - * TOUS les aspects métiers de BTP Xpress :

- *
    - *
  • Chantiers: Vue globale, actifs, en retard, budget, avancement
  • - *
  • Ressources Humaines: Employés actifs, équipes, disponibilités en attente
  • - *
  • Matériel: Disponibilité, maintenance, alertes critiques
  • - *
  • Planning: Événements du jour, conflits détectés
  • - *
  • Finances: Budget vs coût réel, analyse par chantier
  • - *
  • Maintenance: En retard, planifiées, alertes
  • - *
  • Documents: Accès rapide aux 5 derniers documents
  • - *
  • Alertes: Vue consolidée de tout ce qui nécessite attention immédiate
  • - *
- * - *

Principe fondamental : AUCUNE donnée fictive. Toutes les données - * proviennent strictement de l'API backend via les endpoints {@code /api/v1/dashboard/*}

- * - *

Voir {@code DASHBOARD_CONCEPTION.md} pour la documentation détaillée de l'architecture.

- * - * @author BTP Xpress Development Team - * @version 2.0.0 - Dashboard optimisé - * @since 1.0.0 - */ -@Named("dashboardView") -@ViewScoped -@Getter -@Setter -public class DashboardView implements Serializable { - - private static final long serialVersionUID = 1L; - private static final Logger logger = LoggerFactory.getLogger(DashboardView.class); - - /** - * Format de date français : dd/MM/yyyy - */ - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); - - /** - * Format de date-heure français : dd/MM/yyyy HH:mm - */ - private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); - - @Inject - private DashboardService dashboardService; - - // ============================================================================ - // MÉTRIQUES PRINCIPALES (KPIs en haut du dashboard) - // ============================================================================ - - /** - * Nombre total de chantiers dans le système - */ - private long nombreChantiers = 0; - - /** - * Nombre de chantiers actifs (EN_COURS + PLANIFIE) - */ - private long chantiersActifs = 0; - - /** - * Taux d'activité des chantiers en pourcentage - */ - private double tauxActiviteChantiers = 0.0; - - /** - * Nombre total d'équipes - */ - private long nombreEquipes = 0; - - /** - * Nombre d'équipes disponibles - */ - private long equipesDisponibles = 0; - - /** - * Taux de disponibilité des équipes en pourcentage - */ - private double tauxDisponibiliteEquipes = 0.0; - - /** - * Nombre de maintenances en retard (ALERTE CRITIQUE) - */ - private long maintenancesEnRetard = 0; - - /** - * Nombre de maintenances planifiées - */ - private long maintenancesPlanifiees = 0; - - /** - * Indicateur d'alerte de retard de maintenance - */ - private boolean alerteRetardMaintenance = false; - - // ============================================================================ - // MÉTRIQUES RESSOURCES - // ============================================================================ - - /** - * Nombre total d'employés - */ - private long nombreEmployes = 0; - - /** - * Nombre d'employés actifs - */ - private long employesActifs = 0; - - /** - * Taux d'activité des employés en pourcentage - */ - private double tauxActiviteEmployes = 0.0; - - /** - * Nombre total de matériel - */ - private long nombreMateriel = 0; - - /** - * Nombre de matériel disponible - */ - private long materielDisponible = 0; - - /** - * Taux de disponibilité du matériel en pourcentage - */ - private double tauxDisponibiliteMateriel = 0.0; - - /** - * Nombre de disponibilités (congés, absences) en attente de validation - */ - private long disponibilitesEnAttenteCount = 0; - - // ============================================================================ - // MÉTRIQUES PLANNING - // ============================================================================ - - /** - * Nombre d'événements de planning aujourd'hui - */ - private long evenementsAujourdhui = 0; - - /** - * Nombre de conflits de planning détectés sur les 7 prochains jours - */ - private long conflitsPlanningCount = 0; - - // ============================================================================ - // MÉTRIQUES DOCUMENTS - // ============================================================================ - - /** - * Nombre total de documents dans le système - */ - private long nombreDocuments = 0; - - // ============================================================================ - // ALERTES CONSOLIDÉES - // ============================================================================ - - /** - * Nombre total d'alertes nécessitant une attention immédiate - */ - private long totalAlertes = 0; - - /** - * Indicateur d'alerte critique (true si au moins une alerte) - */ - private boolean alerteCritique = false; - - /** - * Nombre d'alertes de maintenance - */ - private long alertesMaintenanceCount = 0; - - /** - * Nombre d'alertes de chantiers en retard - */ - private long alertesChantiersCount = 0; - - /** - * Nombre d'alertes de disponibilités en attente - */ - private long alertesDisponibilitesCount = 0; - - /** - * Nombre d'alertes de conflits de planning - */ - private long alertesConflitsCount = 0; - - // ============================================================================ - // LISTES DE DONNÉES POUR AFFICHAGE - // ============================================================================ - - /** - * Liste des chantiers actifs avec toutes leurs informations - */ - private List chantiersActifsList = new ArrayList<>(); - - /** - * Liste des chantiers en retard - */ - private List chantiersEnRetardList = new ArrayList<>(); - - /** - * Liste des maintenances en retard - */ - private List maintenancesEnRetardList = new ArrayList<>(); - - /** - * Liste des disponibilités en attente de validation - */ - private List disponibilitesEnAttenteList = new ArrayList<>(); - - /** - * Liste des 5 documents les plus récents - */ - private List documentsRecentsList = new ArrayList<>(); - - /** - * Initialise le dashboard en chargeant toutes les données depuis l'API backend. - * - *

Cette méthode est appelée automatiquement après la construction du bean (PostConstruct). - * Elle orchestre le chargement de toutes les métriques et données via le DashboardService.

- * - *

Endpoints API appelés :

- *
    - *
  • {@code GET /api/v1/dashboard} - Métriques principales
  • - *
  • {@code GET /api/v1/dashboard/chantiers} - Chantiers actifs et en retard
  • - *
  • {@code GET /api/v1/dashboard/ressources} - Ressources humaines et matérielles
  • - *
  • {@code GET /api/v1/dashboard/maintenance} - Maintenances
  • - *
  • {@code GET /api/v1/dashboard/alertes} - Alertes consolidées
  • - *
- */ - @PostConstruct - public void init() { - logger.info("═══════════════════════════════════════════════════════════════"); - logger.info("Initialisation du Dashboard BTP Xpress - Version 2.0 Optimisée"); - logger.info("═══════════════════════════════════════════════════════════════"); - - // Chargement séquentiel des différentes sections du dashboard - loadDashboardPrincipal(); - loadDashboardChantiers(); - loadDashboardRessources(); - loadDashboardMaintenance(); - loadAlertes(); - - logger.info("✓ Dashboard initialisé avec succès - {} alertes détectées", totalAlertes); - } - - /** - * Charge les métriques du dashboard principal depuis l'API. - * - *

Récupère les KPIs globaux affichés en haut du dashboard :

- *
    - *
  • Chantiers : total, actifs, taux d'activité
  • - *
  • Équipes : total, disponibles, taux de disponibilité
  • - *
  • Employés : total, actifs, taux d'activité
  • - *
  • Matériel : total, disponible, taux de disponibilité
  • - *
  • Maintenance : en retard, planifiées, alerte
  • - *
  • Planning : événements aujourd'hui
  • - *
  • Documents : total, récents
  • - *
- * - *

Endpoint : {@code GET /api/v1/dashboard}

- */ - private void loadDashboardPrincipal() { - try { - logger.debug("→ Chargement dashboard principal..."); - JsonNode dashboard = dashboardService.getDashboardPrincipal(); - - if (dashboard != null) { - // Métriques chantiers - JsonNode chantiers = dashboard.get("chantiers"); - if (chantiers != null) { - nombreChantiers = chantiers.get("total").asLong(0); - chantiersActifs = chantiers.get("actifs").asLong(0); - tauxActiviteChantiers = chantiers.get("tauxActivite").asDouble(0.0); - logger.debug(" ✓ Chantiers: {} total, {} actifs ({} %)", - nombreChantiers, chantiersActifs, String.format("%.1f", tauxActiviteChantiers)); - } - - // Métriques équipes - JsonNode equipes = dashboard.get("equipes"); - if (equipes != null) { - nombreEquipes = equipes.get("total").asLong(0); - equipesDisponibles = equipes.get("disponibles").asLong(0); - tauxDisponibiliteEquipes = equipes.get("tauxDisponibilite").asDouble(0.0); - logger.debug(" ✓ Équipes: {} total, {} disponibles ({} %)", - nombreEquipes, equipesDisponibles, String.format("%.1f", tauxDisponibiliteEquipes)); - } - - // Métriques employés - JsonNode employes = dashboard.get("employes"); - if (employes != null) { - nombreEmployes = employes.get("total").asLong(0); - employesActifs = employes.get("actifs").asLong(0); - tauxActiviteEmployes = employes.get("tauxActivite").asDouble(0.0); - logger.debug(" ✓ Employés: {} total, {} actifs ({} %)", - nombreEmployes, employesActifs, String.format("%.1f", tauxActiviteEmployes)); - } - - // Métriques matériel - JsonNode materiel = dashboard.get("materiel"); - if (materiel != null) { - nombreMateriel = materiel.get("total").asLong(0); - materielDisponible = materiel.get("disponible").asLong(0); - tauxDisponibiliteMateriel = materiel.get("tauxDisponibilite").asDouble(0.0); - logger.debug(" ✓ Matériel: {} total, {} disponible ({} %)", - nombreMateriel, materielDisponible, String.format("%.1f", tauxDisponibiliteMateriel)); - } - - // Métriques maintenance - JsonNode maintenance = dashboard.get("maintenance"); - if (maintenance != null) { - maintenancesEnRetard = maintenance.get("enRetard").asLong(0); - maintenancesPlanifiees = maintenance.get("planifiees").asLong(0); - alerteRetardMaintenance = maintenance.get("alerteRetard").asBoolean(false); - logger.debug(" ✓ Maintenance: {} en retard, {} planifiées [Alerte: {}]", - maintenancesEnRetard, maintenancesPlanifiees, alerteRetardMaintenance); - } - - // Métriques planning - JsonNode planning = dashboard.get("planning"); - if (planning != null) { - evenementsAujourdhui = planning.get("evenementsAujourdhui").asLong(0); - disponibilitesEnAttenteCount = planning.get("disponibilitesEnAttente").asLong(0); - logger.debug(" ✓ Planning: {} événements aujourd'hui, {} disponibilités en attente", - evenementsAujourdhui, disponibilitesEnAttenteCount); - } - - // Métriques documents - JsonNode documents = dashboard.get("documents"); - if (documents != null) { - nombreDocuments = documents.get("total").asLong(0); - - // Documents récents - JsonNode recentsNode = documents.get("recents"); - if (recentsNode != null && recentsNode.isArray()) { - documentsRecentsList.clear(); - Iterator iterator = recentsNode.elements(); - while (iterator.hasNext()) { - JsonNode doc = iterator.next(); - DocumentRecent docRecent = new DocumentRecent(); - docRecent.setId(doc.get("id").asText()); - docRecent.setNom(doc.get("nom").asText()); - docRecent.setType(doc.get("type").asText()); - - String dateCreationStr = doc.get("dateCreation").asText(); - try { - docRecent.setDateCreation(LocalDateTime.parse(dateCreationStr)); - } catch (Exception e) { - logger.warn("Erreur parsing date document: {}", dateCreationStr); - } - - documentsRecentsList.add(docRecent); - } - } - logger.debug(" ✓ Documents: {} total, {} récents chargés", - nombreDocuments, documentsRecentsList.size()); - } - } else { - logger.warn("⚠ Dashboard principal: réponse null de l'API"); - } - } catch (Exception e) { - logger.error("❌ Erreur lors du chargement du dashboard principal", e); - } - } - - /** - * Charge les métriques et listes des chantiers depuis l'API. - * - *

Récupère :

- *
    - *
  • Liste complète des chantiers actifs (EN_COURS + PLANIFIE)
  • - *
  • Liste des chantiers en retard (date fin prévue dépassée)
  • - *
- * - *

Endpoint : {@code GET /api/v1/dashboard/chantiers}

- */ - private void loadDashboardChantiers() { - try { - logger.debug("→ Chargement dashboard chantiers..."); - JsonNode dashboard = dashboardService.getDashboardChantiers(); - - if (dashboard != null) { - // Chantiers actifs - JsonNode chantiersActifsNode = dashboard.get("chantiersActifs"); - if (chantiersActifsNode != null && chantiersActifsNode.isArray()) { - chantiersActifsList.clear(); - Iterator iterator = chantiersActifsNode.elements(); - - while (iterator.hasNext()) { - JsonNode chantier = iterator.next(); - ChantierResume c = new ChantierResume(); - c.setId(chantier.get("id").asText()); - c.setNom(chantier.get("nom").asText("")); - c.setAdresse(chantier.get("adresse").asText("")); - c.setClient(chantier.get("client").asText("Non assigné")); - - // Dates - if (chantier.has("dateDebut") && !chantier.get("dateDebut").isNull()) { - try { - c.setDateDebut(LocalDate.parse(chantier.get("dateDebut").asText())); - } catch (Exception e) { - logger.warn("Erreur parsing dateDebut: {}", chantier.get("dateDebut").asText()); - } - } - if (chantier.has("dateFinPrevue") && !chantier.get("dateFinPrevue").isNull()) { - try { - c.setDateFinPrevue(LocalDate.parse(chantier.get("dateFinPrevue").asText())); - } catch (Exception e) { - logger.warn("Erreur parsing dateFinPrevue: {}", chantier.get("dateFinPrevue").asText()); - } - } - - // Métriques - c.setStatut(chantier.get("statut").asText("")); - c.setBudget(chantier.get("budget").asDouble(0.0)); - c.setCoutReel(chantier.get("coutReel").asDouble(0.0)); - c.setAvancement(chantier.get("avancement").asInt(0)); - - chantiersActifsList.add(c); - } - - logger.debug(" ✓ {} chantiers actifs chargés", chantiersActifsList.size()); - } - - // Chantiers en retard - JsonNode chantiersEnRetardNode = dashboard.get("chantiersEnRetard"); - if (chantiersEnRetardNode != null && chantiersEnRetardNode.isArray()) { - chantiersEnRetardList.clear(); - Iterator iterator = chantiersEnRetardNode.elements(); - - while (iterator.hasNext()) { - JsonNode chantier = iterator.next(); - ChantierEnRetard c = new ChantierEnRetard(); - c.setId(chantier.get("id").asText()); - c.setNom(chantier.get("nom").asText("")); - - if (chantier.has("dateFinPrevue") && !chantier.get("dateFinPrevue").isNull()) { - try { - c.setDateFinPrevue(LocalDate.parse(chantier.get("dateFinPrevue").asText())); - } catch (Exception e) { - logger.warn("Erreur parsing dateFinPrevue retard: {}", chantier.get("dateFinPrevue").asText()); - } - } - - c.setJoursRetard(chantier.get("joursRetard").asLong(0)); - chantiersEnRetardList.add(c); - } - - logger.debug(" ✓ {} chantiers en retard chargés", chantiersEnRetardList.size()); - } - } else { - logger.warn("⚠ Dashboard chantiers: réponse null de l'API"); - } - } catch (Exception e) { - logger.error("❌ Erreur lors du chargement du dashboard chantiers", e); - } - } - - /** - * Charge les métriques des ressources (RH et matérielles) depuis l'API. - * - *

Récupère :

- *
    - *
  • Statistiques des équipes (déjà chargées dans principal)
  • - *
  • Statistiques des employés (déjà chargées dans principal)
  • - *
  • Statistiques du matériel (déjà chargées dans principal)
  • - *
  • Liste des disponibilités en attente (congés, absences à valider)
  • - *
- * - *

Endpoint : {@code GET /api/v1/dashboard/ressources}

- */ - private void loadDashboardRessources() { - try { - logger.debug("→ Chargement dashboard ressources..."); - JsonNode ressources = dashboardService.getDashboardRessources(); - - if (ressources != null) { - // Les statistiques équipes/employés/matériel sont déjà chargées dans loadDashboardPrincipal - // Ici on charge les disponibilités en attente - - JsonNode disponibilites = ressources.get("disponibilites"); - if (disponibilites != null) { - JsonNode enAttenteDetails = disponibilites.get("enAttenteDetails"); - if (enAttenteDetails != null && enAttenteDetails.isArray()) { - disponibilitesEnAttenteList.clear(); - Iterator iterator = enAttenteDetails.elements(); - - while (iterator.hasNext()) { - JsonNode dispo = iterator.next(); - DisponibiliteEnAttente d = new DisponibiliteEnAttente(); - d.setId(dispo.get("id").asText()); - d.setEmploye(dispo.get("employe").asText("")); - d.setType(dispo.get("type").asText("")); - d.setMotif(dispo.get("motif").asText("")); - - try { - d.setDateDebut(LocalDateTime.parse(dispo.get("dateDebut").asText())); - d.setDateFin(LocalDateTime.parse(dispo.get("dateFin").asText())); - } catch (Exception e) { - logger.warn("Erreur parsing dates disponibilité"); - } - - disponibilitesEnAttenteList.add(d); - } - - logger.debug(" ✓ {} disponibilités en attente chargées", disponibilitesEnAttenteList.size()); - } - } - } else { - logger.warn("⚠ Dashboard ressources: réponse null de l'API"); - } - } catch (Exception e) { - logger.error("❌ Erreur lors du chargement du dashboard ressources", e); - } - } - - /** - * Charge les métriques de maintenance depuis l'API. - * - *

Récupère :

- *
    - *
  • Statistiques de maintenance (en retard, planifiées - déjà dans principal)
  • - *
  • Liste détaillée des maintenances en retard
  • - *
- * - *

Endpoint : {@code GET /api/v1/dashboard/maintenance}

- */ - private void loadDashboardMaintenance() { - try { - logger.debug("→ Chargement dashboard maintenance..."); - JsonNode maintenance = dashboardService.getDashboardMaintenance(); - - if (maintenance != null) { - // Maintenances en retard (détails) - JsonNode maintenancesEnRetardNode = maintenance.get("maintenancesEnRetard"); - if (maintenancesEnRetardNode != null && maintenancesEnRetardNode.isArray()) { - maintenancesEnRetardList.clear(); - Iterator iterator = maintenancesEnRetardNode.elements(); - int count = 0; - - // Limiter à 5 maintenances pour l'affichage - while (iterator.hasNext() && count < 5) { - JsonNode maint = iterator.next(); - MaintenanceEnRetard m = new MaintenanceEnRetard(); - m.setId(maint.get("id").asText()); - m.setMateriel(maint.get("materiel").asText("")); - m.setType(maint.get("type").asText("")); - m.setDescription(maint.get("description").asText("")); - - if (maint.has("datePrevue") && !maint.get("datePrevue").isNull()) { - try { - m.setDatePrevue(LocalDate.parse(maint.get("datePrevue").asText())); - } catch (Exception e) { - logger.warn("Erreur parsing datePrevue maintenance"); - } - } - - m.setJoursRetard(maint.get("joursRetard").asLong(0)); - maintenancesEnRetardList.add(m); - count++; - } - - logger.debug(" ✓ {} maintenances en retard chargées (affichage limité à 5)", - maintenancesEnRetardList.size()); - } - } else { - logger.warn("⚠ Dashboard maintenance: réponse null de l'API"); - } - } catch (Exception e) { - logger.error("❌ Erreur lors du chargement du dashboard maintenance", e); - } - } - - /** - * Charge les alertes consolidées depuis l'API. - * - *

Récupère la vue d'ensemble de toutes les alertes nécessitant une attention immédiate :

- *
    - *
  • Maintenances en retard
  • - *
  • Chantiers en retard
  • - *
  • Disponibilités en attente de validation
  • - *
  • Conflits de planning détectés
  • - *
- * - *

Endpoint : {@code GET /api/v1/dashboard/alertes}

- */ - private void loadAlertes() { - try { - logger.debug("→ Chargement alertes..."); - JsonNode alertes = dashboardService.getAlertes(); - - if (alertes != null) { - totalAlertes = alertes.get("totalAlertes").asLong(0); - alerteCritique = alertes.get("alerteCritique").asBoolean(false); - - // Détail par type d'alerte - JsonNode maintenanceNode = alertes.get("maintenance"); - if (maintenanceNode != null) { - alertesMaintenanceCount = maintenanceNode.get("enRetard").asLong(0); - } - - JsonNode chantiersNode = alertes.get("chantiers"); - if (chantiersNode != null) { - alertesChantiersCount = chantiersNode.get("enRetard").asLong(0); - } - - JsonNode disponibilitesNode = alertes.get("disponibilites"); - if (disponibilitesNode != null) { - alertesDisponibilitesCount = disponibilitesNode.get("enAttente").asLong(0); - } - - JsonNode planningNode = alertes.get("planning"); - if (planningNode != null && planningNode.has("conflits")) { - alertesConflitsCount = planningNode.get("conflits").asLong(0); - conflitsPlanningCount = alertesConflitsCount; - } - - logger.debug(" ✓ Alertes chargées: {} total [Maintenance: {}, Chantiers: {}, Disponibilités: {}, Conflits: {}]", - totalAlertes, alertesMaintenanceCount, alertesChantiersCount, - alertesDisponibilitesCount, alertesConflitsCount); - } else { - logger.warn("⚠ Alertes: réponse null de l'API"); - } - } catch (Exception e) { - logger.error("❌ Erreur lors du chargement des alertes", e); - } - } - - /** - * Rafraîchit toutes les données du dashboard. - * - *

Cette méthode peut être appelée via un bouton "Rafraîchir" ou un composant Poll - * pour mettre à jour les données en temps réel.

- */ - public void rafraichir() { - logger.info("⟳ Rafraîchissement manuel du dashboard demandé"); - init(); - } - - /** - * Rafraîchit uniquement les alertes. - * - *

Méthode optimisée pour le polling automatique des alertes (toutes les 30 secondes).

- */ - public void refreshAlertes() { - logger.debug("⟳ Rafraîchissement des alertes..."); - loadAlertes(); - } - - /** - * Calcule le taux d'utilisation global moyen. - * - *

Moyenne des 3 principaux taux :

- *
    - *
  • Taux d'activité des chantiers
  • - *
  • Taux d'activité des employés
  • - *
  • Taux de disponibilité du matériel
  • - *
- * - * @return Taux d'utilisation global en pourcentage (0-100) - */ - public double getTauxUtilisationGlobal() { - return (tauxActiviteChantiers + tauxActiviteEmployes + tauxDisponibiliteMateriel) / 3.0; - } - - /** - * Retourne l'icône PrimeIcons correspondant à un type de document. - * - * @param type Type de document (DEVIS, FACTURE, CONTRAT, PLAN, AUTRE) - * @return Classe CSS de l'icône PrimeIcons - */ - public String getIconeDocument(String type) { - if (type == null) return "pi pi-file"; - - switch (type.toUpperCase()) { - case "DEVIS": - case "FACTURE": - return "pi pi-file-pdf"; - case "CONTRAT": - return "pi pi-file-edit"; - case "PLAN": - return "pi pi-image"; - default: - return "pi pi-file"; - } - } - - /** - * Retourne la classe CSS de badge coloré selon le statut d'un chantier. - * - * @param statut Statut du chantier (EN_COURS, PLANIFIE, TERMINE, SUSPENDU, ANNULE) - * @return Classe CSS du badge - */ - public String getBadgeClasseStatut(String statut) { - if (statut == null) return ""; - - switch (statut.toUpperCase()) { - case "EN_COURS": - return "badge-en-cours"; - case "PLANIFIE": - return "badge-planifie"; - case "TERMINE": - return "badge-termine"; - case "SUSPENDU": - return "badge-suspendu"; - case "ANNULE": - return "badge-annule"; - default: - return ""; - } - } - - /** - * Retourne la sévérité PrimeFaces d'un badge selon le type de disponibilité. - * - * @param type Type de disponibilité (CONGE, MALADIE, FORMATION) - * @return Sévérité du badge (info, warning, success) - */ - public String getSeveriteDisponibilite(String type) { - if (type == null) return "info"; - - switch (type.toUpperCase()) { - case "CONGE": - return "info"; - case "MALADIE": - return "warning"; - case "FORMATION": - return "success"; - default: - return "info"; - } - } - - // ============================================================================ - // CLASSES INTERNES - DTOs pour l'affichage - // ============================================================================ - - /** - * Résumé d'un chantier actif pour affichage dans le tableau. - */ - @lombok.Getter - @lombok.Setter - public static class ChantierResume implements Serializable { - private String id; - private String nom; - private String adresse; - private String client; - private LocalDate dateDebut; - private LocalDate dateFinPrevue; - private String statut; - private double budget; - private double coutReel; - private int avancement; - - /** - * Retourne la date de début formatée en français. - * @return Date au format dd/MM/yyyy - */ - public String getDateDebutFormatee() { - return dateDebut != null ? dateDebut.format(DATE_FORMATTER) : ""; - } - - /** - * Retourne la date de fin prévue formatée en français. - * @return Date au format dd/MM/yyyy - */ - public String getDateFinPrevueFormatee() { - return dateFinPrevue != null ? dateFinPrevue.format(DATE_FORMATTER) : ""; - } - - /** - * Indique si le coût réel dépasse le budget. - * @return true si dépassement de budget - */ - public boolean isDepassementBudget() { - return coutReel > budget; - } - } - - /** - * Chantier en retard pour affichage dans la timeline. - */ - @lombok.Getter - @lombok.Setter - public static class ChantierEnRetard implements Serializable { - private String id; - private String nom; - private LocalDate dateFinPrevue; - private long joursRetard; - - /** - * Retourne la date de fin prévue formatée en français. - * @return Date au format dd/MM/yyyy - */ - public String getDateFinPrevueFormatee() { - return dateFinPrevue != null ? dateFinPrevue.format(DATE_FORMATTER) : ""; - } - } - - /** - * Maintenance en retard pour affichage dans la liste d'alertes. - */ - @lombok.Getter - @lombok.Setter - public static class MaintenanceEnRetard implements Serializable { - private String id; - private String materiel; - private String type; - private String description; - private LocalDate datePrevue; - private long joursRetard; - - /** - * Retourne la date prévue formatée en français. - * @return Date au format dd/MM/yyyy - */ - public String getDatePrevueFormatee() { - return datePrevue != null ? datePrevue.format(DATE_FORMATTER) : ""; - } - } - - /** - * Disponibilité en attente de validation. - */ - @lombok.Getter - @lombok.Setter - public static class DisponibiliteEnAttente implements Serializable { - private String id; - private String employe; - private String type; - private LocalDateTime dateDebut; - private LocalDateTime dateFin; - private String motif; - - /** - * Retourne la date de début formatée en français. - * @return Date au format dd/MM/yyyy - */ - public String getDateDebutFormatee() { - return dateDebut != null ? dateDebut.format(DATE_FORMATTER) : ""; - } - - /** - * Retourne la date de fin formatée en français. - * @return Date au format dd/MM/yyyy - */ - public String getDateFinFormatee() { - return dateFin != null ? dateFin.format(DATE_FORMATTER) : ""; - } - - /** - * Calcule le nombre de jours de la disponibilité. - * @return Nombre de jours - */ - public long getNombreJours() { - if (dateDebut != null && dateFin != null) { - return java.time.Duration.between(dateDebut, dateFin).toDays() + 1; - } - return 0; - } - } - - /** - * Document récent pour affichage dans la liste. - */ - @lombok.Getter - @lombok.Setter - public static class DocumentRecent implements Serializable { - private String id; - private String nom; - private String type; - private LocalDateTime dateCreation; - - /** - * Retourne la date de création formatée en français avec heure. - * @return Date au format dd/MM/yyyy HH:mm - */ - public String getDateCreationFormatee() { - return dateCreation != null ? dateCreation.format(DATETIME_FORMATTER) : ""; - } - } -} +package dev.lions.btpxpress.view; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.annotation.PostConstruct; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import dev.lions.btpxpress.service.DashboardService; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Bean de vue pour le tableau de bord principal optimisé BTP Xpress. + * + *

Architecture 2025 - Dashboard Complet

+ * + *

Ce bean orchestre l'affichage du tableau de bord professionnel couvrant + * TOUS les aspects métiers de BTP Xpress :

+ *
    + *
  • Chantiers: Vue globale, actifs, en retard, budget, avancement
  • + *
  • Ressources Humaines: Employés actifs, équipes, disponibilités en attente
  • + *
  • Matériel: Disponibilité, maintenance, alertes critiques
  • + *
  • Planning: Événements du jour, conflits détectés
  • + *
  • Finances: Budget vs coût réel, analyse par chantier
  • + *
  • Maintenance: En retard, planifiées, alertes
  • + *
  • Documents: Accès rapide aux 5 derniers documents
  • + *
  • Alertes: Vue consolidée de tout ce qui nécessite attention immédiate
  • + *
+ * + *

Principe fondamental : AUCUNE donnée fictive. Toutes les données + * proviennent strictement de l'API backend via les endpoints {@code /api/v1/dashboard/*}

+ * + *

Voir {@code DASHBOARD_CONCEPTION.md} pour la documentation détaillée de l'architecture.

+ * + * @author BTP Xpress Development Team + * @version 2.0.0 - Dashboard optimisé + * @since 1.0.0 + */ +@Named("dashboardView") +@ViewScoped +@Getter +@Setter +public class DashboardView implements Serializable { + + private static final long serialVersionUID = 1L; + private static final Logger logger = LoggerFactory.getLogger(DashboardView.class); + + /** + * Format de date français : dd/MM/yyyy + */ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + /** + * Format de date-heure français : dd/MM/yyyy HH:mm + */ + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); + + @Inject + private DashboardService dashboardService; + + // ============================================================================ + // MÉTRIQUES PRINCIPALES (KPIs en haut du dashboard) + // ============================================================================ + + /** + * Nombre total de chantiers dans le système + */ + private long nombreChantiers = 0; + + /** + * Nombre de chantiers actifs (EN_COURS + PLANIFIE) + */ + private long chantiersActifs = 0; + + /** + * Taux d'activité des chantiers en pourcentage + */ + private double tauxActiviteChantiers = 0.0; + + /** + * Nombre total d'équipes + */ + private long nombreEquipes = 0; + + /** + * Nombre d'équipes disponibles + */ + private long equipesDisponibles = 0; + + /** + * Taux de disponibilité des équipes en pourcentage + */ + private double tauxDisponibiliteEquipes = 0.0; + + /** + * Nombre de maintenances en retard (ALERTE CRITIQUE) + */ + private long maintenancesEnRetard = 0; + + /** + * Nombre de maintenances planifiées + */ + private long maintenancesPlanifiees = 0; + + /** + * Indicateur d'alerte de retard de maintenance + */ + private boolean alerteRetardMaintenance = false; + + // ============================================================================ + // MÉTRIQUES RESSOURCES + // ============================================================================ + + /** + * Nombre total d'employés + */ + private long nombreEmployes = 0; + + /** + * Nombre d'employés actifs + */ + private long employesActifs = 0; + + /** + * Taux d'activité des employés en pourcentage + */ + private double tauxActiviteEmployes = 0.0; + + /** + * Nombre total de matériel + */ + private long nombreMateriel = 0; + + /** + * Nombre de matériel disponible + */ + private long materielDisponible = 0; + + /** + * Taux de disponibilité du matériel en pourcentage + */ + private double tauxDisponibiliteMateriel = 0.0; + + /** + * Nombre de disponibilités (congés, absences) en attente de validation + */ + private long disponibilitesEnAttenteCount = 0; + + // ============================================================================ + // MÉTRIQUES PLANNING + // ============================================================================ + + /** + * Nombre d'événements de planning aujourd'hui + */ + private long evenementsAujourdhui = 0; + + /** + * Nombre de conflits de planning détectés sur les 7 prochains jours + */ + private long conflitsPlanningCount = 0; + + // ============================================================================ + // MÉTRIQUES DOCUMENTS + // ============================================================================ + + /** + * Nombre total de documents dans le système + */ + private long nombreDocuments = 0; + + // ============================================================================ + // ALERTES CONSOLIDÉES + // ============================================================================ + + /** + * Nombre total d'alertes nécessitant une attention immédiate + */ + private long totalAlertes = 0; + + /** + * Indicateur d'alerte critique (true si au moins une alerte) + */ + private boolean alerteCritique = false; + + /** + * Nombre d'alertes de maintenance + */ + private long alertesMaintenanceCount = 0; + + /** + * Nombre d'alertes de chantiers en retard + */ + private long alertesChantiersCount = 0; + + /** + * Nombre d'alertes de disponibilités en attente + */ + private long alertesDisponibilitesCount = 0; + + /** + * Nombre d'alertes de conflits de planning + */ + private long alertesConflitsCount = 0; + + // ============================================================================ + // LISTES DE DONNÉES POUR AFFICHAGE + // ============================================================================ + + /** + * Liste des chantiers actifs avec toutes leurs informations + */ + private List chantiersActifsList = new ArrayList<>(); + + /** + * Liste des chantiers en retard + */ + private List chantiersEnRetardList = new ArrayList<>(); + + /** + * Liste des maintenances en retard + */ + private List maintenancesEnRetardList = new ArrayList<>(); + + /** + * Liste des disponibilités en attente de validation + */ + private List disponibilitesEnAttenteList = new ArrayList<>(); + + /** + * Liste des 5 documents les plus récents + */ + private List documentsRecentsList = new ArrayList<>(); + + /** + * Initialise le dashboard en chargeant toutes les données depuis l'API backend. + * + *

Cette méthode est appelée automatiquement après la construction du bean (PostConstruct). + * Elle orchestre le chargement de toutes les métriques et données via le DashboardService.

+ * + *

Endpoints API appelés :

+ *
    + *
  • {@code GET /api/v1/dashboard} - Métriques principales
  • + *
  • {@code GET /api/v1/dashboard/chantiers} - Chantiers actifs et en retard
  • + *
  • {@code GET /api/v1/dashboard/ressources} - Ressources humaines et matérielles
  • + *
  • {@code GET /api/v1/dashboard/maintenance} - Maintenances
  • + *
  • {@code GET /api/v1/dashboard/alertes} - Alertes consolidées
  • + *
+ */ + @PostConstruct + public void init() { + logger.info("═══════════════════════════════════════════════════════════════"); + logger.info("Initialisation du Dashboard BTP Xpress - Version 2.0 Optimisée"); + logger.info("═══════════════════════════════════════════════════════════════"); + + // Chargement séquentiel des différentes sections du dashboard + loadDashboardPrincipal(); + loadDashboardChantiers(); + loadDashboardRessources(); + loadDashboardMaintenance(); + loadAlertes(); + + logger.info("✓ Dashboard initialisé avec succès - {} alertes détectées", totalAlertes); + } + + /** + * Charge les métriques du dashboard principal depuis l'API. + * + *

Récupère les KPIs globaux affichés en haut du dashboard :

+ *
    + *
  • Chantiers : total, actifs, taux d'activité
  • + *
  • Équipes : total, disponibles, taux de disponibilité
  • + *
  • Employés : total, actifs, taux d'activité
  • + *
  • Matériel : total, disponible, taux de disponibilité
  • + *
  • Maintenance : en retard, planifiées, alerte
  • + *
  • Planning : événements aujourd'hui
  • + *
  • Documents : total, récents
  • + *
+ * + *

Endpoint : {@code GET /api/v1/dashboard}

+ */ + private void loadDashboardPrincipal() { + try { + logger.debug("→ Chargement dashboard principal..."); + JsonNode dashboard = dashboardService.getDashboardPrincipal(); + + if (dashboard != null) { + // Métriques chantiers + JsonNode chantiers = dashboard.get("chantiers"); + if (chantiers != null) { + nombreChantiers = chantiers.get("total").asLong(0); + chantiersActifs = chantiers.get("actifs").asLong(0); + tauxActiviteChantiers = chantiers.get("tauxActivite").asDouble(0.0); + logger.debug(" ✓ Chantiers: {} total, {} actifs ({} %)", + nombreChantiers, chantiersActifs, String.format("%.1f", tauxActiviteChantiers)); + } + + // Métriques équipes + JsonNode equipes = dashboard.get("equipes"); + if (equipes != null) { + nombreEquipes = equipes.get("total").asLong(0); + equipesDisponibles = equipes.get("disponibles").asLong(0); + tauxDisponibiliteEquipes = equipes.get("tauxDisponibilite").asDouble(0.0); + logger.debug(" ✓ Équipes: {} total, {} disponibles ({} %)", + nombreEquipes, equipesDisponibles, String.format("%.1f", tauxDisponibiliteEquipes)); + } + + // Métriques employés + JsonNode employes = dashboard.get("employes"); + if (employes != null) { + nombreEmployes = employes.get("total").asLong(0); + employesActifs = employes.get("actifs").asLong(0); + tauxActiviteEmployes = employes.get("tauxActivite").asDouble(0.0); + logger.debug(" ✓ Employés: {} total, {} actifs ({} %)", + nombreEmployes, employesActifs, String.format("%.1f", tauxActiviteEmployes)); + } + + // Métriques matériel + JsonNode materiel = dashboard.get("materiel"); + if (materiel != null) { + nombreMateriel = materiel.get("total").asLong(0); + materielDisponible = materiel.get("disponible").asLong(0); + tauxDisponibiliteMateriel = materiel.get("tauxDisponibilite").asDouble(0.0); + logger.debug(" ✓ Matériel: {} total, {} disponible ({} %)", + nombreMateriel, materielDisponible, String.format("%.1f", tauxDisponibiliteMateriel)); + } + + // Métriques maintenance + JsonNode maintenance = dashboard.get("maintenance"); + if (maintenance != null) { + maintenancesEnRetard = maintenance.get("enRetard").asLong(0); + maintenancesPlanifiees = maintenance.get("planifiees").asLong(0); + alerteRetardMaintenance = maintenance.get("alerteRetard").asBoolean(false); + logger.debug(" ✓ Maintenance: {} en retard, {} planifiées [Alerte: {}]", + maintenancesEnRetard, maintenancesPlanifiees, alerteRetardMaintenance); + } + + // Métriques planning + JsonNode planning = dashboard.get("planning"); + if (planning != null) { + evenementsAujourdhui = planning.get("evenementsAujourdhui").asLong(0); + disponibilitesEnAttenteCount = planning.get("disponibilitesEnAttente").asLong(0); + logger.debug(" ✓ Planning: {} événements aujourd'hui, {} disponibilités en attente", + evenementsAujourdhui, disponibilitesEnAttenteCount); + } + + // Métriques documents + JsonNode documents = dashboard.get("documents"); + if (documents != null) { + nombreDocuments = documents.get("total").asLong(0); + + // Documents récents + JsonNode recentsNode = documents.get("recents"); + if (recentsNode != null && recentsNode.isArray()) { + documentsRecentsList.clear(); + Iterator iterator = recentsNode.elements(); + while (iterator.hasNext()) { + JsonNode doc = iterator.next(); + DocumentRecent docRecent = new DocumentRecent(); + docRecent.setId(doc.get("id").asText()); + docRecent.setNom(doc.get("nom").asText()); + docRecent.setType(doc.get("type").asText()); + + String dateCreationStr = doc.get("dateCreation").asText(); + try { + docRecent.setDateCreation(LocalDateTime.parse(dateCreationStr)); + } catch (Exception e) { + logger.warn("Erreur parsing date document: {}", dateCreationStr); + } + + documentsRecentsList.add(docRecent); + } + } + logger.debug(" ✓ Documents: {} total, {} récents chargés", + nombreDocuments, documentsRecentsList.size()); + } + } else { + logger.warn("⚠ Dashboard principal: réponse null de l'API"); + } + } catch (Exception e) { + logger.error("❌ Erreur lors du chargement du dashboard principal", e); + } + } + + /** + * Charge les métriques et listes des chantiers depuis l'API. + * + *

Récupère :

+ *
    + *
  • Liste complète des chantiers actifs (EN_COURS + PLANIFIE)
  • + *
  • Liste des chantiers en retard (date fin prévue dépassée)
  • + *
+ * + *

Endpoint : {@code GET /api/v1/dashboard/chantiers}

+ */ + private void loadDashboardChantiers() { + try { + logger.debug("→ Chargement dashboard chantiers..."); + JsonNode dashboard = dashboardService.getDashboardChantiers(); + + if (dashboard != null) { + // Chantiers actifs + JsonNode chantiersActifsNode = dashboard.get("chantiersActifs"); + if (chantiersActifsNode != null && chantiersActifsNode.isArray()) { + chantiersActifsList.clear(); + Iterator iterator = chantiersActifsNode.elements(); + + while (iterator.hasNext()) { + JsonNode chantier = iterator.next(); + ChantierResume c = new ChantierResume(); + c.setId(chantier.get("id").asText()); + c.setNom(chantier.get("nom").asText("")); + c.setAdresse(chantier.get("adresse").asText("")); + c.setClient(chantier.get("client").asText("Non assigné")); + + // Dates + if (chantier.has("dateDebut") && !chantier.get("dateDebut").isNull()) { + try { + c.setDateDebut(LocalDate.parse(chantier.get("dateDebut").asText())); + } catch (Exception e) { + logger.warn("Erreur parsing dateDebut: {}", chantier.get("dateDebut").asText()); + } + } + if (chantier.has("dateFinPrevue") && !chantier.get("dateFinPrevue").isNull()) { + try { + c.setDateFinPrevue(LocalDate.parse(chantier.get("dateFinPrevue").asText())); + } catch (Exception e) { + logger.warn("Erreur parsing dateFinPrevue: {}", chantier.get("dateFinPrevue").asText()); + } + } + + // Métriques + c.setStatut(chantier.get("statut").asText("")); + c.setBudget(chantier.get("budget").asDouble(0.0)); + c.setCoutReel(chantier.get("coutReel").asDouble(0.0)); + c.setAvancement(chantier.get("avancement").asInt(0)); + + chantiersActifsList.add(c); + } + + logger.debug(" ✓ {} chantiers actifs chargés", chantiersActifsList.size()); + } + + // Chantiers en retard + JsonNode chantiersEnRetardNode = dashboard.get("chantiersEnRetard"); + if (chantiersEnRetardNode != null && chantiersEnRetardNode.isArray()) { + chantiersEnRetardList.clear(); + Iterator iterator = chantiersEnRetardNode.elements(); + + while (iterator.hasNext()) { + JsonNode chantier = iterator.next(); + ChantierEnRetard c = new ChantierEnRetard(); + c.setId(chantier.get("id").asText()); + c.setNom(chantier.get("nom").asText("")); + + if (chantier.has("dateFinPrevue") && !chantier.get("dateFinPrevue").isNull()) { + try { + c.setDateFinPrevue(LocalDate.parse(chantier.get("dateFinPrevue").asText())); + } catch (Exception e) { + logger.warn("Erreur parsing dateFinPrevue retard: {}", chantier.get("dateFinPrevue").asText()); + } + } + + c.setJoursRetard(chantier.get("joursRetard").asLong(0)); + chantiersEnRetardList.add(c); + } + + logger.debug(" ✓ {} chantiers en retard chargés", chantiersEnRetardList.size()); + } + } else { + logger.warn("⚠ Dashboard chantiers: réponse null de l'API"); + } + } catch (Exception e) { + logger.error("❌ Erreur lors du chargement du dashboard chantiers", e); + } + } + + /** + * Charge les métriques des ressources (RH et matérielles) depuis l'API. + * + *

Récupère :

+ *
    + *
  • Statistiques des équipes (déjà chargées dans principal)
  • + *
  • Statistiques des employés (déjà chargées dans principal)
  • + *
  • Statistiques du matériel (déjà chargées dans principal)
  • + *
  • Liste des disponibilités en attente (congés, absences à valider)
  • + *
+ * + *

Endpoint : {@code GET /api/v1/dashboard/ressources}

+ */ + private void loadDashboardRessources() { + try { + logger.debug("→ Chargement dashboard ressources..."); + JsonNode ressources = dashboardService.getDashboardRessources(); + + if (ressources != null) { + // Les statistiques équipes/employés/matériel sont déjà chargées dans loadDashboardPrincipal + // Ici on charge les disponibilités en attente + + JsonNode disponibilites = ressources.get("disponibilites"); + if (disponibilites != null) { + JsonNode enAttenteDetails = disponibilites.get("enAttenteDetails"); + if (enAttenteDetails != null && enAttenteDetails.isArray()) { + disponibilitesEnAttenteList.clear(); + Iterator iterator = enAttenteDetails.elements(); + + while (iterator.hasNext()) { + JsonNode dispo = iterator.next(); + DisponibiliteEnAttente d = new DisponibiliteEnAttente(); + d.setId(dispo.get("id").asText()); + d.setEmploye(dispo.get("employe").asText("")); + d.setType(dispo.get("type").asText("")); + d.setMotif(dispo.get("motif").asText("")); + + try { + d.setDateDebut(LocalDateTime.parse(dispo.get("dateDebut").asText())); + d.setDateFin(LocalDateTime.parse(dispo.get("dateFin").asText())); + } catch (Exception e) { + logger.warn("Erreur parsing dates disponibilité"); + } + + disponibilitesEnAttenteList.add(d); + } + + logger.debug(" ✓ {} disponibilités en attente chargées", disponibilitesEnAttenteList.size()); + } + } + } else { + logger.warn("⚠ Dashboard ressources: réponse null de l'API"); + } + } catch (Exception e) { + logger.error("❌ Erreur lors du chargement du dashboard ressources", e); + } + } + + /** + * Charge les métriques de maintenance depuis l'API. + * + *

Récupère :

+ *
    + *
  • Statistiques de maintenance (en retard, planifiées - déjà dans principal)
  • + *
  • Liste détaillée des maintenances en retard
  • + *
+ * + *

Endpoint : {@code GET /api/v1/dashboard/maintenance}

+ */ + private void loadDashboardMaintenance() { + try { + logger.debug("→ Chargement dashboard maintenance..."); + JsonNode maintenance = dashboardService.getDashboardMaintenance(); + + if (maintenance != null) { + // Maintenances en retard (détails) + JsonNode maintenancesEnRetardNode = maintenance.get("maintenancesEnRetard"); + if (maintenancesEnRetardNode != null && maintenancesEnRetardNode.isArray()) { + maintenancesEnRetardList.clear(); + Iterator iterator = maintenancesEnRetardNode.elements(); + int count = 0; + + // Limiter à 5 maintenances pour l'affichage + while (iterator.hasNext() && count < 5) { + JsonNode maint = iterator.next(); + MaintenanceEnRetard m = new MaintenanceEnRetard(); + m.setId(maint.get("id").asText()); + m.setMateriel(maint.get("materiel").asText("")); + m.setType(maint.get("type").asText("")); + m.setDescription(maint.get("description").asText("")); + + if (maint.has("datePrevue") && !maint.get("datePrevue").isNull()) { + try { + m.setDatePrevue(LocalDate.parse(maint.get("datePrevue").asText())); + } catch (Exception e) { + logger.warn("Erreur parsing datePrevue maintenance"); + } + } + + m.setJoursRetard(maint.get("joursRetard").asLong(0)); + maintenancesEnRetardList.add(m); + count++; + } + + logger.debug(" ✓ {} maintenances en retard chargées (affichage limité à 5)", + maintenancesEnRetardList.size()); + } + } else { + logger.warn("⚠ Dashboard maintenance: réponse null de l'API"); + } + } catch (Exception e) { + logger.error("❌ Erreur lors du chargement du dashboard maintenance", e); + } + } + + /** + * Charge les alertes consolidées depuis l'API. + * + *

Récupère la vue d'ensemble de toutes les alertes nécessitant une attention immédiate :

+ *
    + *
  • Maintenances en retard
  • + *
  • Chantiers en retard
  • + *
  • Disponibilités en attente de validation
  • + *
  • Conflits de planning détectés
  • + *
+ * + *

Endpoint : {@code GET /api/v1/dashboard/alertes}

+ */ + private void loadAlertes() { + try { + logger.debug("→ Chargement alertes..."); + JsonNode alertes = dashboardService.getAlertes(); + + if (alertes != null) { + totalAlertes = alertes.get("totalAlertes").asLong(0); + alerteCritique = alertes.get("alerteCritique").asBoolean(false); + + // Détail par type d'alerte + JsonNode maintenanceNode = alertes.get("maintenance"); + if (maintenanceNode != null) { + alertesMaintenanceCount = maintenanceNode.get("enRetard").asLong(0); + } + + JsonNode chantiersNode = alertes.get("chantiers"); + if (chantiersNode != null) { + alertesChantiersCount = chantiersNode.get("enRetard").asLong(0); + } + + JsonNode disponibilitesNode = alertes.get("disponibilites"); + if (disponibilitesNode != null) { + alertesDisponibilitesCount = disponibilitesNode.get("enAttente").asLong(0); + } + + JsonNode planningNode = alertes.get("planning"); + if (planningNode != null && planningNode.has("conflits")) { + alertesConflitsCount = planningNode.get("conflits").asLong(0); + conflitsPlanningCount = alertesConflitsCount; + } + + logger.debug(" ✓ Alertes chargées: {} total [Maintenance: {}, Chantiers: {}, Disponibilités: {}, Conflits: {}]", + totalAlertes, alertesMaintenanceCount, alertesChantiersCount, + alertesDisponibilitesCount, alertesConflitsCount); + } else { + logger.warn("⚠ Alertes: réponse null de l'API"); + } + } catch (Exception e) { + logger.error("❌ Erreur lors du chargement des alertes", e); + } + } + + /** + * Rafraîchit toutes les données du dashboard. + * + *

Cette méthode peut être appelée via un bouton "Rafraîchir" ou un composant Poll + * pour mettre à jour les données en temps réel.

+ */ + public void rafraichir() { + logger.info("⟳ Rafraîchissement manuel du dashboard demandé"); + init(); + } + + /** + * Rafraîchit uniquement les alertes. + * + *

Méthode optimisée pour le polling automatique des alertes (toutes les 30 secondes).

+ */ + public void refreshAlertes() { + logger.debug("⟳ Rafraîchissement des alertes..."); + loadAlertes(); + } + + /** + * Calcule le taux d'utilisation global moyen. + * + *

Moyenne des 3 principaux taux :

+ *
    + *
  • Taux d'activité des chantiers
  • + *
  • Taux d'activité des employés
  • + *
  • Taux de disponibilité du matériel
  • + *
+ * + * @return Taux d'utilisation global en pourcentage (0-100) + */ + public double getTauxUtilisationGlobal() { + return (tauxActiviteChantiers + tauxActiviteEmployes + tauxDisponibiliteMateriel) / 3.0; + } + + /** + * Retourne l'icône PrimeIcons correspondant à un type de document. + * + * @param type Type de document (DEVIS, FACTURE, CONTRAT, PLAN, AUTRE) + * @return Classe CSS de l'icône PrimeIcons + */ + public String getIconeDocument(String type) { + if (type == null) return "pi pi-file"; + + switch (type.toUpperCase()) { + case "DEVIS": + case "FACTURE": + return "pi pi-file-pdf"; + case "CONTRAT": + return "pi pi-file-edit"; + case "PLAN": + return "pi pi-image"; + default: + return "pi pi-file"; + } + } + + /** + * Retourne la classe CSS de badge coloré selon le statut d'un chantier. + * + * @param statut Statut du chantier (EN_COURS, PLANIFIE, TERMINE, SUSPENDU, ANNULE) + * @return Classe CSS du badge + */ + public String getBadgeClasseStatut(String statut) { + if (statut == null) return ""; + + switch (statut.toUpperCase()) { + case "EN_COURS": + return "badge-en-cours"; + case "PLANIFIE": + return "badge-planifie"; + case "TERMINE": + return "badge-termine"; + case "SUSPENDU": + return "badge-suspendu"; + case "ANNULE": + return "badge-annule"; + default: + return ""; + } + } + + /** + * Retourne la sévérité PrimeFaces d'un badge selon le type de disponibilité. + * + * @param type Type de disponibilité (CONGE, MALADIE, FORMATION) + * @return Sévérité du badge (info, warning, success) + */ + public String getSeveriteDisponibilite(String type) { + if (type == null) return "info"; + + switch (type.toUpperCase()) { + case "CONGE": + return "info"; + case "MALADIE": + return "warning"; + case "FORMATION": + return "success"; + default: + return "info"; + } + } + + // ============================================================================ + // CLASSES INTERNES - DTOs pour l'affichage + // ============================================================================ + + /** + * Résumé d'un chantier actif pour affichage dans le tableau. + */ + @lombok.Getter + @lombok.Setter + public static class ChantierResume implements Serializable { + private String id; + private String nom; + private String adresse; + private String client; + private LocalDate dateDebut; + private LocalDate dateFinPrevue; + private String statut; + private double budget; + private double coutReel; + private int avancement; + + /** + * Retourne la date de début formatée en français. + * @return Date au format dd/MM/yyyy + */ + public String getDateDebutFormatee() { + return dateDebut != null ? dateDebut.format(DATE_FORMATTER) : ""; + } + + /** + * Retourne la date de fin prévue formatée en français. + * @return Date au format dd/MM/yyyy + */ + public String getDateFinPrevueFormatee() { + return dateFinPrevue != null ? dateFinPrevue.format(DATE_FORMATTER) : ""; + } + + /** + * Indique si le coût réel dépasse le budget. + * @return true si dépassement de budget + */ + public boolean isDepassementBudget() { + return coutReel > budget; + } + } + + /** + * Chantier en retard pour affichage dans la timeline. + */ + @lombok.Getter + @lombok.Setter + public static class ChantierEnRetard implements Serializable { + private String id; + private String nom; + private LocalDate dateFinPrevue; + private long joursRetard; + + /** + * Retourne la date de fin prévue formatée en français. + * @return Date au format dd/MM/yyyy + */ + public String getDateFinPrevueFormatee() { + return dateFinPrevue != null ? dateFinPrevue.format(DATE_FORMATTER) : ""; + } + } + + /** + * Maintenance en retard pour affichage dans la liste d'alertes. + */ + @lombok.Getter + @lombok.Setter + public static class MaintenanceEnRetard implements Serializable { + private String id; + private String materiel; + private String type; + private String description; + private LocalDate datePrevue; + private long joursRetard; + + /** + * Retourne la date prévue formatée en français. + * @return Date au format dd/MM/yyyy + */ + public String getDatePrevueFormatee() { + return datePrevue != null ? datePrevue.format(DATE_FORMATTER) : ""; + } + } + + /** + * Disponibilité en attente de validation. + */ + @lombok.Getter + @lombok.Setter + public static class DisponibiliteEnAttente implements Serializable { + private String id; + private String employe; + private String type; + private LocalDateTime dateDebut; + private LocalDateTime dateFin; + private String motif; + + /** + * Retourne la date de début formatée en français. + * @return Date au format dd/MM/yyyy + */ + public String getDateDebutFormatee() { + return dateDebut != null ? dateDebut.format(DATE_FORMATTER) : ""; + } + + /** + * Retourne la date de fin formatée en français. + * @return Date au format dd/MM/yyyy + */ + public String getDateFinFormatee() { + return dateFin != null ? dateFin.format(DATE_FORMATTER) : ""; + } + + /** + * Calcule le nombre de jours de la disponibilité. + * @return Nombre de jours + */ + public long getNombreJours() { + if (dateDebut != null && dateFin != null) { + return java.time.Duration.between(dateDebut, dateFin).toDays() + 1; + } + return 0; + } + } + + /** + * Document récent pour affichage dans la liste. + */ + @lombok.Getter + @lombok.Setter + public static class DocumentRecent implements Serializable { + private String id; + private String nom; + private String type; + private LocalDateTime dateCreation; + + /** + * Retourne la date de création formatée en français avec heure. + * @return Date au format dd/MM/yyyy HH:mm + */ + public String getDateCreationFormatee() { + return dateCreation != null ? dateCreation.format(DATETIME_FORMATTER) : ""; + } + } +} diff --git a/src/main/java/dev/lions/btpxpress/view/DevisView.java b/src/main/java/dev/lions/btpxpress/view/DevisView.java index 63bf5f9..05894fd 100644 --- a/src/main/java/dev/lions/btpxpress/view/DevisView.java +++ b/src/main/java/dev/lions/btpxpress/view/DevisView.java @@ -10,8 +10,6 @@ import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; -import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -23,7 +21,7 @@ import java.util.function.Predicate; @ViewScoped @Getter @Setter -public class DevisView extends BaseListView implements Serializable { +public class DevisView extends BaseListView { private static final Logger LOG = LoggerFactory.getLogger(DevisView.class); @@ -70,6 +68,7 @@ public class DevisView extends BaseListView implements Se // Le client peut être un objet ou une chaîne Object clientObj = data.get("client"); if (clientObj instanceof Map) { + @SuppressWarnings("unchecked") Map clientData = (Map) clientObj; String entreprise = (String) clientData.get("entreprise"); String nom = (String) clientData.get("nom"); diff --git a/src/main/java/dev/lions/btpxpress/view/EmployeView.java b/src/main/java/dev/lions/btpxpress/view/EmployeView.java index e137718..808dc9d 100644 --- a/src/main/java/dev/lions/btpxpress/view/EmployeView.java +++ b/src/main/java/dev/lions/btpxpress/view/EmployeView.java @@ -10,7 +10,6 @@ import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -22,7 +21,7 @@ import java.util.function.Predicate; @ViewScoped @Getter @Setter -public class EmployeView extends BaseListView implements Serializable { +public class EmployeView extends BaseListView { private static final Logger LOG = LoggerFactory.getLogger(EmployeView.class); diff --git a/src/main/java/dev/lions/btpxpress/view/EquipeView.java b/src/main/java/dev/lions/btpxpress/view/EquipeView.java index 0b5bd5a..5d98e36 100644 --- a/src/main/java/dev/lions/btpxpress/view/EquipeView.java +++ b/src/main/java/dev/lions/btpxpress/view/EquipeView.java @@ -10,7 +10,6 @@ import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -21,7 +20,7 @@ import java.util.function.Predicate; @ViewScoped @Getter @Setter -public class EquipeView extends BaseListView implements Serializable { +public class EquipeView extends BaseListView { private static final Logger LOG = LoggerFactory.getLogger(EquipeView.class); @@ -66,6 +65,7 @@ public class EquipeView extends BaseListView implements // Chef d'équipe Object chefObj = data.get("chef"); if (chefObj instanceof Map) { + @SuppressWarnings("unchecked") Map chefData = (Map) chefObj; String prenom = (String) chefData.get("prenom"); String nom = (String) chefData.get("nom"); diff --git a/src/main/java/dev/lions/btpxpress/view/FactureView.java b/src/main/java/dev/lions/btpxpress/view/FactureView.java index dea2f03..a5f6c60 100644 --- a/src/main/java/dev/lions/btpxpress/view/FactureView.java +++ b/src/main/java/dev/lions/btpxpress/view/FactureView.java @@ -10,7 +10,6 @@ import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -22,7 +21,7 @@ import java.util.function.Predicate; @ViewScoped @Getter @Setter -public class FactureView extends BaseListView implements Serializable { +public class FactureView extends BaseListView { private static final Logger LOG = LoggerFactory.getLogger(FactureView.class); @@ -69,6 +68,7 @@ public class FactureView extends BaseListView impleme // Le client peut être un objet ou une chaîne Object clientObj = data.get("client"); if (clientObj instanceof Map) { + @SuppressWarnings("unchecked") Map clientData = (Map) clientObj; String entreprise = (String) clientData.get("entreprise"); String nom = (String) clientData.get("nom"); diff --git a/src/main/java/dev/lions/btpxpress/view/GuestPreferences.java b/src/main/java/dev/lions/btpxpress/view/GuestPreferences.java deleted file mode 100644 index 7669df2..0000000 --- a/src/main/java/dev/lions/btpxpress/view/GuestPreferences.java +++ /dev/null @@ -1,94 +0,0 @@ -package dev.lions.btpxpress.view; - -import jakarta.annotation.PostConstruct; -import jakarta.enterprise.context.SessionScoped; -import jakarta.inject.Named; -import lombok.Getter; -import lombok.Setter; -import org.primefaces.PrimeFaces; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -@Named("guestPreferences") -@SessionScoped -@Getter -@Setter -public class GuestPreferences implements Serializable { - - private static final long serialVersionUID = 1L; - - private String menuMode = "layout-sidebar"; - private String darkMode = "light"; - private String componentTheme = "purple"; - private String topbarTheme = "light"; - private String menuTheme = "light"; - private String inputStyle = "outlined"; - private boolean lightLogo = false; - private List componentThemes = new ArrayList<>(); - - @PostConstruct - public void init() { - componentThemes.add(new ComponentTheme("Bleu", "blue", "#2c84d8")); - componentThemes.add(new ComponentTheme("Vert", "green", "#34B56F")); - componentThemes.add(new ComponentTheme("Orange", "orange", "#FF810E")); - componentThemes.add(new ComponentTheme("Turquoise", "turquoise", "#58AED3")); - componentThemes.add(new ComponentTheme("Avocat", "avocado", "#AEC523")); - componentThemes.add(new ComponentTheme("Violet", "purple", "#464DF2")); - componentThemes.add(new ComponentTheme("Rouge", "red", "#FF9B7B")); - componentThemes.add(new ComponentTheme("Jaune", "yellow", "#FFB340")); - } - - public void setDarkMode(String darkMode) { - this.darkMode = darkMode; - this.menuTheme = darkMode; - this.topbarTheme = darkMode; - this.lightLogo = !this.topbarTheme.equals("light"); - } - - public String getLayout() { - return "layout-" + this.darkMode; - } - - public String getTheme() { - return this.componentTheme + '-' + this.darkMode; - } - - public void setTopbarTheme(String topbarTheme) { - this.topbarTheme = topbarTheme; - this.lightLogo = !this.topbarTheme.equals("light"); - } - - public String getInputStyleClass() { - return this.inputStyle.equals("filled") ? "ui-input-filled" : ""; - } - - public void setComponentTheme(String componentTheme) { - this.componentTheme = componentTheme; - } - - public void onMenuTypeChange() { - if ("layout-horizontal".equals(menuMode)) { - menuTheme = topbarTheme; - PrimeFaces.current().executeScript( - "PrimeFaces.FreyaConfigurator.changeSectionTheme('" + menuTheme + "' , 'layout-menu')" - ); - } - } - - @lombok.Getter - @lombok.Setter - public static class ComponentTheme implements Serializable { - private static final long serialVersionUID = 1L; - private String name; - private String file; - private String color; - - public ComponentTheme(String name, String file, String color) { - this.name = name; - this.file = file; - this.color = color; - } - } -} diff --git a/src/main/java/dev/lions/btpxpress/view/LoginView.java b/src/main/java/dev/lions/btpxpress/view/LoginView.java index e806d53..d97cfb1 100644 --- a/src/main/java/dev/lions/btpxpress/view/LoginView.java +++ b/src/main/java/dev/lions/btpxpress/view/LoginView.java @@ -1,53 +1,53 @@ -package dev.lions.btpxpress.view; - -import jakarta.enterprise.context.RequestScoped; -import jakarta.faces.application.FacesMessage; -import jakarta.faces.context.FacesContext; -import jakarta.inject.Named; -import lombok.Getter; -import lombok.Setter; - -import java.io.Serializable; - -@Named("loginView") -@RequestScoped -@Getter -@Setter -public class LoginView implements Serializable { - - private static final long serialVersionUID = 1L; - - private String username; - private String password; - private boolean rememberMe = false; - - public String login() { - if (username == null || username.trim().isEmpty()) { - addErrorMessage("Le nom d'utilisateur est requis"); - return null; - } - - if (password == null || password.trim().isEmpty()) { - addErrorMessage("Le mot de passe est requis"); - return null; - } - - if ("admin".equals(username) && "admin".equals(password)) { - addInfoMessage("Connexion réussie !"); - return "/dashboard?faces-redirect=true"; - } else { - addErrorMessage("Nom d'utilisateur ou mot de passe incorrect"); - return null; - } - } - - private void addErrorMessage(String message) { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message)); - } - - private void addInfoMessage(String message) { - FacesContext.getCurrentInstance().addMessage(null, - new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message)); - } -} +package dev.lions.btpxpress.view; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.faces.application.FacesMessage; +import jakarta.faces.context.FacesContext; +import jakarta.inject.Named; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +@Named("loginView") +@RequestScoped +@Getter +@Setter +public class LoginView implements Serializable { + + private static final long serialVersionUID = 1L; + + private String username; + private String password; + private boolean rememberMe = false; + + public String login() { + if (username == null || username.trim().isEmpty()) { + addErrorMessage("Le nom d'utilisateur est requis"); + return null; + } + + if (password == null || password.trim().isEmpty()) { + addErrorMessage("Le mot de passe est requis"); + return null; + } + + if ("admin".equals(username) && "admin".equals(password)) { + addInfoMessage("Connexion réussie !"); + return "/dashboard?faces-redirect=true"; + } else { + addErrorMessage("Nom d'utilisateur ou mot de passe incorrect"); + return null; + } + } + + private void addErrorMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message)); + } + + private void addInfoMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message)); + } +} diff --git a/src/main/java/dev/lions/btpxpress/view/MaterielView.java b/src/main/java/dev/lions/btpxpress/view/MaterielView.java index fdde897..0903287 100644 --- a/src/main/java/dev/lions/btpxpress/view/MaterielView.java +++ b/src/main/java/dev/lions/btpxpress/view/MaterielView.java @@ -10,7 +10,6 @@ import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -22,7 +21,7 @@ import java.util.function.Predicate; @ViewScoped @Getter @Setter -public class MaterielView extends BaseListView implements Serializable { +public class MaterielView extends BaseListView { private static final Logger LOG = LoggerFactory.getLogger(MaterielView.class); diff --git a/src/main/java/dev/lions/btpxpress/view/StockView.java b/src/main/java/dev/lions/btpxpress/view/StockView.java index cbce6ae..9fe337d 100644 --- a/src/main/java/dev/lions/btpxpress/view/StockView.java +++ b/src/main/java/dev/lions/btpxpress/view/StockView.java @@ -10,7 +10,6 @@ import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -21,7 +20,7 @@ import java.util.function.Predicate; @ViewScoped @Getter @Setter -public class StockView extends BaseListView implements Serializable { +public class StockView extends BaseListView { private static final Logger LOG = LoggerFactory.getLogger(StockView.class); diff --git a/src/main/java/dev/lions/btpxpress/view/UserSessionBean.java b/src/main/java/dev/lions/btpxpress/view/UserSessionBean.java deleted file mode 100644 index 2c33183..0000000 --- a/src/main/java/dev/lions/btpxpress/view/UserSessionBean.java +++ /dev/null @@ -1,204 +0,0 @@ -package dev.lions.btpxpress.view; - -import io.quarkus.oidc.IdToken; -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.PostConstruct; -import jakarta.enterprise.context.SessionScoped; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.microprofile.jwt.JsonWebToken; - -import java.io.Serializable; - -/** - * Bean de session pour gérer les informations de l'utilisateur connecté. - * - *

Ce bean stocke les informations de session de l'utilisateur authentifié, - * telles que le nom, l'email, l'avatar, et les statistiques rapides.

- * - * @author BTP Xpress Team - * @version 1.0 - */ -@Named("userSession") -@SessionScoped -@Slf4j -public class UserSessionBean implements Serializable { - - private static final long serialVersionUID = 1L; - - @Inject - SecurityIdentity securityIdentity; - - @Inject - @IdToken - JsonWebToken idToken; - - /** - * Récupère le nom complet de l'utilisateur depuis le token OIDC. - * Méthode dynamique qui récupère les informations à chaque appel. - */ - public String getNomComplet() { - try { - if (securityIdentity != null && securityIdentity.getPrincipal() != null && idToken != null) { - // Nom complet (preferred_username ou name) - String nom = idToken.getClaim("name"); - if (nom == null || nom.trim().isEmpty()) { - nom = idToken.getClaim("preferred_username"); - } - if (nom == null || nom.trim().isEmpty()) { - nom = securityIdentity.getPrincipal().getName(); - } - return nom != null ? nom : "Utilisateur"; - } - } catch (Exception e) { - log.error("Erreur lors de la récupération du nom complet", e); - } - return "Utilisateur"; - } - - /** - * Récupère l'email de l'utilisateur depuis le token OIDC. - */ - public String getEmail() { - try { - if (securityIdentity != null && securityIdentity.getPrincipal() != null && idToken != null) { - String email = idToken.getClaim("email"); - return email != null ? email : "utilisateur@btpxpress.com"; - } - } catch (Exception e) { - log.error("Erreur lors de la récupération de l'email", e); - } - return "utilisateur@btpxpress.com"; - } - - /** - * Retourne l'URL de l'avatar (par défaut). - */ - public String getAvatarUrl() { - return "/resources/freya-layout/images/avatar-profilemenu.png"; - } - - /** - * Récupère le rôle de l'utilisateur depuis SecurityIdentity. - */ - public String getRole() { - try { - if (securityIdentity != null && securityIdentity.getRoles() != null && !securityIdentity.getRoles().isEmpty()) { - String role = securityIdentity.getRoles().iterator().next(); - // Formatage du rôle pour affichage (enlever préfixes) - role = role.replace("_", " ").replace("-", " "); - return capitalizeWords(role); - } - } catch (Exception e) { - log.error("Erreur lors de la récupération du rôle", e); - } - return "Utilisateur"; - } - - /** - * Nombre de notifications non lues (TODO: implémenter via API). - */ - public int getNombreNotificationsNonLues() { - return 0; - } - - /** - * Nombre de messages non lus (TODO: implémenter via API). - */ - public int getNombreMessagesNonLus() { - return 0; - } - - /** - * Capitalize first letter of each word. - */ - private String capitalizeWords(String str) { - if (str == null || str.isEmpty()) { - return str; - } - String[] words = str.toLowerCase().split(" "); - StringBuilder result = new StringBuilder(); - for (String word : words) { - if (!word.isEmpty()) { - result.append(Character.toUpperCase(word.charAt(0))) - .append(word.substring(1)) - .append(" "); - } - } - return result.toString().trim(); - } - - /** - * Retourne les initiales de l'utilisateur pour l'avatar. - * - * @return Les initiales (ex: "JD" pour "Jean Dupont") - */ - public String getInitiales() { - String nomComplet = getNomComplet(); - if (nomComplet == null || nomComplet.trim().isEmpty()) { - return "U"; - } - - String[] parts = nomComplet.trim().split("\\s+"); - if (parts.length >= 2) { - return String.valueOf(parts[0].charAt(0)).toUpperCase() + - String.valueOf(parts[1].charAt(0)).toUpperCase(); - } else if (parts.length == 1) { - return parts[0].substring(0, Math.min(2, parts[0].length())).toUpperCase(); - } - return "U"; - } - - /** - * Action de déconnexion OIDC/Keycloak. - * Redirige vers l'endpoint de logout Keycloak pour détruire la session. - * - * @return Null pour déclencher une redirection externe - */ - public String deconnecter() { - try { - log.info("Déconnexion de l'utilisateur: {}", getNomComplet()); - - jakarta.faces.context.FacesContext facesContext = jakarta.faces.context.FacesContext.getCurrentInstance(); - jakarta.faces.context.ExternalContext externalContext = facesContext.getExternalContext(); - - // Construction de l'URL de logout Keycloak - String keycloakLogoutUrl = "https://security.lions.dev/realms/btpxpress/protocol/openid-connect/logout"; - - // URL de redirection après logout - String baseUrl = externalContext.getRequestScheme() + "://" + - externalContext.getRequestServerName() + ":" + - externalContext.getRequestServerPort() + - externalContext.getRequestContextPath(); - - String postLogoutRedirectUri = baseUrl + "/"; - - // Construire l'URL complète avec les paramètres - StringBuilder logoutUrl = new StringBuilder(keycloakLogoutUrl); - logoutUrl.append("?post_logout_redirect_uri=").append(java.net.URLEncoder.encode(postLogoutRedirectUri, "UTF-8")); - - // Ajouter le id_token_hint si disponible - if (idToken != null && idToken.getRawToken() != null) { - logoutUrl.append("&id_token_hint=").append(java.net.URLEncoder.encode(idToken.getRawToken(), "UTF-8")); - } - - log.info("Redirection vers Keycloak logout: {}", keycloakLogoutUrl); - - // Invalider la session HTTP locale - externalContext.invalidateSession(); - - // Rediriger vers Keycloak logout - externalContext.redirect(logoutUrl.toString()); - facesContext.responseComplete(); - - return null; - } catch (Exception e) { - log.error("Erreur lors de la déconnexion", e); - return "/login?faces-redirect=true"; - } - } -} - diff --git a/src/main/resources/META-INF/faces-config.xml b/src/main/resources/META-INF/faces-config.xml index e4b2018..9c79c7a 100644 --- a/src/main/resources/META-INF/faces-config.xml +++ b/src/main/resources/META-INF/faces-config.xml @@ -1,31 +1,31 @@ - - - - btpxpress_freya - - - - fr - fr - en - - - - - org.primefaces.component.FreyaMenu - org.primefaces.freya.component.FreyaMenu - - - - - org.primefaces.component - org.primefaces.component.FreyaMenuRenderer - org.primefaces.freya.component.FreyaMenuRenderer - - - - + + + + btpxpress_freya + + + + fr + fr + en + + + + + org.primefaces.component.FreyaMenu + org.primefaces.freya.component.FreyaMenu + + + + + org.primefaces.component + org.primefaces.component.FreyaMenuRenderer + org.primefaces.freya.component.FreyaMenuRenderer + + + + diff --git a/src/main/resources/META-INF/resources/WEB-INF/btpxpress.taglib.xml b/src/main/resources/META-INF/resources/WEB-INF/btpxpress.taglib.xml new file mode 100644 index 0000000..d1af16f --- /dev/null +++ b/src/main/resources/META-INF/resources/WEB-INF/btpxpress.taglib.xml @@ -0,0 +1,34 @@ + + + + http://btpxpress.lions.dev/components + btpx + Composants réutilisables BTPXpress + + + + facture-statut-badge + Badge de statut pour les factures avec icône et couleur appropriées + components/facture-statut-badge.xhtml + + + + + montant-display + Affichage formaté d'un montant avec devise et mise en évidence optionnelle + components/montant-display.xhtml + + + + + search-filter-panel + Panel de filtres de recherche réutilisable avec boutons d'action + components/search-filter-panel.xhtml + + + + diff --git a/src/main/resources/META-INF/resources/WEB-INF/components/facture-statut-badge.xhtml b/src/main/resources/META-INF/resources/WEB-INF/components/facture-statut-badge.xhtml new file mode 100644 index 0000000..6750863 --- /dev/null +++ b/src/main/resources/META-INF/resources/WEB-INF/components/facture-statut-badge.xhtml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/resources/WEB-INF/components/liste-filters.xhtml b/src/main/resources/META-INF/resources/WEB-INF/components/liste-filters.xhtml index 4e5eea7..27d73dd 100644 --- a/src/main/resources/META-INF/resources/WEB-INF/components/liste-filters.xhtml +++ b/src/main/resources/META-INF/resources/WEB-INF/components/liste-filters.xhtml @@ -1,33 +1,33 @@ - - - - - - -
-
Recherche et filtres
- - - - -
- - -
-
-
- -
- + + + + + + +
+
Recherche et filtres
+ + + + +
+ + +
+
+
+ +
+ diff --git a/src/main/resources/META-INF/resources/WEB-INF/components/liste-table.xhtml b/src/main/resources/META-INF/resources/WEB-INF/components/liste-table.xhtml index c9fa2cc..5affe11 100644 --- a/src/main/resources/META-INF/resources/WEB-INF/components/liste-table.xhtml +++ b/src/main/resources/META-INF/resources/WEB-INF/components/liste-table.xhtml @@ -1,44 +1,44 @@ - - -
-
-
#{title}
- -
- - - -
- Total : #{viewBean.items.size()} élément(s) - -
-
- -
-
-
- -
+ + +
+
+
#{title}
+ +
+ + + +
+ Total : #{viewBean.items.size()} élément(s) + +
+
+ +
+
+
+ +
diff --git a/src/main/resources/META-INF/resources/WEB-INF/components/montant-display.xhtml b/src/main/resources/META-INF/resources/WEB-INF/components/montant-display.xhtml new file mode 100644 index 0000000..b475d74 --- /dev/null +++ b/src/main/resources/META-INF/resources/WEB-INF/components/montant-display.xhtml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/resources/WEB-INF/components/search-filter-panel.xhtml b/src/main/resources/META-INF/resources/WEB-INF/components/search-filter-panel.xhtml new file mode 100644 index 0000000..577c2b5 --- /dev/null +++ b/src/main/resources/META-INF/resources/WEB-INF/components/search-filter-panel.xhtml @@ -0,0 +1,62 @@ + + + + + + + + + + + + +
+ + + + + + +
+ +
+ +
+ + + + + +
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/WEB-INF/config.xhtml b/src/main/resources/META-INF/resources/WEB-INF/config.xhtml index 41d4fbd..aedd2c8 100644 --- a/src/main/resources/META-INF/resources/WEB-INF/config.xhtml +++ b/src/main/resources/META-INF/resources/WEB-INF/config.xhtml @@ -1,94 +1,94 @@ - - - - - - -
- -
Type de Menu
- - - - - - - -
- -
Schéma de Couleurs
- - - - - - - -
-
Mode Topbar et Menu
- - - - - -
- - -
-
Mode Topbar
- - - - - -
- - -
-
Mode Menu
- - - - - -
- -
- -
Style d'Input
- - - - - - -
- -
Couleurs du Thème
-
- -
- - -
-
-
-
-
-
- + + + + + + +
+ +
Type de Menu
+ + + + + + + +
+ +
Schéma de Couleurs
+ + + + + + + +
+
Mode Topbar et Menu
+ + + + + +
+ + +
+
Mode Topbar
+ + + + + +
+ + +
+
Mode Menu
+ + + + + +
+ +
+ +
Style d'Input
+ + + + + + +
+ +
Couleurs du Thème
+
+ +
+ + +
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/WEB-INF/rightpanel.xhtml b/src/main/resources/META-INF/resources/WEB-INF/rightpanel.xhtml index 1f6e4e5..61927a7 100644 --- a/src/main/resources/META-INF/resources/WEB-INF/rightpanel.xhtml +++ b/src/main/resources/META-INF/resources/WEB-INF/rightpanel.xhtml @@ -1,65 +1,65 @@ - - -
-
-
-
-
Mes Tâches
- - - -
-
    -
  • -
    -
    Réviser le devis pour le chantier A
    - -Validation budgétaire - -Vérification matériaux -
    -
  • -
  • -
    -
    Planifier la maintenance préventive
    - Matériel : Pelleteuse BX-2024 -
    -
  • -
  • -
    -
    Finaliser le rapport hebdomadaire
    -
    - -
  • -
-
- -
-
-
Favoris
-
- -
-
-
- -
- + + +
+
+
+
+
Mes Tâches
+ + + +
+
    +
  • +
    +
    Réviser le devis pour le chantier A
    + -Validation budgétaire + -Vérification matériaux +
    +
  • +
  • +
    +
    Planifier la maintenance préventive
    + Matériel : Pelleteuse BX-2024 +
    +
  • +
  • +
    +
    Finaliser le rapport hebdomadaire
    +
    + +
  • +
+
+ +
+
+
Favoris
+
+ +
+
+
+ +
+ diff --git a/src/main/resources/META-INF/resources/WEB-INF/template.xhtml b/src/main/resources/META-INF/resources/WEB-INF/template.xhtml index e7743bd..2718923 100644 --- a/src/main/resources/META-INF/resources/WEB-INF/template.xhtml +++ b/src/main/resources/META-INF/resources/WEB-INF/template.xhtml @@ -32,7 +32,11 @@
- + + + + + diff --git a/src/main/resources/META-INF/resources/WEB-INF/topbar.xhtml b/src/main/resources/META-INF/resources/WEB-INF/topbar.xhtml index 4a35c09..fd670a9 100644 --- a/src/main/resources/META-INF/resources/WEB-INF/topbar.xhtml +++ b/src/main/resources/META-INF/resources/WEB-INF/topbar.xhtml @@ -1,130 +1,130 @@ - - -
-
-
- - - - - - -
- - - - -
- -
- -
- + + +
+
+
+ + + + + + +
+ + + + +
+ +
+ +
+ diff --git a/src/main/resources/META-INF/resources/bon-commande.xhtml b/src/main/resources/META-INF/resources/bon-commande.xhtml index cb32e6f..74d7c5a 100644 --- a/src/main/resources/META-INF/resources/bon-commande.xhtml +++ b/src/main/resources/META-INF/resources/bon-commande.xhtml @@ -1,28 +1,28 @@ - - - Bons de commande - BTP Xpress - - -
-
-
-
-
-
-
Bons de commande
-

Gestion des bons de commande

-
-
-

Page en développement

-
-
-
-
-
-
- + + + Bons de commande - BTP Xpress + + +
+
+
+
+
+
+
Bons de commande
+

Gestion des bons de commande

+
+
+

Page en développement

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/budgets.xhtml b/src/main/resources/META-INF/resources/budgets.xhtml index dc4984e..9c5ca8b 100644 --- a/src/main/resources/META-INF/resources/budgets.xhtml +++ b/src/main/resources/META-INF/resources/budgets.xhtml @@ -1,28 +1,28 @@ - - - Budgets - BTP Xpress - - -
-
-
-
-
-
-
Budgets
-

Gestion des budgets

-
-
-

Page en développement

-
-
-
-
-
-
- + + + Budgets - BTP Xpress + + +
+
+
+
+
+
+
Budgets
+

Gestion des budgets

+
+
+

Page en développement

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/chantiers.xhtml b/src/main/resources/META-INF/resources/chantiers.xhtml index 6de8ce2..a76d73f 100644 --- a/src/main/resources/META-INF/resources/chantiers.xhtml +++ b/src/main/resources/META-INF/resources/chantiers.xhtml @@ -1,103 +1,103 @@ - - - Chantiers - BTP Xpress - - -
-
-
-
-
-

Gestion des Chantiers

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Chantiers - BTP Xpress + + +
+
+
+
+
+

Gestion des Chantiers

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/chantiers/details.xhtml b/src/main/resources/META-INF/resources/chantiers/details.xhtml index af2837e..7da9d3b 100644 --- a/src/main/resources/META-INF/resources/chantiers/details.xhtml +++ b/src/main/resources/META-INF/resources/chantiers/details.xhtml @@ -1,307 +1,307 @@ - - - Détails du chantier - BTP Xpress - - - - - - - -
-
-
- -
-
-
-
-

#{chantiersView.selectedItem.nom}

- - - -
-

- #{chantiersView.selectedItem.client} - - #{chantiersView.selectedItem.adresse} -

-
- - - Début: - - - - - - Fin prévue: - - - -
-
- -
- - -
-
-
- - -
-
- - - - - - - - -
- -
-
-
- Budget total -
- - - - - -
- Alloué au projet -
-
-
- -
-
-
- Coût réel -
- - - - - - -
- Dépensé à ce jour -
-
-
- -
-
-
- Reste disponible -
- - - - - - -
- - #{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Excédent' : 'Dépassement'} - -
-
-
-
- - -
- - - - -
- -
-
Informations générales
-
-
-
- Nom du chantier -

#{chantiersView.selectedItem.nom}

-
-
- Client -

#{chantiersView.selectedItem.client}

-
-
- Adresse -

#{chantiersView.selectedItem.adresse}

-
-
- Statut -
- - - -
-
-
- Avancement -

#{chantiersView.selectedItem.avancement}%

-
-
-
-
- - -
-
Progression du chantier
-
- - - - - -
-
- - -
-
Analyse budgétaire
-
-
-
-
- Budget prévu -
- - - -
-
-
-
-
- Dépensé -
- - - -
-
-
-
-
- - #{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Reste' : 'Dépassement'} - -
- - - -
-
-
-
- - - - - -
-
-
-
-
-
- - - -
-
-
Phases du chantier
- -
- -
-
- - - -
-
-
Équipes affectées
- -
- -
-
- - - -
-
-
Matériels utilisés
- -
- -
-
- - - -
-
-
Documents du chantier
- -
- -
-
- - - -
-
Historique des modifications
- - - - - - Fonctionnalité en cours de développement - - -
-
- -
-
- -
-
-
-
-
+ + + Détails du chantier - BTP Xpress + + + + + + + +
+
+
+ +
+
+
+
+

#{chantiersView.selectedItem.nom}

+ + + +
+

+ #{chantiersView.selectedItem.client} + + #{chantiersView.selectedItem.adresse} +

+
+ + + Début: + + + + + + Fin prévue: + + + +
+
+ +
+ + +
+
+
+ + +
+
+ + + + + + + + +
+ +
+
+
+ Budget total +
+ + + + + +
+ Alloué au projet +
+
+
+ +
+
+
+ Coût réel +
+ + + + + + +
+ Dépensé à ce jour +
+
+
+ +
+
+
+ Reste disponible +
+ + + + + + +
+ + #{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Excédent' : 'Dépassement'} + +
+
+
+
+ + +
+ + + + +
+ +
+
Informations générales
+
+
+
+ Nom du chantier +

#{chantiersView.selectedItem.nom}

+
+
+ Client +

#{chantiersView.selectedItem.client}

+
+
+ Adresse +

#{chantiersView.selectedItem.adresse}

+
+
+ Statut +
+ + + +
+
+
+ Avancement +

#{chantiersView.selectedItem.avancement}%

+
+
+
+
+ + +
+
Progression du chantier
+
+ + + + + +
+
+ + +
+
Analyse budgétaire
+
+
+
+
+ Budget prévu +
+ + + +
+
+
+
+
+ Dépensé +
+ + + +
+
+
+
+
+ + #{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Reste' : 'Dépassement'} + +
+ + + +
+
+
+
+ + + + + +
+
+
+
+
+
+ + + +
+
+
Phases du chantier
+ +
+ +
+
+ + + +
+
+
Équipes affectées
+ +
+ +
+
+ + + +
+
+
Matériels utilisés
+ +
+ +
+
+ + + +
+
+
Documents du chantier
+ +
+ +
+
+ + + +
+
Historique des modifications
+ + + + + + Fonctionnalité en cours de développement + + +
+
+ +
+
+ +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/chantiers/en-cours.xhtml b/src/main/resources/META-INF/resources/chantiers/en-cours.xhtml index 54013eb..a99f548 100644 --- a/src/main/resources/META-INF/resources/chantiers/en-cours.xhtml +++ b/src/main/resources/META-INF/resources/chantiers/en-cours.xhtml @@ -1,97 +1,97 @@ - - - Chantiers en cours - BTP Xpress - - - - - - - -
-
-
-
-
-

Chantiers en cours

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- + + + Chantiers en cours - BTP Xpress + + + + + + + +
+
+
+
+
+

Chantiers en cours

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/chantiers/nouveau.xhtml b/src/main/resources/META-INF/resources/chantiers/nouveau.xhtml index 15afae3..0138c59 100644 --- a/src/main/resources/META-INF/resources/chantiers/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/chantiers/nouveau.xhtml @@ -1,236 +1,236 @@ - - - Nouveau chantier - BTP Xpress - - -
-
-
-
- -
-
-

Créer un nouveau chantier

-

Remplissez les informations du chantier à créer

-
- -
- - - - - - - -
- -
- - - - - Nom descriptif du projet de construction -
- - -
- - - - - Nom du client ou de l'entreprise -
- - -
- - - - - Localisation précise du chantier -
- - -
- - - - - - - - -
- - -
- - - - Pourcentage de réalisation (0-100%) -
-
-
- - - -
- -
- - - -
- - -
- - - - Doit être postérieure à la date de début -
- - -
- -
- - - - -
- Basé sur dates début et fin -
-
-
- - - -
- -
- - - - Budget total alloué au chantier -
- - -
- - - - Coût réel dépensé (actualisé régulièrement) -
- - -
-
-
- État budgétaire - Budget: #{chantiersView.entity.budget} FCFA | Dépensé: #{chantiersView.entity.coutReel} FCFA -
- -
-
-
-
- - -
-
- Les champs marqués d'un - * - sont obligatoires -
-
- - -
-
- -
-
-
-
-
-
-
+ + + Nouveau chantier - BTP Xpress + + +
+
+
+
+ +
+
+

Créer un nouveau chantier

+

Remplissez les informations du chantier à créer

+
+ +
+ + + + + + + +
+ +
+ + + + + Nom descriptif du projet de construction +
+ + +
+ + + + + Nom du client ou de l'entreprise +
+ + +
+ + + + + Localisation précise du chantier +
+ + +
+ + + + + + + + +
+ + +
+ + + + Pourcentage de réalisation (0-100%) +
+
+
+ + + +
+ +
+ + + +
+ + +
+ + + + Doit être postérieure à la date de début +
+ + +
+ +
+ + + + +
+ Basé sur dates début et fin +
+
+
+ + + +
+ +
+ + + + Budget total alloué au chantier +
+ + +
+ + + + Coût réel dépensé (actualisé régulièrement) +
+ + +
+
+
+ État budgétaire + Budget: #{chantiersView.entity.budget} FCFA | Dépensé: #{chantiersView.entity.coutReel} FCFA +
+ +
+
+
+
+ + +
+
+ Les champs marqués d'un + * + sont obligatoires +
+
+ + +
+
+ +
+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/chantiers/planifies.xhtml b/src/main/resources/META-INF/resources/chantiers/planifies.xhtml index b1e75f7..5ab17e8 100644 --- a/src/main/resources/META-INF/resources/chantiers/planifies.xhtml +++ b/src/main/resources/META-INF/resources/chantiers/planifies.xhtml @@ -1,94 +1,94 @@ - - - Chantiers planifiés - BTP Xpress - - - - - - - -
-
-
-
-
-

Chantiers planifiés

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- + + + Chantiers planifiés - BTP Xpress + + + + + + + +
+
+
+
+
+

Chantiers planifiés

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/chantiers/termines.xhtml b/src/main/resources/META-INF/resources/chantiers/termines.xhtml index 10d6f6d..867a442 100644 --- a/src/main/resources/META-INF/resources/chantiers/termines.xhtml +++ b/src/main/resources/META-INF/resources/chantiers/termines.xhtml @@ -1,94 +1,94 @@ - - - Chantiers terminés - BTP Xpress - - - - - - - -
-
-
-
-
-

Chantiers terminés

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
- + + + Chantiers terminés - BTP Xpress + + + + + + + +
+
+
+
+
+

Chantiers terminés

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/clients.xhtml b/src/main/resources/META-INF/resources/clients.xhtml index e687b3d..b6c9bd5 100644 --- a/src/main/resources/META-INF/resources/clients.xhtml +++ b/src/main/resources/META-INF/resources/clients.xhtml @@ -1,95 +1,95 @@ - - - Clients - BTP Xpress - - -
-
-
-
-
-

Gestion des Clients

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Clients - BTP Xpress + + +
+
+
+
+
+

Gestion des Clients

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/clients/details.xhtml b/src/main/resources/META-INF/resources/clients/details.xhtml index e0e93d3..3d85599 100644 --- a/src/main/resources/META-INF/resources/clients/details.xhtml +++ b/src/main/resources/META-INF/resources/clients/details.xhtml @@ -1,85 +1,85 @@ - - - Détails du client - BTP Xpress - - - - - - - -
-
-
-
-
-

Détails du client

- -
- - -
-
- -
-
-

Raison sociale : #{clientsView.selectedItem.raisonSociale}

-
-
-

Nom du contact : #{clientsView.selectedItem.nomContact}

-
-
-

Email : #{clientsView.selectedItem.email}

-
-
-

Téléphone : #{clientsView.selectedItem.telephone}

-
-
-
-
- -
- -

Adresse : #{clientsView.selectedItem.adresse}

-

Ville : #{clientsView.selectedItem.ville}

-

Code postal : #{clientsView.selectedItem.codePostal}

-

Pays : #{clientsView.selectedItem.pays}

-
-
- -
- -

Nombre de chantiers : - -

-

Chiffre d'affaires total : - - - - -

-

Date de création : - - - -

-
-
-
- -
-
-
-
-
-
-
- + + + Détails du client - BTP Xpress + + + + + + + +
+
+
+
+
+

Détails du client

+ +
+ + +
+
+ +
+
+

Raison sociale : #{clientsView.selectedItem.raisonSociale}

+
+
+

Nom du contact : #{clientsView.selectedItem.nomContact}

+
+
+

Email : #{clientsView.selectedItem.email}

+
+
+

Téléphone : #{clientsView.selectedItem.telephone}

+
+
+
+
+ +
+ +

Adresse : #{clientsView.selectedItem.adresse}

+

Ville : #{clientsView.selectedItem.ville}

+

Code postal : #{clientsView.selectedItem.codePostal}

+

Pays : #{clientsView.selectedItem.pays}

+
+
+ +
+ +

Nombre de chantiers : + +

+

Chiffre d'affaires total : + + + + +

+

Date de création : + + + +

+
+
+
+ +
+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/clients/nouveau.xhtml b/src/main/resources/META-INF/resources/clients/nouveau.xhtml index 0bdd97c..5c335fc 100644 --- a/src/main/resources/META-INF/resources/clients/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/clients/nouveau.xhtml @@ -1,95 +1,95 @@ - - - Nouveau client - BTP Xpress - - -
-
-
-
-
-

Créer un nouveau client

- -
- - -
-
- - -
- -
- - -
- -
- - - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
-
- - -
-
-
-
-
-
-
-
-
-
- + + + Nouveau client - BTP Xpress + + +
+
+
+
+
+

Créer un nouveau client

+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+
+
+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/clients/recherche.xhtml b/src/main/resources/META-INF/resources/clients/recherche.xhtml index 847dc0d..b2c141c 100644 --- a/src/main/resources/META-INF/resources/clients/recherche.xhtml +++ b/src/main/resources/META-INF/resources/clients/recherche.xhtml @@ -1,96 +1,96 @@ - - - Recherche de clients - BTP Xpress - - -
-
-
-
-
-

Recherche avancée de clients

- -
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
- + + + Recherche de clients - BTP Xpress + + +
+
+
+
+
+

Recherche avancée de clients

+ +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/dashboard.xhtml b/src/main/resources/META-INF/resources/dashboard.xhtml index 28e764d..3902969 100644 --- a/src/main/resources/META-INF/resources/dashboard.xhtml +++ b/src/main/resources/META-INF/resources/dashboard.xhtml @@ -1,488 +1,488 @@ - - - Tableau de bord - BTP Xpress - - -
-
-
-
-

Tableau de bord - BTP Xpress

-

Bean dashboardView disponible: #{not empty dashboardView}

-

Chantiers actifs: #{dashboardView.chantiersActifs}

-

Test de contenu simple

-
-
-
- - - -
- - #{dashboardView.totalAlertes} alertes nécessitent votre attention immédiate - - Maintenance: #{dashboardView.alertesMaintenanceCount} • - Chantiers: #{dashboardView.alertesChantiersCount} • - Disponibilités: #{dashboardView.alertesDisponibilitesCount} - - -
-
- -
- - -
-
- - -
-
-
-
Chantiers actifs
-

#{dashboardView.chantiersActifs}

-

- Sur #{dashboardView.nombreChantiers} au total -

- -
- -
-
- - -
-
-
-
Équipes disponibles
-

#{dashboardView.equipesDisponibles}/#{dashboardView.nombreEquipes}

-

Taux de disponibilité

- -
- -
-
- - -
-
-
-
Maintenances en retard
-

#{dashboardView.maintenancesEnRetard}

-

#{dashboardView.maintenancesPlanifiees} planifiées

- -
- -
-
- -
-
- - - - -
-
-
-
-
Vue d'ensemble
-

Statistiques globales

-
-
-
- -
-
-
- -
-
#{dashboardView.chantiersActifs}
-
Chantiers actifs
-
-
- -
-
- - -
-
-
- -
-
#{dashboardView.chantiersEnRetardList.size()}
-
Chantiers en retard
-
-
- - - Attention requise - - -
-
- - -
-
-
- -
-
#{dashboardView.evenementsAujourdhui}
-
Événements aujourd'hui
-
-
-
-
- - -
-
-
- -
-
#{dashboardView.nombreDocuments}
-
Documents totaux
-
-
-
-
-
-
-
- - -
-
-
-
-
Ressources
-

État actuel des ressources

-
-
-
- - -
-
- -
-
- #{dashboardView.employesActifs}/#{dashboardView.nombreEmployes} -
-
Employés actifs
-
-
- -
- - -
-
- -
-
- #{dashboardView.materielDisponible}/#{dashboardView.nombreMateriel} -
-
Matériel disponible
-
-
- -
- - -
-
- -
-
#{dashboardView.tauxUtilisationGlobal}%
-
Taux d'utilisation global
-
-
- - - Moyenne chantiers, employés et matériel - -
- -
-
-
- - -
-
-
-
-
Chantiers actifs
-

#{dashboardView.chantiersActifsList.size()} chantiers en cours

-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
-
-
-
Chantiers en retard
-

#{dashboardView.chantiersEnRetardList.size()} chantiers en retard

-
-
- - -
-
-
-
- - #{chantier.nom} -
-

- Date fin prévue: #{chantier.dateFinPrevueFormatee} -

-
- -
-
-
- - -
- -

Tous les chantiers sont dans les temps

-
-
-
-
- - -
-
-
-
-
Maintenances en retard
-

#{dashboardView.maintenancesEnRetardList.size()} maintenances urgentes

-
-
- - -
-
-
-
- - #{maintenance.materiel} -
-

- Type: #{maintenance.type} • - Prévue: #{maintenance.datePrevueFormatee} -

-

- #{maintenance.description} -

-
- -
-
-
- - -
- -

Toutes les maintenances sont à jour

-
-
-
-
- - - - -
-
-
-
-
Disponibilités en attente
-

#{dashboardView.disponibilitesEnAttenteList.size()} demandes à valider

-
-
- - -
-
-
-
- - #{dispo.employe} -
-
- - - Du #{dispo.dateDebutFormatee} au #{dispo.dateFinFormatee} - (#{dispo.nombreJours} jours) - -
- - Motif: #{dispo.motif} - -
-
-
-
- - -
- -

Aucune demande de disponibilité en attente

-
-
-
-
- - -
-
-
-
-
Documents récents
-

5 derniers documents ajoutés

-
-
- -
    - -
  • - -
    -
    #{doc.nom}
    - - #{doc.type} • Ajouté le #{doc.dateCreationFormatee} - -
    - -
  • -
    -
- - -
- -

Aucun document récent

-
-
-
-
- -
-
-
-
+ + + Tableau de bord - BTP Xpress + + +
+
+
+
+

Tableau de bord - BTP Xpress

+

Bean dashboardView disponible: #{not empty dashboardView}

+

Chantiers actifs: #{dashboardView.chantiersActifs}

+

Test de contenu simple

+
+
+
+ + + +
+ + #{dashboardView.totalAlertes} alertes nécessitent votre attention immédiate + + Maintenance: #{dashboardView.alertesMaintenanceCount} • + Chantiers: #{dashboardView.alertesChantiersCount} • + Disponibilités: #{dashboardView.alertesDisponibilitesCount} + + +
+
+ +
+ + +
+
+ + +
+
+
+
Chantiers actifs
+

#{dashboardView.chantiersActifs}

+

+ Sur #{dashboardView.nombreChantiers} au total +

+ +
+ +
+
+ + +
+
+
+
Équipes disponibles
+

#{dashboardView.equipesDisponibles}/#{dashboardView.nombreEquipes}

+

Taux de disponibilité

+ +
+ +
+
+ + +
+
+
+
Maintenances en retard
+

#{dashboardView.maintenancesEnRetard}

+

#{dashboardView.maintenancesPlanifiees} planifiées

+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+
Vue d'ensemble
+

Statistiques globales

+
+
+
+ +
+
+
+ +
+
#{dashboardView.chantiersActifs}
+
Chantiers actifs
+
+
+ +
+
+ + +
+
+
+ +
+
#{dashboardView.chantiersEnRetardList.size()}
+
Chantiers en retard
+
+
+ + + Attention requise + + +
+
+ + +
+
+
+ +
+
#{dashboardView.evenementsAujourdhui}
+
Événements aujourd'hui
+
+
+
+
+ + +
+
+
+ +
+
#{dashboardView.nombreDocuments}
+
Documents totaux
+
+
+
+
+
+
+
+ + +
+
+
+
+
Ressources
+

État actuel des ressources

+
+
+
+ + +
+
+ +
+
+ #{dashboardView.employesActifs}/#{dashboardView.nombreEmployes} +
+
Employés actifs
+
+
+ +
+ + +
+
+ +
+
+ #{dashboardView.materielDisponible}/#{dashboardView.nombreMateriel} +
+
Matériel disponible
+
+
+ +
+ + +
+
+ +
+
#{dashboardView.tauxUtilisationGlobal}%
+
Taux d'utilisation global
+
+
+ + + Moyenne chantiers, employés et matériel + +
+ +
+
+
+ + +
+
+
+
+
Chantiers actifs
+

#{dashboardView.chantiersActifsList.size()} chantiers en cours

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+
+
+
Chantiers en retard
+

#{dashboardView.chantiersEnRetardList.size()} chantiers en retard

+
+
+ + +
+
+
+
+ + #{chantier.nom} +
+

+ Date fin prévue: #{chantier.dateFinPrevueFormatee} +

+
+ +
+
+
+ + +
+ +

Tous les chantiers sont dans les temps

+
+
+
+
+ + +
+
+
+
+
Maintenances en retard
+

#{dashboardView.maintenancesEnRetardList.size()} maintenances urgentes

+
+
+ + +
+
+
+
+ + #{maintenance.materiel} +
+

+ Type: #{maintenance.type} • + Prévue: #{maintenance.datePrevueFormatee} +

+

+ #{maintenance.description} +

+
+ +
+
+
+ + +
+ +

Toutes les maintenances sont à jour

+
+
+
+
+ + + + +
+
+
+
+
Disponibilités en attente
+

#{dashboardView.disponibilitesEnAttenteList.size()} demandes à valider

+
+
+ + +
+
+
+
+ + #{dispo.employe} +
+
+ + + Du #{dispo.dateDebutFormatee} au #{dispo.dateFinFormatee} + (#{dispo.nombreJours} jours) + +
+ + Motif: #{dispo.motif} + +
+
+
+
+ + +
+ +

Aucune demande de disponibilité en attente

+
+
+
+
+ + +
+
+
+
+
Documents récents
+

5 derniers documents ajoutés

+
+
+ +
    + +
  • + +
    +
    #{doc.nom}
    + + #{doc.type} • Ajouté le #{doc.dateCreationFormatee} + +
    + +
  • +
    +
+ + +
+ +

Aucun document récent

+
+
+
+
+ +
+
+
+
diff --git a/src/main/resources/META-INF/resources/devis.xhtml b/src/main/resources/META-INF/resources/devis.xhtml index 6078da9..24d5473 100644 --- a/src/main/resources/META-INF/resources/devis.xhtml +++ b/src/main/resources/META-INF/resources/devis.xhtml @@ -1,112 +1,112 @@ - - - Devis - BTP Xpress - - -
-
-
-
-
-

Gestion des Devis

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - - - - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Devis - BTP Xpress + + +
+
+
+
+
+

Gestion des Devis

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/devis/acceptes.xhtml b/src/main/resources/META-INF/resources/devis/acceptes.xhtml index 509a4c5..d0a07e8 100644 --- a/src/main/resources/META-INF/resources/devis/acceptes.xhtml +++ b/src/main/resources/META-INF/resources/devis/acceptes.xhtml @@ -1,27 +1,27 @@ - - - Devis acceptés - BTP Xpress - - -
-
-
-
-
-
-
Devis acceptés
-

Devis acceptés par les clients

-
-
-

Page en développement

-
-
-
-
-
-
+ + + Devis acceptés - BTP Xpress + + +
+
+
+
+
+
+
Devis acceptés
+

Devis acceptés par les clients

+
+
+

Page en développement

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/devis/attente.xhtml b/src/main/resources/META-INF/resources/devis/attente.xhtml index d7c48b3..423f2c4 100644 --- a/src/main/resources/META-INF/resources/devis/attente.xhtml +++ b/src/main/resources/META-INF/resources/devis/attente.xhtml @@ -1 +1 @@ -Attente - DEVIS - BTP Xpress

Attente

Module en cours de développement...

+Attente - DEVIS - BTP Xpress

Attente

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/devis/expires.xhtml b/src/main/resources/META-INF/resources/devis/expires.xhtml index 0158408..5dc1d70 100644 --- a/src/main/resources/META-INF/resources/devis/expires.xhtml +++ b/src/main/resources/META-INF/resources/devis/expires.xhtml @@ -1,27 +1,27 @@ - - - Devis expirés - BTP Xpress - - -
-
-
-
-
-
-
Devis expirés
-

Devis dont la validité est expirée

-
-
-

Page en développement

-
-
-
-
-
-
+ + + Devis expirés - BTP Xpress + + +
+
+
+
+
+
+
Devis expirés
+

Devis dont la validité est expirée

+
+
+

Page en développement

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/devis/nouveau.xhtml b/src/main/resources/META-INF/resources/devis/nouveau.xhtml index 90cc46c..5d31d47 100644 --- a/src/main/resources/META-INF/resources/devis/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/devis/nouveau.xhtml @@ -1,312 +1,312 @@ - - - Nouveau devis - BTP Xpress - - -
-
-
-
- -
-
-

Créer un nouveau devis

-

Établissez un devis détaillé pour votre client

-
- -
- - - - - - - -
- -
- -
- - - - -
- Généré automatiquement lors de l'enregistrement -
- - -
- - - - - - - - - -
- - -
- - - -
- - -
- - - - - Nom du client ou de l'entreprise -
- - -
- - - - Date limite de validité du devis (généralement 30 jours) -
- - -
- - - - - Description détaillée de la prestation -
-
-
- - - -
-
-
- - Lignes de devis -
-

- Ajoutez les différentes prestations, fournitures et main d'œuvre. - Cette fonctionnalité sera disponible dans une prochaine version. -

-
-
- - -
- -

Gestion des lignes de devis en cours de développement

-

- Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, TVA, etc. -

-
-
- - - -
- -
- - - - Montant hors taxes -
- - -
- -
- - - - -
- Calculé automatiquement (18% du montant HT) -
- - -
- -
- - - - -
- Montant toutes taxes comprises (HT + TVA) -
- - -
-
-
-
-
- Montant HT -
- - - - -
-
-
-
-
- TVA (18%) -
- - - - -
-
-
-
-
- Total TTC -
- - - - - -
-
-
-
-
-
-
-
- - - -
-
- - - - Détaillez les modalités de paiement -
-
- - - -
-
-
- - -
-
- Les champs marqués d'un - * - sont obligatoires -
-
- - - -
-
- -
-
-
-
-
-
-
+ + + Nouveau devis - BTP Xpress + + +
+
+
+
+ +
+
+

Créer un nouveau devis

+

Établissez un devis détaillé pour votre client

+
+ +
+ + + + + + + +
+ +
+ +
+ + + + +
+ Généré automatiquement lors de l'enregistrement +
+ + +
+ + + + + + + + + +
+ + +
+ + + +
+ + +
+ + + + + Nom du client ou de l'entreprise +
+ + +
+ + + + Date limite de validité du devis (généralement 30 jours) +
+ + +
+ + + + + Description détaillée de la prestation +
+
+
+ + + +
+
+
+ + Lignes de devis +
+

+ Ajoutez les différentes prestations, fournitures et main d'œuvre. + Cette fonctionnalité sera disponible dans une prochaine version. +

+
+
+ + +
+ +

Gestion des lignes de devis en cours de développement

+

+ Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, TVA, etc. +

+
+
+ + + +
+ +
+ + + + Montant hors taxes +
+ + +
+ +
+ + + + +
+ Calculé automatiquement (18% du montant HT) +
+ + +
+ +
+ + + + +
+ Montant toutes taxes comprises (HT + TVA) +
+ + +
+
+
+
+
+ Montant HT +
+ + + + +
+
+
+
+
+ TVA (18%) +
+ + + + +
+
+
+
+
+ Total TTC +
+ + + + + +
+
+
+
+
+
+
+
+ + + +
+
+ + + + Détaillez les modalités de paiement +
+
+ + + +
+
+
+ + +
+
+ Les champs marqués d'un + * + sont obligatoires +
+
+ + + +
+
+ +
+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/documentation.xhtml b/src/main/resources/META-INF/resources/documentation.xhtml index 20e94e2..e222877 100644 --- a/src/main/resources/META-INF/resources/documentation.xhtml +++ b/src/main/resources/META-INF/resources/documentation.xhtml @@ -1,24 +1,24 @@ - - - Documentation - BTP Xpress - - -
-
-
-
-

Documentation

-

Documentation de l'application BTP Xpress

-

Module en cours de développement...

-
-
-
-
-
-
- + + + Documentation - BTP Xpress + + +
+
+
+
+

Documentation

+

Documentation de l'application BTP Xpress

+

Module en cours de développement...

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/documents.xhtml b/src/main/resources/META-INF/resources/documents.xhtml index 6d58068..bf69148 100644 --- a/src/main/resources/META-INF/resources/documents.xhtml +++ b/src/main/resources/META-INF/resources/documents.xhtml @@ -1,28 +1,28 @@ - - - Documents - BTP Xpress - - -
-
-
-
-
-
-
Documents
-

Gestion des documents

-
-
-

Page en développement

-
-
-
-
-
-
- + + + Documents - BTP Xpress + + +
+
+
+
+
+
+
Documents
+

Gestion des documents

+
+
+

Page en développement

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/employes.xhtml b/src/main/resources/META-INF/resources/employes.xhtml index 3dd6674..bcc5ab2 100644 --- a/src/main/resources/META-INF/resources/employes.xhtml +++ b/src/main/resources/META-INF/resources/employes.xhtml @@ -1,102 +1,102 @@ - - - Employés - BTP Xpress - - -
-
-
-
-
-

Gestion des Employés

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Employés - BTP Xpress + + +
+
+
+
+
+

Gestion des Employés

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/employes/actifs.xhtml b/src/main/resources/META-INF/resources/employes/actifs.xhtml index 2fc9e49..946b946 100644 --- a/src/main/resources/META-INF/resources/employes/actifs.xhtml +++ b/src/main/resources/META-INF/resources/employes/actifs.xhtml @@ -1 +1 @@ -Actifs - EMPLOYES - BTP Xpress

Actifs

Module en cours de développement...

+Actifs - EMPLOYES - BTP Xpress

Actifs

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/employes/disponibles.xhtml b/src/main/resources/META-INF/resources/employes/disponibles.xhtml index 674ae02..ba512ed 100644 --- a/src/main/resources/META-INF/resources/employes/disponibles.xhtml +++ b/src/main/resources/META-INF/resources/employes/disponibles.xhtml @@ -1 +1 @@ -Disponibles - EMPLOYES - BTP Xpress

Disponibles

Module en cours de développement...

+Disponibles - EMPLOYES - BTP Xpress

Disponibles

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/employes/nouveau.xhtml b/src/main/resources/META-INF/resources/employes/nouveau.xhtml index ee463d4..880132b 100644 --- a/src/main/resources/META-INF/resources/employes/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/employes/nouveau.xhtml @@ -1 +1 @@ -EMPLOYES - BTP Xpress

EMPLOYES

Module en cours de développement...

+EMPLOYES - BTP Xpress

EMPLOYES

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/equipes.xhtml b/src/main/resources/META-INF/resources/equipes.xhtml index fec569e..dd6fe13 100644 --- a/src/main/resources/META-INF/resources/equipes.xhtml +++ b/src/main/resources/META-INF/resources/equipes.xhtml @@ -1,93 +1,93 @@ - - - Équipes - BTP Xpress - - -
-
-
-
-
-

Gestion des Équipes

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Équipes - BTP Xpress + + +
+
+
+
+
+

Gestion des Équipes

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/equipes/disponibles.xhtml b/src/main/resources/META-INF/resources/equipes/disponibles.xhtml index 5385005..59ac200 100644 --- a/src/main/resources/META-INF/resources/equipes/disponibles.xhtml +++ b/src/main/resources/META-INF/resources/equipes/disponibles.xhtml @@ -1 +1 @@ -Disponibles - EQUIPES - BTP Xpress

Disponibles

Module en cours de développement...

+Disponibles - EQUIPES - BTP Xpress

Disponibles

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/equipes/nouveau.xhtml b/src/main/resources/META-INF/resources/equipes/nouveau.xhtml index 578a14d..f69483c 100644 --- a/src/main/resources/META-INF/resources/equipes/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/equipes/nouveau.xhtml @@ -1 +1 @@ -EQUIPES - BTP Xpress

EQUIPES

Module en cours de développement...

+EQUIPES - BTP Xpress

EQUIPES

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/equipes/specialites.xhtml b/src/main/resources/META-INF/resources/equipes/specialites.xhtml index 566cc69..c66766f 100644 --- a/src/main/resources/META-INF/resources/equipes/specialites.xhtml +++ b/src/main/resources/META-INF/resources/equipes/specialites.xhtml @@ -1 +1 @@ -Specialites - EQUIPES - BTP Xpress

Specialites

Module en cours de développement...

+Specialites - EQUIPES - BTP Xpress

Specialites

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/factures.xhtml b/src/main/resources/META-INF/resources/factures.xhtml index 10d4c2a..3a311fb 100644 --- a/src/main/resources/META-INF/resources/factures.xhtml +++ b/src/main/resources/META-INF/resources/factures.xhtml @@ -1,122 +1,122 @@ - - - Factures - BTP Xpress - - -
-
-
-
-
-

Gestion des Factures

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - - - - - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Factures - BTP Xpress + + +
+
+
+
+
+

Gestion des Factures

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/factures/impayees.xhtml b/src/main/resources/META-INF/resources/factures/impayees.xhtml index a7a664d..5fc8192 100644 --- a/src/main/resources/META-INF/resources/factures/impayees.xhtml +++ b/src/main/resources/META-INF/resources/factures/impayees.xhtml @@ -1 +1 @@ -Impayees - FACTURES - BTP Xpress

Impayees

Module en cours de développement...

+Impayees - FACTURES - BTP Xpress

Impayees

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/factures/nouveau.xhtml b/src/main/resources/META-INF/resources/factures/nouveau.xhtml index b807f5f..5421609 100644 --- a/src/main/resources/META-INF/resources/factures/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/factures/nouveau.xhtml @@ -1,392 +1,392 @@ - - - Nouvelle facture - BTP Xpress - - -
-
-
-
- -
-
-

Créer une nouvelle facture

-

Émettez une facture pour un chantier ou prestation réalisée

-
- -
- - - - - - - -
- -
- -
- - - - -
- Généré automatiquement selon la séquence configurée -
- - -
- - - - - - - - - - -
- - -
- - - - - - - - Nature de la facturation -
- - -
- - - - - Nom du client ou de l'entreprise -
- - -
- - - -
- - -
- - - - - Description des prestations facturées -
- - -
- - - - Date limite de paiement (généralement 30 jours) -
- - -
- - - - Date effective du paiement (optionnel) -
-
-
- - - -
-
-
- - Lignes de facturation -
-

- Ajoutez les différentes prestations, fournitures et quantités facturées. - Cette fonctionnalité sera disponible dans une prochaine version. -

-
-
- - -
- -

Gestion des lignes de facture en cours de développement

-

- Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, remise, etc. -

-
-
- - - -
- -
- - - - Montant hors taxes -
- - -
- -
- - - - -
- Calculé automatiquement (18% du montant HT) -
- - -
- -
- - - - -
- Montant toutes taxes comprises (HT + TVA) -
- - -
- - - - Montant déjà encaissé -
- - -
- -
- - - - -
- Reste à encaisser -
- - -
-
-
-
-
- Montant HT -
- - - - -
-
-
-
-
- TVA (18%) -
- - - - -
-
-
-
-
- Total TTC -
- - - - - -
-
-
-
-
- Reste à payer -
- - - - - -
-
-
-
-
-
-
-
- - - -
-
- - - - - - - - - -
-
- - -
-
- - - -
-
-
- - -
-
- Les champs marqués d'un - * - sont obligatoires -
-
- - - -
-
- -
-
-
-
-
-
-
+ + + Nouvelle facture - BTP Xpress + + +
+
+
+
+ +
+
+

Créer une nouvelle facture

+

Émettez une facture pour un chantier ou prestation réalisée

+
+ +
+ + + + + + + +
+ +
+ +
+ + + + +
+ Généré automatiquement selon la séquence configurée +
+ + +
+ + + + + + + + + + +
+ + +
+ + + + + + + + Nature de la facturation +
+ + +
+ + + + + Nom du client ou de l'entreprise +
+ + +
+ + + +
+ + +
+ + + + + Description des prestations facturées +
+ + +
+ + + + Date limite de paiement (généralement 30 jours) +
+ + +
+ + + + Date effective du paiement (optionnel) +
+
+
+ + + +
+
+
+ + Lignes de facturation +
+

+ Ajoutez les différentes prestations, fournitures et quantités facturées. + Cette fonctionnalité sera disponible dans une prochaine version. +

+
+
+ + +
+ +

Gestion des lignes de facture en cours de développement

+

+ Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, remise, etc. +

+
+
+ + + +
+ +
+ + + + Montant hors taxes +
+ + +
+ +
+ + + + +
+ Calculé automatiquement (18% du montant HT) +
+ + +
+ +
+ + + + +
+ Montant toutes taxes comprises (HT + TVA) +
+ + +
+ + + + Montant déjà encaissé +
+ + +
+ +
+ + + + +
+ Reste à encaisser +
+ + +
+
+
+
+
+ Montant HT +
+ + + + +
+
+
+
+
+ TVA (18%) +
+ + + + +
+
+
+
+
+ Total TTC +
+ + + + + +
+
+
+
+
+ Reste à payer +
+ + + + + +
+
+
+
+
+
+
+
+ + + +
+
+ + + + + + + + + +
+
+ + +
+
+ + + +
+
+
+ + +
+
+ Les champs marqués d'un + * + sont obligatoires +
+
+ + + +
+
+ +
+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/factures/payees.xhtml b/src/main/resources/META-INF/resources/factures/payees.xhtml index f2abace..e4f58fd 100644 --- a/src/main/resources/META-INF/resources/factures/payees.xhtml +++ b/src/main/resources/META-INF/resources/factures/payees.xhtml @@ -1 +1 @@ -Payees - FACTURES - BTP Xpress

Payees

Module en cours de développement...

+Payees - FACTURES - BTP Xpress

Payees

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/factures/retard.xhtml b/src/main/resources/META-INF/resources/factures/retard.xhtml index da4ec4a..e9cbb80 100644 --- a/src/main/resources/META-INF/resources/factures/retard.xhtml +++ b/src/main/resources/META-INF/resources/factures/retard.xhtml @@ -1 +1 @@ -Retard - FACTURES - BTP Xpress

Retard

Module en cours de développement...

+Retard - FACTURES - BTP Xpress

Retard

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/fournisseurs.xhtml b/src/main/resources/META-INF/resources/fournisseurs.xhtml index f13734e..6fef7a9 100644 --- a/src/main/resources/META-INF/resources/fournisseurs.xhtml +++ b/src/main/resources/META-INF/resources/fournisseurs.xhtml @@ -1,28 +1,28 @@ - - - Fournisseurs - BTP Xpress - - -
-
-
-
-
-
-
Fournisseurs
-

Gestion des fournisseurs

-
-
-

Page en développement

-
-
-
-
-
-
- + + + Fournisseurs - BTP Xpress + + +
+
+
+
+
+
+
Fournisseurs
+

Gestion des fournisseurs

+
+
+

Page en développement

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/index.xhtml b/src/main/resources/META-INF/resources/index.xhtml new file mode 100644 index 0000000..384e7ac --- /dev/null +++ b/src/main/resources/META-INF/resources/index.xhtml @@ -0,0 +1,58 @@ + + + + + + + + + + + BTP Xpress - Plateforme de Gestion BTP + + + + + + + +
+ +
+
+
+

+
+ BTP Xpress +

+

+ Plateforme de Gestion BTP +

+

+ Gestion complète de vos chantiers, équipes, matériels et facturation. + Optimisez votre activité BTP avec une solution moderne et intuitive. +

+
+ + +
+
+
+
+ + + +
+
+ + + diff --git a/src/main/resources/META-INF/resources/login.xhtml b/src/main/resources/META-INF/resources/login.xhtml index 5f91760..490f054 100644 --- a/src/main/resources/META-INF/resources/login.xhtml +++ b/src/main/resources/META-INF/resources/login.xhtml @@ -1,85 +1,85 @@ - - - - - - - - - - - Connexion - BTP Xpress - - - - - - - - - + + + + + + + + + + + Connexion - BTP Xpress + + + + + + + + + diff --git a/src/main/resources/META-INF/resources/maintenance.xhtml b/src/main/resources/META-INF/resources/maintenance.xhtml index 26fa38c..3156812 100644 --- a/src/main/resources/META-INF/resources/maintenance.xhtml +++ b/src/main/resources/META-INF/resources/maintenance.xhtml @@ -1,23 +1,23 @@ - - - Maintenance - BTP Xpress - - -
-
-
-
-

Gestion de la Maintenance

-

Module en cours de développement...

-
-
-
-
-
-
- + + + Maintenance - BTP Xpress + + +
+
+
+
+

Gestion de la Maintenance

+

Module en cours de développement...

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/maintenance/corrective.xhtml b/src/main/resources/META-INF/resources/maintenance/corrective.xhtml index 8987aaf..5aa3703 100644 --- a/src/main/resources/META-INF/resources/maintenance/corrective.xhtml +++ b/src/main/resources/META-INF/resources/maintenance/corrective.xhtml @@ -1 +1 @@ -Corrective - MAINTENANCE - BTP Xpress

Corrective

Module en cours de développement...

+Corrective - MAINTENANCE - BTP Xpress

Corrective

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/maintenance/nouveau.xhtml b/src/main/resources/META-INF/resources/maintenance/nouveau.xhtml index 61530f4..00056e8 100644 --- a/src/main/resources/META-INF/resources/maintenance/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/maintenance/nouveau.xhtml @@ -1 +1 @@ -MAINTENANCE - BTP Xpress

MAINTENANCE

Module en cours de développement...

+MAINTENANCE - BTP Xpress

MAINTENANCE

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/maintenance/preventive.xhtml b/src/main/resources/META-INF/resources/maintenance/preventive.xhtml index 3068ec3..1025b5a 100644 --- a/src/main/resources/META-INF/resources/maintenance/preventive.xhtml +++ b/src/main/resources/META-INF/resources/maintenance/preventive.xhtml @@ -1,27 +1,27 @@ - - - Maintenance préventive - BTP Xpress - - -
-
-
-
-
-
-
Maintenance préventive
-

Maintenances préventives planifiées

-
-
-

Page en développement

-
-
-
-
-
-
+ + + Maintenance préventive - BTP Xpress + + +
+
+
+
+
+
+
Maintenance préventive
+

Maintenances préventives planifiées

+
+
+

Page en développement

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/maintenance/urgente.xhtml b/src/main/resources/META-INF/resources/maintenance/urgente.xhtml index b0993fd..b311c57 100644 --- a/src/main/resources/META-INF/resources/maintenance/urgente.xhtml +++ b/src/main/resources/META-INF/resources/maintenance/urgente.xhtml @@ -1 +1 @@ -Urgente - MAINTENANCE - BTP Xpress

Urgente

Module en cours de développement...

+Urgente - MAINTENANCE - BTP Xpress

Urgente

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/materiels.xhtml b/src/main/resources/META-INF/resources/materiels.xhtml index 3c4aa69..616cee7 100644 --- a/src/main/resources/META-INF/resources/materiels.xhtml +++ b/src/main/resources/META-INF/resources/materiels.xhtml @@ -1,111 +1,111 @@ - - - Matériels - BTP Xpress - - -
-
-
-
-
-

Gestion des Matériels

- -
-
-
- -
- - - - - -
-
- - -
-
- - - - - - - - -
-
- - - - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Matériels - BTP Xpress + + +
+
+
+
+
+

Gestion des Matériels

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + + + + + + + +
+
+ + + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/messages.xhtml b/src/main/resources/META-INF/resources/messages.xhtml index 50afa25..33e951c 100644 --- a/src/main/resources/META-INF/resources/messages.xhtml +++ b/src/main/resources/META-INF/resources/messages.xhtml @@ -1,23 +1,23 @@ - - - Messages - BTP Xpress - - -
-
-
-
-

Messages

-

Module en cours de développement...

-
-
-
-
-
-
- + + + Messages - BTP Xpress + + +
+
+
+
+

Messages

+

Module en cours de développement...

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/messages/archives.xhtml b/src/main/resources/META-INF/resources/messages/archives.xhtml index cb85be2..a720ed7 100644 --- a/src/main/resources/META-INF/resources/messages/archives.xhtml +++ b/src/main/resources/META-INF/resources/messages/archives.xhtml @@ -1 +1 @@ -Archives - MESSAGES - BTP Xpress

Archives

Module en cours de développement...

+Archives - MESSAGES - BTP Xpress

Archives

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/messages/envoyes.xhtml b/src/main/resources/META-INF/resources/messages/envoyes.xhtml index 72b91fc..3331703 100644 --- a/src/main/resources/META-INF/resources/messages/envoyes.xhtml +++ b/src/main/resources/META-INF/resources/messages/envoyes.xhtml @@ -1 +1 @@ -Envoyes - MESSAGES - BTP Xpress

Envoyes

Module en cours de développement...

+Envoyes - MESSAGES - BTP Xpress

Envoyes

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/messages/nouveau.xhtml b/src/main/resources/META-INF/resources/messages/nouveau.xhtml index ba27016..23341ff 100644 --- a/src/main/resources/META-INF/resources/messages/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/messages/nouveau.xhtml @@ -1 +1 @@ -MESSAGES - BTP Xpress

MESSAGES

Module en cours de développement...

+MESSAGES - BTP Xpress

MESSAGES

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/notifications.xhtml b/src/main/resources/META-INF/resources/notifications.xhtml index 2bb905a..643ab8b 100644 --- a/src/main/resources/META-INF/resources/notifications.xhtml +++ b/src/main/resources/META-INF/resources/notifications.xhtml @@ -1,23 +1,23 @@ - - - Notifications - BTP Xpress - - -
-
-
-
-

Notifications

-

Module en cours de développement...

-
-
-
-
-
-
- + + + Notifications - BTP Xpress + + +
+
+
+
+

Notifications

+

Module en cours de développement...

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/notifications/non-lues.xhtml b/src/main/resources/META-INF/resources/notifications/non-lues.xhtml index dd3c53d..08eae0d 100644 --- a/src/main/resources/META-INF/resources/notifications/non-lues.xhtml +++ b/src/main/resources/META-INF/resources/notifications/non-lues.xhtml @@ -1 +1 @@ -Non Lues - NOTIFICATIONS - BTP Xpress

Non Lues

Module en cours de développement...

+Non Lues - NOTIFICATIONS - BTP Xpress

Non Lues

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/notifications/nouveau.xhtml b/src/main/resources/META-INF/resources/notifications/nouveau.xhtml index 63ad67e..23b105d 100644 --- a/src/main/resources/META-INF/resources/notifications/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/notifications/nouveau.xhtml @@ -1 +1 @@ -NOTIFICATIONS - BTP Xpress

NOTIFICATIONS

Module en cours de développement...

+NOTIFICATIONS - BTP Xpress

NOTIFICATIONS

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/notifications/recentes.xhtml b/src/main/resources/META-INF/resources/notifications/recentes.xhtml index ac90931..dee4bea 100644 --- a/src/main/resources/META-INF/resources/notifications/recentes.xhtml +++ b/src/main/resources/META-INF/resources/notifications/recentes.xhtml @@ -1 +1 @@ -Recentes - NOTIFICATIONS - BTP Xpress

Recentes

Module en cours de développement...

+Recentes - NOTIFICATIONS - BTP Xpress

Recentes

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/notifications/statistiques.xhtml b/src/main/resources/META-INF/resources/notifications/statistiques.xhtml index 6d01d78..204ccee 100644 --- a/src/main/resources/META-INF/resources/notifications/statistiques.xhtml +++ b/src/main/resources/META-INF/resources/notifications/statistiques.xhtml @@ -1 +1 @@ -Statistiques - NOTIFICATIONS - BTP Xpress

Statistiques

Module en cours de développement...

+Statistiques - NOTIFICATIONS - BTP Xpress

Statistiques

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/parametres.xhtml b/src/main/resources/META-INF/resources/parametres.xhtml index 53141fc..ec55472 100644 --- a/src/main/resources/META-INF/resources/parametres.xhtml +++ b/src/main/resources/META-INF/resources/parametres.xhtml @@ -1,28 +1,28 @@ - - - Paramètres - BTP Xpress - - -
-
-
-
-
-
-
Paramètres
-

Configuration de l'application

-
-
-

Page en développement

-
-
-
-
-
-
- + + + Paramètres - BTP Xpress + + +
+
+
+
+
+
+
Paramètres
+

Configuration de l'application

+
+
+

Page en développement

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/planning.xhtml b/src/main/resources/META-INF/resources/planning.xhtml index 72840cd..226beee 100644 --- a/src/main/resources/META-INF/resources/planning.xhtml +++ b/src/main/resources/META-INF/resources/planning.xhtml @@ -1,23 +1,23 @@ - - - Planning - BTP Xpress - - -
-
-
-
-

Planning

-

Module en cours de développement...

-
-
-
-
-
-
- + + + Planning - BTP Xpress + + +
+
+
+
+

Planning

+

Module en cours de développement...

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/planning/calendrier.xhtml b/src/main/resources/META-INF/resources/planning/calendrier.xhtml index 0ca9b05..4d14867 100644 --- a/src/main/resources/META-INF/resources/planning/calendrier.xhtml +++ b/src/main/resources/META-INF/resources/planning/calendrier.xhtml @@ -1 +1 @@ -Calendrier - PLANNING - BTP Xpress

Calendrier

Module en cours de développement...

+Calendrier - PLANNING - BTP Xpress

Calendrier

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/planning/equipes.xhtml b/src/main/resources/META-INF/resources/planning/equipes.xhtml index 974cae7..c287fb1 100644 --- a/src/main/resources/META-INF/resources/planning/equipes.xhtml +++ b/src/main/resources/META-INF/resources/planning/equipes.xhtml @@ -1 +1 @@ -Equipes - PLANNING - BTP Xpress

Equipes

Module en cours de développement...

+Equipes - PLANNING - BTP Xpress

Equipes

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/planning/materiel.xhtml b/src/main/resources/META-INF/resources/planning/materiel.xhtml index 82642e9..0fc4d45 100644 --- a/src/main/resources/META-INF/resources/planning/materiel.xhtml +++ b/src/main/resources/META-INF/resources/planning/materiel.xhtml @@ -1 +1 @@ -Materiel - PLANNING - BTP Xpress

Materiel

Module en cours de développement...

+Materiel - PLANNING - BTP Xpress

Materiel

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/planning/nouveau.xhtml b/src/main/resources/META-INF/resources/planning/nouveau.xhtml index bcc4dc3..3666362 100644 --- a/src/main/resources/META-INF/resources/planning/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/planning/nouveau.xhtml @@ -1 +1 @@ -PLANNING - BTP Xpress

PLANNING

Module en cours de développement...

+PLANNING - BTP Xpress

PLANNING

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/profile.xhtml b/src/main/resources/META-INF/resources/profile.xhtml index 8a064ad..0ab46c5 100644 --- a/src/main/resources/META-INF/resources/profile.xhtml +++ b/src/main/resources/META-INF/resources/profile.xhtml @@ -1,210 +1,210 @@ - - - Mon Profil - BTP Xpress - - -
-
- -
-
-
-
- #{profileView.initiales} -
-
-

#{profileView.nomComplet}

-

- - - #{role} - -

-
-
-
-
- - -
-
-
-

- - Informations Personnelles -

-
- - - -
-
- -
- - #{profileView.username} -
-
- -
- -
- - #{profileView.email} -
-
- -
- -
- - #{profileView.prenom} -
-
- -
- -
- - #{profileView.nom} -
-
- -
- -
- - #{profileView.telephone} -
-
- -
- -
- - #{profileView.organisation} -
-
-
-
-
- - -
- -
-

- - Rôles et Permissions -

- - - -
-
- Rôles attribués - -
- -
- - - -
-
-
- - -
-

- - Sécurité -

- - - -
-
- -
- - #{profileView.derniereConnexion} -
-
- -
- -
- - #{profileView.tokenExpiration} -
-
- -
- - - -
-
-
-
- - -
-
-

- - Activité Récente -

- - - -
-
-
-
- -
-
Projets actifs
-
-
-
-
- -
-
-
- -
-
Tâches complétées
-
-
-
-
- -
-
-
- -
-
Notifications
-
-
-
-
-
- - -
-
-
-
-
-
+ + + Mon Profil - BTP Xpress + + +
+
+ +
+
+
+
+ #{profileView.initiales} +
+
+

#{profileView.nomComplet}

+

+ + + #{role} + +

+
+
+
+
+ + +
+
+
+

+ + Informations Personnelles +

+
+ + + +
+
+ +
+ + #{profileView.username} +
+
+ +
+ +
+ + #{profileView.email} +
+
+ +
+ +
+ + #{profileView.prenom} +
+
+ +
+ +
+ + #{profileView.nom} +
+
+ +
+ +
+ + #{profileView.telephone} +
+
+ +
+ +
+ + #{profileView.organisation} +
+
+
+
+
+ + +
+ +
+

+ + Rôles et Permissions +

+ + + +
+
+ Rôles attribués + +
+ +
+ + + +
+
+
+ + +
+

+ + Sécurité +

+ + + +
+
+ +
+ + #{profileView.derniereConnexion} +
+
+ +
+ +
+ + #{profileView.tokenExpiration} +
+
+ +
+ + + +
+
+
+
+ + +
+
+

+ + Activité Récente +

+ + + +
+
+
+
+ +
+
Projets actifs
+
-
+
+
+ +
+
+
+ +
+
Tâches complétées
+
-
+
+
+ +
+
+
+ +
+
Notifications
+
-
+
+
+
+ + +
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/rapports.xhtml b/src/main/resources/META-INF/resources/rapports.xhtml index 2fe54eb..1000b2b 100644 --- a/src/main/resources/META-INF/resources/rapports.xhtml +++ b/src/main/resources/META-INF/resources/rapports.xhtml @@ -1,23 +1,23 @@ - - - Rapports - BTP Xpress - - -
-
-
-
-

Rapports

-

Module en cours de développement...

-
-
-
-
-
-
- + + + Rapports - BTP Xpress + + +
+
+
+
+

Rapports

+

Module en cours de développement...

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/resources/rapports/ca.xhtml b/src/main/resources/META-INF/resources/rapports/ca.xhtml index 72ca620..ea86cd3 100644 --- a/src/main/resources/META-INF/resources/rapports/ca.xhtml +++ b/src/main/resources/META-INF/resources/rapports/ca.xhtml @@ -1 +1 @@ -Ca - RAPPORTS - BTP Xpress

Ca

Module en cours de développement...

+Ca - RAPPORTS - BTP Xpress

Ca

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/rapports/clients.xhtml b/src/main/resources/META-INF/resources/rapports/clients.xhtml index 505f5da..25d11e2 100644 --- a/src/main/resources/META-INF/resources/rapports/clients.xhtml +++ b/src/main/resources/META-INF/resources/rapports/clients.xhtml @@ -1 +1 @@ -Clients - RAPPORTS - BTP Xpress

Clients

Module en cours de développement...

+Clients - RAPPORTS - BTP Xpress

Clients

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/rapports/equipes.xhtml b/src/main/resources/META-INF/resources/rapports/equipes.xhtml index 4aeb4c5..56df0c6 100644 --- a/src/main/resources/META-INF/resources/rapports/equipes.xhtml +++ b/src/main/resources/META-INF/resources/rapports/equipes.xhtml @@ -1 +1 @@ -Equipes - RAPPORTS - BTP Xpress

Equipes

Module en cours de développement...

+Equipes - RAPPORTS - BTP Xpress

Equipes

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/rapports/nouveau.xhtml b/src/main/resources/META-INF/resources/rapports/nouveau.xhtml index 1c979b5..b31c8c4 100644 --- a/src/main/resources/META-INF/resources/rapports/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/rapports/nouveau.xhtml @@ -1 +1 @@ -RAPPORTS - BTP Xpress

RAPPORTS

Module en cours de développement...

+RAPPORTS - BTP Xpress

RAPPORTS

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/rapports/rentabilite.xhtml b/src/main/resources/META-INF/resources/rapports/rentabilite.xhtml index 4c90d51..223fb1e 100644 --- a/src/main/resources/META-INF/resources/rapports/rentabilite.xhtml +++ b/src/main/resources/META-INF/resources/rapports/rentabilite.xhtml @@ -1 +1 @@ -Rentabilite - RAPPORTS - BTP Xpress

Rentabilite

Module en cours de développement...

+Rentabilite - RAPPORTS - BTP Xpress

Rentabilite

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/resources/css/custom-topbar.css b/src/main/resources/META-INF/resources/resources/css/custom-topbar.css index f62726b..3437639 100644 --- a/src/main/resources/META-INF/resources/resources/css/custom-topbar.css +++ b/src/main/resources/META-INF/resources/resources/css/custom-topbar.css @@ -1,185 +1,185 @@ -/** - * Styles personnalisés pour le topbar et le menu profil utilisateur. - * - * Améliore l'apparence professionnelle du menu dropdown utilisateur - * avec un header élégant affichant les informations de l'utilisateur connecté. - */ - -/* Header du profil utilisateur dans le menu dropdown */ -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header { - padding: 16px; - background: var(--surface-ground, #f8f9fa); - border-bottom: 1px solid var(--surface-border, #dee2e6); - cursor: default; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header:hover { - background: var(--surface-ground, #f8f9fa); -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-info { - display: flex; - align-items: center; - gap: 12px; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .profile-avatar-small { - width: 48px; - height: 48px; - border-radius: 50%; - object-fit: cover; - border: 2px solid var(--primary-color, #464DF2); - flex-shrink: 0; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-details { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; - flex: 1; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-name { - font-weight: 600; - font-size: 14px; - color: var(--text-color, #3E4754); - line-height: 1.4; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-role { - font-size: 12px; - color: var(--text-color-secondary, #6C757D); - line-height: 1.4; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Séparateurs dans le menu profil */ -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-divider { - padding: 0; - margin: 4px 0; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-divider hr { - margin: 0; - border: none; - border-top: 1px solid var(--surface-border, #dee2e6); - height: 1px; -} - -/* Icônes dans le menu profil avec espacement */ -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > .ui-commandlink { - display: flex; - align-items: center; - gap: 10px; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a i, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > .ui-commandlink i { - width: 26px; - height: 26px; - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - color: var(--text-color-secondary, #6C757D); - border-radius: 4px; - flex-shrink: 0; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a:hover i, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > .ui-commandlink:hover i { - background: var(--surface-hover, #e9ecef); - color: var(--primary-color, #464DF2); -} - -/* Badges dans le menu profil */ -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a .ui-badge { - margin-left: auto; -} - -/* Style pour l'option Logout */ -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a { - color: var(--red-500, #ef4444); - display: flex; - align-items: center; - gap: 10px; - text-decoration: none; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a:hover, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink:hover, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a:hover { - background: var(--red-50, #fef2f2); -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a i, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink i, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a i { - color: var(--red-500, #ef4444); -} - -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a:hover i, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink:hover i, -.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a:hover i { - background: var(--red-100, #fee2e2); - color: var(--red-600, #dc2626); -} - -/* Amélioration de l'avatar dans le bouton principal */ -.layout-topbar .layout-topbar-actions > li.user-profile > a > img { - border-radius: 50%; - border: 2px solid transparent; - transition: all 0.2s ease; -} - -.layout-topbar .layout-topbar-actions > li.user-profile > a:hover > img { - border-color: var(--primary-color, #464DF2); - box-shadow: 0 0 0 2px rgba(70, 77, 242, 0.1); -} - -/* Badges de notification dans le topbar */ -.layout-topbar .layout-topbar-actions > li.topbar-item .ui-badge { - position: absolute; - top: -4px; - right: -4px; - background: var(--red-500, #ef4444); - color: white; - font-size: 10px; - font-weight: 600; - min-width: 18px; - height: 18px; - line-height: 18px; - padding: 0 5px; - border-radius: 9px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -/* Responsive pour petits écrans */ -@media (max-width: 768px) { - .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header { - padding: 12px; - } - - .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .profile-avatar-small { - width: 40px; - height: 40px; - } - - .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-name { - font-size: 13px; - } - - .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-role { - font-size: 11px; - } -} - +/** + * Styles personnalisés pour le topbar et le menu profil utilisateur. + * + * Améliore l'apparence professionnelle du menu dropdown utilisateur + * avec un header élégant affichant les informations de l'utilisateur connecté. + */ + +/* Header du profil utilisateur dans le menu dropdown */ +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header { + padding: 16px; + background: var(--surface-ground, #f8f9fa); + border-bottom: 1px solid var(--surface-border, #dee2e6); + cursor: default; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header:hover { + background: var(--surface-ground, #f8f9fa); +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-info { + display: flex; + align-items: center; + gap: 12px; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .profile-avatar-small { + width: 48px; + height: 48px; + border-radius: 50%; + object-fit: cover; + border: 2px solid var(--primary-color, #464DF2); + flex-shrink: 0; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-details { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + flex: 1; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-name { + font-weight: 600; + font-size: 14px; + color: var(--text-color, #3E4754); + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-role { + font-size: 12px; + color: var(--text-color-secondary, #6C757D); + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Séparateurs dans le menu profil */ +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-divider { + padding: 0; + margin: 4px 0; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-divider hr { + margin: 0; + border: none; + border-top: 1px solid var(--surface-border, #dee2e6); + height: 1px; +} + +/* Icônes dans le menu profil avec espacement */ +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > .ui-commandlink { + display: flex; + align-items: center; + gap: 10px; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a i, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > .ui-commandlink i { + width: 26px; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: var(--text-color-secondary, #6C757D); + border-radius: 4px; + flex-shrink: 0; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a:hover i, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > .ui-commandlink:hover i { + background: var(--surface-hover, #e9ecef); + color: var(--primary-color, #464DF2); +} + +/* Badges dans le menu profil */ +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li > a .ui-badge { + margin-left: auto; +} + +/* Style pour l'option Logout */ +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a { + color: var(--red-500, #ef4444); + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a:hover, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink:hover, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a:hover { + background: var(--red-50, #fef2f2); +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a i, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink i, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a i { + color: var(--red-500, #ef4444); +} + +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child a:hover i, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child .ui-commandlink:hover i, +.layout-topbar .layout-topbar-actions > li.user-profile > ul > li:last-child form a:hover i { + background: var(--red-100, #fee2e2); + color: var(--red-600, #dc2626); +} + +/* Amélioration de l'avatar dans le bouton principal */ +.layout-topbar .layout-topbar-actions > li.user-profile > a > img { + border-radius: 50%; + border: 2px solid transparent; + transition: all 0.2s ease; +} + +.layout-topbar .layout-topbar-actions > li.user-profile > a:hover > img { + border-color: var(--primary-color, #464DF2); + box-shadow: 0 0 0 2px rgba(70, 77, 242, 0.1); +} + +/* Badges de notification dans le topbar */ +.layout-topbar .layout-topbar-actions > li.topbar-item .ui-badge { + position: absolute; + top: -4px; + right: -4px; + background: var(--red-500, #ef4444); + color: white; + font-size: 10px; + font-weight: 600; + min-width: 18px; + height: 18px; + line-height: 18px; + padding: 0 5px; + border-radius: 9px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Responsive pour petits écrans */ +@media (max-width: 768px) { + .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header { + padding: 12px; + } + + .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .profile-avatar-small { + width: 40px; + height: 40px; + } + + .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-name { + font-size: 13px; + } + + .layout-topbar .layout-topbar-actions > li.user-profile > ul > li.user-profile-header .user-role { + font-size: 11px; + } +} + diff --git a/src/main/resources/META-INF/resources/stock.xhtml b/src/main/resources/META-INF/resources/stock.xhtml index 338bc4b..1f5ed63 100644 --- a/src/main/resources/META-INF/resources/stock.xhtml +++ b/src/main/resources/META-INF/resources/stock.xhtml @@ -1,121 +1,121 @@ - - - Stock - BTP Xpress - - -
-
-
-
-
-

Gestion du Stock

- -
-
-
- -
- - - - - -
-
- - -
-
- - -
-
- - - - - - - - -
-
- - - - - - - -
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ + + Stock - BTP Xpress + + +
+
+
+
+
+

Gestion du Stock

+ +
+
+
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + + + + + + +
+
+ + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/stock/commandes.xhtml b/src/main/resources/META-INF/resources/stock/commandes.xhtml index e0ce4e2..a2849bd 100644 --- a/src/main/resources/META-INF/resources/stock/commandes.xhtml +++ b/src/main/resources/META-INF/resources/stock/commandes.xhtml @@ -1 +1 @@ -Commandes - STOCK - BTP Xpress

Commandes

Module en cours de développement...

+Commandes - STOCK - BTP Xpress

Commandes

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/stock/inventaire.xhtml b/src/main/resources/META-INF/resources/stock/inventaire.xhtml index 7940674..3d61702 100644 --- a/src/main/resources/META-INF/resources/stock/inventaire.xhtml +++ b/src/main/resources/META-INF/resources/stock/inventaire.xhtml @@ -1 +1 @@ -Inventaire - STOCK - BTP Xpress

Inventaire

Module en cours de développement...

+Inventaire - STOCK - BTP Xpress

Inventaire

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/stock/nouveau.xhtml b/src/main/resources/META-INF/resources/stock/nouveau.xhtml index ca17859..973d563 100644 --- a/src/main/resources/META-INF/resources/stock/nouveau.xhtml +++ b/src/main/resources/META-INF/resources/stock/nouveau.xhtml @@ -1 +1 @@ -STOCK - BTP Xpress

STOCK

Module en cours de développement...

+STOCK - BTP Xpress

STOCK

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/stock/sorties.xhtml b/src/main/resources/META-INF/resources/stock/sorties.xhtml index 0746aef..a2981e1 100644 --- a/src/main/resources/META-INF/resources/stock/sorties.xhtml +++ b/src/main/resources/META-INF/resources/stock/sorties.xhtml @@ -1 +1 @@ -Sorties - STOCK - BTP Xpress

Sorties

Module en cours de développement...

+Sorties - STOCK - BTP Xpress

Sorties

Module en cours de développement...

diff --git a/src/main/resources/META-INF/resources/utilisateurs.xhtml b/src/main/resources/META-INF/resources/utilisateurs.xhtml index 1a1d5b1..25d3565 100644 --- a/src/main/resources/META-INF/resources/utilisateurs.xhtml +++ b/src/main/resources/META-INF/resources/utilisateurs.xhtml @@ -1,28 +1,28 @@ - - - Utilisateurs - BTP Xpress - - -
-
-
-
-
-
-
Utilisateurs
-

Gestion des utilisateurs

-
-
-

Page en développement

-
-
-
-
-
-
- + + + Utilisateurs - BTP Xpress + + +
+
+
+
+
+
+
Utilisateurs
+

Gestion des utilisateurs

+
+
+

Page en développement

+
+
+
+
+
+
+ diff --git a/src/main/resources/META-INF/web.xml b/src/main/resources/META-INF/web.xml index 4a4428b..35bec6e 100644 --- a/src/main/resources/META-INF/web.xml +++ b/src/main/resources/META-INF/web.xml @@ -1,126 +1,126 @@ - - - - BTP Xpress Client - - - index.xhtml - dashboard.xhtml - login.xhtml - - - - - - jakarta.faces.STATE_SAVING_METHOD - server - - - jakarta.faces.PROJECT_STAGE - Development - - - jakarta.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE - true - - - jakarta.faces.PARTIAL_STATE_SAVING - true - - - jakarta.faces.FACELETS_LIBRARIES - /WEB-INF/primefaces-freya.taglib.xml - - - jakarta.faces.FACELETS_REFRESH_PERIOD - 0 - - - jakarta.faces.FACELETS_SKIP_COMMENTS - true - - - - - primefaces.THEME - freya-#{guestPreferences.theme} - - - primefaces.FONT_AWESOME - true - - - primefaces.UPLOADER - auto - - - primefaces.CLIENT_SIDE_VALIDATION - true - - - - - Character Encoding Filter - dev.lions.btpxpress.filter.CharacterEncodingFilter - - - Character Encoding Filter - /* - - - - - Security Headers Filter - dev.lions.btpxpress.filter.SecurityHeadersFilter - - - Security Headers Filter - /* - - - - - Faces Servlet - jakarta.faces.webapp.FacesServlet - 1 - - - Faces Servlet - *.jsf - - - Faces Servlet - *.xhtml - - - Faces Servlet - / - - - - - ttf - application/font-sfnt - - - woff - application/font-woff - - - woff2 - application/font-woff2 - - - eot - application/vnd.ms-fontobject - - - svg - image/svg+xml - - - + + + + BTP Xpress Client + + + index.xhtml + dashboard.xhtml + login.xhtml + + + + + + jakarta.faces.STATE_SAVING_METHOD + server + + + jakarta.faces.PROJECT_STAGE + Development + + + jakarta.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE + true + + + jakarta.faces.PARTIAL_STATE_SAVING + true + + + jakarta.faces.FACELETS_LIBRARIES + /WEB-INF/primefaces-freya.taglib.xml + + + jakarta.faces.FACELETS_REFRESH_PERIOD + 0 + + + jakarta.faces.FACELETS_SKIP_COMMENTS + true + + + + + primefaces.THEME + freya-#{guestPreferences.theme} + + + primefaces.FONT_AWESOME + true + + + primefaces.UPLOADER + auto + + + primefaces.CLIENT_SIDE_VALIDATION + true + + + + + Character Encoding Filter + dev.lions.btpxpress.filter.CharacterEncodingFilter + + + Character Encoding Filter + /* + + + + + Security Headers Filter + dev.lions.btpxpress.filter.SecurityHeadersFilter + + + Security Headers Filter + /* + + + + + Faces Servlet + jakarta.faces.webapp.FacesServlet + 1 + + + Faces Servlet + *.jsf + + + Faces Servlet + *.xhtml + + + Faces Servlet + / + + + + + ttf + application/font-sfnt + + + woff + application/font-woff + + + woff2 + application/font-woff2 + + + eot + application/vnd.ms-fontobject + + + svg + image/svg+xml + + + diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 80fec87..986c9f0 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -29,7 +29,7 @@ quarkus.http.host=0.0.0.0 # CORS Configuration pour production # Frontend accessible depuis btpxpress.lions.dev -quarkus.http.cors=true +quarkus.http.cors.enabled=true quarkus.http.cors.origins=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS,PATCH quarkus.http.cors.headers=Content-Type,Authorization,X-Requested-With,X-CSRF-Token @@ -104,7 +104,7 @@ quarkus.log.category."dev.lions.btpxpress".level=INFO quarkus.log.category."org.hibernate".level=WARN quarkus.log.category."io.quarkus".level=INFO quarkus.log.category."io.quarkus.oidc".level=WARN -quarkus.log.console.enable=true +quarkus.log.console.enabled=true quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n # Cache optimisé pour production diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 73ca2c6..0e82950 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,9 +15,9 @@ jakarta.faces.VALIDATE_EMPTY_FIELDS=auto quarkus.arc.remove-unused-beans=false -quarkus.http.port=8080 -quarkus.http.cors=true -quarkus.http.cors.origins=http://localhost:8080,https://security.lions.dev +quarkus.http.port=8081 +quarkus.http.cors.enabled=true +quarkus.http.cors.origins=http://localhost:8081,https://security.lions.dev %dev.quarkus.oidc.enabled=true %prod.quarkus.oidc.enabled=true @@ -71,10 +71,10 @@ quarkus.log.level=INFO quarkus.log.category."dev.lions.btpxpress".level=DEBUG quarkus.log.category."io.quarkus.oidc".level=DEBUG quarkus.log.category."io.quarkus.security".level=DEBUG -quarkus.log.console.enable=true +quarkus.log.console.enabled=true quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n -btpxpress.api.base-url=http://localhost:8080 +btpxpress.api.base-url=http://localhost:8081 btpxpress.api.timeout=30000 quarkus.rest-client."dev.lions.btpxpress.service.BtpXpressApiClient".url=${btpxpress.api.base-url} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index f580b52..f2271fd 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -1,118 +1,118 @@ - - - - BTP Xpress Client - - - index.xhtml - dashboard.xhtml - login.xhtml - - - - - - jakarta.faces.STATE_SAVING_METHOD - server - - - jakarta.faces.PROJECT_STAGE - Development - - - jakarta.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE - true - - - jakarta.faces.PARTIAL_STATE_SAVING - true - - - jakarta.faces.FACELETS_LIBRARIES - /WEB-INF/primefaces-freya.taglib.xml - - - - - primefaces.THEME - freya-#{guestPreferences.theme} - - - primefaces.FONT_AWESOME - true - - - primefaces.UPLOADER - auto - - - primefaces.CLIENT_SIDE_VALIDATION - true - - - - - Character Encoding Filter - dev.lions.btpxpress.filter.CharacterEncodingFilter - - - Character Encoding Filter - /* - - - - - Security Headers Filter - dev.lions.btpxpress.filter.SecurityHeadersFilter - - - Security Headers Filter - /* - - - - - Faces Servlet - jakarta.faces.webapp.FacesServlet - 1 - - - Faces Servlet - *.jsf - - - Faces Servlet - *.xhtml - - - Faces Servlet - / - - - - - ttf - application/font-sfnt - - - woff - application/font-woff - - - woff2 - application/font-woff2 - - - eot - application/vnd.ms-fontobject - - - svg - image/svg+xml - - - + + + + BTP Xpress Client + + + index.xhtml + dashboard.xhtml + login.xhtml + + + + + + jakarta.faces.STATE_SAVING_METHOD + server + + + jakarta.faces.PROJECT_STAGE + Development + + + jakarta.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_IS_SYSTEM_TIMEZONE + true + + + jakarta.faces.PARTIAL_STATE_SAVING + true + + + jakarta.faces.FACELETS_LIBRARIES + /WEB-INF/primefaces-freya.taglib.xml + + + + + primefaces.THEME + freya-#{guestPreferences.theme} + + + primefaces.FONT_AWESOME + true + + + primefaces.UPLOADER + auto + + + primefaces.CLIENT_SIDE_VALIDATION + true + + + + + Character Encoding Filter + dev.lions.btpxpress.filter.CharacterEncodingFilter + + + Character Encoding Filter + /* + + + + + Security Headers Filter + dev.lions.btpxpress.filter.SecurityHeadersFilter + + + Security Headers Filter + /* + + + + + Faces Servlet + jakarta.faces.webapp.FacesServlet + 1 + + + Faces Servlet + *.jsf + + + Faces Servlet + *.xhtml + + + Faces Servlet + / + + + + + ttf + application/font-sfnt + + + woff + application/font-woff + + + woff2 + application/font-woff2 + + + eot + application/vnd.ms-fontobject + + + svg + image/svg+xml + + +