chore(quarkus-327): bump to Quarkus 3.27.3 LTS, rename deprecated config keys
This commit is contained in:
110
.dockerignore
110
.dockerignore
@@ -1,55 +1,55 @@
|
|||||||
# Docker ignore pour BTP Xpress Client
|
# Docker ignore pour BTP Xpress Client
|
||||||
|
|
||||||
# Répertoires de build et target
|
# Répertoires de build et target
|
||||||
target/
|
target/
|
||||||
.mvn/
|
.mvn/
|
||||||
.quarkus/
|
.quarkus/
|
||||||
|
|
||||||
# Fichiers IDE
|
# Fichiers IDE
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
.settings/
|
.settings/
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
|
||||||
# Documentation (non nécessaire dans l'image)
|
# Documentation (non nécessaire dans l'image)
|
||||||
*.md
|
*.md
|
||||||
!README.md
|
!README.md
|
||||||
|
|
||||||
# Git
|
# Git
|
||||||
.git/
|
.git/
|
||||||
.gitignore
|
.gitignore
|
||||||
.gitattributes
|
.gitattributes
|
||||||
|
|
||||||
# Fichiers de configuration locale
|
# Fichiers de configuration locale
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
src/test/
|
src/test/
|
||||||
|
|
||||||
# Fichiers temporaires
|
# Fichiers temporaires
|
||||||
*.tmp
|
*.tmp
|
||||||
*.bak
|
*.bak
|
||||||
*.swp
|
*.swp
|
||||||
*~
|
*~
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Scripts de déploiement (non nécessaires dans l'image)
|
# Scripts de déploiement (non nécessaires dans l'image)
|
||||||
scripts/
|
scripts/
|
||||||
*.sh
|
*.sh
|
||||||
*.ps1
|
*.ps1
|
||||||
*.bat
|
*.bat
|
||||||
|
|
||||||
# Kubernetes (géré séparément)
|
# Kubernetes (géré séparément)
|
||||||
kubernetes/
|
kubernetes/
|
||||||
*.yaml
|
*.yaml
|
||||||
*.yml
|
*.yml
|
||||||
|
|
||||||
|
|||||||
116
.gitignore
vendored
Normal file
116
.gitignore
vendored
Normal file
@@ -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
|
||||||
@@ -1,186 +1,186 @@
|
|||||||
# Audit de Configuration - BTP Xpress Client ↔ Serveur
|
# Audit de Configuration - BTP Xpress Client ↔ Serveur
|
||||||
|
|
||||||
## ✅ Résumé de l'audit effectué
|
## ✅ Résumé de l'audit effectué
|
||||||
|
|
||||||
Date : 2025-11-01
|
Date : 2025-11-01
|
||||||
Portée : Configuration complète du client PrimeFaces et mapping avec le serveur backend
|
Portée : Configuration complète du client PrimeFaces et mapping avec le serveur backend
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Structure du Projet Client
|
## 1. Structure du Projet Client
|
||||||
|
|
||||||
### ✅ Structure des fichiers
|
### ✅ Structure des fichiers
|
||||||
- **XHTML** : `src/main/resources/META-INF/resources/` (structure Quarkus correcte)
|
- **XHTML** : `src/main/resources/META-INF/resources/` (structure Quarkus correcte)
|
||||||
- **Configuration** : `src/main/resources/META-INF/web.xml` et `application.properties`
|
- **Configuration** : `src/main/resources/META-INF/web.xml` et `application.properties`
|
||||||
- **Beans CDI** : `src/main/java/dev/lions/btpxpress/`
|
- **Beans CDI** : `src/main/java/dev/lions/btpxpress/`
|
||||||
- **Services** : `src/main/java/dev/lions/btpxpress/service/`
|
- **Services** : `src/main/java/dev/lions/btpxpress/service/`
|
||||||
|
|
||||||
### ✅ Fichiers créés/vérifiés
|
### ✅ Fichiers créés/vérifiés
|
||||||
- ✅ `BtpXpressApiClient.java` - Interface REST Client pour communication backend
|
- ✅ `BtpXpressApiClient.java` - Interface REST Client pour communication backend
|
||||||
- ✅ `ChantierService.java` - Service encapsulant les appels API chantiers
|
- ✅ `ChantierService.java` - Service encapsulant les appels API chantiers
|
||||||
- ✅ `application.properties` - Configuration complète OIDC + REST Client
|
- ✅ `application.properties` - Configuration complète OIDC + REST Client
|
||||||
- ✅ `pom.xml` - Dépendances OIDC et JWT ajoutées
|
- ✅ `pom.xml` - Dépendances OIDC et JWT ajoutées
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Configuration OIDC / Keycloak
|
## 2. Configuration OIDC / Keycloak
|
||||||
|
|
||||||
### ✅ Client (PrimeFaces)
|
### ✅ Client (PrimeFaces)
|
||||||
```properties
|
```properties
|
||||||
quarkus.oidc.enabled=true
|
quarkus.oidc.enabled=true
|
||||||
quarkus.oidc.auth-server-url=https://security.lions.dev/realms/btpxpress
|
quarkus.oidc.auth-server-url=https://security.lions.dev/realms/btpxpress
|
||||||
quarkus.oidc.client-id=btpxpress-frontend
|
quarkus.oidc.client-id=btpxpress-frontend
|
||||||
quarkus.oidc.application-type=web-app
|
quarkus.oidc.application-type=web-app
|
||||||
quarkus.oidc.token.issuer=https://security.lions.dev/realms/btpxpress
|
quarkus.oidc.token.issuer=https://security.lions.dev/realms/btpxpress
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ Serveur (Backend)
|
### ✅ Serveur (Backend)
|
||||||
```properties
|
```properties
|
||||||
mp.jwt.verify.publickey.location=https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs
|
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
|
mp.jwt.verify.issuer=https://security.lions.dev/realms/btpxpress
|
||||||
quarkus.smallrye-jwt.enabled=true
|
quarkus.smallrye-jwt.enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ Vérifications
|
### ✅ Vérifications
|
||||||
- ✅ **Même realm** : `btpxpress`
|
- ✅ **Même realm** : `btpxpress`
|
||||||
- ✅ **Même serveur Keycloak** : `https://security.lions.dev`
|
- ✅ **Même serveur Keycloak** : `https://security.lions.dev`
|
||||||
- ✅ **Client ID frontend** : `btpxpress-frontend` (doit exister dans Keycloak)
|
- ✅ **Client ID frontend** : `btpxpress-frontend` (doit exister dans Keycloak)
|
||||||
- ✅ **JWT Validation** : Backend valide les tokens via certificats publics
|
- ✅ **JWT Validation** : Backend valide les tokens via certificats publics
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Communication Client ↔ Serveur
|
## 3. Communication Client ↔ Serveur
|
||||||
|
|
||||||
### ✅ Configuration REST Client
|
### ✅ Configuration REST Client
|
||||||
```properties
|
```properties
|
||||||
btpxpress.api.base-url=http://localhost:8080
|
btpxpress.api.base-url=http://localhost:8080
|
||||||
quarkus.rest-client."dev.lions.btpxpress.service.BtpXpressApiClient".url=${btpxpress.api.base-url}
|
quarkus.rest-client."dev.lions.btpxpress.service.BtpXpressApiClient".url=${btpxpress.api.base-url}
|
||||||
```
|
```
|
||||||
|
|
||||||
### ✅ Endpoints mappés
|
### ✅ Endpoints mappés
|
||||||
|
|
||||||
| Client (Interface) | Serveur (Resource) | Endpoint | Status |
|
| Client (Interface) | Serveur (Resource) | Endpoint | Status |
|
||||||
|-------------------|-------------------|----------|--------|
|
|-------------------|-------------------|----------|--------|
|
||||||
| `BtpXpressApiClient.getChantiers()` | `ChantierResource.getAllChantiers()` | `GET /api/v1/chantiers` | ✅ Existe |
|
| `BtpXpressApiClient.getChantiers()` | `ChantierResource.getAllChantiers()` | `GET /api/v1/chantiers` | ✅ Existe |
|
||||||
| `BtpXpressApiClient.getChantier(id)` | `ChantierResource.getChantierById()` | `GET /api/v1/chantiers/{id}` | ✅ Existe |
|
| `BtpXpressApiClient.getChantier(id)` | `ChantierResource.getChantierById()` | `GET /api/v1/chantiers/{id}` | ✅ Existe |
|
||||||
| `BtpXpressApiClient.getClients()` | `ClientResource.getAllClients()` | `GET /api/v1/clients` | ✅ Existe |
|
| `BtpXpressApiClient.getClients()` | `ClientResource.getAllClients()` | `GET /api/v1/clients` | ✅ Existe |
|
||||||
| `BtpXpressApiClient.getClient(id)` | `ClientResource.getClientById()` | `GET /api/v1/clients/{id}` | ✅ Existe |
|
| `BtpXpressApiClient.getClient(id)` | `ClientResource.getClientById()` | `GET /api/v1/clients/{id}` | ✅ Existe |
|
||||||
|
|
||||||
### ✅ CORS Configuration
|
### ✅ CORS Configuration
|
||||||
|
|
||||||
**Serveur** (`application.properties`) :
|
**Serveur** (`application.properties`) :
|
||||||
```properties
|
```properties
|
||||||
quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:3000,http://localhost:5173,http://localhost:8081}
|
quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:3000,http://localhost:5173,http://localhost:8081}
|
||||||
```
|
```
|
||||||
✅ **Port 8081 ajouté** aux origines autorisées
|
✅ **Port 8081 ajouté** aux origines autorisées
|
||||||
|
|
||||||
**Client** :
|
**Client** :
|
||||||
```properties
|
```properties
|
||||||
quarkus.http.cors.origins=http://localhost:8080,https://security.lions.dev
|
quarkus.http.cors.origins=http://localhost:8080,https://security.lions.dev
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Ports et URLs
|
## 4. Ports et URLs
|
||||||
|
|
||||||
| Service | Port | URL | Description |
|
| Service | Port | URL | Description |
|
||||||
|---------|------|-----|-------------|
|
|---------|------|-----|-------------|
|
||||||
| Backend | 8080 | http://localhost:8080 | API REST backend |
|
| Backend | 8080 | http://localhost:8080 | API REST backend |
|
||||||
| Client | 8081 | http://localhost:8081 | Application PrimeFaces |
|
| Client | 8081 | http://localhost:8081 | Application PrimeFaces |
|
||||||
| Keycloak | - | https://security.lions.dev | Authentification OIDC |
|
| Keycloak | - | https://security.lions.dev | Authentification OIDC |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Dépendances Maven
|
## 5. Dépendances Maven
|
||||||
|
|
||||||
### ✅ Client (`pom.xml`)
|
### ✅ Client (`pom.xml`)
|
||||||
- ✅ `quarkus-oidc` - Authentification OIDC
|
- ✅ `quarkus-oidc` - Authentification OIDC
|
||||||
- ✅ `quarkus-smallrye-jwt` - Support JWT
|
- ✅ `quarkus-smallrye-jwt` - Support JWT
|
||||||
- ✅ `quarkus-rest-client` - REST Client
|
- ✅ `quarkus-rest-client` - REST Client
|
||||||
- ✅ `quarkus-rest-jackson` - Sérialisation JSON
|
- ✅ `quarkus-rest-jackson` - Sérialisation JSON
|
||||||
- ✅ `quarkus-primefaces` - PrimeFaces integration
|
- ✅ `quarkus-primefaces` - PrimeFaces integration
|
||||||
- ✅ `freya-theme` - Thème PrimeFaces Freya
|
- ✅ `freya-theme` - Thème PrimeFaces Freya
|
||||||
|
|
||||||
### ✅ Serveur (vérifié)
|
### ✅ Serveur (vérifié)
|
||||||
- ✅ `quarkus-smallrye-jwt` - Validation JWT
|
- ✅ `quarkus-smallrye-jwt` - Validation JWT
|
||||||
- ✅ CORS activé avec origine `http://localhost:8081`
|
- ✅ CORS activé avec origine `http://localhost:8081`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Flux d'Authentification
|
## 6. Flux d'Authentification
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||||
│ Client │ │ Keycloak │ │ Backend │
|
│ Client │ │ Keycloak │ │ Backend │
|
||||||
│ Port 8081 │ │security.lions│ │ Port 8080 │
|
│ Port 8081 │ │security.lions│ │ Port 8080 │
|
||||||
└─────────────┘ └──────────────┘ └─────────────┘
|
└─────────────┘ └──────────────┘ └─────────────┘
|
||||||
│ │ │
|
│ │ │
|
||||||
│ 1. Accès page protégée │
|
│ 1. Accès page protégée │
|
||||||
│────────────────────────────────────────────────►│
|
│────────────────────────────────────────────────►│
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ 2. Redirection OIDC │
|
│ │ 2. Redirection OIDC │
|
||||||
│◄────────────────────────────────────────────────│
|
│◄────────────────────────────────────────────────│
|
||||||
│ │ │
|
│ │ │
|
||||||
│ 3. Redirect Keycloak │ │
|
│ 3. Redirect Keycloak │ │
|
||||||
│────────────────────────►│ │
|
│────────────────────────►│ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ 4. Authentification │ │
|
│ 4. Authentification │ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ 5. Token JWT │ │
|
│ 5. Token JWT │ │
|
||||||
│◄────────────────────────│ │
|
│◄────────────────────────│ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ 6. Appel API + Token │ │
|
│ 6. Appel API + Token │ │
|
||||||
│────────────────────────────────────────────────►│
|
│────────────────────────────────────────────────►│
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ 7. Validation token │
|
│ │ 7. Validation token │
|
||||||
│ │◄───────────────────────│
|
│ │◄───────────────────────│
|
||||||
│ │ │
|
│ │ │
|
||||||
│ 8. Réponse API │ │
|
│ 8. Réponse API │ │
|
||||||
│◄────────────────────────────────────────────────│
|
│◄────────────────────────────────────────────────│
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Points de Vérification Requis
|
## 7. Points de Vérification Requis
|
||||||
|
|
||||||
### ⚠️ À vérifier dans Keycloak
|
### ⚠️ À vérifier dans Keycloak
|
||||||
1. **Client `btpxpress-frontend` existe** dans le realm `btpxpress`
|
1. **Client `btpxpress-frontend` existe** dans le realm `btpxpress`
|
||||||
2. **Redirect URIs** incluent `http://localhost:8081/*`
|
2. **Redirect URIs** incluent `http://localhost:8081/*`
|
||||||
3. **Web Origins** incluent `http://localhost:8081`
|
3. **Web Origins** incluent `http://localhost:8081`
|
||||||
4. **Client Secret** configuré si nécessaire (pour confidential client)
|
4. **Client Secret** configuré si nécessaire (pour confidential client)
|
||||||
|
|
||||||
### ⚠️ À tester
|
### ⚠️ À tester
|
||||||
1. **Authentification OIDC** : Vérifier la redirection vers Keycloak
|
1. **Authentification OIDC** : Vérifier la redirection vers Keycloak
|
||||||
2. **Token JWT** : Vérifier l'envoi automatique dans les requêtes REST
|
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
|
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`
|
4. **Endpoints API** : Tester les appels `GET /api/v1/chantiers` et `/api/v1/clients`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Configuration Complète Validée
|
## 8. Configuration Complète Validée
|
||||||
|
|
||||||
| Composant | Configuration | Status |
|
| Composant | Configuration | Status |
|
||||||
|-----------|--------------|--------|
|
|-----------|--------------|--------|
|
||||||
| Structure fichiers | Quarkus standard | ✅ |
|
| Structure fichiers | Quarkus standard | ✅ |
|
||||||
| OIDC Client | `btpxpress-frontend` | ✅ |
|
| OIDC Client | `btpxpress-frontend` | ✅ |
|
||||||
| OIDC Server | `security.lions.dev` | ✅ |
|
| OIDC Server | `security.lions.dev` | ✅ |
|
||||||
| REST Client | `BtpXpressApiClient` | ✅ |
|
| REST Client | `BtpXpressApiClient` | ✅ |
|
||||||
| Services | `ChantierService` | ✅ |
|
| Services | `ChantierService` | ✅ |
|
||||||
| CORS Backend | Port 8081 autorisé | ✅ |
|
| CORS Backend | Port 8081 autorisé | ✅ |
|
||||||
| CORS Client | Port 8080 autorisé | ✅ |
|
| CORS Client | Port 8080 autorisé | ✅ |
|
||||||
| Endpoints mappés | Tous vérifiés | ✅ |
|
| Endpoints mappés | Tous vérifiés | ✅ |
|
||||||
| Dépendances | Toutes présentes | ✅ |
|
| Dépendances | Toutes présentes | ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Conclusion
|
## 🎯 Conclusion
|
||||||
|
|
||||||
**✅ La configuration est complète et correcte** :
|
**✅ La configuration est complète et correcte** :
|
||||||
- Le client PrimeFaces est correctement configuré pour communiquer avec le backend
|
- Le client PrimeFaces est correctement configuré pour communiquer avec le backend
|
||||||
- L'authentification OIDC est configurée avec Keycloak sur `security.lions.dev`
|
- L'authentification OIDC est configurée avec Keycloak sur `security.lions.dev`
|
||||||
- Les endpoints REST sont mappés correctement
|
- Les endpoints REST sont mappés correctement
|
||||||
- Le CORS est configuré pour autoriser la communication bidirectionnelle
|
- Le CORS est configuré pour autoriser la communication bidirectionnelle
|
||||||
- Toutes les dépendances nécessaires sont présentes
|
- 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.
|
**⚠️ Action requise** : Vérifier dans Keycloak que le client `btpxpress-frontend` existe et est correctement configuré avec les redirect URIs appropriés.
|
||||||
|
|
||||||
|
|||||||
150
CONFIGURATION.md
150
CONFIGURATION.md
@@ -1,75 +1,75 @@
|
|||||||
# Configuration BTP Xpress Client - PrimeFaces Freya
|
# Configuration BTP Xpress Client - PrimeFaces Freya
|
||||||
|
|
||||||
## ✅ Vérifications effectuées
|
## ✅ Vérifications effectuées
|
||||||
|
|
||||||
### 1. Structure du projet
|
### 1. Structure du projet
|
||||||
- ✅ Fichiers XHTML dans `src/main/resources/META-INF/resources/`
|
- ✅ Fichiers XHTML dans `src/main/resources/META-INF/resources/`
|
||||||
- ✅ Configuration dans `src/main/resources/META-INF/web.xml`
|
- ✅ Configuration dans `src/main/resources/META-INF/web.xml`
|
||||||
- ✅ Beans CDI dans `src/main/java/dev/lions/btpxpress/`
|
- ✅ Beans CDI dans `src/main/java/dev/lions/btpxpress/`
|
||||||
|
|
||||||
### 2. Configuration OIDC / Keycloak
|
### 2. Configuration OIDC / Keycloak
|
||||||
- ✅ **Serveur Keycloak** : `https://security.lions.dev/realms/btpxpress`
|
- ✅ **Serveur Keycloak** : `https://security.lions.dev/realms/btpxpress`
|
||||||
- ✅ **Client ID** : `btpxpress-frontend`
|
- ✅ **Client ID** : `btpxpress-frontend`
|
||||||
- ✅ **Type d'application** : `web-app`
|
- ✅ **Type d'application** : `web-app`
|
||||||
- ✅ **Redirection** : `/` (restauration du chemin après authentification)
|
- ✅ **Redirection** : `/` (restauration du chemin après authentification)
|
||||||
- ✅ **Cookies** : Configurés pour la session
|
- ✅ **Cookies** : Configurés pour la session
|
||||||
- ✅ **TLS** : `required` (production)
|
- ✅ **TLS** : `required` (production)
|
||||||
|
|
||||||
### 3. Communication avec le backend
|
### 3. Communication avec le backend
|
||||||
- ✅ **URL Backend** : `http://localhost:8080`
|
- ✅ **URL Backend** : `http://localhost:8080`
|
||||||
- ✅ **Endpoints API** : `/api/v1/*`
|
- ✅ **Endpoints API** : `/api/v1/*`
|
||||||
- ✅ **REST Client** : `BtpXpressApiClient` configuré
|
- ✅ **REST Client** : `BtpXpressApiClient` configuré
|
||||||
- ✅ **Service** : `ChantierService` créé pour encapsuler les appels API
|
- ✅ **Service** : `ChantierService` créé pour encapsuler les appels API
|
||||||
- ✅ **CORS Backend** : `http://localhost:8081` ajouté aux origines autorisées
|
- ✅ **CORS Backend** : `http://localhost:8081` ajouté aux origines autorisées
|
||||||
|
|
||||||
### 4. Configuration serveur backend
|
### 4. Configuration serveur backend
|
||||||
- ✅ **Port** : `8080`
|
- ✅ **Port** : `8080`
|
||||||
- ✅ **CORS Origins** : `http://localhost:3000,http://localhost:5173,http://localhost:8081`
|
- ✅ **CORS Origins** : `http://localhost:3000,http://localhost:5173,http://localhost:8081`
|
||||||
- ✅ **JWT Validation** : `https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs`
|
- ✅ **JWT Validation** : `https://security.lions.dev/realms/btpxpress/protocol/openid-connect/certs`
|
||||||
- ✅ **Issuer** : `https://security.lions.dev/realms/btpxpress`
|
- ✅ **Issuer** : `https://security.lions.dev/realms/btpxpress`
|
||||||
|
|
||||||
## 📋 Mapping Client ↔ Serveur
|
## 📋 Mapping Client ↔ Serveur
|
||||||
|
|
||||||
| Client (PrimeFaces) | Serveur (Quarkus) | Description |
|
| Client (PrimeFaces) | Serveur (Quarkus) | Description |
|
||||||
|---------------------|-------------------|-------------|
|
|---------------------|-------------------|-------------|
|
||||||
| `http://localhost:8081` | `http://localhost:8080` | Communication HTTP |
|
| `http://localhost:8081` | `http://localhost:8080` | Communication HTTP |
|
||||||
| `BtpXpressApiClient` | `@Path("/api/v1/*")` | Interface REST Client |
|
| `BtpXpressApiClient` | `@Path("/api/v1/*")` | Interface REST Client |
|
||||||
| OIDC Client `btpxpress-frontend` | JWT Validation | Authentification |
|
| OIDC Client `btpxpress-frontend` | JWT Validation | Authentification |
|
||||||
| `ChantierService` | `ChantierResource` | Service métier chantiers |
|
| `ChantierService` | `ChantierResource` | Service métier chantiers |
|
||||||
|
|
||||||
## 🔐 Authentification
|
## 🔐 Authentification
|
||||||
|
|
||||||
1. **Client accède à une page protégée** → Redirection vers Keycloak
|
1. **Client accède à une page protégée** → Redirection vers Keycloak
|
||||||
2. **Keycloak (security.lions.dev)** → Authentification utilisateur
|
2. **Keycloak (security.lions.dev)** → Authentification utilisateur
|
||||||
3. **Keycloak retourne le token** → Stocké dans la session du client
|
3. **Keycloak retourne le token** → Stocké dans la session du client
|
||||||
4. **Client fait appel API** → Token JWT envoyé dans header `Authorization`
|
4. **Client fait appel API** → Token JWT envoyé dans header `Authorization`
|
||||||
5. **Backend valide le token** → Via les certificats Keycloak publics
|
5. **Backend valide le token** → Via les certificats Keycloak publics
|
||||||
|
|
||||||
## 🚀 Démarrage
|
## 🚀 Démarrage
|
||||||
|
|
||||||
1. **Backend** :
|
1. **Backend** :
|
||||||
```bash
|
```bash
|
||||||
cd btpxpress-server
|
cd btpxpress-server
|
||||||
mvn quarkus:dev
|
mvn quarkus:dev
|
||||||
```
|
```
|
||||||
→ Accessible sur http://localhost:8080
|
→ Accessible sur http://localhost:8080
|
||||||
|
|
||||||
2. **Client** :
|
2. **Client** :
|
||||||
```bash
|
```bash
|
||||||
cd btpxpress-client
|
cd btpxpress-client
|
||||||
mvn quarkus:dev
|
mvn quarkus:dev
|
||||||
```
|
```
|
||||||
→ Accessible sur http://localhost:8081
|
→ Accessible sur http://localhost:8081
|
||||||
|
|
||||||
3. **Accès** :
|
3. **Accès** :
|
||||||
- Page d'accueil : http://localhost:8081/
|
- Page d'accueil : http://localhost:8081/
|
||||||
- Dashboard : http://localhost:8081/dashboard.xhtml
|
- Dashboard : http://localhost:8081/dashboard.xhtml
|
||||||
- Login : http://localhost:8081/login.xhtml
|
- Login : http://localhost:8081/login.xhtml
|
||||||
|
|
||||||
## ⚠️ Points d'attention
|
## ⚠️ Points d'attention
|
||||||
|
|
||||||
- Le client doit être configuré avec le **même realm Keycloak** que le serveur (`btpxpress`)
|
- Le client doit être configuré avec le **même realm Keycloak** que le serveur (`btpxpress`)
|
||||||
- Le client ID `btpxpress-frontend` doit exister dans Keycloak
|
- Le client ID `btpxpress-frontend` doit exister dans Keycloak
|
||||||
- Les tokens JWT doivent être envoyés automatiquement via le REST Client
|
- Les tokens JWT doivent être envoyés automatiquement via le REST Client
|
||||||
- Le backend doit accepter les requêtes CORS depuis `http://localhost:8081`
|
- Le backend doit accepter les requêtes CORS depuis `http://localhost:8081`
|
||||||
|
|
||||||
|
|||||||
94
Dockerfile
94
Dockerfile
@@ -1,47 +1,47 @@
|
|||||||
####
|
####
|
||||||
# Dockerfile pour BTP Xpress Client (Frontend) - Développement
|
# Dockerfile pour BTP Xpress Client (Frontend) - Développement
|
||||||
# Utilisé pour les builds de développement local
|
# Utilisé pour les builds de développement local
|
||||||
####
|
####
|
||||||
|
|
||||||
## Stage 1 : Build avec Maven
|
## Stage 1 : Build avec Maven
|
||||||
FROM maven:3.9.6-eclipse-temurin-17 AS build
|
FROM maven:3.9.6-eclipse-temurin-17 AS build
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
# Copier pom.xml et télécharger les dépendances
|
# Copier pom.xml et télécharger les dépendances
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
RUN mvn dependency:go-offline -B
|
RUN mvn dependency:go-offline -B
|
||||||
|
|
||||||
# Copier le code source
|
# Copier le code source
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
|
|
||||||
# Build de l'application (uber-jar pour compatibilité lionsctl)
|
# Build de l'application (uber-jar pour compatibilité lionsctl)
|
||||||
RUN mvn clean package -DskipTests -B -Dquarkus.package.type=uber-jar
|
RUN mvn clean package -DskipTests -B -Dquarkus.package.type=uber-jar
|
||||||
|
|
||||||
## Stage 2 : Runtime image
|
## Stage 2 : Runtime image
|
||||||
FROM eclipse-temurin:17-jre-alpine
|
FROM eclipse-temurin:17-jre-alpine
|
||||||
|
|
||||||
ENV LANGUAGE='fr_FR:fr'
|
ENV LANGUAGE='fr_FR:fr'
|
||||||
|
|
||||||
# Installer curl pour les health checks
|
# Installer curl pour les health checks
|
||||||
RUN apk add --no-cache curl
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
# Créer un utilisateur non-root pour la sécurité
|
# Créer un utilisateur non-root pour la sécurité
|
||||||
RUN addgroup -g 185 -S appuser && adduser -u 185 -S appuser -G appuser
|
RUN addgroup -g 185 -S appuser && adduser -u 185 -S appuser -G appuser
|
||||||
RUN mkdir -p /deployments && chown -R appuser:appuser /deployments
|
RUN mkdir -p /deployments && chown -R appuser:appuser /deployments
|
||||||
|
|
||||||
# Copier le JAR depuis le build (lionsctl utilise uber-jar)
|
# Copier le JAR depuis le build (lionsctl utilise uber-jar)
|
||||||
# Note: Le fichier sera btpxpress-client-1.0.0-runner.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
|
COPY --from=build --chown=appuser:appuser /build/target/*-runner.jar /deployments/app.jar
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
# Variables d'environnement JVM optimisées
|
# 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"
|
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
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
CMD curl -f http://localhost:8080/q/health/ready || exit 1
|
CMD curl -f http://localhost:8080/q/health/ready || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/app.jar"]
|
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/app.jar"]
|
||||||
|
|
||||||
|
|||||||
168
Dockerfile.prod
168
Dockerfile.prod
@@ -1,84 +1,84 @@
|
|||||||
####
|
####
|
||||||
# Dockerfile de production pour BTP Xpress Client (Frontend)
|
# Dockerfile de production pour BTP Xpress Client (Frontend)
|
||||||
# Multi-stage build optimisé avec sécurité renforcée
|
# Multi-stage build optimisé avec sécurité renforcée
|
||||||
####
|
####
|
||||||
|
|
||||||
## Stage 1 : Build avec Maven
|
## Stage 1 : Build avec Maven
|
||||||
FROM maven:3.9.6-eclipse-temurin-17 AS builder
|
FROM maven:3.9.6-eclipse-temurin-17 AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copier pom.xml et télécharger les dépendances (cache Docker)
|
# Copier pom.xml et télécharger les dépendances (cache Docker)
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
RUN mvn dependency:go-offline -B
|
RUN mvn dependency:go-offline -B
|
||||||
|
|
||||||
# Copier le code source
|
# Copier le code source
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
|
|
||||||
# Build de l'application avec profil production (fast-jar par défaut)
|
# Build de l'application avec profil production (fast-jar par défaut)
|
||||||
RUN mvn clean package -DskipTests -B
|
RUN mvn clean package -DskipTests -B
|
||||||
|
|
||||||
## Stage 2 : Image de production optimisée et sécurisée
|
## Stage 2 : Image de production optimisée et sécurisée
|
||||||
FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
|
FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
|
||||||
|
|
||||||
ENV LANGUAGE='fr_FR:fr'
|
ENV LANGUAGE='fr_FR:fr'
|
||||||
|
|
||||||
# Variables d'environnement de production
|
# Variables d'environnement de production
|
||||||
# Ces valeurs peuvent être surchargées via docker-compose ou Kubernetes
|
# Ces valeurs peuvent être surchargées via docker-compose ou Kubernetes
|
||||||
ENV QUARKUS_PROFILE=prod
|
ENV QUARKUS_PROFILE=prod
|
||||||
ENV QUARKUS_HTTP_PORT=8080
|
ENV QUARKUS_HTTP_PORT=8080
|
||||||
ENV QUARKUS_HTTP_HOST=0.0.0.0
|
ENV QUARKUS_HTTP_HOST=0.0.0.0
|
||||||
|
|
||||||
# Configuration Keycloak/OIDC (production)
|
# Configuration Keycloak/OIDC (production)
|
||||||
ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/btpxpress
|
ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/btpxpress
|
||||||
ENV QUARKUS_OIDC_CLIENT_ID=btpxpress-frontend
|
ENV QUARKUS_OIDC_CLIENT_ID=btpxpress-frontend
|
||||||
ENV QUARKUS_OIDC_ENABLED=true
|
ENV QUARKUS_OIDC_ENABLED=true
|
||||||
ENV QUARKUS_OIDC_TLS_VERIFICATION=required
|
ENV QUARKUS_OIDC_TLS_VERIFICATION=required
|
||||||
|
|
||||||
# Configuration API Backend
|
# Configuration API Backend
|
||||||
ENV BTPXPRESS_API_BASE_URL=https://api.btpxpress.lions.dev
|
ENV BTPXPRESS_API_BASE_URL=https://api.btpxpress.lions.dev
|
||||||
|
|
||||||
# Configuration CORS
|
# Configuration CORS
|
||||||
ENV QUARKUS_HTTP_CORS_ORIGINS=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev
|
ENV QUARKUS_HTTP_CORS_ORIGINS=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev
|
||||||
ENV QUARKUS_HTTP_CORS_ALLOW_CREDENTIALS=true
|
ENV QUARKUS_HTTP_CORS_ALLOW_CREDENTIALS=true
|
||||||
|
|
||||||
# Installer curl pour les health checks
|
# Installer curl pour les health checks
|
||||||
USER root
|
USER root
|
||||||
RUN microdnf install -y curl && \
|
RUN microdnf install -y curl && \
|
||||||
microdnf clean all && \
|
microdnf clean all && \
|
||||||
rm -rf /var/cache/yum
|
rm -rf /var/cache/yum
|
||||||
|
|
||||||
# Créer les répertoires et permissions pour utilisateur non-root
|
# Créer les répertoires et permissions pour utilisateur non-root
|
||||||
RUN mkdir -p /deployments /app/logs && \
|
RUN mkdir -p /deployments /app/logs && \
|
||||||
chown -R 185:185 /deployments /app/logs
|
chown -R 185:185 /deployments /app/logs
|
||||||
|
|
||||||
# Passer à l'utilisateur non-root pour la sécurité
|
# Passer à l'utilisateur non-root pour la sécurité
|
||||||
USER 185
|
USER 185
|
||||||
|
|
||||||
# Copier l'application depuis le builder (format fast-jar Quarkus)
|
# Copier l'application depuis le builder (format fast-jar Quarkus)
|
||||||
COPY --from=builder --chown=185 /app/target/quarkus-app/ /deployments/
|
COPY --from=builder --chown=185 /app/target/quarkus-app/ /deployments/
|
||||||
|
|
||||||
# Exposer le port
|
# Exposer le port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Variables JVM optimisées pour production avec sécurité
|
# Variables JVM optimisées pour production avec sécurité
|
||||||
ENV JAVA_OPTS="-Xmx768m -Xms256m \
|
ENV JAVA_OPTS="-Xmx768m -Xms256m \
|
||||||
-XX:+UseG1GC \
|
-XX:+UseG1GC \
|
||||||
-XX:MaxGCPauseMillis=200 \
|
-XX:MaxGCPauseMillis=200 \
|
||||||
-XX:+UseStringDeduplication \
|
-XX:+UseStringDeduplication \
|
||||||
-XX:+ParallelRefProcEnabled \
|
-XX:+ParallelRefProcEnabled \
|
||||||
-XX:+HeapDumpOnOutOfMemoryError \
|
-XX:+HeapDumpOnOutOfMemoryError \
|
||||||
-XX:HeapDumpPath=/app/logs/heapdump.hprof \
|
-XX:HeapDumpPath=/app/logs/heapdump.hprof \
|
||||||
-Djava.security.egd=file:/dev/./urandom \
|
-Djava.security.egd=file:/dev/./urandom \
|
||||||
-Djava.awt.headless=true \
|
-Djava.awt.headless=true \
|
||||||
-Dfile.encoding=UTF-8 \
|
-Dfile.encoding=UTF-8 \
|
||||||
-Djava.util.logging.manager=org.jboss.logmanager.LogManager \
|
-Djava.util.logging.manager=org.jboss.logmanager.LogManager \
|
||||||
-Dquarkus.profile=${QUARKUS_PROFILE}"
|
-Dquarkus.profile=${QUARKUS_PROFILE}"
|
||||||
|
|
||||||
# Health check avec endpoints Quarkus
|
# Health check avec endpoints Quarkus
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \
|
||||||
CMD curl -f http://localhost:8080/q/health/ready || exit 1
|
CMD curl -f http://localhost:8080/q/health/ready || exit 1
|
||||||
|
|
||||||
# Point d'entrée avec profil production (format fast-jar)
|
# Point d'entrée avec profil production (format fast-jar)
|
||||||
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"]
|
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /deployments/quarkus-run.jar"]
|
||||||
|
|
||||||
|
|||||||
263
EXECUTIVE_SUMMARY_OPTIMIZATIONS.md
Normal file
263
EXECUTIVE_SUMMARY_OPTIMIZATIONS.md
Normal file
@@ -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<Map<String, Object>> facturesData = factureService.getAllFactures();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution** :
|
||||||
|
```java
|
||||||
|
// ✅ Charge seulement 10-50 factures par page
|
||||||
|
LazyDataModel<Facture> 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
|
||||||
|
<!-- ❌ Re-rend tout le formulaire -->
|
||||||
|
<p:commandButton update="@form" action="#{bean.filter}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution** :
|
||||||
|
```xml
|
||||||
|
<!-- ✅ Re-rend seulement la table et les messages -->
|
||||||
|
<p:commandButton process="@this filtresPanel"
|
||||||
|
update="facturesTable messages"
|
||||||
|
action="#{bean.filter}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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
|
||||||
|
<!-- Répété 20+ fois dans le projet -->
|
||||||
|
<p:tag value="#{facture.statut}"
|
||||||
|
severity="#{facture.statut == 'PAYEE' ? 'success' : 'warning'}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Après** : Composant réutilisable
|
||||||
|
```xml
|
||||||
|
<!-- Utilisé partout, maintenu en un seul endroit -->
|
||||||
|
<btpx:facture-statut-badge statut="#{facture.statut}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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<SelectItem> 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
|
||||||
150
FIX_431_ERROR.md
150
FIX_431_ERROR.md
@@ -1,75 +1,75 @@
|
|||||||
# Solution pour l'erreur HTTP 431 "Request Header Fields Too Large"
|
# Solution pour l'erreur HTTP 431 "Request Header Fields Too Large"
|
||||||
|
|
||||||
## Problème
|
## 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.
|
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
|
## Solutions appliquées
|
||||||
|
|
||||||
### 1. Configuration Quarkus HTTP
|
### 1. Configuration Quarkus HTTP
|
||||||
```properties
|
```properties
|
||||||
quarkus.http.max-headers-size=64K
|
quarkus.http.max-headers-size=64K
|
||||||
quarkus.vertx.max-headers-size=64K
|
quarkus.vertx.max-headers-size=64K
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Optimisation OIDC Token Management
|
### 2. Optimisation OIDC Token Management
|
||||||
```properties
|
```properties
|
||||||
quarkus.oidc.token-state-manager.split-tokens=true
|
quarkus.oidc.token-state-manager.split-tokens=true
|
||||||
quarkus.oidc.token-state-manager.strategy=id-refresh-tokens
|
quarkus.oidc.token-state-manager.strategy=id-refresh-tokens
|
||||||
quarkus.oidc.token-state-manager.encryption-required=false
|
quarkus.oidc.token-state-manager.encryption-required=false
|
||||||
quarkus.oidc.token-state-manager.cookie-max-size=8192
|
quarkus.oidc.token-state-manager.cookie-max-size=8192
|
||||||
```
|
```
|
||||||
|
|
||||||
Ces configurations :
|
Ces configurations :
|
||||||
- **split-tokens** : Divise les tokens en plusieurs cookies pour éviter qu'un seul cookie soit trop volumineux
|
- **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
|
- **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)
|
- **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
|
- **cookie-max-size=8192** : Limite la taille d'un cookie individuel à 8KB
|
||||||
|
|
||||||
## Actions à effectuer
|
## Actions à effectuer
|
||||||
|
|
||||||
### ⚠️ IMPORTANT : Supprimer les cookies du navigateur
|
### ⚠️ IMPORTANT : Supprimer les cookies du navigateur
|
||||||
|
|
||||||
Les cookies existants peuvent être trop volumineux. Vous devez :
|
Les cookies existants peuvent être trop volumineux. Vous devez :
|
||||||
|
|
||||||
1. **Ouvrir les outils développeur** (F12)
|
1. **Ouvrir les outils développeur** (F12)
|
||||||
2. **Onglet Application > Cookies**
|
2. **Onglet Application > Cookies**
|
||||||
3. **Supprimer tous les cookies** pour `http://localhost:8081`
|
3. **Supprimer tous les cookies** pour `http://localhost:8081`
|
||||||
4. **Redémarrer l'application Quarkus**
|
4. **Redémarrer l'application Quarkus**
|
||||||
5. **Recharger la page**
|
5. **Recharger la page**
|
||||||
|
|
||||||
Ou via la console du navigateur :
|
Ou via la console du navigateur :
|
||||||
```javascript
|
```javascript
|
||||||
// Supprimer tous les cookies pour localhost:8081
|
// Supprimer tous les cookies pour localhost:8081
|
||||||
document.cookie.split(";").forEach(c => {
|
document.cookie.split(";").forEach(c => {
|
||||||
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
|
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Redémarrer l'application
|
### Redémarrer l'application
|
||||||
|
|
||||||
Après modification de `application.properties`, vous **devez redémarrer** l'application Quarkus :
|
Après modification de `application.properties`, vous **devez redémarrer** l'application Quarkus :
|
||||||
```bash
|
```bash
|
||||||
# Arrêter l'application (Ctrl+C)
|
# Arrêter l'application (Ctrl+C)
|
||||||
# Puis relancer
|
# Puis relancer
|
||||||
mvn quarkus:dev
|
mvn quarkus:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Vérification
|
## Vérification
|
||||||
|
|
||||||
Une fois les cookies supprimés et l'application redémarrée :
|
Une fois les cookies supprimés et l'application redémarrée :
|
||||||
1. Accédez à http://localhost:8081/dashboard.xhtml
|
1. Accédez à http://localhost:8081/dashboard.xhtml
|
||||||
2. Vous serez redirigé vers Keycloak pour l'authentification
|
2. Vous serez redirigé vers Keycloak pour l'authentification
|
||||||
3. Après authentification, les nouveaux cookies (optimisés) seront créés
|
3. Après authentification, les nouveaux cookies (optimisés) seront créés
|
||||||
|
|
||||||
## Si le problème persiste
|
## Si le problème persiste
|
||||||
|
|
||||||
1. **Augmenter encore la limite** :
|
1. **Augmenter encore la limite** :
|
||||||
```properties
|
```properties
|
||||||
quarkus.http.max-headers-size=128K
|
quarkus.http.max-headers-size=128K
|
||||||
quarkus.vertx.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
|
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
|
3. **Mode navigation privée** : Tester dans une fenêtre de navigation privée pour éviter les cookies existants
|
||||||
|
|
||||||
|
|||||||
75
FOOTER_CONFIGURATION.md
Normal file
75
FOOTER_CONFIGURATION.md
Normal file
@@ -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
|
||||||
|
<ui:include src="./footer.xhtml"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Après** :
|
||||||
|
```xhtml
|
||||||
|
<!-- Footer conditionnel : désactivé par défaut pour application métier -->
|
||||||
|
<!-- Pour l'activer sur une page spécifique, ajouter : <ui:param name="showFooter" value="true"/> -->
|
||||||
|
<ui:fragment rendered="#{showFooter == true}">
|
||||||
|
<ui:include src="./footer.xhtml"/>
|
||||||
|
</ui:fragment>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 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
|
||||||
|
<ui:composition template="/WEB-INF/template.xhtml">
|
||||||
|
<ui:param name="showFooter" value="true"/>
|
||||||
|
|
||||||
|
<ui:define name="content">
|
||||||
|
<!-- Contenu de la page -->
|
||||||
|
</ui:define>
|
||||||
|
</ui:composition>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 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
|
||||||
|
|
||||||
281
IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md
Normal file
281
IMPLEMENTATION_EXAMPLE_LAZY_LOADING.md
Normal file
@@ -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<Facture> 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<String, FilterMeta> filterBy) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> 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<Facture> load(int first, int pageSize,
|
||||||
|
Map<String, SortMeta> sortBy,
|
||||||
|
Map<String, FilterMeta> filterBy) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> 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<Facture> 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<String, Object> buildFilterParams(Map<String, FilterMeta> filterBy) {
|
||||||
|
Map<String, Object> 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<Facture> getFacturesLazy(Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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<String, Object> 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<String, Object> clientData = (Map<String, Object>) 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<Map<String, Object>> 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.)
|
||||||
|
|
||||||
221
LAZY_LOADING_IMPLEMENTATION_SPEC.md
Normal file
221
LAZY_LOADING_IMPLEMENTATION_SPEC.md
Normal file
@@ -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<Facture> 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
|
||||||
|
|
||||||
776
PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md
Normal file
776
PRIMEFACES_BEST_PRACTICES_OPTIMIZATION.md
Normal file
@@ -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<Map<String, Object>> 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<Facture> {
|
||||||
|
|
||||||
|
private final FactureService factureService;
|
||||||
|
|
||||||
|
public FactureLazyDataModel(FactureService factureService) {
|
||||||
|
this.factureService = factureService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int count(Map<String, FilterMeta> filterBy) {
|
||||||
|
// Appel API pour compter le nombre total avec filtres
|
||||||
|
return factureService.countFactures(buildFilterParams(filterBy));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Facture> load(int first, int pageSize,
|
||||||
|
Map<String, SortMeta> sortBy,
|
||||||
|
Map<String, FilterMeta> filterBy) {
|
||||||
|
// Appel API avec pagination, tri et filtres
|
||||||
|
Map<String, Object> 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<String, Object> buildSortParams(Map<String, SortMeta> sortBy) {
|
||||||
|
Map<String, Object> 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<String, Object> buildFilterParams(Map<String, FilterMeta> filterBy) {
|
||||||
|
Map<String, Object> 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<Facture> lazyModel;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
lazyModel = new FactureLazyDataModel(factureService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LazyDataModel<Facture> getLazyModel() {
|
||||||
|
return lazyModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Modifier factures.xhtml pour utiliser lazy loading
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<p:dataTable id="facturesTable"
|
||||||
|
value="#{factureView.lazyModel}"
|
||||||
|
var="facture"
|
||||||
|
lazy="true"
|
||||||
|
paginator="true"
|
||||||
|
rows="10"
|
||||||
|
rowsPerPageTemplate="10,20,50"
|
||||||
|
paginatorPosition="both"
|
||||||
|
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
|
||||||
|
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
|
||||||
|
filterDelay="500"
|
||||||
|
emptyMessage="Aucune facture trouvée">
|
||||||
|
|
||||||
|
<!-- Colonnes avec filtres intégrés -->
|
||||||
|
<p:column headerText="Numéro"
|
||||||
|
sortBy="#{facture.numero}"
|
||||||
|
filterBy="#{facture.numero}"
|
||||||
|
filterMatchMode="contains">
|
||||||
|
<h:outputText value="#{facture.numero}"/>
|
||||||
|
</p:column>
|
||||||
|
|
||||||
|
<!-- ... autres colonnes ... -->
|
||||||
|
</p:dataTable>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<p:commandButton value="Filtrer"
|
||||||
|
update="@form"
|
||||||
|
action="#{factureView.applyFilters}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bonne pratique** ✅ :
|
||||||
|
```xml
|
||||||
|
<p:commandButton value="Filtrer"
|
||||||
|
update="facturesTable messages"
|
||||||
|
process="@this filtresPanel"
|
||||||
|
action="#{factureView.applyFilters}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<h:form id="factureForm">
|
||||||
|
<p:panel id="filtresPanel">
|
||||||
|
<p:inputText id="filtreNumero" value="#{factureView.filtreNumero}"/>
|
||||||
|
<p:inputText id="filtreClient" value="#{factureView.filtreClient}"/>
|
||||||
|
|
||||||
|
<p:commandButton value="Rechercher"
|
||||||
|
process="@this filtresPanel"
|
||||||
|
update="facturesTable messages"
|
||||||
|
action="#{factureView.search}"/>
|
||||||
|
</p:panel>
|
||||||
|
|
||||||
|
<p:messages id="messages"/>
|
||||||
|
|
||||||
|
<p:dataTable id="facturesTable" value="#{factureView.lazyModel}" var="facture">
|
||||||
|
<!-- colonnes -->
|
||||||
|
</p:dataTable>
|
||||||
|
</h:form>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Utiliser p:ajax pour les événements
|
||||||
|
|
||||||
|
**Pour les changements de filtres en temps réel** :
|
||||||
|
```xml
|
||||||
|
<p:inputText id="filtreNumero" value="#{factureView.filtreNumero}">
|
||||||
|
<p:ajax event="keyup"
|
||||||
|
delay="500"
|
||||||
|
update="facturesTable"
|
||||||
|
process="@this"
|
||||||
|
listener="#{factureView.onFilterChange}"/>
|
||||||
|
</p:inputText>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Désactiver les auto-updates inutiles
|
||||||
|
|
||||||
|
**Éviter** :
|
||||||
|
```xml
|
||||||
|
<p:dataTable autoUpdate="true"> <!-- ❌ Mauvaise pratique -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**Préférer** :
|
||||||
|
```xml
|
||||||
|
<p:dataTable id="table">
|
||||||
|
<p:ajax event="rowSelect" update="detailPanel" listener="#{bean.onRowSelect}"/>
|
||||||
|
</p:dataTable>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 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
|
||||||
|
<p:dataTable id="facturesTable"
|
||||||
|
value="#{factureView.items}"
|
||||||
|
var="facture"
|
||||||
|
paginator="true"
|
||||||
|
rows="10"
|
||||||
|
styleClass="p-datatable-striped"
|
||||||
|
emptyMessage="Aucune facture">
|
||||||
|
<p:column headerText="Numéro">
|
||||||
|
<h:outputText value="#{facture.numero}"/>
|
||||||
|
</p:column>
|
||||||
|
</p:dataTable>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Après** :
|
||||||
|
```xml
|
||||||
|
<fr:dataTable value="#{factureView.lazyModel}"
|
||||||
|
var="facture"
|
||||||
|
paginator="true"
|
||||||
|
rows="10"
|
||||||
|
lazy="true"
|
||||||
|
stripedRows="true">
|
||||||
|
<p:column headerText="Numéro">
|
||||||
|
<h:outputText value="#{facture.numero}"/>
|
||||||
|
</p:column>
|
||||||
|
</fr:dataTable>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
|
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||||
|
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
|
||||||
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
|
<cc:interface>
|
||||||
|
<cc:attribute name="statut" required="true" type="java.lang.String"/>
|
||||||
|
<cc:attribute name="enRetard" type="java.lang.Boolean" default="false"/>
|
||||||
|
</cc:interface>
|
||||||
|
|
||||||
|
<cc:implementation>
|
||||||
|
<p:tag value="#{cc.attrs.statut}"
|
||||||
|
severity="#{cc.attrs.statut == 'PAYEE' ? 'success' :
|
||||||
|
(cc.attrs.statut == 'ANNULEE' ? 'danger' :
|
||||||
|
(cc.attrs.enRetard ? 'danger' : 'warning'))}"
|
||||||
|
icon="#{cc.attrs.statut == 'PAYEE' ? 'pi pi-check' :
|
||||||
|
(cc.attrs.statut == 'ANNULEE' ? 'pi pi-times' :
|
||||||
|
(cc.attrs.enRetard ? 'pi pi-exclamation-triangle' : 'pi pi-clock'))}"/>
|
||||||
|
</cc:implementation>
|
||||||
|
</ui:composition>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Utilisation** :
|
||||||
|
```xml
|
||||||
|
<p:column headerText="Statut">
|
||||||
|
<btpx:facture-statut-badge statut="#{facture.statut}"
|
||||||
|
enRetard="#{factureView.isEnRetard(facture)}"/>
|
||||||
|
</p:column>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Créer un composant pour les montants
|
||||||
|
|
||||||
|
**Fichier** : `/WEB-INF/components/montant-display.xhtml`
|
||||||
|
```xml
|
||||||
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
|
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||||
|
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
|
||||||
|
|
||||||
|
<cc:interface>
|
||||||
|
<cc:attribute name="montant" required="true" type="java.lang.Double"/>
|
||||||
|
<cc:attribute name="devise" default="Fcfa"/>
|
||||||
|
<cc:attribute name="highlight" type="java.lang.Boolean" default="false"/>
|
||||||
|
<cc:attribute name="highlightColor" default="red"/>
|
||||||
|
</cc:interface>
|
||||||
|
|
||||||
|
<cc:implementation>
|
||||||
|
<span style="#{cc.attrs.highlight ? 'color: ' + cc.attrs.highlightColor + '; font-weight: bold;' : ''}">
|
||||||
|
<h:outputText value="#{cc.attrs.montant}">
|
||||||
|
<f:converter converterId="fcfaConverter"/>
|
||||||
|
</h:outputText>
|
||||||
|
<h:outputText value=" #{cc.attrs.devise}"/>
|
||||||
|
</span>
|
||||||
|
</cc:implementation>
|
||||||
|
</ui:composition>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Utilisation** :
|
||||||
|
```xml
|
||||||
|
<p:column headerText="Reste à payer">
|
||||||
|
<btpx:montant-display montant="#{factureView.getMontantRestant(facture)}"
|
||||||
|
highlight="#{factureView.getMontantRestant(facture) > 0}"/>
|
||||||
|
</p:column>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Créer un Composant de Filtre Réutilisable
|
||||||
|
|
||||||
|
**Fichier** : `/WEB-INF/components/search-filter-panel.xhtml`
|
||||||
|
```xml
|
||||||
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
|
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||||
|
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
|
||||||
|
xmlns:p="http://primefaces.org/ui"
|
||||||
|
xmlns:fr="http://primefaces.org/freya">
|
||||||
|
|
||||||
|
<cc:interface>
|
||||||
|
<cc:attribute name="bean" required="true"/>
|
||||||
|
<cc:attribute name="tableId" required="true"/>
|
||||||
|
<cc:facet name="filters" required="true"/>
|
||||||
|
</cc:interface>
|
||||||
|
|
||||||
|
<cc:implementation>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
|
<h3 class="m-0">
|
||||||
|
<i class="pi pi-filter mr-2"></i>
|
||||||
|
Filtres de recherche
|
||||||
|
</h3>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<fr:commandButton value="Rechercher"
|
||||||
|
icon="pi pi-search"
|
||||||
|
severity="primary"
|
||||||
|
process="@this @parent"
|
||||||
|
update="#{cc.attrs.tableId} messages"
|
||||||
|
action="#{cc.attrs.bean.applyFilters}"/>
|
||||||
|
|
||||||
|
<fr:commandButton value="Réinitialiser"
|
||||||
|
icon="pi pi-refresh"
|
||||||
|
severity="secondary"
|
||||||
|
outlined="true"
|
||||||
|
process="@this"
|
||||||
|
update="@parent #{cc.attrs.tableId}"
|
||||||
|
action="#{cc.attrs.bean.resetFilters}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cc:renderFacet name="filters"/>
|
||||||
|
</div>
|
||||||
|
</cc:implementation>
|
||||||
|
</ui:composition>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 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<T, ID> implements Serializable {
|
||||||
|
|
||||||
|
protected LazyDataModel<T> lazyModel;
|
||||||
|
protected T selectedItem;
|
||||||
|
protected boolean loading;
|
||||||
|
|
||||||
|
// Cache pour éviter les rechargements inutiles
|
||||||
|
private transient Map<String, Object> lastFilterParams;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
initializeLazyModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void initializeLazyModel();
|
||||||
|
|
||||||
|
public void applyFilters() {
|
||||||
|
Map<String, Object> 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<String, Object> 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<SelectItem> 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<SelectItem> 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
|
||||||
|
<h:form id="factureForm">
|
||||||
|
<p:messages id="messages" showDetail="true" closable="true"/>
|
||||||
|
|
||||||
|
<fr:fieldInput label="Numéro de facture"
|
||||||
|
value="#{factureView.entity.numero}"
|
||||||
|
required="true"
|
||||||
|
requiredMessage="Le numéro est obligatoire">
|
||||||
|
<f:validateLength minimum="3" maximum="20"/>
|
||||||
|
</fr:fieldInput>
|
||||||
|
|
||||||
|
<fr:fieldCalendar label="Date d'émission"
|
||||||
|
value="#{factureView.entity.dateEmission}"
|
||||||
|
required="true"
|
||||||
|
showIcon="true">
|
||||||
|
<f:validator validatorId="dateValidator"/>
|
||||||
|
</fr:fieldCalendar>
|
||||||
|
|
||||||
|
<fr:commandButton value="Enregistrer"
|
||||||
|
icon="pi pi-save"
|
||||||
|
action="#{factureView.save}"
|
||||||
|
update="messages"
|
||||||
|
process="@form"
|
||||||
|
validateClient="true"/>
|
||||||
|
</h:form>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Messages d'erreur personnalisés
|
||||||
|
|
||||||
|
**Créer un validateur personnalisé** :
|
||||||
|
```java
|
||||||
|
@FacesValidator("dateValidator")
|
||||||
|
public class DateValidator implements Validator<LocalDate> {
|
||||||
|
|
||||||
|
@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
|
||||||
|
<p:growl id="growl"
|
||||||
|
life="3000"
|
||||||
|
sticky="false"
|
||||||
|
showDetail="true"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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
|
||||||
|
<p:commandButton value="Filtrer"
|
||||||
|
update="@form"
|
||||||
|
action="#{factureView.applyFilters}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bonne pratique** ✅ :
|
||||||
|
```xml
|
||||||
|
<p:commandButton value="Filtrer"
|
||||||
|
update="facturesTable messages"
|
||||||
|
process="@this filtresPanel"
|
||||||
|
action="#{factureView.applyFilters}"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Utiliser process et update de manière ciblée
|
||||||
|
|
||||||
|
|
||||||
@@ -1,335 +1,335 @@
|
|||||||
# 🔒 Sécurisation Complète de l'Application Frontend BTP Xpress
|
# 🔒 Sécurisation Complète de l'Application Frontend BTP Xpress
|
||||||
|
|
||||||
**Date** : 2025-01-20
|
**Date** : 2025-01-20
|
||||||
**Version** : 1.0.0
|
**Version** : 1.0.0
|
||||||
**Statut** : ✅ **SÉCURISÉ POUR PRODUCTION**
|
**Statut** : ✅ **SÉCURISÉ POUR PRODUCTION**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Vue d'ensemble
|
## 📋 Vue d'ensemble
|
||||||
|
|
||||||
L'application frontend BTP Xpress est maintenant complètement sécurisée pour la production avec :
|
L'application frontend BTP Xpress est maintenant complètement sécurisée pour la production avec :
|
||||||
- ✅ Headers de sécurité HTTP complets
|
- ✅ Headers de sécurité HTTP complets
|
||||||
- ✅ Configuration OIDC/Keycloak sécurisée
|
- ✅ Configuration OIDC/Keycloak sécurisée
|
||||||
- ✅ CORS restreint aux domaines autorisés
|
- ✅ CORS restreint aux domaines autorisés
|
||||||
- ✅ HTTPS/TLS forcé via Ingress
|
- ✅ HTTPS/TLS forcé via Ingress
|
||||||
- ✅ Cookies sécurisés (HttpOnly, Secure, SameSite)
|
- ✅ Cookies sécurisés (HttpOnly, Secure, SameSite)
|
||||||
- ✅ Content Security Policy (CSP) stricte
|
- ✅ Content Security Policy (CSP) stricte
|
||||||
- ✅ Protection contre les attaques courantes
|
- ✅ Protection contre les attaques courantes
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 1. Headers de Sécurité HTTP
|
## 🔐 1. Headers de Sécurité HTTP
|
||||||
|
|
||||||
### Filtre de Sécurité (`SecurityHeadersFilter`)
|
### Filtre de Sécurité (`SecurityHeadersFilter`)
|
||||||
|
|
||||||
Le filtre `SecurityHeadersFilter` ajoute automatiquement les headers suivants à toutes les réponses :
|
Le filtre `SecurityHeadersFilter` ajoute automatiquement les headers suivants à toutes les réponses :
|
||||||
|
|
||||||
| Header | Valeur | Protection |
|
| Header | Valeur | Protection |
|
||||||
|--------|--------|------------|
|
|--------|--------|------------|
|
||||||
| **Strict-Transport-Security** | `max-age=31536000; includeSubDomains; preload` | Force HTTPS pendant 1 an |
|
| **Strict-Transport-Security** | `max-age=31536000; includeSubDomains; preload` | Force HTTPS pendant 1 an |
|
||||||
| **X-Frame-Options** | `DENY` | Empêche le clickjacking |
|
| **X-Frame-Options** | `DENY` | Empêche le clickjacking |
|
||||||
| **X-Content-Type-Options** | `nosniff` | Empêche le MIME sniffing |
|
| **X-Content-Type-Options** | `nosniff` | Empêche le MIME sniffing |
|
||||||
| **X-XSS-Protection** | `1; mode=block` | Active la protection XSS du navigateur |
|
| **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 |
|
| **Referrer-Policy** | `strict-origin-when-cross-origin` | Contrôle les informations de referrer |
|
||||||
| **Content-Security-Policy** | Voir ci-dessous | Politique de sécurité stricte |
|
| **Content-Security-Policy** | Voir ci-dessous | Politique de sécurité stricte |
|
||||||
| **Permissions-Policy** | Désactive geolocation, microphone, etc. | Limite les fonctionnalités du navigateur |
|
| **Permissions-Policy** | Désactive geolocation, microphone, etc. | Limite les fonctionnalités du navigateur |
|
||||||
| **X-Permitted-Cross-Domain-Policies** | `none` | Bloque les politiques Flash/Silverlight |
|
| **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-Embedder-Policy** | `require-corp` | Protection contre les fuites de données |
|
||||||
| **Cross-Origin-Opener-Policy** | `same-origin` | Isolation des fenêtres |
|
| **Cross-Origin-Opener-Policy** | `same-origin` | Isolation des fenêtres |
|
||||||
| **Cross-Origin-Resource-Policy** | `same-origin` | Contrôle des ressources cross-origin |
|
| **Cross-Origin-Resource-Policy** | `same-origin` | Contrôle des ressources cross-origin |
|
||||||
|
|
||||||
### Content Security Policy (CSP)
|
### Content Security Policy (CSP)
|
||||||
|
|
||||||
```http
|
```http
|
||||||
default-src 'self';
|
default-src 'self';
|
||||||
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev;
|
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev;
|
||||||
style-src 'self' 'unsafe-inline' https://security.lions.dev;
|
style-src 'self' 'unsafe-inline' https://security.lions.dev;
|
||||||
img-src 'self' data: https: blob:;
|
img-src 'self' data: https: blob:;
|
||||||
font-src 'self' data: https://security.lions.dev;
|
font-src 'self' data: https://security.lions.dev;
|
||||||
connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.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;
|
frame-src 'self' https://security.lions.dev;
|
||||||
object-src 'none';
|
object-src 'none';
|
||||||
base-uri 'self';
|
base-uri 'self';
|
||||||
form-action 'self' https://security.lions.dev;
|
form-action 'self' https://security.lions.dev;
|
||||||
frame-ancestors 'none';
|
frame-ancestors 'none';
|
||||||
upgrade-insecure-requests;
|
upgrade-insecure-requests;
|
||||||
```
|
```
|
||||||
|
|
||||||
**Explication** :
|
**Explication** :
|
||||||
- Autorise uniquement les ressources depuis `self` et `security.lions.dev`
|
- Autorise uniquement les ressources depuis `self` et `security.lions.dev`
|
||||||
- Bloque les iframes externes (sauf Keycloak)
|
- Bloque les iframes externes (sauf Keycloak)
|
||||||
- Force l'upgrade vers HTTPS
|
- Force l'upgrade vers HTTPS
|
||||||
- Empêche l'injection de code malveillant
|
- Empêche l'injection de code malveillant
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌐 2. Configuration OIDC / Keycloak
|
## 🌐 2. Configuration OIDC / Keycloak
|
||||||
|
|
||||||
### Serveur d'authentification
|
### Serveur d'authentification
|
||||||
- **URL** : `https://security.lions.dev/realms/btpxpress`
|
- **URL** : `https://security.lions.dev/realms/btpxpress`
|
||||||
- **Client ID** : `btpxpress-frontend`
|
- **Client ID** : `btpxpress-frontend`
|
||||||
- **Type** : `web-app` (application publique)
|
- **Type** : `web-app` (application publique)
|
||||||
- **TLS Verification** : `required` (obligatoire en production)
|
- **TLS Verification** : `required` (obligatoire en production)
|
||||||
|
|
||||||
### Cookies de session sécurisés
|
### Cookies de session sécurisés
|
||||||
- ✅ **HttpOnly** : `true` (protection XSS)
|
- ✅ **HttpOnly** : `true` (protection XSS)
|
||||||
- ✅ **Secure** : `true` (HTTPS uniquement)
|
- ✅ **Secure** : `true` (HTTPS uniquement)
|
||||||
- ✅ **SameSite** : `strict` (protection CSRF)
|
- ✅ **SameSite** : `strict` (protection CSRF)
|
||||||
- ✅ **Path** : `/`
|
- ✅ **Path** : `/`
|
||||||
- ✅ **Encryption** : `required` (tokens chiffrés)
|
- ✅ **Encryption** : `required` (tokens chiffrés)
|
||||||
- ✅ **Max Size** : `8192 bytes`
|
- ✅ **Max Size** : `8192 bytes`
|
||||||
|
|
||||||
### Gestion des tokens
|
### Gestion des tokens
|
||||||
- ✅ **Split Tokens** : Activé (tokens divisés)
|
- ✅ **Split Tokens** : Activé (tokens divisés)
|
||||||
- ✅ **Strategy** : `id-refresh-tokens` (refresh automatique)
|
- ✅ **Strategy** : `id-refresh-tokens` (refresh automatique)
|
||||||
- ✅ **Session Age Extension** : `PT30M` (30 minutes)
|
- ✅ **Session Age Extension** : `PT30M` (30 minutes)
|
||||||
- ✅ **Restore Path After Redirect** : `true` (navigation fluide)
|
- ✅ **Restore Path After Redirect** : `true` (navigation fluide)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔒 3. Configuration CORS
|
## 🔒 3. Configuration CORS
|
||||||
|
|
||||||
### Origines autorisées (Production)
|
### Origines autorisées (Production)
|
||||||
```properties
|
```properties
|
||||||
quarkus.http.cors.origins=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev
|
quarkus.http.cors.origins=https://btpxpress.lions.dev,https://www.btpxpress.lions.dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Méthodes HTTP autorisées
|
### Méthodes HTTP autorisées
|
||||||
- `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH`
|
- `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH`
|
||||||
|
|
||||||
### Headers autorisés
|
### Headers autorisés
|
||||||
- `Content-Type`
|
- `Content-Type`
|
||||||
- `Authorization`
|
- `Authorization`
|
||||||
- `X-Requested-With`
|
- `X-Requested-With`
|
||||||
- `X-CSRF-Token`
|
- `X-CSRF-Token`
|
||||||
|
|
||||||
### Credentials
|
### Credentials
|
||||||
- ✅ **Access-Control-Allow-Credentials** : `true`
|
- ✅ **Access-Control-Allow-Credentials** : `true`
|
||||||
- ✅ **Max Age** : `3600 seconds` (1 heure)
|
- ✅ **Max Age** : `3600 seconds` (1 heure)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛡️ 4. Configuration Ingress Kubernetes
|
## 🛡️ 4. Configuration Ingress Kubernetes
|
||||||
|
|
||||||
### TLS/HTTPS
|
### TLS/HTTPS
|
||||||
- ✅ **SSL Redirect** : Forcé
|
- ✅ **SSL Redirect** : Forcé
|
||||||
- ✅ **Force SSL Redirect** : Activé
|
- ✅ **Force SSL Redirect** : Activé
|
||||||
- ✅ **Cert Manager** : Let's Encrypt (automatique)
|
- ✅ **Cert Manager** : Let's Encrypt (automatique)
|
||||||
- ✅ **TLS Protocols** : `TLSv1.2`, `TLSv1.3`
|
- ✅ **TLS Protocols** : `TLSv1.2`, `TLSv1.3`
|
||||||
|
|
||||||
### Headers ajoutés par Nginx Ingress
|
### Headers ajoutés par Nginx Ingress
|
||||||
Les headers suivants sont ajoutés au niveau de l'Ingress :
|
Les headers suivants sont ajoutés au niveau de l'Ingress :
|
||||||
- `X-Frame-Options: DENY`
|
- `X-Frame-Options: DENY`
|
||||||
- `X-Content-Type-Options: nosniff`
|
- `X-Content-Type-Options: nosniff`
|
||||||
- `X-XSS-Protection: 1; mode=block`
|
- `X-XSS-Protection: 1; mode=block`
|
||||||
- `Referrer-Policy: strict-origin-when-cross-origin`
|
- `Referrer-Policy: strict-origin-when-cross-origin`
|
||||||
- `Permissions-Policy: geolocation=(), microphone=(), camera=()`
|
- `Permissions-Policy: geolocation=(), microphone=(), camera=()`
|
||||||
- `X-Permitted-Cross-Domain-Policies: none`
|
- `X-Permitted-Cross-Domain-Policies: none`
|
||||||
|
|
||||||
### Configuration HSTS
|
### 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`).
|
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
|
## 🔐 5. Permissions et Accès
|
||||||
|
|
||||||
### Pages publiques (sans authentification)
|
### Pages publiques (sans authentification)
|
||||||
Les ressources statiques sont accessibles sans authentification :
|
Les ressources statiques sont accessibles sans authentification :
|
||||||
- `/*.css`, `/*.js`, `/*.png`, `/*.jpg`, etc.
|
- `/*.css`, `/*.js`, `/*.png`, `/*.jpg`, etc.
|
||||||
- `/resources/*`
|
- `/resources/*`
|
||||||
|
|
||||||
### Pages protégées (authentification requise)
|
### Pages protégées (authentification requise)
|
||||||
Toutes les autres pages nécessitent une authentification OIDC :
|
Toutes les autres pages nécessitent une authentification OIDC :
|
||||||
- ✅ Redirection automatique vers Keycloak si non authentifié
|
- ✅ Redirection automatique vers Keycloak si non authentifié
|
||||||
- ✅ Restauration du chemin après authentification
|
- ✅ Restauration du chemin après authentification
|
||||||
- ✅ Session maintenue pendant 30 minutes
|
- ✅ Session maintenue pendant 30 minutes
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 6. Configuration de Production
|
## 🔧 6. Configuration de Production
|
||||||
|
|
||||||
### Fichier de configuration
|
### Fichier de configuration
|
||||||
**Fichier** : `src/main/resources/application-prod.properties`
|
**Fichier** : `src/main/resources/application-prod.properties`
|
||||||
|
|
||||||
### Variables d'environnement requises
|
### Variables d'environnement requises
|
||||||
- `BTPXPRESS_API_BASE_URL` : URL de l'API backend (défaut: `https://api.btpxpress.lions.dev`)
|
- `BTPXPRESS_API_BASE_URL` : URL de l'API backend (défaut: `https://api.btpxpress.lions.dev`)
|
||||||
|
|
||||||
### Activation en production
|
### Activation en production
|
||||||
Pour activer la configuration de production :
|
Pour activer la configuration de production :
|
||||||
```bash
|
```bash
|
||||||
export QUARKUS_PROFILE=prod
|
export QUARKUS_PROFILE=prod
|
||||||
# ou
|
# ou
|
||||||
java -Dquarkus.profile=prod -jar btpxpress-client.jar
|
java -Dquarkus.profile=prod -jar btpxpress-client.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✅ 7. Checklist de Sécurisation
|
## ✅ 7. Checklist de Sécurisation
|
||||||
|
|
||||||
### Headers de sécurité
|
### Headers de sécurité
|
||||||
- [x] Strict-Transport-Security (HSTS)
|
- [x] Strict-Transport-Security (HSTS)
|
||||||
- [x] X-Frame-Options
|
- [x] X-Frame-Options
|
||||||
- [x] X-Content-Type-Options
|
- [x] X-Content-Type-Options
|
||||||
- [x] X-XSS-Protection
|
- [x] X-XSS-Protection
|
||||||
- [x] Referrer-Policy
|
- [x] Referrer-Policy
|
||||||
- [x] Content-Security-Policy (CSP)
|
- [x] Content-Security-Policy (CSP)
|
||||||
- [x] Permissions-Policy
|
- [x] Permissions-Policy
|
||||||
- [x] Cross-Origin-Embedder-Policy
|
- [x] Cross-Origin-Embedder-Policy
|
||||||
- [x] Cross-Origin-Opener-Policy
|
- [x] Cross-Origin-Opener-Policy
|
||||||
- [x] Cross-Origin-Resource-Policy
|
- [x] Cross-Origin-Resource-Policy
|
||||||
|
|
||||||
### Authentification
|
### Authentification
|
||||||
- [x] OIDC/Keycloak configuré
|
- [x] OIDC/Keycloak configuré
|
||||||
- [x] TLS verification requis
|
- [x] TLS verification requis
|
||||||
- [x] Cookies sécurisés (HttpOnly, Secure, SameSite)
|
- [x] Cookies sécurisés (HttpOnly, Secure, SameSite)
|
||||||
- [x] Tokens chiffrés
|
- [x] Tokens chiffrés
|
||||||
- [x] Refresh tokens automatique
|
- [x] Refresh tokens automatique
|
||||||
|
|
||||||
### CORS
|
### CORS
|
||||||
- [x] Origines restreintes à `btpxpress.lions.dev`
|
- [x] Origines restreintes à `btpxpress.lions.dev`
|
||||||
- [x] Credentials autorisés
|
- [x] Credentials autorisés
|
||||||
- [x] Méthodes HTTP limitées
|
- [x] Méthodes HTTP limitées
|
||||||
|
|
||||||
### Infrastructure
|
### Infrastructure
|
||||||
- [x] HTTPS forcé via Ingress
|
- [x] HTTPS forcé via Ingress
|
||||||
- [x] Certificats TLS automatiques (Let's Encrypt)
|
- [x] Certificats TLS automatiques (Let's Encrypt)
|
||||||
- [x] TLS 1.2+ uniquement
|
- [x] TLS 1.2+ uniquement
|
||||||
- [x] Headers sécurité au niveau Ingress
|
- [x] Headers sécurité au niveau Ingress
|
||||||
|
|
||||||
### Application
|
### Application
|
||||||
- [x] Filtre de sécurité activé
|
- [x] Filtre de sécurité activé
|
||||||
- [x] Configuration production séparée
|
- [x] Configuration production séparée
|
||||||
- [x] Logs sécurisés (pas de secrets)
|
- [x] Logs sécurisés (pas de secrets)
|
||||||
- [x] Limites HTTP configurées
|
- [x] Limites HTTP configurées
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 8. Déploiement
|
## 🚀 8. Déploiement
|
||||||
|
|
||||||
### Prérequis
|
### Prérequis
|
||||||
1. ✅ Certificat TLS configuré pour `btpxpress.lions.dev`
|
1. ✅ Certificat TLS configuré pour `btpxpress.lions.dev`
|
||||||
2. ✅ Keycloak accessible sur `https://security.lions.dev`
|
2. ✅ Keycloak accessible sur `https://security.lions.dev`
|
||||||
3. ✅ Client OIDC `btpxpress-frontend` configuré dans Keycloak
|
3. ✅ Client OIDC `btpxpress-frontend` configuré dans Keycloak
|
||||||
4. ✅ Ingress Kubernetes configuré avec annotations de sécurité
|
4. ✅ Ingress Kubernetes configuré avec annotations de sécurité
|
||||||
|
|
||||||
### Vérification post-déploiement
|
### Vérification post-déploiement
|
||||||
|
|
||||||
#### 1. Vérifier les headers de sécurité
|
#### 1. Vérifier les headers de sécurité
|
||||||
```bash
|
```bash
|
||||||
curl -I https://btpxpress.lions.dev
|
curl -I https://btpxpress.lions.dev
|
||||||
|
|
||||||
# Vérifier la présence de :
|
# Vérifier la présence de :
|
||||||
# - Strict-Transport-Security
|
# - Strict-Transport-Security
|
||||||
# - X-Frame-Options
|
# - X-Frame-Options
|
||||||
# - X-Content-Type-Options
|
# - X-Content-Type-Options
|
||||||
# - Content-Security-Policy
|
# - Content-Security-Policy
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Tester l'authentification
|
#### 2. Tester l'authentification
|
||||||
1. Accéder à `https://btpxpress.lions.dev`
|
1. Accéder à `https://btpxpress.lions.dev`
|
||||||
2. Vérifier la redirection vers Keycloak
|
2. Vérifier la redirection vers Keycloak
|
||||||
3. S'authentifier
|
3. S'authentifier
|
||||||
4. Vérifier le retour vers l'application
|
4. Vérifier le retour vers l'application
|
||||||
|
|
||||||
#### 3. Vérifier les cookies
|
#### 3. Vérifier les cookies
|
||||||
Dans les DevTools du navigateur :
|
Dans les DevTools du navigateur :
|
||||||
- ✅ Cookies avec `HttpOnly`
|
- ✅ Cookies avec `HttpOnly`
|
||||||
- ✅ Cookies avec `Secure`
|
- ✅ Cookies avec `Secure`
|
||||||
- ✅ Cookies avec `SameSite=Strict`
|
- ✅ Cookies avec `SameSite=Strict`
|
||||||
|
|
||||||
#### 4. Tester HTTPS
|
#### 4. Tester HTTPS
|
||||||
```bash
|
```bash
|
||||||
# Vérifier que HTTP redirige vers HTTPS
|
# Vérifier que HTTP redirige vers HTTPS
|
||||||
curl -I http://btpxpress.lions.dev
|
curl -I http://btpxpress.lions.dev
|
||||||
# Attendu : 301 ou 302 vers https://
|
# Attendu : 301 ou 302 vers https://
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. Vérifier CSP
|
#### 5. Vérifier CSP
|
||||||
Dans la console du navigateur :
|
Dans la console du navigateur :
|
||||||
- Aucune violation CSP
|
- Aucune violation CSP
|
||||||
- Ressources chargées uniquement depuis les origines autorisées
|
- Ressources chargées uniquement depuis les origines autorisées
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📊 9. Tests de Sécurité Recommandés
|
## 📊 9. Tests de Sécurité Recommandés
|
||||||
|
|
||||||
### Outils en ligne
|
### Outils en ligne
|
||||||
- [SSL Labs](https://www.ssllabs.com/ssltest/) : Test du certificat TLS
|
- [SSL Labs](https://www.ssllabs.com/ssltest/) : Test du certificat TLS
|
||||||
- [Security Headers](https://securityheaders.com/) : Vérification des headers de sécurité
|
- [Security Headers](https://securityheaders.com/) : Vérification des headers de sécurité
|
||||||
- [Mozilla Observatory](https://observatory.mozilla.org/) : Audit de sécurité complet
|
- [Mozilla Observatory](https://observatory.mozilla.org/) : Audit de sécurité complet
|
||||||
|
|
||||||
### Commandes locales
|
### Commandes locales
|
||||||
```bash
|
```bash
|
||||||
# Test SSL
|
# Test SSL
|
||||||
openssl s_client -connect btpxpress.lions.dev:443
|
openssl s_client -connect btpxpress.lions.dev:443
|
||||||
|
|
||||||
# Test headers
|
# Test headers
|
||||||
curl -I https://btpxpress.lions.dev
|
curl -I https://btpxpress.lions.dev
|
||||||
|
|
||||||
# Test CSP
|
# Test CSP
|
||||||
curl -H "Content-Security-Policy-Report-Only: default-src 'self'" https://btpxpress.lions.dev
|
curl -H "Content-Security-Policy-Report-Only: default-src 'self'" https://btpxpress.lions.dev
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔍 10. Monitoring et Alertes
|
## 🔍 10. Monitoring et Alertes
|
||||||
|
|
||||||
### Headers à surveiller
|
### Headers à surveiller
|
||||||
- Taux de violations CSP (si reporting configuré)
|
- Taux de violations CSP (si reporting configuré)
|
||||||
- Échecs d'authentification OIDC
|
- Échecs d'authentification OIDC
|
||||||
- Erreurs de certificat TLS
|
- Erreurs de certificat TLS
|
||||||
- Redirections HTTP → HTTPS
|
- Redirections HTTP → HTTPS
|
||||||
|
|
||||||
### Logs à surveiller
|
### Logs à surveiller
|
||||||
- Erreurs d'authentification OIDC
|
- Erreurs d'authentification OIDC
|
||||||
- Violations de sécurité détectées
|
- Violations de sécurité détectées
|
||||||
- Échecs de validation de token
|
- Échecs de validation de token
|
||||||
- Tentatives d'accès non autorisées
|
- Tentatives d'accès non autorisées
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📝 11. Maintenance
|
## 📝 11. Maintenance
|
||||||
|
|
||||||
### Mise à jour des certificats
|
### Mise à jour des certificats
|
||||||
Les certificats Let's Encrypt sont renouvelés automatiquement par cert-manager.
|
Les certificats Let's Encrypt sont renouvelés automatiquement par cert-manager.
|
||||||
|
|
||||||
### Mise à jour de la CSP
|
### Mise à jour de la CSP
|
||||||
Si des violations CSP sont détectées en production, ajuster la CSP dans `SecurityHeadersFilter.java`.
|
Si des violations CSP sont détectées en production, ajuster la CSP dans `SecurityHeadersFilter.java`.
|
||||||
|
|
||||||
### Mise à jour des dépendances
|
### Mise à jour des dépendances
|
||||||
Maintenir les dépendances à jour pour corriger les vulnérabilités :
|
Maintenir les dépendances à jour pour corriger les vulnérabilités :
|
||||||
```bash
|
```bash
|
||||||
mvn versions:display-dependency-updates
|
mvn versions:display-dependency-updates
|
||||||
mvn versions:display-plugin-updates
|
mvn versions:display-plugin-updates
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 12. Références
|
## 📚 12. Références
|
||||||
|
|
||||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||||
- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security)
|
- [MDN Web Security](https://developer.mozilla.org/en-US/docs/Web/Security)
|
||||||
- [Quarkus Security Guide](https://quarkus.io/guides/security)
|
- [Quarkus Security Guide](https://quarkus.io/guides/security)
|
||||||
- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
|
- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✅ Conclusion
|
## ✅ Conclusion
|
||||||
|
|
||||||
L'application frontend BTP Xpress est maintenant **complètement sécurisée** pour la production avec :
|
L'application frontend BTP Xpress est maintenant **complètement sécurisée** pour la production avec :
|
||||||
- ✅ **Headers de sécurité complets** (10+ headers)
|
- ✅ **Headers de sécurité complets** (10+ headers)
|
||||||
- ✅ **OIDC/Keycloak sécurisé** (TLS, cookies sécurisés)
|
- ✅ **OIDC/Keycloak sécurisé** (TLS, cookies sécurisés)
|
||||||
- ✅ **CORS restreint** (btpxpress.lions.dev uniquement)
|
- ✅ **CORS restreint** (btpxpress.lions.dev uniquement)
|
||||||
- ✅ **HTTPS forcé** (TLS 1.2+)
|
- ✅ **HTTPS forcé** (TLS 1.2+)
|
||||||
- ✅ **CSP stricte** (protection injection)
|
- ✅ **CSP stricte** (protection injection)
|
||||||
- ✅ **Infrastructure Kubernetes sécurisée**
|
- ✅ **Infrastructure Kubernetes sécurisée**
|
||||||
|
|
||||||
**Statut** : ✅ **PRÊT POUR PRODUCTION**
|
**Statut** : ✅ **PRÊT POUR PRODUCTION**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Auteur** : Équipe BTP Xpress
|
**Auteur** : Équipe BTP Xpress
|
||||||
**Date de dernière mise à jour** : 2025-01-20
|
**Date de dernière mise à jour** : 2025-01-20
|
||||||
**Version** : 1.0.0
|
**Version** : 1.0.0
|
||||||
|
|
||||||
|
|||||||
279
pom.xml
279
pom.xml
@@ -1,153 +1,128 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>dev.lions</groupId>
|
<groupId>dev.lions</groupId>
|
||||||
<artifactId>btpxpress-client</artifactId>
|
<artifactId>btpxpress-client</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.0.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>BTP Xpress Client - PrimeFaces Freya</name>
|
<name>BTP Xpress Client - PrimeFaces Freya</name>
|
||||||
<description>Application cliente BTP Xpress basée sur Quarkus et PrimeFaces Freya</description>
|
<description>Application cliente BTP Xpress basée sur Quarkus et PrimeFaces Freya</description>
|
||||||
<properties>
|
<properties>
|
||||||
<compiler-plugin.version>3.13.0</compiler-plugin.version>
|
<compiler-plugin.version>3.13.0</compiler-plugin.version>
|
||||||
<maven.compiler.release>17</maven.compiler.release>
|
<maven.compiler.release>21</maven.compiler.release>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||||
<quarkus.platform.version>3.15.1</quarkus.platform.version>
|
<quarkus.platform.version>3.27.3</quarkus.platform.version>
|
||||||
<skipTests>false</skipTests>
|
<skipTests>false</skipTests>
|
||||||
<freya.theme.version>5.0.0-jakarta</freya.theme.version>
|
</properties>
|
||||||
</properties>
|
|
||||||
|
<repositories>
|
||||||
<repositories>
|
<repository>
|
||||||
<repository>
|
<id>gitea-lionsdev</id>
|
||||||
<id>lions-maven-repo</id>
|
<name>Lions Dev Gitea Maven</name>
|
||||||
<name>Lions Dev Maven Repository</name>
|
<url>https://git.lions.dev/api/packages/lionsdev/maven</url>
|
||||||
<url>https://git.lions.dev/lionsdev/btpxpress-maven-repo/raw/branch/main</url>
|
<releases><enabled>true</enabled></releases>
|
||||||
</repository>
|
<snapshots><enabled>true</enabled></snapshots>
|
||||||
</repositories>
|
</repository>
|
||||||
|
</repositories>
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
<dependencyManagement>
|
||||||
<dependency>
|
<dependencies>
|
||||||
<groupId>${quarkus.platform.group-id}</groupId>
|
<dependency>
|
||||||
<artifactId>${quarkus.platform.artifact-id}</artifactId>
|
<groupId>${quarkus.platform.group-id}</groupId>
|
||||||
<version>${quarkus.platform.version}</version>
|
<artifactId>${quarkus.platform.artifact-id}</artifactId>
|
||||||
<type>pom</type>
|
<version>${quarkus.platform.version}</version>
|
||||||
<scope>import</scope>
|
<type>pom</type>
|
||||||
</dependency>
|
<scope>import</scope>
|
||||||
</dependencies>
|
</dependency>
|
||||||
</dependencyManagement>
|
</dependencies>
|
||||||
<dependencies>
|
</dependencyManagement>
|
||||||
<dependency>
|
<dependencies>
|
||||||
<groupId>io.quarkus</groupId>
|
<dependency>
|
||||||
<artifactId>quarkus-arc</artifactId>
|
<groupId>io.quarkus</groupId>
|
||||||
</dependency>
|
<artifactId>quarkus-arc</artifactId>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>io.quarkiverse.primefaces</groupId>
|
|
||||||
<artifactId>quarkus-primefaces</artifactId>
|
<!-- ================================================================ -->
|
||||||
<version>3.15.0-RC2</version>
|
<!-- lions-faces-layout : layout Freya + beans OIDC + assets Freya -->
|
||||||
</dependency>
|
<!-- Remplace : primefaces-freya-extension (mort), freya-theme, freya -->
|
||||||
<dependency>
|
<!-- Fournit transitivement : primefaces, freya-theme-jakarta, -->
|
||||||
<groupId>org.primefaces</groupId>
|
<!-- quarkus-primefaces, quarkus-omnifaces, quarkus-oidc -->
|
||||||
<artifactId>freya-theme</artifactId>
|
<!-- ================================================================ -->
|
||||||
<version>${freya.theme.version}</version>
|
<dependency>
|
||||||
</dependency>
|
<groupId>dev.lions</groupId>
|
||||||
<dependency>
|
<artifactId>lions-faces-layout</artifactId>
|
||||||
<groupId>org.primefaces</groupId>
|
<version>1.0.0</version>
|
||||||
<artifactId>freya</artifactId>
|
</dependency>
|
||||||
<version>${freya.theme.version}</version>
|
|
||||||
</dependency>
|
<dependency>
|
||||||
<dependency>
|
<groupId>org.projectlombok</groupId>
|
||||||
<groupId>jakarta.faces</groupId>
|
<artifactId>lombok</artifactId>
|
||||||
<artifactId>jakarta.faces-api</artifactId>
|
<version>1.18.30</version>
|
||||||
<version>3.0.0</version>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.servlet</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
<artifactId>quarkus-logging-json</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.enterprise</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>jakarta.enterprise.cdi-api</artifactId>
|
<artifactId>quarkus-rest-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.el</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>jakarta.el-api</artifactId>
|
<artifactId>quarkus-rest-jackson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>quarkus-smallrye-jwt</artifactId>
|
||||||
<version>1.18.30</version>
|
</dependency>
|
||||||
<scope>provided</scope>
|
<dependency>
|
||||||
</dependency>
|
<groupId>io.quarkus</groupId>
|
||||||
<dependency>
|
<artifactId>quarkus-junit5</artifactId>
|
||||||
<groupId>io.quarkus</groupId>
|
<scope>test</scope>
|
||||||
<artifactId>quarkus-logging-json</artifactId>
|
</dependency>
|
||||||
</dependency>
|
<dependency>
|
||||||
<dependency>
|
<groupId>io.rest-assured</groupId>
|
||||||
<groupId>io.quarkus</groupId>
|
<artifactId>rest-assured</artifactId>
|
||||||
<artifactId>quarkus-rest-client</artifactId>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
</dependencies>
|
||||||
<groupId>io.quarkus</groupId>
|
<build>
|
||||||
<artifactId>quarkus-rest-jackson</artifactId>
|
<plugins>
|
||||||
</dependency>
|
<plugin>
|
||||||
|
<groupId>${quarkus.platform.group-id}</groupId>
|
||||||
<dependency>
|
<artifactId>quarkus-maven-plugin</artifactId>
|
||||||
<groupId>io.quarkus</groupId>
|
<version>${quarkus.platform.version}</version>
|
||||||
<artifactId>quarkus-oidc</artifactId>
|
<extensions>true</extensions>
|
||||||
</dependency>
|
<executions>
|
||||||
|
<execution>
|
||||||
<dependency>
|
<goals>
|
||||||
<groupId>io.quarkus</groupId>
|
<goal>build</goal>
|
||||||
<artifactId>quarkus-smallrye-jwt</artifactId>
|
<goal>generate-code</goal>
|
||||||
</dependency>
|
<goal>generate-code-tests</goal>
|
||||||
|
</goals>
|
||||||
<dependency>
|
</execution>
|
||||||
<groupId>io.quarkus</groupId>
|
</executions>
|
||||||
<artifactId>quarkus-junit5</artifactId>
|
</plugin>
|
||||||
<scope>test</scope>
|
<plugin>
|
||||||
</dependency>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<dependency>
|
<version>${compiler-plugin.version}</version>
|
||||||
<groupId>io.rest-assured</groupId>
|
<configuration>
|
||||||
<artifactId>rest-assured</artifactId>
|
<parameters>true</parameters>
|
||||||
<scope>test</scope>
|
</configuration>
|
||||||
</dependency>
|
</plugin>
|
||||||
</dependencies>
|
<plugin>
|
||||||
<build>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<plugins>
|
<version>3.5.0</version>
|
||||||
<plugin>
|
<configuration>
|
||||||
<groupId>${quarkus.platform.group-id}</groupId>
|
<systemPropertyVariables>
|
||||||
<artifactId>quarkus-maven-plugin</artifactId>
|
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||||
<version>${quarkus.platform.version}</version>
|
</systemPropertyVariables>
|
||||||
<extensions>true</extensions>
|
</configuration>
|
||||||
<executions>
|
</plugin>
|
||||||
<execution>
|
</plugins>
|
||||||
<goals>
|
</build>
|
||||||
<goal>build</goal>
|
|
||||||
<goal>generate-code</goal>
|
|
||||||
<goal>generate-code-tests</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>${compiler-plugin.version}</version>
|
|
||||||
<configuration>
|
|
||||||
<parameters>true</parameters>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
|
||||||
<version>3.5.0</version>
|
|
||||||
<configuration>
|
|
||||||
<systemPropertyVariables>
|
|
||||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
|
||||||
</systemPropertyVariables>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
</project>
|
||||||
@@ -1,87 +1,87 @@
|
|||||||
package dev.lions.btpxpress.converter;
|
package dev.lions.btpxpress.converter;
|
||||||
|
|
||||||
import jakarta.faces.component.UIComponent;
|
import jakarta.faces.component.UIComponent;
|
||||||
import jakarta.faces.context.FacesContext;
|
import jakarta.faces.context.FacesContext;
|
||||||
import jakarta.faces.convert.Converter;
|
import jakarta.faces.convert.Converter;
|
||||||
import jakarta.faces.convert.FacesConverter;
|
import jakarta.faces.convert.FacesConverter;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.DecimalFormatSymbols;
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter personnalisé pour formater les montants en Franc CFA (Fcfa).
|
* Converter personnalisé pour formater les montants en Franc CFA (Fcfa).
|
||||||
*
|
*
|
||||||
* <p>Ce converter formate les nombres avec des espaces comme séparateurs de milliers
|
* <p>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.</p>
|
* au lieu de virgules, conformément au format standard du Franc CFA.</p>
|
||||||
*
|
*
|
||||||
* <p>Exemple : 1234567 devient "1 234 567 Fcfa"</p>
|
* <p>Exemple : 1234567 devient "1 234 567 Fcfa"</p>
|
||||||
*
|
*
|
||||||
* @author BTP Xpress Team
|
* @author BTP Xpress Team
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
*/
|
*/
|
||||||
@FacesConverter("fcfaConverter")
|
@FacesConverter("fcfaConverter")
|
||||||
public class FcfaConverter implements Converter<Number> {
|
public class FcfaConverter implements Converter<Number> {
|
||||||
|
|
||||||
private static final DecimalFormatSymbols SYMBOLS;
|
private static final DecimalFormatSymbols SYMBOLS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
SYMBOLS = new DecimalFormatSymbols(Locale.FRENCH);
|
SYMBOLS = new DecimalFormatSymbols(Locale.FRENCH);
|
||||||
SYMBOLS.setGroupingSeparator(' ');
|
SYMBOLS.setGroupingSeparator(' ');
|
||||||
SYMBOLS.setDecimalSeparator(',');
|
SYMBOLS.setDecimalSeparator(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convertit une chaîne de caractères en nombre.
|
* Convertit une chaîne de caractères en nombre.
|
||||||
*
|
*
|
||||||
* @param context Le contexte Faces
|
* @param context Le contexte Faces
|
||||||
* @param component Le composant UI
|
* @param component Le composant UI
|
||||||
* @param value La valeur string à convertir
|
* @param value La valeur string à convertir
|
||||||
* @return Le nombre converti, ou null si la valeur est vide/null
|
* @return Le nombre converti, ou null si la valeur est vide/null
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Number getAsObject(FacesContext context, UIComponent component, String value) {
|
public Number getAsObject(FacesContext context, UIComponent component, String value) {
|
||||||
if (value == null || value.trim().isEmpty()) {
|
if (value == null || value.trim().isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Retirer les espaces et le préfixe "Fcfa" si présent
|
// Retirer les espaces et le préfixe "Fcfa" si présent
|
||||||
String cleanedValue = value.replaceAll("\\s+", "")
|
String cleanedValue = value.replaceAll("\\s+", "")
|
||||||
.replace("Fcfa", "")
|
.replace("Fcfa", "")
|
||||||
.replace("fcfa", "")
|
.replace("fcfa", "")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
return new BigDecimal(cleanedValue);
|
return new BigDecimal(cleanedValue);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException("Impossible de convertir '" + value + "' en nombre", e);
|
throw new IllegalArgumentException("Impossible de convertir '" + value + "' en nombre", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convertit un nombre en chaîne de caractères formatée.
|
* Convertit un nombre en chaîne de caractères formatée.
|
||||||
*
|
*
|
||||||
* @param context Le contexte Faces
|
* @param context Le contexte Faces
|
||||||
* @param component Le composant UI
|
* @param component Le composant UI
|
||||||
* @param value Le nombre à convertir
|
* @param value Le nombre à convertir
|
||||||
* @return La chaîne formatée avec espaces comme séparateurs de milliers
|
* @return La chaîne formatée avec espaces comme séparateurs de milliers
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getAsString(FacesContext context, UIComponent component, Number value) {
|
public String getAsString(FacesContext context, UIComponent component, Number value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formater avec espaces comme séparateurs de milliers (format Fcfa standard)
|
// Formater avec espaces comme séparateurs de milliers (format Fcfa standard)
|
||||||
// Le pattern "#" avec groupingUsed=true utilise le groupingSeparator défini dans SYMBOLS (espace)
|
// Le pattern "#" avec groupingUsed=true utilise le groupingSeparator défini dans SYMBOLS (espace)
|
||||||
DecimalFormat formatter = new DecimalFormat("#", SYMBOLS);
|
DecimalFormat formatter = new DecimalFormat("#", SYMBOLS);
|
||||||
formatter.setGroupingSize(3);
|
formatter.setGroupingSize(3);
|
||||||
formatter.setGroupingUsed(true);
|
formatter.setGroupingUsed(true);
|
||||||
formatter.setMaximumFractionDigits(0);
|
formatter.setMaximumFractionDigits(0);
|
||||||
|
|
||||||
long amount = value.longValue();
|
long amount = value.longValue();
|
||||||
return formatter.format(amount);
|
return formatter.format(amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
package dev.lions.btpxpress.filter;
|
package dev.lions.btpxpress.filter;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
import jakarta.servlet.Filter;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.FilterConfig;
|
import jakarta.servlet.FilterConfig;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
import jakarta.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class CharacterEncodingFilter implements Filter {
|
public class CharacterEncodingFilter implements Filter {
|
||||||
|
|
||||||
private static final String DEFAULT_ENCODING = "UTF-8";
|
private static final String DEFAULT_ENCODING = "UTF-8";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(FilterConfig filterConfig) throws ServletException {
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
request.setCharacterEncoding(DEFAULT_ENCODING);
|
request.setCharacterEncoding(DEFAULT_ENCODING);
|
||||||
response.setCharacterEncoding(DEFAULT_ENCODING);
|
response.setCharacterEncoding(DEFAULT_ENCODING);
|
||||||
response.setContentType("text/html; charset=" + DEFAULT_ENCODING);
|
response.setContentType("text/html; charset=" + DEFAULT_ENCODING);
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +1,116 @@
|
|||||||
package dev.lions.btpxpress.filter;
|
package dev.lions.btpxpress.filter;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
import jakarta.servlet.Filter;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.FilterConfig;
|
import jakarta.servlet.FilterConfig;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
import jakarta.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filtre de sécurité qui ajoute les headers HTTP de sécurité essentiels
|
* Filtre de sécurité qui ajoute les headers HTTP de sécurité essentiels
|
||||||
* pour protéger l'application contre diverses attaques.
|
* pour protéger l'application contre diverses attaques.
|
||||||
*/
|
*/
|
||||||
public class SecurityHeadersFilter implements Filter {
|
public class SecurityHeadersFilter implements Filter {
|
||||||
|
|
||||||
private static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security";
|
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_FRAME_OPTIONS = "X-Frame-Options";
|
||||||
private static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-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 X_XSS_PROTECTION = "X-XSS-Protection";
|
||||||
private static final String REFERRER_POLICY = "Referrer-Policy";
|
private static final String REFERRER_POLICY = "Referrer-Policy";
|
||||||
private static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
|
private static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
|
||||||
private static final String PERMISSIONS_POLICY = "Permissions-Policy";
|
private static final String PERMISSIONS_POLICY = "Permissions-Policy";
|
||||||
|
|
||||||
// HSTS - Force HTTPS pendant 1 an, inclut les sous-domaines
|
// HSTS - Force HTTPS pendant 1 an, inclut les sous-domaines
|
||||||
private static final String HSTS_VALUE = "max-age=31536000; includeSubDomains; preload";
|
private static final String HSTS_VALUE = "max-age=31536000; includeSubDomains; preload";
|
||||||
|
|
||||||
// X-Frame-Options - Empêche le clickjacking
|
// X-Frame-Options - Empêche le clickjacking
|
||||||
private static final String X_FRAME_OPTIONS_VALUE = "DENY";
|
private static final String X_FRAME_OPTIONS_VALUE = "DENY";
|
||||||
|
|
||||||
// X-Content-Type-Options - Empêche le MIME sniffing
|
// X-Content-Type-Options - Empêche le MIME sniffing
|
||||||
private static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff";
|
private static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff";
|
||||||
|
|
||||||
// X-XSS-Protection - Active la protection XSS du navigateur (legacy mais utile)
|
// X-XSS-Protection - Active la protection XSS du navigateur (legacy mais utile)
|
||||||
private static final String X_XSS_PROTECTION_VALUE = "1; mode=block";
|
private static final String X_XSS_PROTECTION_VALUE = "1; mode=block";
|
||||||
|
|
||||||
// Referrer-Policy - Contrôle les informations de referrer envoyées
|
// Referrer-Policy - Contrôle les informations de referrer envoyées
|
||||||
private static final String REFERRER_POLICY_VALUE = "strict-origin-when-cross-origin";
|
private static final String REFERRER_POLICY_VALUE = "strict-origin-when-cross-origin";
|
||||||
|
|
||||||
// Content Security Policy - Politique de sécurité stricte
|
// Content Security Policy - Politique de sécurité stricte
|
||||||
// Autorise uniquement les ressources depuis le même domaine et security.lions.dev
|
// Autorise uniquement les ressources depuis le même domaine et security.lions.dev
|
||||||
private static final String CSP_VALUE =
|
private static final String CSP_VALUE =
|
||||||
"default-src 'self'; " +
|
"default-src 'self'; " +
|
||||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; " +
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; " +
|
||||||
"style-src 'self' 'unsafe-inline' https://security.lions.dev; " +
|
"style-src 'self' 'unsafe-inline' https://security.lions.dev; " +
|
||||||
"img-src 'self' data: https: blob:; " +
|
"img-src 'self' data: https: blob:; " +
|
||||||
"font-src 'self' data: https://security.lions.dev; " +
|
"font-src 'self' data: https://security.lions.dev; " +
|
||||||
"connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.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; " +
|
"frame-src 'self' https://security.lions.dev; " +
|
||||||
"object-src 'none'; " +
|
"object-src 'none'; " +
|
||||||
"base-uri 'self'; " +
|
"base-uri 'self'; " +
|
||||||
"form-action 'self' https://security.lions.dev; " +
|
"form-action 'self' https://security.lions.dev; " +
|
||||||
"frame-ancestors 'none'; " +
|
"frame-ancestors 'none'; " +
|
||||||
"upgrade-insecure-requests;";
|
"upgrade-insecure-requests;";
|
||||||
|
|
||||||
// Permissions Policy - Désactive les fonctionnalités non nécessaires
|
// Permissions Policy - Désactive les fonctionnalités non nécessaires
|
||||||
private static final String PERMISSIONS_POLICY_VALUE =
|
private static final String PERMISSIONS_POLICY_VALUE =
|
||||||
"geolocation=(), " +
|
"geolocation=(), " +
|
||||||
"microphone=(), " +
|
"microphone=(), " +
|
||||||
"camera=(), " +
|
"camera=(), " +
|
||||||
"payment=(), " +
|
"payment=(), " +
|
||||||
"usb=(), " +
|
"usb=(), " +
|
||||||
"magnetometer=(), " +
|
"magnetometer=(), " +
|
||||||
"gyroscope=(), " +
|
"gyroscope=(), " +
|
||||||
"speaker=()";
|
"speaker=()";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(FilterConfig filterConfig) throws ServletException {
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
// Initialisation non nécessaire
|
// Initialisation non nécessaire
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
|
||||||
// Ajouter les headers de sécurité uniquement pour les requêtes HTTPS
|
// Ajouter les headers de sécurité uniquement pour les requêtes HTTPS
|
||||||
if (httpRequest.isSecure() ||
|
if (httpRequest.isSecure() ||
|
||||||
"https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Proto")) ||
|
"https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Proto")) ||
|
||||||
"https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Scheme"))) {
|
"https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Scheme"))) {
|
||||||
|
|
||||||
// Strict Transport Security (HSTS)
|
// Strict Transport Security (HSTS)
|
||||||
httpResponse.setHeader(STRICT_TRANSPORT_SECURITY, HSTS_VALUE);
|
httpResponse.setHeader(STRICT_TRANSPORT_SECURITY, HSTS_VALUE);
|
||||||
|
|
||||||
// Content Security Policy
|
// Content Security Policy
|
||||||
httpResponse.setHeader(CONTENT_SECURITY_POLICY, CSP_VALUE);
|
httpResponse.setHeader(CONTENT_SECURITY_POLICY, CSP_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers de sécurité applicables même en HTTP (développement)
|
// Headers de sécurité applicables même en HTTP (développement)
|
||||||
// Ces headers seront toujours présents
|
// Ces headers seront toujours présents
|
||||||
httpResponse.setHeader(X_FRAME_OPTIONS, X_FRAME_OPTIONS_VALUE);
|
httpResponse.setHeader(X_FRAME_OPTIONS, X_FRAME_OPTIONS_VALUE);
|
||||||
httpResponse.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_VALUE);
|
httpResponse.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_VALUE);
|
||||||
httpResponse.setHeader(X_XSS_PROTECTION, X_XSS_PROTECTION_VALUE);
|
httpResponse.setHeader(X_XSS_PROTECTION, X_XSS_PROTECTION_VALUE);
|
||||||
httpResponse.setHeader(REFERRER_POLICY, REFERRER_POLICY_VALUE);
|
httpResponse.setHeader(REFERRER_POLICY, REFERRER_POLICY_VALUE);
|
||||||
httpResponse.setHeader(PERMISSIONS_POLICY, PERMISSIONS_POLICY_VALUE);
|
httpResponse.setHeader(PERMISSIONS_POLICY, PERMISSIONS_POLICY_VALUE);
|
||||||
|
|
||||||
// Headers supplémentaires pour renforcer la sécurité
|
// Headers supplémentaires pour renforcer la sécurité
|
||||||
httpResponse.setHeader("X-Permitted-Cross-Domain-Policies", "none");
|
httpResponse.setHeader("X-Permitted-Cross-Domain-Policies", "none");
|
||||||
httpResponse.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
httpResponse.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
||||||
httpResponse.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
httpResponse.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
||||||
httpResponse.setHeader("Cross-Origin-Resource-Policy", "same-origin");
|
httpResponse.setHeader("Cross-Origin-Resource-Policy", "same-origin");
|
||||||
|
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
// Nettoyage non nécessaire
|
// Nettoyage non nécessaire
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,273 +1,336 @@
|
|||||||
package dev.lions.btpxpress.service;
|
package dev.lions.btpxpress.service;
|
||||||
|
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface REST Client pour communiquer avec l'API backend BTP Xpress.
|
* Interface REST Client pour communiquer avec l'API backend BTP Xpress.
|
||||||
* <p>
|
* <p>
|
||||||
* Ce client permet au frontend PrimeFaces de communiquer avec le backend Quarkus
|
* Ce client permet au frontend PrimeFaces de communiquer avec le backend Quarkus
|
||||||
* en utilisant les endpoints REST exposés sur /api/v1/*. L'authentification
|
* en utilisant les endpoints REST exposés sur /api/v1/*. L'authentification
|
||||||
* est gérée automatiquement via les tokens JWT Keycloak.
|
* est gérée automatiquement via les tokens JWT Keycloak.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author BTP Xpress Development Team
|
* @author BTP Xpress Development Team
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@RegisterRestClient(configKey = "btpxpress.api")
|
@RegisterRestClient(configKey = "btpxpress.api")
|
||||||
@RegisterClientHeaders
|
@RegisterClientHeaders
|
||||||
@Path("/api/v1")
|
@Path("/api/v1")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public interface BtpXpressApiClient {
|
public interface BtpXpressApiClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère la liste des chantiers.
|
* Récupère la liste des chantiers.
|
||||||
* Correspond à {@code ChantierResource.getAllChantiers()} dans le serveur.
|
* Correspond à {@code ChantierResource.getAllChantiers()} dans le serveur.
|
||||||
*
|
*
|
||||||
* @return Réponse HTTP contenant la liste des chantiers.
|
* @return Réponse HTTP contenant la liste des chantiers.
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("/chantiers")
|
@Path("/chantiers")
|
||||||
Response getChantiers();
|
Response getChantiers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère un chantier par son identifiant.
|
* Récupère un chantier par son identifiant.
|
||||||
* Correspond à {@code ChantierResource.getChantierById()} dans le serveur.
|
* Correspond à {@code ChantierResource.getChantierById()} dans le serveur.
|
||||||
*
|
*
|
||||||
* @param id L'identifiant du chantier.
|
* @param id L'identifiant du chantier.
|
||||||
* @return Réponse HTTP contenant le chantier.
|
* @return Réponse HTTP contenant le chantier.
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("/chantiers/{id}")
|
@Path("/chantiers/{id}")
|
||||||
Response getChantier(@PathParam("id") Long id);
|
Response getChantier(@PathParam("id") Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère la liste des clients.
|
* Crée un nouveau chantier.
|
||||||
* Correspond à {@code ClientResource.getAllClients()} dans le serveur.
|
* Correspond à {@code ChantierResource.createChantier()} dans le serveur.
|
||||||
*
|
*
|
||||||
* @return Réponse HTTP contenant la liste des clients.
|
* @param chantierDTO Les données du chantier à créer.
|
||||||
*/
|
* @return Réponse HTTP contenant le chantier créé.
|
||||||
@GET
|
*/
|
||||||
@Path("/clients")
|
@POST
|
||||||
Response getClients();
|
@Path("/chantiers")
|
||||||
|
Response createChantier(Object chantierDTO);
|
||||||
/**
|
|
||||||
* Récupère un client par son identifiant.
|
/**
|
||||||
* Correspond à {@code ClientResource.getClientById()} dans le serveur.
|
* Met à jour un chantier existant.
|
||||||
*
|
* Correspond à {@code ChantierResource.updateChantier()} dans le serveur.
|
||||||
* @param id L'identifiant du client.
|
*
|
||||||
* @return Réponse HTTP contenant le client.
|
* @param id L'identifiant du chantier.
|
||||||
*/
|
* @param chantierDTO Les nouvelles données du chantier.
|
||||||
@GET
|
* @return Réponse HTTP contenant le chantier mis à jour.
|
||||||
@Path("/clients/{id}")
|
*/
|
||||||
Response getClient(@PathParam("id") Long id);
|
@PUT
|
||||||
|
@Path("/chantiers/{id}")
|
||||||
// === ENDPOINTS DASHBOARD ===
|
Response updateChantier(@PathParam("id") String id, Object chantierDTO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère le dashboard principal avec les métriques globales.
|
* Supprime un chantier.
|
||||||
* Correspond à {@code DashboardResource.getDashboardPrincipal()} dans le serveur.
|
* Correspond à {@code ChantierResource.deleteChantier()} dans le serveur.
|
||||||
*
|
*
|
||||||
* @return Réponse HTTP contenant les métriques du dashboard.
|
* @param id L'identifiant du chantier.
|
||||||
*/
|
* @param permanent Si true, suppression définitive, sinon suppression logique.
|
||||||
@GET
|
* @return Réponse HTTP (204 No Content en cas de succès).
|
||||||
@Path("/dashboard")
|
*/
|
||||||
Response getDashboardPrincipal();
|
@DELETE
|
||||||
|
@Path("/chantiers/{id}")
|
||||||
/**
|
Response deleteChantier(@PathParam("id") String id, @QueryParam("permanent") @DefaultValue("false") boolean permanent);
|
||||||
* Récupère le dashboard des chantiers avec métriques détaillées.
|
|
||||||
* Correspond à {@code DashboardResource.getDashboardChantiers()} dans le serveur.
|
/**
|
||||||
*
|
* Récupère la liste des clients.
|
||||||
* @return Réponse HTTP contenant les métriques des chantiers.
|
* Correspond à {@code ClientResource.getAllClients()} dans le serveur.
|
||||||
*/
|
*
|
||||||
@GET
|
* @return Réponse HTTP contenant la liste des clients.
|
||||||
@Path("/dashboard/chantiers")
|
*/
|
||||||
Response getDashboardChantiers();
|
@GET
|
||||||
|
@Path("/clients")
|
||||||
/**
|
Response getClients();
|
||||||
* Récupère les métriques financières.
|
|
||||||
* Correspond à {@code DashboardResource.getDashboardFinances()} dans le serveur.
|
/**
|
||||||
*
|
* Récupère un client par son identifiant.
|
||||||
* @param periode Période en jours (défaut: 30).
|
* Correspond à {@code ClientResource.getClientById()} dans le serveur.
|
||||||
* @return Réponse HTTP contenant les métriques financières.
|
*
|
||||||
*/
|
* @param id L'identifiant du client.
|
||||||
@GET
|
* @return Réponse HTTP contenant le client.
|
||||||
@Path("/dashboard/finances")
|
*/
|
||||||
Response getDashboardFinances(@QueryParam("periode") @DefaultValue("30") int periode);
|
@GET
|
||||||
|
@Path("/clients/{id}")
|
||||||
/**
|
Response getClient(@PathParam("id") Long id);
|
||||||
* Récupère les métriques de maintenance.
|
|
||||||
* Correspond à {@code DashboardResource.getDashboardMaintenance()} dans le serveur.
|
// === ENDPOINTS DASHBOARD ===
|
||||||
*
|
|
||||||
* @return Réponse HTTP contenant les métriques de maintenance.
|
/**
|
||||||
*/
|
* Récupère le dashboard principal avec les métriques globales.
|
||||||
@GET
|
* Correspond à {@code DashboardResource.getDashboardPrincipal()} dans le serveur.
|
||||||
@Path("/dashboard/maintenance")
|
*
|
||||||
Response getDashboardMaintenance();
|
* @return Réponse HTTP contenant les métriques du dashboard.
|
||||||
|
*/
|
||||||
/**
|
@GET
|
||||||
* Récupère les métriques des ressources (équipes, employés, matériel).
|
@Path("/dashboard")
|
||||||
* Correspond à {@code DashboardResource.getDashboardRessources()} dans le serveur.
|
Response getDashboardPrincipal();
|
||||||
*
|
|
||||||
* @return Réponse HTTP contenant les métriques des ressources.
|
/**
|
||||||
*/
|
* Récupère le dashboard des chantiers avec métriques détaillées.
|
||||||
@GET
|
* Correspond à {@code DashboardResource.getDashboardChantiers()} dans le serveur.
|
||||||
@Path("/dashboard/ressources")
|
*
|
||||||
Response getDashboardRessources();
|
* @return Réponse HTTP contenant les métriques des chantiers.
|
||||||
|
*/
|
||||||
/**
|
@GET
|
||||||
* Récupère les alertes nécessitant une attention immédiate.
|
@Path("/dashboard/chantiers")
|
||||||
* Correspond à {@code DashboardResource.getAlertes()} dans le serveur.
|
Response getDashboardChantiers();
|
||||||
*
|
|
||||||
* @return Réponse HTTP contenant les alertes.
|
/**
|
||||||
*/
|
* Récupère les métriques financières.
|
||||||
@GET
|
* Correspond à {@code DashboardResource.getDashboardFinances()} dans le serveur.
|
||||||
@Path("/dashboard/alertes")
|
*
|
||||||
Response getAlertes();
|
* @param periode Période en jours (défaut: 30).
|
||||||
|
* @return Réponse HTTP contenant les métriques financières.
|
||||||
/**
|
*/
|
||||||
* Récupère les KPIs principaux.
|
@GET
|
||||||
* Correspond à {@code DashboardResource.getKPI()} dans le serveur.
|
@Path("/dashboard/finances")
|
||||||
*
|
Response getDashboardFinances(@QueryParam("periode") @DefaultValue("30") int periode);
|
||||||
* @param periode Période en jours (défaut: 30).
|
|
||||||
* @return Réponse HTTP contenant les KPIs.
|
/**
|
||||||
*/
|
* Récupère les métriques de maintenance.
|
||||||
@GET
|
* Correspond à {@code DashboardResource.getDashboardMaintenance()} dans le serveur.
|
||||||
@Path("/dashboard/kpi")
|
*
|
||||||
Response getKPI(@QueryParam("periode") @DefaultValue("30") int periode);
|
* @return Réponse HTTP contenant les métriques de maintenance.
|
||||||
|
*/
|
||||||
/**
|
@GET
|
||||||
* Récupère les activités récentes.
|
@Path("/dashboard/maintenance")
|
||||||
* Correspond à {@code DashboardResource.getActivitesRecentes()} dans le serveur.
|
Response getDashboardMaintenance();
|
||||||
*
|
|
||||||
* @param limit Nombre d'activités à récupérer (défaut: 10).
|
/**
|
||||||
* @return Réponse HTTP contenant les activités récentes.
|
* Récupère les métriques des ressources (équipes, employés, matériel).
|
||||||
*/
|
* Correspond à {@code DashboardResource.getDashboardRessources()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/dashboard/activites-recentes")
|
* @return Réponse HTTP contenant les métriques des ressources.
|
||||||
Response getActivitesRecentes(@QueryParam("limit") @DefaultValue("10") int limit);
|
*/
|
||||||
|
@GET
|
||||||
/**
|
@Path("/dashboard/ressources")
|
||||||
* Récupère le résumé quotidien.
|
Response getDashboardRessources();
|
||||||
* Correspond à {@code DashboardResource.getResumeQuotidien()} dans le serveur.
|
|
||||||
*
|
/**
|
||||||
* @return Réponse HTTP contenant le résumé quotidien.
|
* Récupère les alertes nécessitant une attention immédiate.
|
||||||
*/
|
* Correspond à {@code DashboardResource.getAlertes()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/dashboard/resume-quotidien")
|
* @return Réponse HTTP contenant les alertes.
|
||||||
Response getResumeQuotidien();
|
*/
|
||||||
|
@GET
|
||||||
/**
|
@Path("/dashboard/alertes")
|
||||||
* Récupère la liste des devis.
|
Response getAlertes();
|
||||||
* Correspond à {@code DevisResource.getAllDevis()} dans le serveur.
|
|
||||||
*
|
/**
|
||||||
* @return Réponse HTTP contenant la liste des devis.
|
* Récupère les KPIs principaux.
|
||||||
*/
|
* Correspond à {@code DashboardResource.getKPI()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/devis")
|
* @param periode Période en jours (défaut: 30).
|
||||||
Response getDevis();
|
* @return Réponse HTTP contenant les KPIs.
|
||||||
|
*/
|
||||||
/**
|
@GET
|
||||||
* Récupère la liste des factures.
|
@Path("/dashboard/kpi")
|
||||||
* Correspond à {@code FactureResource.getAllFactures()} dans le serveur.
|
Response getKPI(@QueryParam("periode") @DefaultValue("30") int periode);
|
||||||
*
|
|
||||||
* @return Réponse HTTP contenant la liste des factures.
|
/**
|
||||||
*/
|
* Récupère les activités récentes.
|
||||||
@GET
|
* Correspond à {@code DashboardResource.getActivitesRecentes()} dans le serveur.
|
||||||
@Path("/factures")
|
*
|
||||||
Response getFactures();
|
* @param limit Nombre d'activités à récupérer (défaut: 10).
|
||||||
|
* @return Réponse HTTP contenant les activités récentes.
|
||||||
// === ENDPOINTS EMPLOYÉS ===
|
*/
|
||||||
|
@GET
|
||||||
/**
|
@Path("/dashboard/activites-recentes")
|
||||||
* Récupère la liste des employés.
|
Response getActivitesRecentes(@QueryParam("limit") @DefaultValue("10") int limit);
|
||||||
* Correspond à {@code EmployeResource.getAllEmployes()} dans le serveur.
|
|
||||||
*
|
/**
|
||||||
* @return Réponse HTTP contenant la liste des employés.
|
* Récupère le résumé quotidien.
|
||||||
*/
|
* Correspond à {@code DashboardResource.getResumeQuotidien()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/employes")
|
* @return Réponse HTTP contenant le résumé quotidien.
|
||||||
Response getEmployes();
|
*/
|
||||||
|
@GET
|
||||||
/**
|
@Path("/dashboard/resume-quotidien")
|
||||||
* Récupère un employé par son identifiant.
|
Response getResumeQuotidien();
|
||||||
*
|
|
||||||
* @param id L'identifiant de l'employé.
|
/**
|
||||||
* @return Réponse HTTP contenant l'employé.
|
* Récupère la liste des devis.
|
||||||
*/
|
* Correspond à {@code DevisResource.getAllDevis()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/employes/{id}")
|
* @return Réponse HTTP contenant la liste des devis.
|
||||||
Response getEmploye(@PathParam("id") String id);
|
*/
|
||||||
|
@GET
|
||||||
// === ENDPOINTS ÉQUIPES ===
|
@Path("/devis")
|
||||||
|
Response getDevis();
|
||||||
/**
|
|
||||||
* Récupère la liste des équipes.
|
/**
|
||||||
* Correspond à {@code EquipeResource.getAllEquipes()} dans le serveur.
|
* Récupère la liste des factures.
|
||||||
*
|
* Correspond à {@code FactureResource.getAllFactures()} dans le serveur.
|
||||||
* @return Réponse HTTP contenant la liste des équipes.
|
*
|
||||||
*/
|
* @return Réponse HTTP contenant la liste des factures.
|
||||||
@GET
|
*/
|
||||||
@Path("/equipes")
|
@GET
|
||||||
Response getEquipes();
|
@Path("/factures")
|
||||||
|
Response getFactures();
|
||||||
/**
|
|
||||||
* Récupère une équipe par son identifiant.
|
// === ENDPOINTS EMPLOYÉS ===
|
||||||
*
|
|
||||||
* @param id L'identifiant de l'équipe.
|
/**
|
||||||
* @return Réponse HTTP contenant l'équipe.
|
* Récupère la liste des employés.
|
||||||
*/
|
* Correspond à {@code EmployeResource.getAllEmployes()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/equipes/{id}")
|
* @return Réponse HTTP contenant la liste des employés.
|
||||||
Response getEquipe(@PathParam("id") String id);
|
*/
|
||||||
|
@GET
|
||||||
// === ENDPOINTS MATÉRIELS ===
|
@Path("/employes")
|
||||||
|
Response getEmployes();
|
||||||
/**
|
|
||||||
* Récupère la liste des matériels.
|
/**
|
||||||
* Correspond à {@code MaterielResource.getAllMateriels()} dans le serveur.
|
* Récupère un employé par son identifiant.
|
||||||
*
|
*
|
||||||
* @return Réponse HTTP contenant la liste des matériels.
|
* @param id L'identifiant de l'employé.
|
||||||
*/
|
* @return Réponse HTTP contenant l'employé.
|
||||||
@GET
|
*/
|
||||||
@Path("/materiels")
|
@GET
|
||||||
Response getMateriels();
|
@Path("/employes/{id}")
|
||||||
|
Response getEmploye(@PathParam("id") String id);
|
||||||
/**
|
|
||||||
* Récupère un matériel par son identifiant.
|
// === ENDPOINTS ÉQUIPES ===
|
||||||
*
|
|
||||||
* @param id L'identifiant du matériel.
|
/**
|
||||||
* @return Réponse HTTP contenant le matériel.
|
* Récupère la liste des équipes.
|
||||||
*/
|
* Correspond à {@code EquipeResource.getAllEquipes()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/materiels/{id}")
|
* @return Réponse HTTP contenant la liste des équipes.
|
||||||
Response getMateriel(@PathParam("id") String id);
|
*/
|
||||||
|
@GET
|
||||||
// === ENDPOINTS STOCKS ===
|
@Path("/equipes")
|
||||||
|
Response getEquipes();
|
||||||
/**
|
|
||||||
* Récupère la liste des stocks.
|
/**
|
||||||
* Correspond à {@code StockResource.getAllStocks()} dans le serveur.
|
* Récupère une équipe par son identifiant.
|
||||||
*
|
*
|
||||||
* @return Réponse HTTP contenant la liste des stocks.
|
* @param id L'identifiant de l'équipe.
|
||||||
*/
|
* @return Réponse HTTP contenant l'équipe.
|
||||||
@GET
|
*/
|
||||||
@Path("/stocks")
|
@GET
|
||||||
Response getStocks();
|
@Path("/equipes/{id}")
|
||||||
|
Response getEquipe(@PathParam("id") String id);
|
||||||
/**
|
|
||||||
* Récupère un stock par son identifiant.
|
// === ENDPOINTS MATÉRIELS ===
|
||||||
*
|
|
||||||
* @param id L'identifiant du stock.
|
/**
|
||||||
* @return Réponse HTTP contenant le stock.
|
* Récupère la liste des matériels.
|
||||||
*/
|
* Correspond à {@code MaterielResource.getAllMateriels()} dans le serveur.
|
||||||
@GET
|
*
|
||||||
@Path("/stocks/{id}")
|
* @return Réponse HTTP contenant la liste des matériels.
|
||||||
Response getStock(@PathParam("id") String id);
|
*/
|
||||||
}
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1,177 @@
|
|||||||
package dev.lions.btpxpress.service;
|
package dev.lions.btpxpress.service;
|
||||||
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service de gestion des chantiers côté client.
|
* Service de gestion des chantiers côté client.
|
||||||
* <p>
|
* <p>
|
||||||
* Ce service encapsule la communication avec l'API backend pour les opérations
|
* 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
|
* liées aux chantiers. Il utilise le REST Client pour effectuer les appels HTTP
|
||||||
* vers le backend.
|
* vers le backend.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author BTP Xpress Development Team
|
* @author BTP Xpress Development Team
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class ChantierService {
|
public class ChantierService {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ChantierService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ChantierService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@RestClient
|
@RestClient
|
||||||
BtpXpressApiClient apiClient;
|
BtpXpressApiClient apiClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère tous les chantiers depuis l'API backend.
|
* Récupère tous les chantiers depuis l'API backend.
|
||||||
*
|
*
|
||||||
* @return Liste des chantiers, ou liste vide en cas d'erreur.
|
* @return Liste des chantiers, ou liste vide en cas d'erreur.
|
||||||
*/
|
*/
|
||||||
public List<Map<String, Object>> getAllChantiers() {
|
public List<Map<String, Object>> getAllChantiers() {
|
||||||
try {
|
try {
|
||||||
LOG.debug("Récupération de la liste des chantiers depuis l'API backend.");
|
LOG.debug("Récupération de la liste des chantiers depuis l'API backend.");
|
||||||
Response response = apiClient.getChantiers();
|
Response response = apiClient.getChantiers();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Map<String, Object>> chantiers = response.readEntity(List.class);
|
List<Map<String, Object>> chantiers = response.readEntity(List.class);
|
||||||
LOG.debug("Chantiers récupérés avec succès : {} élément(s)", chantiers != null ? chantiers.size() : 0);
|
LOG.debug("Chantiers récupérés avec succès : {} élément(s)", chantiers != null ? chantiers.size() : 0);
|
||||||
return chantiers != null ? chantiers : new ArrayList<>();
|
return chantiers != null ? chantiers : new ArrayList<>();
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Erreur lors de la récupération des chantiers. Code HTTP : {}", response.getStatus());
|
LOG.warn("Erreur lors de la récupération des chantiers. Code HTTP : {}", response.getStatus());
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les chantiers : {}", e.getMessage(), e);
|
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les chantiers : {}", e.getMessage(), e);
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère un chantier par son identifiant depuis l'API backend.
|
* Récupère un chantier par son identifiant depuis l'API backend.
|
||||||
*
|
*
|
||||||
* @param id L'identifiant du chantier.
|
* @param id L'identifiant du chantier.
|
||||||
* @return Le chantier sous forme de Map, ou null en cas d'erreur.
|
* @return Le chantier sous forme de Map, ou null en cas d'erreur.
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> getChantierById(Long id) {
|
public Map<String, Object> getChantierById(Long id) {
|
||||||
try {
|
try {
|
||||||
LOG.debug("Récupération du chantier avec ID : {}", id);
|
LOG.debug("Récupération du chantier avec ID : {}", id);
|
||||||
Response response = apiClient.getChantier(id);
|
Response response = apiClient.getChantier(id);
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Map<String, Object> chantier = response.readEntity(Map.class);
|
@SuppressWarnings("unchecked")
|
||||||
LOG.debug("Chantier récupéré avec succès.");
|
Map<String, Object> chantier = response.readEntity(Map.class);
|
||||||
return chantier;
|
LOG.debug("Chantier récupéré avec succès.");
|
||||||
} else {
|
return chantier;
|
||||||
LOG.warn("Erreur lors de la récupération du chantier. Code HTTP : {}", response.getStatus());
|
} else {
|
||||||
return null;
|
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);
|
} catch (Exception e) {
|
||||||
return null;
|
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<String, Object> createChantier(Map<String, Object> 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<String, Object> 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<String, Object> updateChantier(String id, Map<String, Object> 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<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ public class ClientService {
|
|||||||
LOG.debug("Récupération du client avec ID : {}", id);
|
LOG.debug("Récupération du client avec ID : {}", id);
|
||||||
Response response = apiClient.getClient(id);
|
Response response = apiClient.getClient(id);
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> client = response.readEntity(Map.class);
|
Map<String, Object> client = response.readEntity(Map.class);
|
||||||
LOG.debug("Client récupéré avec succès.");
|
LOG.debug("Client récupéré avec succès.");
|
||||||
return client;
|
return client;
|
||||||
|
|||||||
@@ -1,311 +1,341 @@
|
|||||||
package dev.lions.btpxpress.service;
|
package dev.lions.btpxpress.service;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service pour récupérer et transformer les données du dashboard depuis l'API backend.
|
* Service pour récupérer et transformer les données du dashboard depuis l'API backend.
|
||||||
*
|
*
|
||||||
* <p>Ce service encapsule tous les appels à l'API dashboard et transforme
|
* <p>Ce service encapsule tous les appels à l'API dashboard et transforme
|
||||||
* les réponses JSON en objets Java utilisables par les vues JSF.</p>
|
* les réponses JSON en objets Java utilisables par les vues JSF.</p>
|
||||||
*
|
*
|
||||||
* @author BTP Xpress Team
|
* @author BTP Xpress Team
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
*/
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class DashboardService {
|
public class DashboardService {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DashboardService.class);
|
private static final Logger logger = LoggerFactory.getLogger(DashboardService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@RestClient
|
@RestClient
|
||||||
BtpXpressApiClient apiClient;
|
BtpXpressApiClient apiClient;
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les métriques du dashboard principal.
|
* Récupère les métriques du dashboard principal.
|
||||||
*
|
*
|
||||||
* @return JsonNode contenant les métriques ou null en cas d'erreur
|
* @return JsonNode contenant les métriques ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getDashboardPrincipal() {
|
public JsonNode getDashboardPrincipal() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getDashboardPrincipal();
|
Response response = apiClient.getDashboardPrincipal();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
logger.warn("Réponse vide du dashboard principal");
|
logger.warn("Réponse vide du dashboard principal");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// REST Client avec Jackson désérialise déjà en Map/Object
|
// REST Client avec Jackson désérialise déjà en Map/Object
|
||||||
return convertToJsonNode(entity);
|
return convertToJsonNode(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API dashboard principal: status {}", response.getStatus());
|
logger.error("Erreur API dashboard principal: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du dashboard principal", e);
|
logger.error("Erreur lors de la récupération du dashboard principal", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convertit un objet en JsonNode, quel que soit son type (String, Map, Object, etc.).
|
* Convertit un objet en JsonNode, quel que soit son type (String, Map, Object, etc.).
|
||||||
*/
|
*/
|
||||||
private JsonNode convertToJsonNode(Object entity) {
|
private JsonNode convertToJsonNode(Object entity) {
|
||||||
try {
|
try {
|
||||||
if (entity instanceof String) {
|
if (entity instanceof String) {
|
||||||
return objectMapper.readTree((String) entity);
|
return objectMapper.readTree((String) entity);
|
||||||
} else if (entity instanceof Map || entity instanceof List) {
|
} else if (entity instanceof Map || entity instanceof List) {
|
||||||
// Map ou List sont déjà désérialisés par REST Client
|
// Map ou List sont déjà désérialisés par REST Client
|
||||||
return objectMapper.valueToTree(entity);
|
return objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
// Pour les autres objets, conversion via ObjectMapper
|
// Pour les autres objets, conversion via ObjectMapper
|
||||||
return objectMapper.valueToTree(entity);
|
return objectMapper.valueToTree(entity);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la conversion en JsonNode", e);
|
logger.error("Erreur lors de la conversion en JsonNode", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les métriques des chantiers.
|
* Récupère les métriques des chantiers.
|
||||||
*
|
*
|
||||||
* @return JsonNode contenant les métriques des chantiers ou null en cas d'erreur
|
* @return JsonNode contenant les métriques des chantiers ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getDashboardChantiers() {
|
public JsonNode getDashboardChantiers() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getDashboardChantiers();
|
Response response = apiClient.getDashboardChantiers();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return convertToJsonNode(entity);
|
return convertToJsonNode(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API dashboard chantiers: status {}", response.getStatus());
|
logger.error("Erreur API dashboard chantiers: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du dashboard chantiers", e);
|
logger.error("Erreur lors de la récupération du dashboard chantiers", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les métriques financières.
|
* Récupère les métriques financières.
|
||||||
*
|
*
|
||||||
* @param periode Période en jours (défaut: 30)
|
* @param periode Période en jours (défaut: 30)
|
||||||
* @return JsonNode contenant les métriques financières ou null en cas d'erreur
|
* @return JsonNode contenant les métriques financières ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getDashboardFinances(int periode) {
|
public JsonNode getDashboardFinances(int periode) {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getDashboardFinances(periode);
|
Response response = apiClient.getDashboardFinances(periode);
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API dashboard finances: status {}", response.getStatus());
|
logger.error("Erreur API dashboard finances: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du dashboard finances", e);
|
logger.error("Erreur lors de la récupération du dashboard finances", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les métriques de maintenance.
|
* Récupère les métriques de maintenance.
|
||||||
*
|
*
|
||||||
* @return JsonNode contenant les métriques de maintenance ou null en cas d'erreur
|
* @return JsonNode contenant les métriques de maintenance ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getDashboardMaintenance() {
|
public JsonNode getDashboardMaintenance() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getDashboardMaintenance();
|
Response response = apiClient.getDashboardMaintenance();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API dashboard maintenance: status {}", response.getStatus());
|
logger.error("Erreur API dashboard maintenance: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du dashboard maintenance", e);
|
logger.error("Erreur lors de la récupération du dashboard maintenance", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les métriques des ressources.
|
* Récupère les métriques des ressources.
|
||||||
*
|
*
|
||||||
* @return JsonNode contenant les métriques des ressources ou null en cas d'erreur
|
* @return JsonNode contenant les métriques des ressources ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getDashboardRessources() {
|
public JsonNode getDashboardRessources() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getDashboardRessources();
|
Response response = apiClient.getDashboardRessources();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API dashboard ressources: status {}", response.getStatus());
|
logger.error("Erreur API dashboard ressources: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du dashboard ressources", e);
|
logger.error("Erreur lors de la récupération du dashboard ressources", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les alertes.
|
* Récupère les alertes.
|
||||||
*
|
*
|
||||||
* @return JsonNode contenant les alertes ou null en cas d'erreur
|
* @return JsonNode contenant les alertes ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getAlertes() {
|
public JsonNode getAlertes() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getAlertes();
|
Response response = apiClient.getAlertes();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API alertes: status {}", response.getStatus());
|
logger.error("Erreur API alertes: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération des alertes", e);
|
logger.error("Erreur lors de la récupération des alertes", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les KPIs.
|
* Récupère les KPIs.
|
||||||
*
|
*
|
||||||
* @param periode Période en jours (défaut: 30)
|
* @param periode Période en jours (défaut: 30)
|
||||||
* @return JsonNode contenant les KPIs ou null en cas d'erreur
|
* @return JsonNode contenant les KPIs ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getKPI(int periode) {
|
public JsonNode getKPI(int periode) {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getKPI(periode);
|
Response response = apiClient.getKPI(periode);
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API KPI: status {}", response.getStatus());
|
logger.error("Erreur API KPI: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération des KPIs", e);
|
logger.error("Erreur lors de la récupération des KPIs", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les activités récentes.
|
* Récupère les activités récentes.
|
||||||
*
|
*
|
||||||
* @param limit Nombre d'activités à récupérer
|
* @param limit Nombre d'activités à récupérer
|
||||||
* @return JsonNode contenant les activités récentes ou null en cas d'erreur
|
* @return JsonNode contenant les activités récentes ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getActivitesRecentes(int limit) {
|
public JsonNode getActivitesRecentes(int limit) {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getActivitesRecentes(limit);
|
Response response = apiClient.getActivitesRecentes(limit);
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API activités récentes: status {}", response.getStatus());
|
logger.error("Erreur API activités récentes: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération des activités récentes", e);
|
logger.error("Erreur lors de la récupération des activités récentes", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère le résumé quotidien.
|
* Récupère le résumé quotidien.
|
||||||
*
|
*
|
||||||
* @return JsonNode contenant le résumé quotidien ou null en cas d'erreur
|
* @return JsonNode contenant le résumé quotidien ou null en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public JsonNode getResumeQuotidien() {
|
public JsonNode getResumeQuotidien() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getResumeQuotidien();
|
Response response = apiClient.getResumeQuotidien();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
Object entity = response.getEntity();
|
Object entity = response.getEntity();
|
||||||
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
return entity instanceof String ? objectMapper.readTree((String) entity) : objectMapper.valueToTree(entity);
|
||||||
} else {
|
} else {
|
||||||
logger.error("Erreur API résumé quotidien: status {}", response.getStatus());
|
logger.error("Erreur API résumé quotidien: status {}", response.getStatus());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du résumé quotidien", e);
|
logger.error("Erreur lors de la récupération du résumé quotidien", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère le nombre de clients.
|
* Récupère le nombre de clients.
|
||||||
*
|
*
|
||||||
* @return Nombre de clients ou 0 en cas d'erreur
|
* @return Nombre de clients ou 0 en cas d'erreur
|
||||||
*/
|
*/
|
||||||
public int getNombreClients() {
|
public int getNombreClients() {
|
||||||
try {
|
try {
|
||||||
Response response = apiClient.getClients();
|
Response response = apiClient.getClients();
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
List<?> clients = (List<?>) response.getEntity();
|
List<?> clients = (List<?>) response.getEntity();
|
||||||
return clients != null ? clients.size() : 0;
|
return clients != null ? clients.size() : 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Erreur lors de la récupération du nombre de clients", e);
|
logger.error("Erreur lors de la récupération du nombre de clients", e);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère le nombre de devis en attente.
|
* 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
|
*
|
||||||
*/
|
* @return Nombre de devis en attente ou 0 en cas d'erreur
|
||||||
public int getNombreDevisEnAttente() {
|
*/
|
||||||
try {
|
@SuppressWarnings("unchecked")
|
||||||
Response response = apiClient.getDevis();
|
public int getNombreDevisEnAttente() {
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
try {
|
||||||
List<?> devis = (List<?>) response.getEntity();
|
Response response = apiClient.getDevis();
|
||||||
// TODO: Filtrer par statut EN_ATTENTE si l'API le permet
|
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
||||||
return devis != null ? devis.size() : 0;
|
List<Map<String, Object>> devis = response.readEntity(List.class);
|
||||||
}
|
if (devis == null) {
|
||||||
return 0;
|
return 0;
|
||||||
} catch (Exception e) {
|
}
|
||||||
logger.error("Erreur lors de la récupération du nombre de devis", e);
|
// Filtrer par statut EN_ATTENTE côté client
|
||||||
return 0;
|
long count = devis.stream()
|
||||||
}
|
.filter(d -> {
|
||||||
}
|
Object statut = d.get("statut");
|
||||||
|
return statut != null &&
|
||||||
/**
|
(statut.toString().equalsIgnoreCase("EN_ATTENTE") ||
|
||||||
* Récupère le nombre de factures impayées.
|
statut.toString().equalsIgnoreCase("EN ATTENTE"));
|
||||||
*
|
})
|
||||||
* @return Nombre de factures impayées ou 0 en cas d'erreur
|
.count();
|
||||||
*/
|
return (int) count;
|
||||||
public int getNombreFacturesImpayees() {
|
}
|
||||||
try {
|
return 0;
|
||||||
Response response = apiClient.getFactures();
|
} catch (Exception e) {
|
||||||
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
|
logger.error("Erreur lors de la récupération du nombre de devis", e);
|
||||||
List<?> factures = (List<?>) response.getEntity();
|
return 0;
|
||||||
// TODO: Filtrer par statut IMPAYEE si l'API le permet
|
}
|
||||||
return factures != null ? factures.size() : 0;
|
}
|
||||||
}
|
|
||||||
return 0;
|
/**
|
||||||
} catch (Exception e) {
|
* Récupère le nombre de factures impayées.
|
||||||
logger.error("Erreur lors de la récupération du nombre de factures", e);
|
* Filtre côté client les factures avec statut IMPAYEE ou EN_RETARD.
|
||||||
return 0;
|
*
|
||||||
}
|
* @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<Map<String, Object>> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* Ce service encapsule la communication avec l'API backend pour les opérations
|
||||||
|
* liées aux messages.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* Ce service encapsule la communication avec l'API backend pour les opérations
|
||||||
|
* liées aux notifications.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,383 +1,383 @@
|
|||||||
package dev.lions.btpxpress.view;
|
package dev.lions.btpxpress.view;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.faces.application.FacesMessage;
|
import jakarta.faces.application.FacesMessage;
|
||||||
import jakarta.faces.context.FacesContext;
|
import jakarta.faces.context.FacesContext;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classe de base pour les vues de type liste/CRUD.
|
* Classe de base pour les vues de type liste/CRUD.
|
||||||
*
|
*
|
||||||
* Fonctionnalités:
|
* Fonctionnalités:
|
||||||
* - Chargement et affichage de listes
|
* - Chargement et affichage de listes
|
||||||
* - Filtrage multi-critères
|
* - Filtrage multi-critères
|
||||||
* - Tri (ascendant/descendant)
|
* - Tri (ascendant/descendant)
|
||||||
* - Pagination
|
* - Pagination
|
||||||
* - CRUD complet (Create, Read, Update, Delete)
|
* - CRUD complet (Create, Read, Update, Delete)
|
||||||
* - Sélection simple/multiple
|
* - Sélection simple/multiple
|
||||||
* - Messages utilisateur (succès, erreur, warning)
|
* - Messages utilisateur (succès, erreur, warning)
|
||||||
* - Lazy loading pour grandes listes
|
* - Lazy loading pour grandes listes
|
||||||
*
|
*
|
||||||
* Principe DRY: Toute la logique commune des écrans de liste est centralisée ici.
|
* Principe DRY: Toute la logique commune des écrans de liste est centralisée ici.
|
||||||
*
|
*
|
||||||
* @param <T> Type d'entité
|
* @param <T> Type d'entité
|
||||||
* @param <ID> Type de l'identifiant
|
* @param <ID> Type de l'identifiant
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public abstract class BaseListView<T, ID> implements Serializable {
|
public abstract class BaseListView<T, ID> implements Serializable {
|
||||||
|
|
||||||
protected static final Logger LOG = LoggerFactory.getLogger(BaseListView.class);
|
protected static final Logger LOG = LoggerFactory.getLogger(BaseListView.class);
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
// ========== Données ==========
|
// ========== Données ==========
|
||||||
protected List<T> items = new ArrayList<>();
|
protected List<T> items = new ArrayList<>();
|
||||||
protected List<T> filteredItems = new ArrayList<>();
|
protected List<T> filteredItems = new ArrayList<>();
|
||||||
protected T selectedItem;
|
protected T selectedItem;
|
||||||
protected List<T> selectedItems = new ArrayList<>();
|
protected List<T> selectedItems = new ArrayList<>();
|
||||||
protected T entity; // Pour les formulaires create/edit
|
protected T entity; // Pour les formulaires create/edit
|
||||||
|
|
||||||
// ========== États ==========
|
// ========== États ==========
|
||||||
protected boolean loading = false;
|
protected boolean loading = false;
|
||||||
protected boolean editing = false; // Mode édition vs création
|
protected boolean editing = false; // Mode édition vs création
|
||||||
protected String globalFilter; // Recherche globale
|
protected String globalFilter; // Recherche globale
|
||||||
|
|
||||||
// ========== Pagination ==========
|
// ========== Pagination ==========
|
||||||
protected int first = 0; // Index de départ
|
protected int first = 0; // Index de départ
|
||||||
protected int pageSize = 10; // Taille de page
|
protected int pageSize = 10; // Taille de page
|
||||||
protected int totalRecords = 0; // Nombre total d'enregistrements
|
protected int totalRecords = 0; // Nombre total d'enregistrements
|
||||||
|
|
||||||
// ========== Tri ==========
|
// ========== Tri ==========
|
||||||
protected String sortField; // Champ de tri
|
protected String sortField; // Champ de tri
|
||||||
protected boolean sortAscending = true; // Ordre de tri
|
protected boolean sortAscending = true; // Ordre de tri
|
||||||
|
|
||||||
// ========== Sélection ==========
|
// ========== Sélection ==========
|
||||||
protected String selectionMode = "single"; // single, multiple, checkbox
|
protected String selectionMode = "single"; // single, multiple, checkbox
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialisation du bean au chargement de la page.
|
* Initialisation du bean au chargement de la page.
|
||||||
*/
|
*/
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
LOG.debug("Initialisation de {}", getClass().getSimpleName());
|
LOG.debug("Initialisation de {}", getClass().getSimpleName());
|
||||||
try {
|
try {
|
||||||
initializeFields();
|
initializeFields();
|
||||||
loadItems();
|
loadItems();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Erreur lors de l'initialisation", e);
|
LOG.error("Erreur lors de l'initialisation", e);
|
||||||
addErrorMessage("Erreur lors du chargement des données");
|
addErrorMessage("Erreur lors du chargement des données");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialiser les champs spécifiques de la vue.
|
* Initialiser les champs spécifiques de la vue.
|
||||||
* Override si nécessaire.
|
* Override si nécessaire.
|
||||||
*/
|
*/
|
||||||
protected void initializeFields() {
|
protected void initializeFields() {
|
||||||
// À surcharger dans les classes filles si besoin
|
// À surcharger dans les classes filles si besoin
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Charger les items depuis la source de données.
|
* Charger les items depuis la source de données.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
public abstract void loadItems();
|
public abstract void loadItems();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recharger les données (alias pour loadItems).
|
* Recharger les données (alias pour loadItems).
|
||||||
*/
|
*/
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
LOG.debug("Rafraîchissement des données");
|
LOG.debug("Rafraîchissement des données");
|
||||||
loadItems();
|
loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Filtrage ==========
|
// ========== Filtrage ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appliquer les filtres à la liste d'items.
|
* Appliquer les filtres à la liste d'items.
|
||||||
*/
|
*/
|
||||||
protected void applyFilters(List<T> sourceItems, List<Predicate<T>> filters) {
|
protected void applyFilters(List<T> sourceItems, List<Predicate<T>> filters) {
|
||||||
if (filters == null || filters.isEmpty()) {
|
if (filters == null || filters.isEmpty()) {
|
||||||
filteredItems = new ArrayList<>(sourceItems);
|
filteredItems = new ArrayList<>(sourceItems);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredItems = sourceItems.stream()
|
filteredItems = sourceItems.stream()
|
||||||
.filter(filters.stream().reduce(Predicate::and).orElse(x -> true))
|
.filter(filters.stream().reduce(Predicate::and).orElse(x -> true))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recherche avec les critères de filtrage actuels.
|
* Recherche avec les critères de filtrage actuels.
|
||||||
*/
|
*/
|
||||||
public void search() {
|
public void search() {
|
||||||
LOG.debug("Recherche lancée pour {}", getClass().getSimpleName());
|
LOG.debug("Recherche lancée pour {}", getClass().getSimpleName());
|
||||||
first = 0; // Retour à la première page
|
first = 0; // Retour à la première page
|
||||||
loadItems();
|
loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Réinitialiser tous les filtres.
|
* Réinitialiser tous les filtres.
|
||||||
*/
|
*/
|
||||||
public void resetFilters() {
|
public void resetFilters() {
|
||||||
LOG.debug("Réinitialisation des filtres pour {}", getClass().getSimpleName());
|
LOG.debug("Réinitialisation des filtres pour {}", getClass().getSimpleName());
|
||||||
globalFilter = null;
|
globalFilter = null;
|
||||||
sortField = null;
|
sortField = null;
|
||||||
sortAscending = true;
|
sortAscending = true;
|
||||||
first = 0;
|
first = 0;
|
||||||
resetFilterFields();
|
resetFilterFields();
|
||||||
loadItems();
|
loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Réinitialiser les champs de filtre spécifiques.
|
* Réinitialiser les champs de filtre spécifiques.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
protected abstract void resetFilterFields();
|
protected abstract void resetFilterFields();
|
||||||
|
|
||||||
// ========== Tri ==========
|
// ========== Tri ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trier la liste par un champ donné.
|
* Trier la liste par un champ donné.
|
||||||
*/
|
*/
|
||||||
public void sort(String field) {
|
public void sort(String field) {
|
||||||
if (field.equals(sortField)) {
|
if (field.equals(sortField)) {
|
||||||
sortAscending = !sortAscending;
|
sortAscending = !sortAscending;
|
||||||
} else {
|
} else {
|
||||||
sortField = field;
|
sortField = field;
|
||||||
sortAscending = true;
|
sortAscending = true;
|
||||||
}
|
}
|
||||||
LOG.debug("Tri par {} ({})", field, sortAscending ? "ASC" : "DESC");
|
LOG.debug("Tri par {} ({})", field, sortAscending ? "ASC" : "DESC");
|
||||||
loadItems();
|
loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Navigation ==========
|
// ========== Navigation ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Naviguer vers la page de détails d'un item.
|
* Naviguer vers la page de détails d'un item.
|
||||||
*/
|
*/
|
||||||
public String viewDetails(ID id) {
|
public String viewDetails(ID id) {
|
||||||
LOG.debug("Redirection vers détails : {}", id);
|
LOG.debug("Redirection vers détails : {}", id);
|
||||||
return getDetailsPath() + "?id=" + id + "&faces-redirect=true";
|
return getDetailsPath() + "?id=" + id + "&faces-redirect=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Naviguer vers la page de détails de l'item sélectionné.
|
* Naviguer vers la page de détails de l'item sélectionné.
|
||||||
*/
|
*/
|
||||||
public String viewSelectedDetails() {
|
public String viewSelectedDetails() {
|
||||||
if (selectedItem == null) {
|
if (selectedItem == null) {
|
||||||
addWarningMessage("Aucun élément sélectionné");
|
addWarningMessage("Aucun élément sélectionné");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return viewDetails(getEntityId(selectedItem));
|
return viewDetails(getEntityId(selectedItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtenir le chemin de la page de détails.
|
* Obtenir le chemin de la page de détails.
|
||||||
*/
|
*/
|
||||||
protected abstract String getDetailsPath();
|
protected abstract String getDetailsPath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Naviguer vers la page de création.
|
* Naviguer vers la page de création.
|
||||||
*/
|
*/
|
||||||
public String createNew() {
|
public String createNew() {
|
||||||
LOG.debug("Redirection vers création");
|
LOG.debug("Redirection vers création");
|
||||||
return getCreatePath() + "?faces-redirect=true";
|
return getCreatePath() + "?faces-redirect=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtenir le chemin de la page de création.
|
* Obtenir le chemin de la page de création.
|
||||||
*/
|
*/
|
||||||
protected abstract String getCreatePath();
|
protected abstract String getCreatePath();
|
||||||
|
|
||||||
// ========== CRUD ==========
|
// ========== CRUD ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Préparer un nouvel item pour création.
|
* Préparer un nouvel item pour création.
|
||||||
*/
|
*/
|
||||||
public void prepareNew() {
|
public void prepareNew() {
|
||||||
LOG.debug("Préparation nouvelle entité");
|
LOG.debug("Préparation nouvelle entité");
|
||||||
entity = createNewEntity();
|
entity = createNewEntity();
|
||||||
editing = false;
|
editing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Créer une nouvelle instance de l'entité.
|
* Créer une nouvelle instance de l'entité.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
protected abstract T createNewEntity();
|
protected abstract T createNewEntity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Préparer un item pour édition.
|
* Préparer un item pour édition.
|
||||||
*/
|
*/
|
||||||
public void prepareEdit(T item) {
|
public void prepareEdit(T item) {
|
||||||
LOG.debug("Préparation édition : {}", item);
|
LOG.debug("Préparation édition : {}", item);
|
||||||
entity = item;
|
entity = item;
|
||||||
editing = true;
|
editing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sauvegarder l'entité (création ou modification).
|
* Sauvegarder l'entité (création ou modification).
|
||||||
*/
|
*/
|
||||||
public void save() {
|
public void save() {
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
if (editing) {
|
if (editing) {
|
||||||
performUpdate();
|
performUpdate();
|
||||||
addSuccessMessage("Modification réussie");
|
addSuccessMessage("Modification réussie");
|
||||||
} else {
|
} else {
|
||||||
performCreate();
|
performCreate();
|
||||||
addSuccessMessage("Création réussie");
|
addSuccessMessage("Création réussie");
|
||||||
}
|
}
|
||||||
|
|
||||||
loadItems();
|
loadItems();
|
||||||
entity = null;
|
entity = null;
|
||||||
editing = false;
|
editing = false;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Erreur lors de la sauvegarde", e);
|
LOG.error("Erreur lors de la sauvegarde", e);
|
||||||
addErrorMessage("Erreur lors de la sauvegarde : " + e.getMessage());
|
addErrorMessage("Erreur lors de la sauvegarde : " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Créer une nouvelle entité.
|
* Créer une nouvelle entité.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
protected abstract void performCreate();
|
protected abstract void performCreate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mettre à jour une entité existante.
|
* Mettre à jour une entité existante.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
protected abstract void performUpdate();
|
protected abstract void performUpdate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supprimer l'item sélectionné.
|
* Supprimer l'item sélectionné.
|
||||||
*/
|
*/
|
||||||
public void delete() {
|
public void delete() {
|
||||||
if (selectedItem == null) {
|
if (selectedItem == null) {
|
||||||
addWarningMessage("Aucun élément sélectionné");
|
addWarningMessage("Aucun élément sélectionné");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
LOG.info("Suppression : {}", selectedItem);
|
LOG.info("Suppression : {}", selectedItem);
|
||||||
performDelete();
|
performDelete();
|
||||||
items.remove(selectedItem);
|
items.remove(selectedItem);
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
addSuccessMessage("Suppression réussie");
|
addSuccessMessage("Suppression réussie");
|
||||||
loadItems();
|
loadItems();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Erreur lors de la suppression", e);
|
LOG.error("Erreur lors de la suppression", e);
|
||||||
addErrorMessage("Erreur lors de la suppression : " + e.getMessage());
|
addErrorMessage("Erreur lors de la suppression : " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supprimer les items sélectionnés (sélection multiple).
|
* Supprimer les items sélectionnés (sélection multiple).
|
||||||
*/
|
*/
|
||||||
public void deleteSelected() {
|
public void deleteSelected() {
|
||||||
if (selectedItems == null || selectedItems.isEmpty()) {
|
if (selectedItems == null || selectedItems.isEmpty()) {
|
||||||
addWarningMessage("Aucun élément sélectionné");
|
addWarningMessage("Aucun élément sélectionné");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading = true;
|
loading = true;
|
||||||
int count = selectedItems.size();
|
int count = selectedItems.size();
|
||||||
for (T item : selectedItems) {
|
for (T item : selectedItems) {
|
||||||
selectedItem = item;
|
selectedItem = item;
|
||||||
performDelete();
|
performDelete();
|
||||||
}
|
}
|
||||||
loadItems();
|
loadItems();
|
||||||
selectedItems.clear();
|
selectedItems.clear();
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
addSuccessMessage(count + " élément(s) supprimé(s)");
|
addSuccessMessage(count + " élément(s) supprimé(s)");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Erreur lors de la suppression multiple", e);
|
LOG.error("Erreur lors de la suppression multiple", e);
|
||||||
addErrorMessage("Erreur lors de la suppression");
|
addErrorMessage("Erreur lors de la suppression");
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Effectuer la suppression réelle.
|
* Effectuer la suppression réelle.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
protected abstract void performDelete();
|
protected abstract void performDelete();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtenir l'ID d'une entité.
|
* Obtenir l'ID d'une entité.
|
||||||
* DOIT être implémenté par les classes filles.
|
* DOIT être implémenté par les classes filles.
|
||||||
*/
|
*/
|
||||||
protected abstract ID getEntityId(T entity);
|
protected abstract ID getEntityId(T entity);
|
||||||
|
|
||||||
// ========== Messages utilisateur ==========
|
// ========== Messages utilisateur ==========
|
||||||
|
|
||||||
protected void addSuccessMessage(String message) {
|
protected void addSuccessMessage(String message) {
|
||||||
FacesContext.getCurrentInstance().addMessage(null,
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addErrorMessage(String message) {
|
protected void addErrorMessage(String message) {
|
||||||
FacesContext.getCurrentInstance().addMessage(null,
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addWarningMessage(String message) {
|
protected void addWarningMessage(String message) {
|
||||||
FacesContext.getCurrentInstance().addMessage(null,
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", message));
|
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", message));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addInfoMessage(String message) {
|
protected void addInfoMessage(String message) {
|
||||||
FacesContext.getCurrentInstance().addMessage(null,
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
|
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Utilitaires ==========
|
// ========== Utilitaires ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifier si la liste est vide.
|
* Vérifier si la liste est vide.
|
||||||
*/
|
*/
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return items == null || items.isEmpty();
|
return items == null || items.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtenir le nombre d'items.
|
* Obtenir le nombre d'items.
|
||||||
*/
|
*/
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return items == null ? 0 : items.size();
|
return items == null ? 0 : items.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifier si un item est sélectionné.
|
* Vérifier si un item est sélectionné.
|
||||||
*/
|
*/
|
||||||
public boolean hasSelection() {
|
public boolean hasSelection() {
|
||||||
return selectedItem != null;
|
return selectedItem != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vérifier si plusieurs items sont sélectionnés.
|
* Vérifier si plusieurs items sont sélectionnés.
|
||||||
*/
|
*/
|
||||||
public boolean hasMultipleSelection() {
|
public boolean hasMultipleSelection() {
|
||||||
return selectedItems != null && !selectedItems.isEmpty();
|
return selectedItems != null && !selectedItems.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,273 +1,397 @@
|
|||||||
package dev.lions.btpxpress.view;
|
package dev.lions.btpxpress.view;
|
||||||
|
|
||||||
import dev.lions.btpxpress.service.ChantierService;
|
import dev.lions.btpxpress.service.ChantierService;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.faces.view.ViewScoped;
|
import jakarta.faces.view.ViewScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Named;
|
import jakarta.inject.Named;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
||||||
import java.util.ArrayList;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@Named("chantiersView")
|
@Named("chantiersView")
|
||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class ChantiersView extends BaseListView<ChantiersView.Chantier, Long> implements Serializable {
|
public class ChantiersView extends BaseListView<ChantiersView.Chantier, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ChantiersView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ChantiersView.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ChantierService chantierService;
|
ChantierService chantierService;
|
||||||
|
|
||||||
private String filtreNom;
|
private String filtreNom;
|
||||||
private String filtreClient;
|
private String filtreClient;
|
||||||
private String filtreStatut;
|
private String filtreStatut;
|
||||||
private Long chantierId;
|
private Long chantierId;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
if (filtreStatut == null) {
|
if (filtreStatut == null) {
|
||||||
filtreStatut = "TOUS";
|
filtreStatut = "TOUS";
|
||||||
}
|
}
|
||||||
loadItems();
|
loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Définit le filtre de statut (utilisé depuis les pages filtrées).
|
* Définit le filtre de statut (utilisé depuis les pages filtrées).
|
||||||
*/
|
*/
|
||||||
public void setFiltreStatut(String statut) {
|
public void setFiltreStatut(String statut) {
|
||||||
this.filtreStatut = statut;
|
this.filtreStatut = statut;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadItems() {
|
public void loadItems() {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
items = new ArrayList<>();
|
items = new ArrayList<>();
|
||||||
|
|
||||||
// Récupération depuis l'API backend
|
// Récupération depuis l'API backend
|
||||||
List<Map<String, Object>> chantiersData = chantierService.getAllChantiers();
|
List<Map<String, Object>> chantiersData = chantierService.getAllChantiers();
|
||||||
|
|
||||||
for (Map<String, Object> data : chantiersData) {
|
for (Map<String, Object> data : chantiersData) {
|
||||||
Chantier c = new Chantier();
|
Chantier c = new Chantier();
|
||||||
|
|
||||||
// Mapping des données de l'API vers l'objet 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);
|
// Stocker l'UUID original comme String pour les opérations CRUD
|
||||||
c.setNom((String) data.get("nom"));
|
Object idObj = data.get("id");
|
||||||
|
if (idObj != null) {
|
||||||
// Le client peut être un objet ou une chaîne
|
String idString = idObj.toString();
|
||||||
Object clientObj = data.get("client");
|
// Stocker l'UUID comme String dans un champ caché, et utiliser hashCode pour l'affichage
|
||||||
if (clientObj instanceof Map) {
|
c.setId(Long.valueOf(idString.hashCode())); // Pour compatibilité avec l'interface existante
|
||||||
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
c.setUuidOriginal(idString); // Stocker l'UUID original
|
||||||
c.setClient((String) clientData.get("raisonSociale"));
|
}
|
||||||
} else if (clientObj instanceof String) {
|
c.setNom((String) data.get("nom"));
|
||||||
c.setClient((String) clientObj);
|
|
||||||
} else {
|
// Le client peut être un objet ou une chaîne
|
||||||
c.setClient("N/A");
|
Object clientObj = data.get("client");
|
||||||
}
|
if (clientObj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
c.setAdresse((String) data.get("adresse"));
|
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
||||||
|
c.setClient((String) clientData.get("raisonSociale"));
|
||||||
// Conversion des dates
|
// Extraire l'ID du client si disponible
|
||||||
if (data.get("dateDebut") != null) {
|
Object clientIdObj = clientData.get("id");
|
||||||
c.setDateDebut(LocalDate.parse(data.get("dateDebut").toString()));
|
if (clientIdObj != null) {
|
||||||
}
|
c.setClientId(clientIdObj.toString());
|
||||||
if (data.get("dateFinPrevue") != null) {
|
}
|
||||||
c.setDateFinPrevue(LocalDate.parse(data.get("dateFinPrevue").toString()));
|
} else if (clientObj instanceof String) {
|
||||||
}
|
c.setClient((String) clientObj);
|
||||||
|
} else {
|
||||||
c.setStatut((String) data.get("statut"));
|
c.setClient("N/A");
|
||||||
|
}
|
||||||
// Avancement en pourcentage
|
|
||||||
Object avancementObj = data.get("avancement");
|
// Vérifier aussi si clientId est directement dans les données
|
||||||
if (avancementObj != null) {
|
Object clientIdDirect = data.get("clientId");
|
||||||
c.setAvancement(avancementObj instanceof Integer ?
|
if (clientIdDirect != null && c.getClientId() == null) {
|
||||||
(Integer) avancementObj :
|
c.setClientId(clientIdDirect.toString());
|
||||||
Integer.parseInt(avancementObj.toString()));
|
}
|
||||||
} else {
|
|
||||||
c.setAvancement(0);
|
c.setAdresse((String) data.get("adresse"));
|
||||||
}
|
|
||||||
|
// Conversion des dates
|
||||||
// Budget et coût réel
|
if (data.get("dateDebut") != null) {
|
||||||
Object montantObj = data.get("montant");
|
c.setDateDebut(LocalDate.parse(data.get("dateDebut").toString()));
|
||||||
if (montantObj != null) {
|
}
|
||||||
c.setBudget(montantObj instanceof Number ?
|
if (data.get("dateFinPrevue") != null) {
|
||||||
((Number) montantObj).doubleValue() :
|
c.setDateFinPrevue(LocalDate.parse(data.get("dateFinPrevue").toString()));
|
||||||
Double.parseDouble(montantObj.toString()));
|
}
|
||||||
} else {
|
|
||||||
c.setBudget(0.0);
|
c.setStatut((String) data.get("statut"));
|
||||||
}
|
|
||||||
|
// Avancement en pourcentage
|
||||||
Object coutReelObj = data.get("coutReel");
|
Object avancementObj = data.get("avancement");
|
||||||
if (coutReelObj != null) {
|
if (avancementObj != null) {
|
||||||
c.setCoutReel(coutReelObj instanceof Number ?
|
c.setAvancement(avancementObj instanceof Integer ?
|
||||||
((Number) coutReelObj).doubleValue() :
|
(Integer) avancementObj :
|
||||||
Double.parseDouble(coutReelObj.toString()));
|
Integer.parseInt(avancementObj.toString()));
|
||||||
} else {
|
} else {
|
||||||
c.setCoutReel(0.0);
|
c.setAvancement(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(c);
|
// Budget et coût réel
|
||||||
}
|
Object montantObj = data.get("montant");
|
||||||
|
if (montantObj != null) {
|
||||||
LOG.info("Chantiers chargés depuis l'API : {} élément(s)", items.size());
|
c.setBudget(montantObj instanceof Number ?
|
||||||
applyFilters(items, buildFilters());
|
((Number) montantObj).doubleValue() :
|
||||||
} catch (Exception e) {
|
Double.parseDouble(montantObj.toString()));
|
||||||
LOG.error("Erreur chargement chantiers depuis l'API", e);
|
} else {
|
||||||
// En cas d'erreur, on garde une liste vide
|
c.setBudget(0.0);
|
||||||
items = new ArrayList<>();
|
}
|
||||||
} finally {
|
|
||||||
loading = false;
|
Object coutReelObj = data.get("coutReel");
|
||||||
}
|
if (coutReelObj != null) {
|
||||||
}
|
c.setCoutReel(coutReelObj instanceof Number ?
|
||||||
|
((Number) coutReelObj).doubleValue() :
|
||||||
private List<Predicate<Chantier>> buildFilters() {
|
Double.parseDouble(coutReelObj.toString()));
|
||||||
List<Predicate<Chantier>> filters = new ArrayList<>();
|
} else {
|
||||||
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
|
c.setCoutReel(0.0);
|
||||||
filters.add(c -> c.getNom().toLowerCase().contains(filtreNom.toLowerCase()));
|
}
|
||||||
}
|
|
||||||
if (filtreClient != null && !filtreClient.trim().isEmpty()) {
|
items.add(c);
|
||||||
filters.add(c -> c.getClient().toLowerCase().contains(filtreClient.toLowerCase()));
|
}
|
||||||
}
|
|
||||||
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
|
LOG.info("Chantiers chargés depuis l'API : {} élément(s)", items.size());
|
||||||
filters.add(c -> c.getStatut().equals(filtreStatut));
|
applyFilters(items, buildFilters());
|
||||||
}
|
} catch (Exception e) {
|
||||||
return filters;
|
LOG.error("Erreur chargement chantiers depuis l'API", e);
|
||||||
}
|
// En cas d'erreur, on garde une liste vide
|
||||||
|
items = new ArrayList<>();
|
||||||
@Override
|
} finally {
|
||||||
protected void resetFilterFields() {
|
loading = false;
|
||||||
filtreNom = null;
|
}
|
||||||
filtreClient = null;
|
}
|
||||||
filtreStatut = "TOUS";
|
|
||||||
}
|
private List<Predicate<Chantier>> buildFilters() {
|
||||||
|
List<Predicate<Chantier>> filters = new ArrayList<>();
|
||||||
@Override
|
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
|
||||||
protected String getDetailsPath() {
|
filters.add(c -> c.getNom().toLowerCase().contains(filtreNom.toLowerCase()));
|
||||||
return "/chantiers/";
|
}
|
||||||
}
|
if (filtreClient != null && !filtreClient.trim().isEmpty()) {
|
||||||
|
filters.add(c -> c.getClient().toLowerCase().contains(filtreClient.toLowerCase()));
|
||||||
@Override
|
}
|
||||||
protected String getCreatePath() {
|
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
|
||||||
return "/chantiers/nouveau";
|
filters.add(c -> c.getStatut().equals(filtreStatut));
|
||||||
}
|
}
|
||||||
|
return filters;
|
||||||
@Override
|
}
|
||||||
protected void performDelete() {
|
|
||||||
LOG.info("Suppression chantier : {}", selectedItem.getId());
|
@Override
|
||||||
// TODO: Appeler chantierService.delete(selectedItem.getId())
|
protected void resetFilterFields() {
|
||||||
}
|
filtreNom = null;
|
||||||
|
filtreClient = null;
|
||||||
@Override
|
filtreStatut = "TOUS";
|
||||||
protected Chantier createNewEntity() {
|
}
|
||||||
Chantier c = new Chantier();
|
|
||||||
c.setStatut("PLANIFIE");
|
@Override
|
||||||
c.setAvancement(0);
|
protected String getDetailsPath() {
|
||||||
c.setDateDebut(LocalDate.now());
|
return "/chantiers/";
|
||||||
c.setDateCreation(LocalDateTime.now());
|
}
|
||||||
return c;
|
|
||||||
}
|
@Override
|
||||||
|
protected String getCreatePath() {
|
||||||
@Override
|
return "/chantiers/nouveau";
|
||||||
protected void performCreate() {
|
}
|
||||||
entity.setId(System.currentTimeMillis()); // Simulation ID
|
|
||||||
entity.setDateCreation(LocalDateTime.now());
|
@Override
|
||||||
entity.setDateModification(LocalDateTime.now());
|
protected void performDelete() {
|
||||||
items.add(entity);
|
if (selectedItem == null || selectedItem.getId() == null) {
|
||||||
LOG.info("Nouveau chantier créé : {}", entity.getNom());
|
LOG.warn("Aucun chantier sélectionné pour la suppression");
|
||||||
// TODO: Appeler chantierService.create(entity)
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
LOG.info("Suppression chantier : {}", selectedItem.getId());
|
||||||
protected void performUpdate() {
|
|
||||||
entity.setDateModification(LocalDateTime.now());
|
// Convertir l'ID Long en String UUID (le backend utilise UUID)
|
||||||
LOG.info("Chantier modifié : {}", entity.getNom());
|
String idString = convertIdToString(selectedItem);
|
||||||
// TODO: Appeler chantierService.update(entity)
|
|
||||||
}
|
boolean success = chantierService.deleteChantier(idString, false);
|
||||||
|
if (success) {
|
||||||
@Override
|
LOG.info("Chantier supprimé avec succès : {}", idString);
|
||||||
protected Long getEntityId(Chantier chantier) {
|
// Recharger la liste après suppression
|
||||||
return chantier.getId();
|
loadItems();
|
||||||
}
|
} else {
|
||||||
|
LOG.error("Échec de la suppression du chantier : {}", idString);
|
||||||
/**
|
}
|
||||||
* Initialise un nouveau chantier pour la création.
|
}
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String createNew() {
|
protected Chantier createNewEntity() {
|
||||||
prepareNew();
|
Chantier c = new Chantier();
|
||||||
return getCreatePath() + "?faces-redirect=true";
|
c.setStatut("PLANIFIE");
|
||||||
}
|
c.setAvancement(0);
|
||||||
|
c.setDateDebut(LocalDate.now());
|
||||||
/**
|
c.setDateCreation(LocalDateTime.now());
|
||||||
* Sauvegarde un nouveau chantier.
|
return c;
|
||||||
*/
|
}
|
||||||
public String saveNew() {
|
|
||||||
if (selectedItem == null) {
|
@Override
|
||||||
selectedItem = new Chantier();
|
protected void performCreate() {
|
||||||
}
|
if (entity == null) {
|
||||||
selectedItem.setId(System.currentTimeMillis()); // Simulation ID
|
LOG.warn("Aucune entité à créer");
|
||||||
selectedItem.setDateCreation(LocalDateTime.now());
|
return;
|
||||||
selectedItem.setDateModification(LocalDateTime.now());
|
}
|
||||||
items.add(selectedItem);
|
|
||||||
LOG.info("Nouveau chantier créé : {}", selectedItem.getNom());
|
LOG.info("Création d'un nouveau chantier : {}", entity.getNom());
|
||||||
return "/chantiers?faces-redirect=true";
|
|
||||||
}
|
// Convertir l'entité Chantier en Map pour l'API
|
||||||
|
Map<String, Object> chantierData = convertChantierToMap(entity);
|
||||||
/**
|
|
||||||
* Charge un chantier par son ID depuis les paramètres de la requête.
|
Map<String, Object> createdChantier = chantierService.createChantier(chantierData);
|
||||||
*/
|
if (createdChantier != null) {
|
||||||
public void loadChantierById() {
|
LOG.info("Chantier créé avec succès : {}", createdChantier.get("id"));
|
||||||
if (chantierId != null) {
|
// Recharger la liste après création
|
||||||
loadItems(); // S'assurer que les items sont chargés
|
loadItems();
|
||||||
selectedItem = items.stream()
|
} else {
|
||||||
.filter(c -> c.getId().equals(chantierId))
|
LOG.error("Échec de la création du chantier : {}", entity.getNom());
|
||||||
.findFirst()
|
}
|
||||||
.orElse(null);
|
}
|
||||||
if (selectedItem == null) {
|
|
||||||
LOG.warn("Chantier avec ID {} non trouvé", chantierId);
|
@Override
|
||||||
}
|
protected void performUpdate() {
|
||||||
}
|
if (entity == null || entity.getId() == null) {
|
||||||
}
|
LOG.warn("Aucune entité à mettre à jour ou ID manquant");
|
||||||
|
return;
|
||||||
/**
|
}
|
||||||
* Affiche les détails d'un chantier.
|
|
||||||
*/
|
LOG.info("Mise à jour du chantier : {} (ID: {})", entity.getNom(), entity.getId());
|
||||||
public String viewDetails(Long id) {
|
|
||||||
selectedItem = items.stream()
|
// Convertir l'ID Long en String UUID
|
||||||
.filter(c -> c.getId().equals(id))
|
String idString = convertIdToString(entity);
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
// Convertir l'entité Chantier en Map pour l'API
|
||||||
if (selectedItem != null) {
|
Map<String, Object> chantierData = convertChantierToMap(entity);
|
||||||
return getDetailsPath() + "details?id=" + id + "&faces-redirect=true";
|
|
||||||
}
|
Map<String, Object> updatedChantier = chantierService.updateChantier(idString, chantierData);
|
||||||
return "/chantiers?faces-redirect=true";
|
if (updatedChantier != null) {
|
||||||
}
|
LOG.info("Chantier mis à jour avec succès : {}", idString);
|
||||||
|
// Recharger la liste après mise à jour
|
||||||
@lombok.Getter
|
loadItems();
|
||||||
@lombok.Setter
|
} else {
|
||||||
public static class Chantier {
|
LOG.error("Échec de la mise à jour du chantier : {}", idString);
|
||||||
private Long id;
|
}
|
||||||
private String nom;
|
}
|
||||||
private String client;
|
|
||||||
private String adresse;
|
@Override
|
||||||
private LocalDate dateDebut;
|
protected Long getEntityId(Chantier chantier) {
|
||||||
private LocalDate dateFinPrevue;
|
return chantier.getId();
|
||||||
private String statut;
|
}
|
||||||
private int avancement;
|
|
||||||
private double budget;
|
/**
|
||||||
private double coutReel;
|
* Initialise un nouveau chantier pour la création.
|
||||||
private LocalDateTime dateCreation;
|
*/
|
||||||
private LocalDateTime dateModification;
|
@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<String, Object> convertChantierToMap(Chantier chantier) {
|
||||||
|
Map<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,250 +1,249 @@
|
|||||||
package dev.lions.btpxpress.view;
|
package dev.lions.btpxpress.view;
|
||||||
|
|
||||||
import dev.lions.btpxpress.service.ClientService;
|
import dev.lions.btpxpress.service.ClientService;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.faces.view.ViewScoped;
|
import jakarta.faces.view.ViewScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Named;
|
import jakarta.inject.Named;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
||||||
import java.util.ArrayList;
|
import java.util.List;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
import java.util.Map;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
@Named("clientsView")
|
||||||
@Named("clientsView")
|
@ViewScoped
|
||||||
@ViewScoped
|
@Getter
|
||||||
@Getter
|
@Setter
|
||||||
@Setter
|
public class ClientsView extends BaseListView<ClientsView.Client, Long> {
|
||||||
public class ClientsView extends BaseListView<ClientsView.Client, Long> implements Serializable {
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ClientsView.class);
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClientsView.class);
|
|
||||||
|
@Inject
|
||||||
@Inject
|
ClientService clientService;
|
||||||
ClientService clientService;
|
|
||||||
|
private String filtreNom;
|
||||||
private String filtreNom;
|
private String filtreEmail;
|
||||||
private String filtreEmail;
|
private String filtreVille;
|
||||||
private String filtreVille;
|
private Long clientId;
|
||||||
private Long clientId;
|
|
||||||
|
@PostConstruct
|
||||||
@PostConstruct
|
public void init() {
|
||||||
public void init() {
|
loadItems();
|
||||||
loadItems();
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public void loadItems() {
|
||||||
public void loadItems() {
|
loading = true;
|
||||||
loading = true;
|
try {
|
||||||
try {
|
items = new ArrayList<>();
|
||||||
items = new ArrayList<>();
|
|
||||||
|
// Récupération depuis l'API backend
|
||||||
// Récupération depuis l'API backend
|
List<Map<String, Object>> clientsData = clientService.getAllClients();
|
||||||
List<Map<String, Object>> clientsData = clientService.getAllClients();
|
|
||||||
|
for (Map<String, Object> data : clientsData) {
|
||||||
for (Map<String, Object> data : clientsData) {
|
Client c = new Client();
|
||||||
Client c = new Client();
|
|
||||||
|
// Mapping des données de l'API vers l'objet 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);
|
||||||
c.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
|
|
||||||
|
// Raison sociale : entreprise ou "Particulier" si vide
|
||||||
// Raison sociale : entreprise ou "Particulier" si vide
|
String entreprise = (String) data.get("entreprise");
|
||||||
String entreprise = (String) data.get("entreprise");
|
c.setRaisonSociale(entreprise != null && !entreprise.trim().isEmpty() ?
|
||||||
c.setRaisonSociale(entreprise != null && !entreprise.trim().isEmpty() ?
|
entreprise : "Particulier");
|
||||||
entreprise : "Particulier");
|
|
||||||
|
// Nom complet du contact : prénom + nom
|
||||||
// Nom complet du contact : prénom + nom
|
String prenom = (String) data.get("prenom");
|
||||||
String prenom = (String) data.get("prenom");
|
String nom = (String) data.get("nom");
|
||||||
String nom = (String) data.get("nom");
|
c.setNomContact((prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
|
||||||
c.setNomContact((prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
|
|
||||||
|
c.setEmail((String) data.get("email"));
|
||||||
c.setEmail((String) data.get("email"));
|
c.setTelephone((String) data.get("telephone"));
|
||||||
c.setTelephone((String) data.get("telephone"));
|
c.setAdresse((String) data.get("adresse"));
|
||||||
c.setAdresse((String) data.get("adresse"));
|
c.setVille((String) data.get("ville"));
|
||||||
c.setVille((String) data.get("ville"));
|
c.setCodePostal((String) data.get("codePostal"));
|
||||||
c.setCodePostal((String) data.get("codePostal"));
|
|
||||||
|
// Nombre de chantiers (relation)
|
||||||
// Nombre de chantiers (relation)
|
Object chantiersObj = data.get("chantiers");
|
||||||
Object chantiersObj = data.get("chantiers");
|
if (chantiersObj instanceof List) {
|
||||||
if (chantiersObj instanceof List) {
|
c.setNombreChantiers(((List<?>) chantiersObj).size());
|
||||||
c.setNombreChantiers(((List<?>) chantiersObj).size());
|
} else {
|
||||||
} else {
|
c.setNombreChantiers(0);
|
||||||
c.setNombreChantiers(0);
|
}
|
||||||
}
|
|
||||||
|
// Chiffre d'affaires total (à calculer ou récupérer)
|
||||||
// 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
|
||||||
// Pour l'instant, on met 0 car cette donnée n'est pas dans l'API
|
c.setChiffreAffairesTotal(0.0);
|
||||||
c.setChiffreAffairesTotal(0.0);
|
|
||||||
|
// Date de création
|
||||||
// Date de création
|
if (data.get("dateCreation") != null) {
|
||||||
if (data.get("dateCreation") != null) {
|
c.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
|
||||||
c.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
|
}
|
||||||
}
|
|
||||||
|
// Date de modification
|
||||||
// Date de modification
|
if (data.get("dateModification") != null) {
|
||||||
if (data.get("dateModification") != null) {
|
c.setDateModification(LocalDateTime.parse(data.get("dateModification").toString()));
|
||||||
c.setDateModification(LocalDateTime.parse(data.get("dateModification").toString()));
|
}
|
||||||
}
|
|
||||||
|
items.add(c);
|
||||||
items.add(c);
|
}
|
||||||
}
|
|
||||||
|
LOG.info("Clients chargés depuis l'API : {} élément(s)", items.size());
|
||||||
LOG.info("Clients chargés depuis l'API : {} élément(s)", items.size());
|
applyFilters(items, buildFilters());
|
||||||
applyFilters(items, buildFilters());
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
LOG.error("Erreur chargement clients depuis l'API", e);
|
||||||
LOG.error("Erreur chargement clients depuis l'API", e);
|
// En cas d'erreur, on garde une liste vide
|
||||||
// En cas d'erreur, on garde une liste vide
|
items = new ArrayList<>();
|
||||||
items = new ArrayList<>();
|
} finally {
|
||||||
} finally {
|
loading = false;
|
||||||
loading = false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private List<Predicate<Client>> buildFilters() {
|
||||||
private List<Predicate<Client>> buildFilters() {
|
List<Predicate<Client>> filters = new ArrayList<>();
|
||||||
List<Predicate<Client>> filters = new ArrayList<>();
|
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
|
||||||
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
|
filters.add(c -> c.getRaisonSociale().toLowerCase().contains(filtreNom.toLowerCase()) ||
|
||||||
filters.add(c -> c.getRaisonSociale().toLowerCase().contains(filtreNom.toLowerCase()) ||
|
c.getNomContact().toLowerCase().contains(filtreNom.toLowerCase()));
|
||||||
c.getNomContact().toLowerCase().contains(filtreNom.toLowerCase()));
|
}
|
||||||
}
|
if (filtreEmail != null && !filtreEmail.trim().isEmpty()) {
|
||||||
if (filtreEmail != null && !filtreEmail.trim().isEmpty()) {
|
filters.add(c -> c.getEmail().toLowerCase().contains(filtreEmail.toLowerCase()));
|
||||||
filters.add(c -> c.getEmail().toLowerCase().contains(filtreEmail.toLowerCase()));
|
}
|
||||||
}
|
if (filtreVille != null && !filtreVille.trim().isEmpty()) {
|
||||||
if (filtreVille != null && !filtreVille.trim().isEmpty()) {
|
filters.add(c -> c.getVille().toLowerCase().contains(filtreVille.toLowerCase()));
|
||||||
filters.add(c -> c.getVille().toLowerCase().contains(filtreVille.toLowerCase()));
|
}
|
||||||
}
|
return filters;
|
||||||
return filters;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected void resetFilterFields() {
|
||||||
protected void resetFilterFields() {
|
filtreNom = null;
|
||||||
filtreNom = null;
|
filtreEmail = null;
|
||||||
filtreEmail = null;
|
filtreVille = null;
|
||||||
filtreVille = null;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected String getDetailsPath() {
|
||||||
protected String getDetailsPath() {
|
return "/clients/";
|
||||||
return "/clients/";
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected String getCreatePath() {
|
||||||
protected String getCreatePath() {
|
return "/clients/nouveau";
|
||||||
return "/clients/nouveau";
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected void performDelete() {
|
||||||
protected void performDelete() {
|
LOG.info("Suppression client : {}", selectedItem.getId());
|
||||||
LOG.info("Suppression client : {}", selectedItem.getId());
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected Client createNewEntity() {
|
||||||
protected Client createNewEntity() {
|
Client client = new Client();
|
||||||
Client client = new Client();
|
client.setDateCreation(LocalDateTime.now());
|
||||||
client.setDateCreation(LocalDateTime.now());
|
client.setDateModification(LocalDateTime.now());
|
||||||
client.setDateModification(LocalDateTime.now());
|
client.setNombreChantiers(0);
|
||||||
client.setNombreChantiers(0);
|
client.setChiffreAffairesTotal(0.0);
|
||||||
client.setChiffreAffairesTotal(0.0);
|
return client;
|
||||||
return client;
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected void performCreate() {
|
||||||
protected void performCreate() {
|
if (selectedItem.getId() == null) {
|
||||||
if (selectedItem.getId() == null) {
|
selectedItem.setId(System.currentTimeMillis());
|
||||||
selectedItem.setId(System.currentTimeMillis());
|
}
|
||||||
}
|
selectedItem.setDateCreation(LocalDateTime.now());
|
||||||
selectedItem.setDateCreation(LocalDateTime.now());
|
selectedItem.setDateModification(LocalDateTime.now());
|
||||||
selectedItem.setDateModification(LocalDateTime.now());
|
items.add(selectedItem);
|
||||||
items.add(selectedItem);
|
LOG.info("Created: {}", selectedItem);
|
||||||
LOG.info("Created: {}", selectedItem);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected void performUpdate() {
|
||||||
protected void performUpdate() {
|
selectedItem.setDateModification(LocalDateTime.now());
|
||||||
selectedItem.setDateModification(LocalDateTime.now());
|
LOG.info("Updated: {}", selectedItem);
|
||||||
LOG.info("Updated: {}", selectedItem);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
protected Long getEntityId(Client entity) {
|
||||||
protected Long getEntityId(Client entity) {
|
return entity.getId();
|
||||||
return entity.getId();
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Initialise un nouveau client pour la création.
|
||||||
* Initialise un nouveau client pour la création.
|
*/
|
||||||
*/
|
@Override
|
||||||
@Override
|
public String createNew() {
|
||||||
public String createNew() {
|
selectedItem = new Client();
|
||||||
selectedItem = new Client();
|
return getCreatePath() + "?faces-redirect=true";
|
||||||
return getCreatePath() + "?faces-redirect=true";
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Sauvegarde un nouveau client.
|
||||||
* Sauvegarde un nouveau client.
|
*/
|
||||||
*/
|
public String saveNew() {
|
||||||
public String saveNew() {
|
if (selectedItem == null) {
|
||||||
if (selectedItem == null) {
|
selectedItem = new Client();
|
||||||
selectedItem = new Client();
|
}
|
||||||
}
|
selectedItem.setId(System.currentTimeMillis());
|
||||||
selectedItem.setId(System.currentTimeMillis());
|
selectedItem.setDateCreation(LocalDateTime.now());
|
||||||
selectedItem.setDateCreation(LocalDateTime.now());
|
selectedItem.setDateModification(LocalDateTime.now());
|
||||||
selectedItem.setDateModification(LocalDateTime.now());
|
selectedItem.setNombreChantiers(0);
|
||||||
selectedItem.setNombreChantiers(0);
|
selectedItem.setChiffreAffairesTotal(0.0);
|
||||||
selectedItem.setChiffreAffairesTotal(0.0);
|
items.add(selectedItem);
|
||||||
items.add(selectedItem);
|
LOG.info("Nouveau client créé : {}", selectedItem.getRaisonSociale());
|
||||||
LOG.info("Nouveau client créé : {}", selectedItem.getRaisonSociale());
|
return "/clients?faces-redirect=true";
|
||||||
return "/clients?faces-redirect=true";
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Affiche les détails d'un client.
|
||||||
* Affiche les détails d'un client.
|
*/
|
||||||
*/
|
public String viewDetails(Long id) {
|
||||||
public String viewDetails(Long id) {
|
selectedItem = items.stream()
|
||||||
selectedItem = items.stream()
|
.filter(c -> c.getId().equals(id))
|
||||||
.filter(c -> c.getId().equals(id))
|
.findFirst()
|
||||||
.findFirst()
|
.orElse(null);
|
||||||
.orElse(null);
|
if (selectedItem != null) {
|
||||||
if (selectedItem != null) {
|
return getDetailsPath() + "details?id=" + id + "&faces-redirect=true";
|
||||||
return getDetailsPath() + "details?id=" + id + "&faces-redirect=true";
|
}
|
||||||
}
|
return "/clients?faces-redirect=true";
|
||||||
return "/clients?faces-redirect=true";
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Charge un client par son ID depuis les paramètres de la requête.
|
||||||
* Charge un client par son ID depuis les paramètres de la requête.
|
*/
|
||||||
*/
|
public void loadClientById() {
|
||||||
public void loadClientById() {
|
if (clientId != null) {
|
||||||
if (clientId != null) {
|
loadItems(); // S'assurer que les items sont chargés
|
||||||
loadItems(); // S'assurer que les items sont chargés
|
selectedItem = items.stream()
|
||||||
selectedItem = items.stream()
|
.filter(c -> c.getId().equals(clientId))
|
||||||
.filter(c -> c.getId().equals(clientId))
|
.findFirst()
|
||||||
.findFirst()
|
.orElse(null);
|
||||||
.orElse(null);
|
if (selectedItem == null) {
|
||||||
if (selectedItem == null) {
|
LOG.warn("Client avec ID {} non trouvé", clientId);
|
||||||
LOG.warn("Client avec ID {} non trouvé", clientId);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@lombok.Getter
|
||||||
@lombok.Getter
|
@lombok.Setter
|
||||||
@lombok.Setter
|
public static class Client {
|
||||||
public static class Client {
|
private Long id;
|
||||||
private Long id;
|
private String raisonSociale;
|
||||||
private String raisonSociale;
|
private String nomContact;
|
||||||
private String nomContact;
|
private String email;
|
||||||
private String email;
|
private String telephone;
|
||||||
private String telephone;
|
private String adresse;
|
||||||
private String adresse;
|
private String ville;
|
||||||
private String ville;
|
private String codePostal;
|
||||||
private String codePostal;
|
private String pays;
|
||||||
private String pays;
|
private int nombreChantiers;
|
||||||
private int nombreChantiers;
|
private double chiffreAffairesTotal;
|
||||||
private double chiffreAffairesTotal;
|
private LocalDateTime dateCreation;
|
||||||
private LocalDateTime dateCreation;
|
private LocalDateTime dateModification;
|
||||||
private LocalDateTime dateModification;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,8 +10,6 @@ import lombok.Setter;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -23,7 +21,7 @@ import java.util.function.Predicate;
|
|||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class DevisView extends BaseListView<DevisView.Devis, Long> implements Serializable {
|
public class DevisView extends BaseListView<DevisView.Devis, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DevisView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DevisView.class);
|
||||||
|
|
||||||
@@ -70,6 +68,7 @@ public class DevisView extends BaseListView<DevisView.Devis, Long> implements Se
|
|||||||
// Le client peut être un objet ou une chaîne
|
// Le client peut être un objet ou une chaîne
|
||||||
Object clientObj = data.get("client");
|
Object clientObj = data.get("client");
|
||||||
if (clientObj instanceof Map) {
|
if (clientObj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
||||||
String entreprise = (String) clientData.get("entreprise");
|
String entreprise = (String) clientData.get("entreprise");
|
||||||
String nom = (String) clientData.get("nom");
|
String nom = (String) clientData.get("nom");
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import lombok.Setter;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -22,7 +21,7 @@ import java.util.function.Predicate;
|
|||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class EmployeView extends BaseListView<EmployeView.Employe, Long> implements Serializable {
|
public class EmployeView extends BaseListView<EmployeView.Employe, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(EmployeView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(EmployeView.class);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import lombok.Setter;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -21,7 +20,7 @@ import java.util.function.Predicate;
|
|||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class EquipeView extends BaseListView<EquipeView.Equipe, Long> implements Serializable {
|
public class EquipeView extends BaseListView<EquipeView.Equipe, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(EquipeView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(EquipeView.class);
|
||||||
|
|
||||||
@@ -66,6 +65,7 @@ public class EquipeView extends BaseListView<EquipeView.Equipe, Long> implements
|
|||||||
// Chef d'équipe
|
// Chef d'équipe
|
||||||
Object chefObj = data.get("chef");
|
Object chefObj = data.get("chef");
|
||||||
if (chefObj instanceof Map) {
|
if (chefObj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> chefData = (Map<String, Object>) chefObj;
|
Map<String, Object> chefData = (Map<String, Object>) chefObj;
|
||||||
String prenom = (String) chefData.get("prenom");
|
String prenom = (String) chefData.get("prenom");
|
||||||
String nom = (String) chefData.get("nom");
|
String nom = (String) chefData.get("nom");
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import lombok.Setter;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -22,7 +21,7 @@ import java.util.function.Predicate;
|
|||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class FactureView extends BaseListView<FactureView.Facture, Long> implements Serializable {
|
public class FactureView extends BaseListView<FactureView.Facture, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FactureView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FactureView.class);
|
||||||
|
|
||||||
@@ -69,6 +68,7 @@ public class FactureView extends BaseListView<FactureView.Facture, Long> impleme
|
|||||||
// Le client peut être un objet ou une chaîne
|
// Le client peut être un objet ou une chaîne
|
||||||
Object clientObj = data.get("client");
|
Object clientObj = data.get("client");
|
||||||
if (clientObj instanceof Map) {
|
if (clientObj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
||||||
String entreprise = (String) clientData.get("entreprise");
|
String entreprise = (String) clientData.get("entreprise");
|
||||||
String nom = (String) clientData.get("nom");
|
String nom = (String) clientData.get("nom");
|
||||||
|
|||||||
@@ -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<ComponentTheme> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +1,53 @@
|
|||||||
package dev.lions.btpxpress.view;
|
package dev.lions.btpxpress.view;
|
||||||
|
|
||||||
import jakarta.enterprise.context.RequestScoped;
|
import jakarta.enterprise.context.RequestScoped;
|
||||||
import jakarta.faces.application.FacesMessage;
|
import jakarta.faces.application.FacesMessage;
|
||||||
import jakarta.faces.context.FacesContext;
|
import jakarta.faces.context.FacesContext;
|
||||||
import jakarta.inject.Named;
|
import jakarta.inject.Named;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@Named("loginView")
|
@Named("loginView")
|
||||||
@RequestScoped
|
@RequestScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class LoginView implements Serializable {
|
public class LoginView implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private boolean rememberMe = false;
|
private boolean rememberMe = false;
|
||||||
|
|
||||||
public String login() {
|
public String login() {
|
||||||
if (username == null || username.trim().isEmpty()) {
|
if (username == null || username.trim().isEmpty()) {
|
||||||
addErrorMessage("Le nom d'utilisateur est requis");
|
addErrorMessage("Le nom d'utilisateur est requis");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password == null || password.trim().isEmpty()) {
|
if (password == null || password.trim().isEmpty()) {
|
||||||
addErrorMessage("Le mot de passe est requis");
|
addErrorMessage("Le mot de passe est requis");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("admin".equals(username) && "admin".equals(password)) {
|
if ("admin".equals(username) && "admin".equals(password)) {
|
||||||
addInfoMessage("Connexion réussie !");
|
addInfoMessage("Connexion réussie !");
|
||||||
return "/dashboard?faces-redirect=true";
|
return "/dashboard?faces-redirect=true";
|
||||||
} else {
|
} else {
|
||||||
addErrorMessage("Nom d'utilisateur ou mot de passe incorrect");
|
addErrorMessage("Nom d'utilisateur ou mot de passe incorrect");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addErrorMessage(String message) {
|
private void addErrorMessage(String message) {
|
||||||
FacesContext.getCurrentInstance().addMessage(null,
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInfoMessage(String message) {
|
private void addInfoMessage(String message) {
|
||||||
FacesContext.getCurrentInstance().addMessage(null,
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
|
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import lombok.Setter;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -22,7 +21,7 @@ import java.util.function.Predicate;
|
|||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class MaterielView extends BaseListView<MaterielView.Materiel, Long> implements Serializable {
|
public class MaterielView extends BaseListView<MaterielView.Materiel, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MaterielView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MaterielView.class);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import lombok.Setter;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -21,7 +20,7 @@ import java.util.function.Predicate;
|
|||||||
@ViewScoped
|
@ViewScoped
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class StockView extends BaseListView<StockView.Stock, Long> implements Serializable {
|
public class StockView extends BaseListView<StockView.Stock, Long> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(StockView.class);
|
private static final Logger LOG = LoggerFactory.getLogger(StockView.class);
|
||||||
|
|
||||||
|
|||||||
@@ -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é.
|
|
||||||
*
|
|
||||||
* <p>Ce bean stocke les informations de session de l'utilisateur authentifié,
|
|
||||||
* telles que le nom, l'email, l'avatar, et les statistiques rapides.</p>
|
|
||||||
*
|
|
||||||
* @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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<faces-config version="4.0"
|
<faces-config version="4.0"
|
||||||
xmlns="https://jakarta.ee/xml/ns/jakartaee"
|
xmlns="https://jakarta.ee/xml/ns/jakartaee"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
|
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
|
||||||
https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd">
|
https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd">
|
||||||
|
|
||||||
<name>btpxpress_freya</name>
|
<name>btpxpress_freya</name>
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
<locale-config>
|
<locale-config>
|
||||||
<default-locale>fr</default-locale>
|
<default-locale>fr</default-locale>
|
||||||
<supported-locale>fr</supported-locale>
|
<supported-locale>fr</supported-locale>
|
||||||
<supported-locale>en</supported-locale>
|
<supported-locale>en</supported-locale>
|
||||||
</locale-config>
|
</locale-config>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<component>
|
<component>
|
||||||
<component-type>org.primefaces.component.FreyaMenu</component-type>
|
<component-type>org.primefaces.component.FreyaMenu</component-type>
|
||||||
<component-class>org.primefaces.freya.component.FreyaMenu</component-class>
|
<component-class>org.primefaces.freya.component.FreyaMenu</component-class>
|
||||||
</component>
|
</component>
|
||||||
|
|
||||||
<render-kit>
|
<render-kit>
|
||||||
<renderer>
|
<renderer>
|
||||||
<component-family>org.primefaces.component</component-family>
|
<component-family>org.primefaces.component</component-family>
|
||||||
<renderer-type>org.primefaces.component.FreyaMenuRenderer</renderer-type>
|
<renderer-type>org.primefaces.component.FreyaMenuRenderer</renderer-type>
|
||||||
<renderer-class>org.primefaces.freya.component.FreyaMenuRenderer</renderer-class>
|
<renderer-class>org.primefaces.freya.component.FreyaMenuRenderer</renderer-class>
|
||||||
</renderer>
|
</renderer>
|
||||||
</render-kit>
|
</render-kit>
|
||||||
|
|
||||||
</faces-config>
|
</faces-config>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<facelet-taglib xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
|
||||||
|
http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_3.xsd"
|
||||||
|
version="2.3">
|
||||||
|
|
||||||
|
<namespace>http://btpxpress.lions.dev/components</namespace>
|
||||||
|
<short-name>btpx</short-name>
|
||||||
|
<description>Composants réutilisables BTPXpress</description>
|
||||||
|
|
||||||
|
<!-- Composant Badge de Statut Facture -->
|
||||||
|
<tag>
|
||||||
|
<tag-name>facture-statut-badge</tag-name>
|
||||||
|
<description>Badge de statut pour les factures avec icône et couleur appropriées</description>
|
||||||
|
<source>components/facture-statut-badge.xhtml</source>
|
||||||
|
</tag>
|
||||||
|
|
||||||
|
<!-- Composant Affichage Montant -->
|
||||||
|
<tag>
|
||||||
|
<tag-name>montant-display</tag-name>
|
||||||
|
<description>Affichage formaté d'un montant avec devise et mise en évidence optionnelle</description>
|
||||||
|
<source>components/montant-display.xhtml</source>
|
||||||
|
</tag>
|
||||||
|
|
||||||
|
<!-- Composant Panel de Filtres -->
|
||||||
|
<tag>
|
||||||
|
<tag-name>search-filter-panel</tag-name>
|
||||||
|
<description>Panel de filtres de recherche réutilisable avec boutons d'action</description>
|
||||||
|
<source>components/search-filter-panel.xhtml</source>
|
||||||
|
</tag>
|
||||||
|
|
||||||
|
</facelet-taglib>
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
|
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||||
|
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
|
||||||
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
|
<cc:interface>
|
||||||
|
<cc:attribute name="statut" required="true" type="java.lang.String"
|
||||||
|
shortDescription="Le statut de la facture (BROUILLON, EMISE, PAYEE, etc.)"/>
|
||||||
|
<cc:attribute name="enRetard" type="java.lang.Boolean" default="false"
|
||||||
|
shortDescription="Indique si la facture est en retard"/>
|
||||||
|
<cc:attribute name="styleClass" type="java.lang.String" default=""
|
||||||
|
shortDescription="Classes CSS additionnelles"/>
|
||||||
|
</cc:interface>
|
||||||
|
|
||||||
|
<cc:implementation>
|
||||||
|
<p:tag value="#{cc.attrs.statut}"
|
||||||
|
styleClass="#{cc.attrs.styleClass}"
|
||||||
|
severity="#{cc.attrs.statut == 'PAYEE' ? 'success' :
|
||||||
|
(cc.attrs.statut == 'ANNULEE' ? 'danger' :
|
||||||
|
(cc.attrs.enRetard ? 'danger' :
|
||||||
|
(cc.attrs.statut == 'BROUILLON' ? 'secondary' : 'warning')))}"
|
||||||
|
icon="#{cc.attrs.statut == 'PAYEE' ? 'pi pi-check-circle' :
|
||||||
|
(cc.attrs.statut == 'ANNULEE' ? 'pi pi-times-circle' :
|
||||||
|
(cc.attrs.enRetard ? 'pi pi-exclamation-triangle' :
|
||||||
|
(cc.attrs.statut == 'BROUILLON' ? 'pi pi-file-edit' : 'pi pi-clock')))}"/>
|
||||||
|
</cc:implementation>
|
||||||
|
</ui:composition>
|
||||||
|
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui">
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value=""/>
|
<ui:param name="viewBean" value=""/>
|
||||||
<ui:param name="tableId" value=""/>
|
<ui:param name="tableId" value=""/>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5>Recherche et filtres</h5>
|
<h5>Recherche et filtres</h5>
|
||||||
<h:form id="#{formId}">
|
<h:form id="#{formId}">
|
||||||
<ui:insert name="filter-fields">
|
<ui:insert name="filter-fields">
|
||||||
<!-- Les champs de filtre spécifiques à chaque page -->
|
<!-- Les champs de filtre spécifiques à chaque page -->
|
||||||
</ui:insert>
|
</ui:insert>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p:commandButton value="Rechercher"
|
<p:commandButton value="Rechercher"
|
||||||
icon="pi pi-search"
|
icon="pi pi-search"
|
||||||
action="#{viewBean.search()}"
|
action="#{viewBean.search()}"
|
||||||
update="#{tableId}"
|
update="#{tableId}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
<p:commandButton value="Réinitialiser"
|
<p:commandButton value="Réinitialiser"
|
||||||
icon="pi pi-refresh"
|
icon="pi pi-refresh"
|
||||||
action="#{viewBean.resetFilters()}"
|
action="#{viewBean.resetFilters()}"
|
||||||
update="#{tableId} #{formId}"
|
update="#{tableId} #{formId}"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
</div>
|
</div>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui">
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h5>#{title}</h5>
|
<h5>#{title}</h5>
|
||||||
<p:commandButton value="Nouveau"
|
<p:commandButton value="Nouveau"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
action="#{viewBean.createNew()}"
|
action="#{viewBean.createNew()}"
|
||||||
styleClass="ui-button-primary"
|
styleClass="ui-button-primary"
|
||||||
rendered="#{not empty createPath}"/>
|
rendered="#{not empty createPath}"/>
|
||||||
</div>
|
</div>
|
||||||
<h:form id="#{formId}">
|
<h:form id="#{formId}">
|
||||||
<p:dataTable id="#{tableId}"
|
<p:dataTable id="#{tableId}"
|
||||||
value="#{viewBean.items}"
|
value="#{viewBean.items}"
|
||||||
var="#{var}"
|
var="#{var}"
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
paginator="true"
|
paginator="true"
|
||||||
rows="10"
|
rows="10"
|
||||||
rowsPerPageTemplate="10,20,50"
|
rowsPerPageTemplate="10,20,50"
|
||||||
emptyMessage="Aucun résultat trouvé"
|
emptyMessage="Aucun résultat trouvé"
|
||||||
loading="#{viewBean.loading}"
|
loading="#{viewBean.loading}"
|
||||||
selection="#{viewBean.selectedItem}"
|
selection="#{viewBean.selectedItem}"
|
||||||
selectionMode="single">
|
selectionMode="single">
|
||||||
<f:facet name="header">
|
<f:facet name="header">
|
||||||
<div class="flex align-items-center justify-content-between">
|
<div class="flex align-items-center justify-content-between">
|
||||||
<span>Total : #{viewBean.items.size()} élément(s)</span>
|
<span>Total : #{viewBean.items.size()} élément(s)</span>
|
||||||
<p:commandButton icon="pi pi-trash"
|
<p:commandButton icon="pi pi-trash"
|
||||||
title="Supprimer"
|
title="Supprimer"
|
||||||
disabled="#{empty viewBean.selectedItem}"
|
disabled="#{empty viewBean.selectedItem}"
|
||||||
action="#{viewBean.delete()}"
|
action="#{viewBean.delete()}"
|
||||||
update="#{tableId}"
|
update="#{tableId}"
|
||||||
styleClass="ui-button-danger ui-button-text"/>
|
styleClass="ui-button-danger ui-button-text"/>
|
||||||
</div>
|
</div>
|
||||||
</f:facet>
|
</f:facet>
|
||||||
<ui:insert name="columns"/>
|
<ui:insert name="columns"/>
|
||||||
</p:dataTable>
|
</p:dataTable>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
|
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||||
|
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
|
||||||
|
|
||||||
|
<cc:interface>
|
||||||
|
<cc:attribute name="montant" required="true" type="java.lang.Double"
|
||||||
|
shortDescription="Le montant à afficher"/>
|
||||||
|
<cc:attribute name="devise" default="Fcfa"
|
||||||
|
shortDescription="La devise (par défaut: Fcfa)"/>
|
||||||
|
<cc:attribute name="highlight" type="java.lang.Boolean" default="false"
|
||||||
|
shortDescription="Mettre en évidence le montant"/>
|
||||||
|
<cc:attribute name="highlightColor" default="red"
|
||||||
|
shortDescription="Couleur de mise en évidence"/>
|
||||||
|
<cc:attribute name="showIcon" type="java.lang.Boolean" default="false"
|
||||||
|
shortDescription="Afficher une icône de devise"/>
|
||||||
|
<cc:attribute name="styleClass" type="java.lang.String" default=""
|
||||||
|
shortDescription="Classes CSS additionnelles"/>
|
||||||
|
</cc:interface>
|
||||||
|
|
||||||
|
<cc:implementation>
|
||||||
|
<span class="#{cc.attrs.styleClass}"
|
||||||
|
style="#{cc.attrs.highlight ? 'color: ' + cc.attrs.highlightColor + '; font-weight: bold;' : ''}">
|
||||||
|
<i class="pi pi-money-bill mr-1"
|
||||||
|
rendered="#{cc.attrs.showIcon}"
|
||||||
|
style="#{cc.attrs.highlight ? 'color: ' + cc.attrs.highlightColor : ''}"></i>
|
||||||
|
<h:outputText value="#{cc.attrs.montant}">
|
||||||
|
<f:converter converterId="fcfaConverter"/>
|
||||||
|
</h:outputText>
|
||||||
|
<h:outputText value=" #{cc.attrs.devise}"/>
|
||||||
|
</span>
|
||||||
|
</cc:implementation>
|
||||||
|
</ui:composition>
|
||||||
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
|
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||||
|
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
|
||||||
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
|
<cc:interface>
|
||||||
|
<cc:attribute name="bean" required="true"
|
||||||
|
shortDescription="Le bean de vue contenant les méthodes de filtrage"/>
|
||||||
|
<cc:attribute name="tableId" required="true"
|
||||||
|
shortDescription="L'ID de la table à mettre à jour"/>
|
||||||
|
<cc:attribute name="title" default="Filtres de recherche"
|
||||||
|
shortDescription="Titre du panel de filtres"/>
|
||||||
|
<cc:attribute name="collapsed" type="java.lang.Boolean" default="false"
|
||||||
|
shortDescription="Panel replié par défaut"/>
|
||||||
|
<cc:facet name="filters" required="true"/>
|
||||||
|
</cc:interface>
|
||||||
|
|
||||||
|
<cc:implementation>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<p:panel id="filterPanel"
|
||||||
|
header="#{cc.attrs.title}"
|
||||||
|
toggleable="true"
|
||||||
|
collapsed="#{cc.attrs.collapsed}"
|
||||||
|
styleClass="filter-panel">
|
||||||
|
|
||||||
|
<f:facet name="icons">
|
||||||
|
<i class="pi pi-filter"></i>
|
||||||
|
</f:facet>
|
||||||
|
|
||||||
|
<div class="formgrid grid">
|
||||||
|
<cc:renderFacet name="filters"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 mt-3 justify-content-end">
|
||||||
|
<p:commandButton value="Rechercher"
|
||||||
|
icon="pi pi-search"
|
||||||
|
styleClass="ui-button-primary"
|
||||||
|
process="@this filterPanel"
|
||||||
|
update="#{cc.attrs.tableId} messages"
|
||||||
|
action="#{cc.attrs.bean.applyFilters}"/>
|
||||||
|
|
||||||
|
<p:commandButton value="Réinitialiser"
|
||||||
|
icon="pi pi-refresh"
|
||||||
|
styleClass="ui-button-secondary ui-button-outlined"
|
||||||
|
process="@this"
|
||||||
|
update="filterPanel #{cc.attrs.tableId} messages"
|
||||||
|
action="#{cc.attrs.bean.resetFilters}"/>
|
||||||
|
|
||||||
|
<p:commandButton value="Exporter"
|
||||||
|
icon="pi pi-download"
|
||||||
|
styleClass="ui-button-help ui-button-outlined"
|
||||||
|
rendered="#{cc.attrs.bean.exportEnabled}"
|
||||||
|
action="#{cc.attrs.bean.export}"/>
|
||||||
|
</div>
|
||||||
|
</p:panel>
|
||||||
|
</div>
|
||||||
|
</cc:implementation>
|
||||||
|
</ui:composition>
|
||||||
|
|
||||||
@@ -1,94 +1,94 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui">
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
<a href="#" id="layout-config-button" class="layout-config-button">
|
<a href="#" id="layout-config-button" class="layout-config-button">
|
||||||
<i class="pi pi-cog"/>
|
<i class="pi pi-cog"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div id="layout-config" class="layout-config">
|
<div id="layout-config" class="layout-config">
|
||||||
<h:form id="config-form" styleClass="layout-config-form">
|
<h:form id="config-form" styleClass="layout-config-form">
|
||||||
<h5 style="margin-top: 0">Type de Menu</h5>
|
<h5 style="margin-top: 0">Type de Menu</h5>
|
||||||
<p:selectOneRadio value="#{guestPreferences.menuMode}" layout="pageDirection"
|
<p:selectOneRadio value="#{guestPreferences.menuMode}" layout="pageDirection"
|
||||||
onchange="PrimeFaces.FreyaConfigurator.changeMenuMode(event.target.value)" >
|
onchange="PrimeFaces.FreyaConfigurator.changeMenuMode(event.target.value)" >
|
||||||
<f:selectItem itemLabel="Horizontal" itemValue="layout-horizontal" />
|
<f:selectItem itemLabel="Horizontal" itemValue="layout-horizontal" />
|
||||||
<f:selectItem itemLabel="Sidebar" itemValue="layout-sidebar" />
|
<f:selectItem itemLabel="Sidebar" itemValue="layout-sidebar" />
|
||||||
<f:selectItem itemLabel="Slim" itemValue="layout-slim" />
|
<f:selectItem itemLabel="Slim" itemValue="layout-slim" />
|
||||||
<p:ajax listener="#{guestPreferences.onMenuTypeChange}" update="config-form" />
|
<p:ajax listener="#{guestPreferences.onMenuTypeChange}" update="config-form" />
|
||||||
</p:selectOneRadio>
|
</p:selectOneRadio>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<h5>Schéma de Couleurs</h5>
|
<h5>Schéma de Couleurs</h5>
|
||||||
<p:selectOneRadio value="#{guestPreferences.darkMode}" layout="pageDirection"
|
<p:selectOneRadio value="#{guestPreferences.darkMode}" layout="pageDirection"
|
||||||
onchange="PrimeFaces.FreyaConfigurator.changeLayout('#{guestPreferences.componentTheme}', event.target.value)" >
|
onchange="PrimeFaces.FreyaConfigurator.changeLayout('#{guestPreferences.componentTheme}', event.target.value)" >
|
||||||
<f:selectItem itemLabel="Clair" itemValue="light" />
|
<f:selectItem itemLabel="Clair" itemValue="light" />
|
||||||
<f:selectItem itemLabel="Sombre" itemValue="dark" />
|
<f:selectItem itemLabel="Sombre" itemValue="dark" />
|
||||||
<p:ajax onstart="PrimeFaces.FreyaConfigurator.beforeResourceChange()" update="config-form logolink"/>
|
<p:ajax onstart="PrimeFaces.FreyaConfigurator.beforeResourceChange()" update="config-form logolink"/>
|
||||||
</p:selectOneRadio>
|
</p:selectOneRadio>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{guestPreferences.menuMode eq 'layout-horizontal'}">
|
<p:outputPanel rendered="#{guestPreferences.menuMode eq 'layout-horizontal'}">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h5>Mode Topbar et Menu</h5>
|
<h5>Mode Topbar et Menu</h5>
|
||||||
<p:selectOneRadio value="#{guestPreferences.topbarTheme}" layout="pageDirection"
|
<p:selectOneRadio value="#{guestPreferences.topbarTheme}" layout="pageDirection"
|
||||||
onchange="PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-topbar');PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-menu')" >
|
onchange="PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-topbar');PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-menu')" >
|
||||||
<f:selectItem itemLabel="Clair" itemValue="light" itemDisabled="#{guestPreferences.darkMode != 'light'}" />
|
<f:selectItem itemLabel="Clair" itemValue="light" itemDisabled="#{guestPreferences.darkMode != 'light'}" />
|
||||||
<f:selectItem itemLabel="Sombre" itemValue="dark" itemDisabled="#{guestPreferences.darkMode != 'light'}"/>
|
<f:selectItem itemLabel="Sombre" itemValue="dark" itemDisabled="#{guestPreferences.darkMode != 'light'}"/>
|
||||||
<p:ajax update="logolink config-form"/>
|
<p:ajax update="logolink config-form"/>
|
||||||
</p:selectOneRadio>
|
</p:selectOneRadio>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{guestPreferences.menuMode != 'layout-horizontal'}">
|
<p:outputPanel rendered="#{guestPreferences.menuMode != 'layout-horizontal'}">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h5>Mode Topbar</h5>
|
<h5>Mode Topbar</h5>
|
||||||
<p:selectOneRadio value="#{guestPreferences.topbarTheme}" layout="pageDirection"
|
<p:selectOneRadio value="#{guestPreferences.topbarTheme}" layout="pageDirection"
|
||||||
onchange="PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-topbar')" >
|
onchange="PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-topbar')" >
|
||||||
<f:selectItem itemLabel="Clair" itemValue="light" itemDisabled="#{guestPreferences.darkMode != 'light'}" />
|
<f:selectItem itemLabel="Clair" itemValue="light" itemDisabled="#{guestPreferences.darkMode != 'light'}" />
|
||||||
<f:selectItem itemLabel="Sombre" itemValue="dark" itemDisabled="#{guestPreferences.darkMode != 'light'}"/>
|
<f:selectItem itemLabel="Sombre" itemValue="dark" itemDisabled="#{guestPreferences.darkMode != 'light'}"/>
|
||||||
<p:ajax update="logolink config-form"/>
|
<p:ajax update="logolink config-form"/>
|
||||||
</p:selectOneRadio>
|
</p:selectOneRadio>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{guestPreferences.menuMode != 'layout-horizontal'}">
|
<p:outputPanel rendered="#{guestPreferences.menuMode != 'layout-horizontal'}">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h5>Mode Menu</h5>
|
<h5>Mode Menu</h5>
|
||||||
<p:selectOneRadio value="#{guestPreferences.menuTheme}" layout="pageDirection"
|
<p:selectOneRadio value="#{guestPreferences.menuTheme}" layout="pageDirection"
|
||||||
onchange="PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-menu')" >
|
onchange="PrimeFaces.FreyaConfigurator.changeSectionTheme(event.target.value , 'layout-menu')" >
|
||||||
<f:selectItem itemLabel="Clair" itemValue="light" itemDisabled="#{guestPreferences.darkMode != 'light'}" />
|
<f:selectItem itemLabel="Clair" itemValue="light" itemDisabled="#{guestPreferences.darkMode != 'light'}" />
|
||||||
<f:selectItem itemLabel="Sombre" itemValue="dark" itemDisabled="#{guestPreferences.darkMode != 'light'}"/>
|
<f:selectItem itemLabel="Sombre" itemValue="dark" itemDisabled="#{guestPreferences.darkMode != 'light'}"/>
|
||||||
<p:ajax update="logolink config-form"/>
|
<p:ajax update="logolink config-form"/>
|
||||||
</p:selectOneRadio>
|
</p:selectOneRadio>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<h5>Style d'Input</h5>
|
<h5>Style d'Input</h5>
|
||||||
<p:selectOneRadio value="#{guestPreferences.inputStyle}" layout="pageDirection"
|
<p:selectOneRadio value="#{guestPreferences.inputStyle}" layout="pageDirection"
|
||||||
onchange="PrimeFaces.FreyaConfigurator.updateInputStyle(event.target.value)">
|
onchange="PrimeFaces.FreyaConfigurator.updateInputStyle(event.target.value)">
|
||||||
<f:selectItem itemLabel="Outlined" itemValue="outlined" />
|
<f:selectItem itemLabel="Outlined" itemValue="outlined" />
|
||||||
<f:selectItem itemLabel="Filled" itemValue="filled" />
|
<f:selectItem itemLabel="Filled" itemValue="filled" />
|
||||||
<p:ajax />
|
<p:ajax />
|
||||||
</p:selectOneRadio>
|
</p:selectOneRadio>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<h5>Couleurs du Thème</h5>
|
<h5>Couleurs du Thème</h5>
|
||||||
<div class="layout-themes">
|
<div class="layout-themes">
|
||||||
<ui:repeat value="#{guestPreferences.componentThemes}" var="componentTheme">
|
<ui:repeat value="#{guestPreferences.componentThemes}" var="componentTheme">
|
||||||
<div>
|
<div>
|
||||||
<p:commandLink actionListener="#{guestPreferences.setComponentTheme(componentTheme.file)}"
|
<p:commandLink actionListener="#{guestPreferences.setComponentTheme(componentTheme.file)}"
|
||||||
style="background-color: #{componentTheme.color};" title="#{componentTheme.name}"
|
style="background-color: #{componentTheme.color};" title="#{componentTheme.name}"
|
||||||
process="@this"
|
process="@this"
|
||||||
update="config-form"
|
update="config-form"
|
||||||
onstart="PrimeFaces.FreyaConfigurator.beforeResourceChange()"
|
onstart="PrimeFaces.FreyaConfigurator.beforeResourceChange()"
|
||||||
oncomplete="PrimeFaces.FreyaConfigurator.changeComponentsTheme('#{componentTheme.file}', '#{guestPreferences.darkMode}')">
|
oncomplete="PrimeFaces.FreyaConfigurator.changeComponentsTheme('#{componentTheme.file}', '#{guestPreferences.darkMode}')">
|
||||||
</p:commandLink>
|
</p:commandLink>
|
||||||
</div>
|
</div>
|
||||||
</ui:repeat>
|
</ui:repeat>
|
||||||
</div>
|
</div>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +1,65 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui">
|
xmlns:p="http://primefaces.org/ui">
|
||||||
|
|
||||||
<div class="layout-rightpanel">
|
<div class="layout-rightpanel">
|
||||||
<div class="rightpanel-wrapper">
|
<div class="rightpanel-wrapper">
|
||||||
<div class="rightpanel-section tasks-section">
|
<div class="rightpanel-section tasks-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h6>Mes Tâches</h6>
|
<h6>Mes Tâches</h6>
|
||||||
<h:form>
|
<h:form>
|
||||||
<p:commandButton type="button" icon="pi pi-plus" styleClass="ui-button-secondary ui-button-flat rounded-button" />
|
<p:commandButton type="button" icon="pi pi-plus" styleClass="ui-button-secondary ui-button-flat rounded-button" />
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<div class="task-info">
|
<div class="task-info">
|
||||||
<h6>Réviser le devis pour le chantier A</h6>
|
<h6>Réviser le devis pour le chantier A</h6>
|
||||||
<span>-Validation budgétaire</span>
|
<span>-Validation budgétaire</span>
|
||||||
<span>-Vérification matériaux</span>
|
<span>-Vérification matériaux</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="task-info">
|
<div class="task-info">
|
||||||
<h6>Planifier la maintenance préventive</h6>
|
<h6>Planifier la maintenance préventive</h6>
|
||||||
<span>Matériel : Pelleteuse BX-2024</span>
|
<span>Matériel : Pelleteuse BX-2024</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="done">
|
<li class="done">
|
||||||
<div class="task-info">
|
<div class="task-info">
|
||||||
<h6>Finaliser le rapport hebdomadaire</h6>
|
<h6>Finaliser le rapport hebdomadaire</h6>
|
||||||
</div>
|
</div>
|
||||||
<i class="pi pi-check"></i>
|
<i class="pi pi-check"></i>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rightpanel-section favorites-section">
|
<div class="rightpanel-section favorites-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h6>Favoris</h6>
|
<h6>Favoris</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="favorite-items">
|
<div class="favorite-items">
|
||||||
<a href="dashboard.xhtml" class="favorite-item">
|
<a href="dashboard.xhtml" class="favorite-item">
|
||||||
<i class="pi pi-home" style="font-size: 1.5rem;"></i>
|
<i class="pi pi-home" style="font-size: 1.5rem;"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="chantiers.xhtml" class="favorite-item">
|
<a href="chantiers.xhtml" class="favorite-item">
|
||||||
<i class="pi pi-building" style="font-size: 1.5rem;"></i>
|
<i class="pi pi-building" style="font-size: 1.5rem;"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="clients.xhtml" class="favorite-item">
|
<a href="clients.xhtml" class="favorite-item">
|
||||||
<i class="pi pi-users" style="font-size: 1.5rem;"></i>
|
<i class="pi pi-users" style="font-size: 1.5rem;"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="rapports.xhtml" class="favorite-item">
|
<a href="rapports.xhtml" class="favorite-item">
|
||||||
<i class="pi pi-chart-bar" style="font-size: 1.5rem;"></i>
|
<i class="pi pi-chart-bar" style="font-size: 1.5rem;"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="add-item">
|
<a href="#" class="add-item">
|
||||||
<i class="pi pi-plus"></i>
|
<i class="pi pi-plus"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,11 @@
|
|||||||
<div class="layout-content">
|
<div class="layout-content">
|
||||||
<ui:insert name="content"/>
|
<ui:insert name="content"/>
|
||||||
</div>
|
</div>
|
||||||
<ui:include src="./footer.xhtml"/>
|
<!-- Footer conditionnel : désactivé par défaut pour application métier -->
|
||||||
|
<!-- Pour l'activer sur une page spécifique, ajouter : <ui:param name="showFooter" value="true"/> -->
|
||||||
|
<ui:fragment rendered="#{showFooter == true}">
|
||||||
|
<ui:include src="./footer.xhtml"/>
|
||||||
|
</ui:fragment>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p:ajaxStatus style="width:32px;height:32px;position:fixed;right:7px;bottom:7px">
|
<p:ajaxStatus style="width:32px;height:32px;position:fixed;right:7px;bottom:7px">
|
||||||
|
|||||||
@@ -1,130 +1,130 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
xmlns:po="http://primefaces.org/freya">
|
xmlns:po="http://primefaces.org/freya">
|
||||||
|
|
||||||
<div class="layout-topbar">
|
<div class="layout-topbar">
|
||||||
<div class="layout-topbar-wrapper">
|
<div class="layout-topbar-wrapper">
|
||||||
<div class="layout-topbar-left">
|
<div class="layout-topbar-left">
|
||||||
<a href="#" class="menu-button">
|
<a href="#" class="menu-button">
|
||||||
<i class="pi pi-bars"/>
|
<i class="pi pi-bars"/>
|
||||||
</a>
|
</a>
|
||||||
<h:link id="logolink" outcome="/dashboard" styleClass="layout-topbar-logo">
|
<h:link id="logolink" outcome="/dashboard" styleClass="layout-topbar-logo">
|
||||||
<p:graphicImage name="images/#{ guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}" library="freya-layout" />
|
<p:graphicImage name="images/#{ guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}" library="freya-layout" />
|
||||||
</h:link>
|
</h:link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui:include src="./menu.xhtml" />
|
<ui:include src="./menu.xhtml" />
|
||||||
|
|
||||||
<div class="layout-topbar-right">
|
<div class="layout-topbar-right">
|
||||||
<ul class="layout-topbar-actions">
|
<ul class="layout-topbar-actions">
|
||||||
<li class="topbar-item search-item">
|
<li class="topbar-item search-item">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<i class="topbar-icon pi pi-search"/>
|
<i class="topbar-icon pi pi-search"/>
|
||||||
</a>
|
</a>
|
||||||
<h:form>
|
<h:form>
|
||||||
<h:panelGroup styleClass="search-input-wrapper">
|
<h:panelGroup styleClass="search-input-wrapper">
|
||||||
<p:inputText placeholder="Search..." />
|
<p:inputText placeholder="Search..." />
|
||||||
<i class="pi pi-search"/>
|
<i class="pi pi-search"/>
|
||||||
</h:panelGroup>
|
</h:panelGroup>
|
||||||
</h:form>
|
</h:form>
|
||||||
<ul>
|
<ul>
|
||||||
<h:form onsubmit="return false;">
|
<h:form onsubmit="return false;">
|
||||||
<h:panelGroup styleClass="search-input-wrapper">
|
<h:panelGroup styleClass="search-input-wrapper">
|
||||||
<p:inputText placeholder="Search..." />
|
<p:inputText placeholder="Search..." />
|
||||||
<i class="pi pi-search"/>
|
<i class="pi pi-search"/>
|
||||||
</h:panelGroup>
|
</h:panelGroup>
|
||||||
</h:form>
|
</h:form>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="topbar-item notifications-item">
|
<li class="topbar-item notifications-item">
|
||||||
<a href="/notifications">
|
<a href="/notifications">
|
||||||
<i class="topbar-icon pi pi-bell"/>
|
<i class="topbar-icon pi pi-bell"/>
|
||||||
<p:outputPanel rendered="#{userSession.nombreNotificationsNonLues > 0}">
|
<p:outputPanel rendered="#{userSession.nombreNotificationsNonLues > 0}">
|
||||||
<span class="ui-badge">#{userSession.nombreNotificationsNonLues}</span>
|
<span class="ui-badge">#{userSession.nombreNotificationsNonLues}</span>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="topbar-item messages-item">
|
<li class="topbar-item messages-item">
|
||||||
<a href="/messages">
|
<a href="/messages">
|
||||||
<i class="topbar-icon pi pi-envelope"/>
|
<i class="topbar-icon pi pi-envelope"/>
|
||||||
<p:outputPanel rendered="#{userSession.nombreMessagesNonLus > 0}">
|
<p:outputPanel rendered="#{userSession.nombreMessagesNonLus > 0}">
|
||||||
<span class="ui-badge">#{userSession.nombreMessagesNonLus}</span>
|
<span class="ui-badge">#{userSession.nombreMessagesNonLus}</span>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="topbar-item user-profile">
|
<li class="topbar-item user-profile">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<p:graphicImage name="images/avatar-profilemenu.png" library="freya-layout" />
|
<p:graphicImage name="images/avatar-profilemenu.png" library="freya-layout" />
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="user-profile-header">
|
<li class="user-profile-header">
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<p:graphicImage name="images/avatar-profilemenu.png" library="freya-layout" styleClass="profile-avatar-small" />
|
<p:graphicImage name="images/avatar-profilemenu.png" library="freya-layout" styleClass="profile-avatar-small" />
|
||||||
<div class="user-details">
|
<div class="user-details">
|
||||||
<span class="user-name">#{userSession.nomComplet}</span>
|
<span class="user-name">#{userSession.nomComplet}</span>
|
||||||
<span class="user-role">#{userSession.role}</span>
|
<span class="user-role">#{userSession.role}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="user-profile-divider">
|
<li class="user-profile-divider">
|
||||||
<hr/>
|
<hr/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/profile">
|
<a href="/profile">
|
||||||
<i class="pi pi-user"></i>
|
<i class="pi pi-user"></i>
|
||||||
<span>Profile</span>
|
<span>Profile</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<i class="pi pi-cog"></i>
|
<i class="pi pi-cog"></i>
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/messages">
|
<a href="/messages">
|
||||||
<i class="pi pi-envelope"></i>
|
<i class="pi pi-envelope"></i>
|
||||||
<span>Messages</span>
|
<span>Messages</span>
|
||||||
<p:outputPanel rendered="#{userSession.nombreMessagesNonLus > 0}">
|
<p:outputPanel rendered="#{userSession.nombreMessagesNonLus > 0}">
|
||||||
<span class="ui-badge">#{userSession.nombreMessagesNonLus}</span>
|
<span class="ui-badge">#{userSession.nombreMessagesNonLus}</span>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/notifications">
|
<a href="/notifications">
|
||||||
<i class="pi pi-bell"></i>
|
<i class="pi pi-bell"></i>
|
||||||
<span>Notifications</span>
|
<span>Notifications</span>
|
||||||
<p:outputPanel rendered="#{userSession.nombreNotificationsNonLues > 0}">
|
<p:outputPanel rendered="#{userSession.nombreNotificationsNonLues > 0}">
|
||||||
<span class="ui-badge">#{userSession.nombreNotificationsNonLues}</span>
|
<span class="ui-badge">#{userSession.nombreNotificationsNonLues}</span>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="user-profile-divider">
|
<li class="user-profile-divider">
|
||||||
<hr/>
|
<hr/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<h:form>
|
<h:form>
|
||||||
<p:commandButton action="#{userSession.deconnecter()}"
|
<p:commandButton action="#{userSession.deconnecter()}"
|
||||||
value="Logout"
|
value="Logout"
|
||||||
styleClass="logout-link p-button-text"
|
styleClass="logout-link p-button-text"
|
||||||
ajax="false"
|
ajax="false"
|
||||||
icon="pi pi-sign-out"/>
|
icon="pi pi-sign-out"/>
|
||||||
</h:form>
|
</h:form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<a href="#" class="layout-rightpanel-button">
|
<a href="#" class="layout-rightpanel-button">
|
||||||
<i class="pi pi-arrow-left"/>
|
<i class="pi pi-arrow-left"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Bons de commande - BTP Xpress</ui:define>
|
<ui:define name="title">Bons de commande - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Bons de commande</h6>
|
<h6>Bons de commande</h6>
|
||||||
<p class="subtitle">Gestion des bons de commande</p>
|
<p class="subtitle">Gestion des bons de commande</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Budgets - BTP Xpress</ui:define>
|
<ui:define name="title">Budgets - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Budgets</h6>
|
<h6>Budgets</h6>
|
||||||
<p class="subtitle">Gestion des budgets</p>
|
<p class="subtitle">Gestion des budgets</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,103 +1,103 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Chantiers - BTP Xpress</ui:define>
|
<ui:define name="title">Chantiers - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Chantiers</h1>
|
<h1>Gestion des Chantiers</h1>
|
||||||
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
||||||
action="#{chantiersView.createNew()}"
|
action="#{chantiersView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
||||||
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreClient" value="Client"/>
|
<h:outputLabel for="filtreClient" value="Client"/>
|
||||||
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
||||||
placeholder="Rechercher par client..." style="width: 100%;"/>
|
placeholder="Rechercher par client..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreStatut" value="Statut"/>
|
<h:outputLabel for="filtreStatut" value="Statut"/>
|
||||||
<p:selectOneMenu id="filtreStatut" value="#{chantiersView.filtreStatut}" style="width: 100%;">
|
<p:selectOneMenu id="filtreStatut" value="#{chantiersView.filtreStatut}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="En cours" itemValue="EN_COURS"/>
|
<f:selectItem itemLabel="En cours" itemValue="EN_COURS"/>
|
||||||
<f:selectItem itemLabel="Terminés" itemValue="TERMINE"/>
|
<f:selectItem itemLabel="Terminés" itemValue="TERMINE"/>
|
||||||
<f:selectItem itemLabel="Planifiés" itemValue="PLANIFIE"/>
|
<f:selectItem itemLabel="Planifiés" itemValue="PLANIFIE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="chantiersForm"/>
|
<ui:param name="formId" value="chantiersForm"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="var" value="chantier"/>
|
<ui:param name="var" value="chantier"/>
|
||||||
<ui:param name="title" value="Liste des chantiers"/>
|
<ui:param name="title" value="Liste des chantiers"/>
|
||||||
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
||||||
<h:outputText value="#{chantier.nom}"/>
|
<h:outputText value="#{chantier.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Client" sortBy="#{chantier.client}">
|
<p:column headerText="Client" sortBy="#{chantier.client}">
|
||||||
<h:outputText value="#{chantier.client}"/>
|
<h:outputText value="#{chantier.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Adresse">
|
<p:column headerText="Adresse">
|
||||||
<h:outputText value="#{chantier.adresse}"/>
|
<h:outputText value="#{chantier.adresse}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
||||||
<h:outputText value="#{chantier.dateDebut}">
|
<h:outputText value="#{chantier.dateDebut}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Statut" sortBy="#{chantier.statut}">
|
<p:column headerText="Statut" sortBy="#{chantier.statut}">
|
||||||
<p:tag value="#{chantier.statut}"
|
<p:tag value="#{chantier.statut}"
|
||||||
severity="#{chantier.statut == 'TERMINE' ? 'success' : (chantier.statut == 'EN_COURS' ? 'info' : 'warning')}"/>
|
severity="#{chantier.statut == 'TERMINE' ? 'success' : (chantier.statut == 'EN_COURS' ? 'info' : 'warning')}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Avancement">
|
<p:column headerText="Avancement">
|
||||||
<p:progressBar value="#{chantier.avancement}" showValue="true"
|
<p:progressBar value="#{chantier.avancement}" showValue="true"
|
||||||
styleClass="ui-progressbar-success"/>
|
styleClass="ui-progressbar-success"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Budget">
|
<p:column headerText="Budget">
|
||||||
<h:outputText value="#{chantier.budget}">
|
<h:outputText value="#{chantier.budget}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa" style="margin-left: 4px;"/>
|
<h:outputText value=" Fcfa" style="margin-left: 4px;"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,307 +1,307 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Détails du chantier - BTP Xpress</ui:define>
|
<ui:define name="title">Détails du chantier - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<f:metadata>
|
<f:metadata>
|
||||||
<f:viewParam name="id" value="#{chantiersView.chantierId}"/>
|
<f:viewParam name="id" value="#{chantiersView.chantierId}"/>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.loadChantierById()}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.loadChantierById()}"/>
|
||||||
</f:metadata>
|
</f:metadata>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<!-- En-tête avec actions -->
|
<!-- En-tête avec actions -->
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="flex align-items-start justify-content-between flex-wrap gap-3">
|
<div class="flex align-items-start justify-content-between flex-wrap gap-3">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<div class="flex align-items-center gap-3 mb-2">
|
<div class="flex align-items-center gap-3 mb-2">
|
||||||
<h2 class="text-900 font-bold m-0">#{chantiersView.selectedItem.nom}</h2>
|
<h2 class="text-900 font-bold m-0">#{chantiersView.selectedItem.nom}</h2>
|
||||||
<ui:include src="/WEB-INF/components/status-badge.xhtml">
|
<ui:include src="/WEB-INF/components/status-badge.xhtml">
|
||||||
<ui:param name="value" value="#{chantiersView.selectedItem.statut}"/>
|
<ui:param name="value" value="#{chantiersView.selectedItem.statut}"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-600 mt-0 mb-2">
|
<p class="text-600 mt-0 mb-2">
|
||||||
<i class="pi pi-building mr-2"></i>#{chantiersView.selectedItem.client}
|
<i class="pi pi-building mr-2"></i>#{chantiersView.selectedItem.client}
|
||||||
<span class="mx-2">•</span>
|
<span class="mx-2">•</span>
|
||||||
<i class="pi pi-map-marker mr-2"></i>#{chantiersView.selectedItem.adresse}
|
<i class="pi pi-map-marker mr-2"></i>#{chantiersView.selectedItem.adresse}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex align-items-center gap-3 text-sm">
|
<div class="flex align-items-center gap-3 text-sm">
|
||||||
<span class="text-600">
|
<span class="text-600">
|
||||||
<i class="pi pi-calendar mr-1"></i>
|
<i class="pi pi-calendar mr-1"></i>
|
||||||
Début: <h:outputText value="#{chantiersView.selectedItem.dateDebut}">
|
Début: <h:outputText value="#{chantiersView.selectedItem.dateDebut}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-600">
|
<span class="text-600">
|
||||||
<i class="pi pi-calendar-times mr-1"></i>
|
<i class="pi pi-calendar-times mr-1"></i>
|
||||||
Fin prévue: <h:outputText value="#{chantiersView.selectedItem.dateFinPrevue}">
|
Fin prévue: <h:outputText value="#{chantiersView.selectedItem.dateFinPrevue}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<p:commandButton value="Retour"
|
<p:commandButton value="Retour"
|
||||||
icon="pi pi-arrow-left"
|
icon="pi pi-arrow-left"
|
||||||
outcome="/chantiers"
|
outcome="/chantiers"
|
||||||
styleClass="ui-button-secondary ui-button-outlined"/>
|
styleClass="ui-button-secondary ui-button-outlined"/>
|
||||||
<p:splitButton value="Modifier"
|
<p:splitButton value="Modifier"
|
||||||
icon="pi pi-pencil"
|
icon="pi pi-pencil"
|
||||||
styleClass="ui-button-primary"
|
styleClass="ui-button-primary"
|
||||||
model="#{chantiersView.chantierActions}"/>
|
model="#{chantiersView.chantierActions}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- KPI Cards -->
|
<!-- KPI Cards -->
|
||||||
<div class="grid mb-3">
|
<div class="grid mb-3">
|
||||||
<div class="col-12 md:col-6 lg:col-3">
|
<div class="col-12 md:col-6 lg:col-3">
|
||||||
<ui:include src="/WEB-INF/components/detail-card.xhtml">
|
<ui:include src="/WEB-INF/components/detail-card.xhtml">
|
||||||
<ui:param name="title" value="Avancement"/>
|
<ui:param name="title" value="Avancement"/>
|
||||||
<ui:param name="value" value="#{chantiersView.selectedItem.avancement}%"/>
|
<ui:param name="value" value="#{chantiersView.selectedItem.avancement}%"/>
|
||||||
<ui:param name="icon" value="pi-chart-line"/>
|
<ui:param name="icon" value="pi-chart-line"/>
|
||||||
<ui:param name="iconColor" value="primary"/>
|
<ui:param name="iconColor" value="primary"/>
|
||||||
<ui:param name="trend" value="+5%"/>
|
<ui:param name="trend" value="+5%"/>
|
||||||
<ui:param name="footer" value="vs semaine dernière"/>
|
<ui:param name="footer" value="vs semaine dernière"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6 lg:col-3">
|
<div class="col-12 md:col-6 lg:col-3">
|
||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<span class="text-600 font-medium text-sm mb-2">Budget total</span>
|
<span class="text-600 font-medium text-sm mb-2">Budget total</span>
|
||||||
<div class="text-900 font-bold text-2xl mb-2">
|
<div class="text-900 font-bold text-2xl mb-2">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{chantiersView.selectedItem.budget}"/>
|
<ui:param name="amount" value="#{chantiersView.selectedItem.budget}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
<ui:param name="bold" value="true"/>
|
<ui:param name="bold" value="true"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-500 text-xs">Alloué au projet</span>
|
<span class="text-500 text-xs">Alloué au projet</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6 lg:col-3">
|
<div class="col-12 md:col-6 lg:col-3">
|
||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<span class="text-600 font-medium text-sm mb-2">Coût réel</span>
|
<span class="text-600 font-medium text-sm mb-2">Coût réel</span>
|
||||||
<div class="text-900 font-bold text-2xl mb-2">
|
<div class="text-900 font-bold text-2xl mb-2">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{chantiersView.selectedItem.coutReel}"/>
|
<ui:param name="amount" value="#{chantiersView.selectedItem.coutReel}"/>
|
||||||
<ui:param name="color" value="#{chantiersView.selectedItem.coutReel > chantiersView.selectedItem.budget ? 'danger' : 'success'}"/>
|
<ui:param name="color" value="#{chantiersView.selectedItem.coutReel > chantiersView.selectedItem.budget ? 'danger' : 'success'}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
<ui:param name="bold" value="true"/>
|
<ui:param name="bold" value="true"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-500 text-xs">Dépensé à ce jour</span>
|
<span class="text-500 text-xs">Dépensé à ce jour</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6 lg:col-3">
|
<div class="col-12 md:col-6 lg:col-3">
|
||||||
<div class="card mb-0">
|
<div class="card mb-0">
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<span class="text-600 font-medium text-sm mb-2">Reste disponible</span>
|
<span class="text-600 font-medium text-sm mb-2">Reste disponible</span>
|
||||||
<div class="text-900 font-bold text-2xl mb-2">
|
<div class="text-900 font-bold text-2xl mb-2">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel}"/>
|
<ui:param name="amount" value="#{chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel}"/>
|
||||||
<ui:param name="color" value="#{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) < 0 ? 'danger' : 'success'}"/>
|
<ui:param name="color" value="#{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) < 0 ? 'danger' : 'success'}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
<ui:param name="bold" value="true"/>
|
<ui:param name="bold" value="true"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-500 text-xs">
|
<span class="text-500 text-xs">
|
||||||
#{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Excédent' : 'Dépassement'}
|
#{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Excédent' : 'Dépassement'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Onglets détaillés -->
|
<!-- Onglets détaillés -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<p:tabView dynamic="true" cache="false">
|
<p:tabView dynamic="true" cache="false">
|
||||||
|
|
||||||
<!-- ONGLET 1: Vue d'ensemble -->
|
<!-- ONGLET 1: Vue d'ensemble -->
|
||||||
<p:tab title="Vue d'ensemble" icon="pi pi-home">
|
<p:tab title="Vue d'ensemble" icon="pi pi-home">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<!-- Informations générales -->
|
<!-- Informations générales -->
|
||||||
<div class="col-12 lg:col-6">
|
<div class="col-12 lg:col-6">
|
||||||
<h5 class="text-900 font-bold mb-3">Informations générales</h5>
|
<h5 class="text-900 font-bold mb-3">Informations générales</h5>
|
||||||
<div class="surface-50 border-round p-3 mb-3">
|
<div class="surface-50 border-round p-3 mb-3">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<span class="text-600 text-sm">Nom du chantier</span>
|
<span class="text-600 text-sm">Nom du chantier</span>
|
||||||
<p class="text-900 font-medium mt-1 mb-0">#{chantiersView.selectedItem.nom}</p>
|
<p class="text-900 font-medium mt-1 mb-0">#{chantiersView.selectedItem.nom}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<span class="text-600 text-sm">Client</span>
|
<span class="text-600 text-sm">Client</span>
|
||||||
<p class="text-900 font-medium mt-1 mb-0">#{chantiersView.selectedItem.client}</p>
|
<p class="text-900 font-medium mt-1 mb-0">#{chantiersView.selectedItem.client}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<span class="text-600 text-sm">Adresse</span>
|
<span class="text-600 text-sm">Adresse</span>
|
||||||
<p class="text-900 font-medium mt-1 mb-0">#{chantiersView.selectedItem.adresse}</p>
|
<p class="text-900 font-medium mt-1 mb-0">#{chantiersView.selectedItem.adresse}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<span class="text-600 text-sm">Statut</span>
|
<span class="text-600 text-sm">Statut</span>
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
<ui:include src="/WEB-INF/components/status-badge.xhtml">
|
<ui:include src="/WEB-INF/components/status-badge.xhtml">
|
||||||
<ui:param name="value" value="#{chantiersView.selectedItem.statut}"/>
|
<ui:param name="value" value="#{chantiersView.selectedItem.statut}"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<span class="text-600 text-sm">Avancement</span>
|
<span class="text-600 text-sm">Avancement</span>
|
||||||
<p class="text-900 font-bold text-xl mt-1 mb-0">#{chantiersView.selectedItem.avancement}%</p>
|
<p class="text-900 font-bold text-xl mt-1 mb-0">#{chantiersView.selectedItem.avancement}%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Progression visuelle -->
|
<!-- Progression visuelle -->
|
||||||
<div class="col-12 lg:col-6">
|
<div class="col-12 lg:col-6">
|
||||||
<h5 class="text-900 font-bold mb-3">Progression du chantier</h5>
|
<h5 class="text-900 font-bold mb-3">Progression du chantier</h5>
|
||||||
<div class="surface-50 border-round p-3 mb-3">
|
<div class="surface-50 border-round p-3 mb-3">
|
||||||
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
|
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
|
||||||
<ui:param name="value" value="#{chantiersView.selectedItem.avancement}"/>
|
<ui:param name="value" value="#{chantiersView.selectedItem.avancement}"/>
|
||||||
<ui:param name="label" value="Réalisation globale"/>
|
<ui:param name="label" value="Réalisation globale"/>
|
||||||
<ui:param name="height" value="1.5rem"/>
|
<ui:param name="height" value="1.5rem"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Analyse budgétaire -->
|
<!-- Analyse budgétaire -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h5 class="text-900 font-bold mb-3">Analyse budgétaire</h5>
|
<h5 class="text-900 font-bold mb-3">Analyse budgétaire</h5>
|
||||||
<div class="surface-50 border-round p-3">
|
<div class="surface-50 border-round p-3">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm">Budget prévu</span>
|
<span class="text-600 text-sm">Budget prévu</span>
|
||||||
<div class="text-primary font-bold text-xl mt-2">
|
<div class="text-primary font-bold text-xl mt-2">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{chantiersView.selectedItem.budget}"/>
|
<ui:param name="amount" value="#{chantiersView.selectedItem.budget}"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm">Dépensé</span>
|
<span class="text-600 text-sm">Dépensé</span>
|
||||||
<div class="font-bold text-xl mt-2"
|
<div class="font-bold text-xl mt-2"
|
||||||
style="color: #{chantiersView.selectedItem.coutReel > chantiersView.selectedItem.budget ? '#EF4444' : '#10B981'}">
|
style="color: #{chantiersView.selectedItem.coutReel > chantiersView.selectedItem.budget ? '#EF4444' : '#10B981'}">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{chantiersView.selectedItem.coutReel}"/>
|
<ui:param name="amount" value="#{chantiersView.selectedItem.coutReel}"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm">
|
<span class="text-600 text-sm">
|
||||||
#{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Reste' : 'Dépassement'}
|
#{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? 'Reste' : 'Dépassement'}
|
||||||
</span>
|
</span>
|
||||||
<div class="font-bold text-xl mt-2"
|
<div class="font-bold text-xl mt-2"
|
||||||
style="color: #{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? '#10B981' : '#EF4444'}">
|
style="color: #{(chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel) >= 0 ? '#10B981' : '#EF4444'}">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel}"/>
|
<ui:param name="amount" value="#{chantiersView.selectedItem.budget - chantiersView.selectedItem.coutReel}"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 mt-3">
|
<div class="col-12 mt-3">
|
||||||
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
|
<ui:include src="/WEB-INF/components/progress-indicator.xhtml">
|
||||||
<ui:param name="value" value="#{(chantiersView.selectedItem.coutReel / chantiersView.selectedItem.budget) * 100}"/>
|
<ui:param name="value" value="#{(chantiersView.selectedItem.coutReel / chantiersView.selectedItem.budget) * 100}"/>
|
||||||
<ui:param name="label" value="Utilisation du budget"/>
|
<ui:param name="label" value="Utilisation du budget"/>
|
||||||
<ui:param name="labelPosition" value="top"/>
|
<ui:param name="labelPosition" value="top"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:tab>
|
</p:tab>
|
||||||
|
|
||||||
<!-- ONGLET 2: Phases -->
|
<!-- ONGLET 2: Phases -->
|
||||||
<p:tab title="Phases" icon="pi pi-sitemap">
|
<p:tab title="Phases" icon="pi pi-sitemap">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h5 class="text-900 font-bold m-0">Phases du chantier</h5>
|
<h5 class="text-900 font-bold m-0">Phases du chantier</h5>
|
||||||
<p:commandButton value="Ajouter une phase"
|
<p:commandButton value="Ajouter une phase"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
styleClass="ui-button-success ui-button-sm"/>
|
styleClass="ui-button-success ui-button-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<p:message severity="info" text="Fonctionnalité de gestion des phases en cours de développement"/>
|
<p:message severity="info" text="Fonctionnalité de gestion des phases en cours de développement"/>
|
||||||
</div>
|
</div>
|
||||||
</p:tab>
|
</p:tab>
|
||||||
|
|
||||||
<!-- ONGLET 3: Équipes -->
|
<!-- ONGLET 3: Équipes -->
|
||||||
<p:tab title="Équipes" icon="pi pi-users">
|
<p:tab title="Équipes" icon="pi pi-users">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h5 class="text-900 font-bold m-0">Équipes affectées</h5>
|
<h5 class="text-900 font-bold m-0">Équipes affectées</h5>
|
||||||
<p:commandButton value="Affecter une équipe"
|
<p:commandButton value="Affecter une équipe"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
styleClass="ui-button-success ui-button-sm"/>
|
styleClass="ui-button-success ui-button-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<p:message severity="info" text="Fonctionnalité d'affectation des équipes en cours de développement"/>
|
<p:message severity="info" text="Fonctionnalité d'affectation des équipes en cours de développement"/>
|
||||||
</div>
|
</div>
|
||||||
</p:tab>
|
</p:tab>
|
||||||
|
|
||||||
<!-- ONGLET 4: Matériels -->
|
<!-- ONGLET 4: Matériels -->
|
||||||
<p:tab title="Matériels" icon="pi pi-wrench">
|
<p:tab title="Matériels" icon="pi pi-wrench">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h5 class="text-900 font-bold m-0">Matériels utilisés</h5>
|
<h5 class="text-900 font-bold m-0">Matériels utilisés</h5>
|
||||||
<p:commandButton value="Ajouter du matériel"
|
<p:commandButton value="Ajouter du matériel"
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
styleClass="ui-button-success ui-button-sm"/>
|
styleClass="ui-button-success ui-button-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<p:message severity="info" text="Fonctionnalité de gestion des matériels en cours de développement"/>
|
<p:message severity="info" text="Fonctionnalité de gestion des matériels en cours de développement"/>
|
||||||
</div>
|
</div>
|
||||||
</p:tab>
|
</p:tab>
|
||||||
|
|
||||||
<!-- ONGLET 5: Documents -->
|
<!-- ONGLET 5: Documents -->
|
||||||
<p:tab title="Documents" icon="pi pi-folder">
|
<p:tab title="Documents" icon="pi pi-folder">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h5 class="text-900 font-bold m-0">Documents du chantier</h5>
|
<h5 class="text-900 font-bold m-0">Documents du chantier</h5>
|
||||||
<p:commandButton value="Ajouter un document"
|
<p:commandButton value="Ajouter un document"
|
||||||
icon="pi pi-upload"
|
icon="pi pi-upload"
|
||||||
styleClass="ui-button-success ui-button-sm"/>
|
styleClass="ui-button-success ui-button-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<p:message severity="info" text="Fonctionnalité de gestion documentaire en cours de développement"/>
|
<p:message severity="info" text="Fonctionnalité de gestion documentaire en cours de développement"/>
|
||||||
</div>
|
</div>
|
||||||
</p:tab>
|
</p:tab>
|
||||||
|
|
||||||
<!-- ONGLET 6: Historique -->
|
<!-- ONGLET 6: Historique -->
|
||||||
<p:tab title="Historique" icon="pi pi-history">
|
<p:tab title="Historique" icon="pi pi-history">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<h5 class="text-900 font-bold mb-3">Historique des modifications</h5>
|
<h5 class="text-900 font-bold mb-3">Historique des modifications</h5>
|
||||||
<p:timeline value="#{chantiersView.chantierHistory}" align="alternate">
|
<p:timeline value="#{chantiersView.chantierHistory}" align="alternate">
|
||||||
<p:templateSlot name="marker">
|
<p:templateSlot name="marker">
|
||||||
<i class="pi pi-circle-fill text-primary"></i>
|
<i class="pi pi-circle-fill text-primary"></i>
|
||||||
</p:templateSlot>
|
</p:templateSlot>
|
||||||
<p:templateSlot name="content">
|
<p:templateSlot name="content">
|
||||||
<small class="text-600">Fonctionnalité en cours de développement</small>
|
<small class="text-600">Fonctionnalité en cours de développement</small>
|
||||||
</p:templateSlot>
|
</p:templateSlot>
|
||||||
</p:timeline>
|
</p:timeline>
|
||||||
</div>
|
</div>
|
||||||
</p:tab>
|
</p:tab>
|
||||||
|
|
||||||
</p:tabView>
|
</p:tabView>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,97 +1,97 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Chantiers en cours - BTP Xpress</ui:define>
|
<ui:define name="title">Chantiers en cours - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<f:metadata>
|
<f:metadata>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.setFiltreStatut('EN_COURS')}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.setFiltreStatut('EN_COURS')}"/>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.init()}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.init()}"/>
|
||||||
</f:metadata>
|
</f:metadata>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Chantiers en cours</h1>
|
<h1>Chantiers en cours</h1>
|
||||||
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
||||||
action="#{chantiersView.createNew()}"
|
action="#{chantiersView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
||||||
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="filtreClient" value="Client"/>
|
<h:outputLabel for="filtreClient" value="Client"/>
|
||||||
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
||||||
placeholder="Rechercher par client..." style="width: 100%;"/>
|
placeholder="Rechercher par client..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="chantiersForm"/>
|
<ui:param name="formId" value="chantiersForm"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="var" value="chantier"/>
|
<ui:param name="var" value="chantier"/>
|
||||||
<ui:param name="title" value="Liste des chantiers en cours"/>
|
<ui:param name="title" value="Liste des chantiers en cours"/>
|
||||||
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
||||||
<h:outputText value="#{chantier.nom}"/>
|
<h:outputText value="#{chantier.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Client" sortBy="#{chantier.client}">
|
<p:column headerText="Client" sortBy="#{chantier.client}">
|
||||||
<h:outputText value="#{chantier.client}"/>
|
<h:outputText value="#{chantier.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Adresse">
|
<p:column headerText="Adresse">
|
||||||
<h:outputText value="#{chantier.adresse}"/>
|
<h:outputText value="#{chantier.adresse}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
||||||
<h:outputText value="#{chantier.dateDebut}">
|
<h:outputText value="#{chantier.dateDebut}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Avancement">
|
<p:column headerText="Avancement">
|
||||||
<p:progressBar value="#{chantier.avancement}"
|
<p:progressBar value="#{chantier.avancement}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
styleClass="ui-progressbar-success"/>
|
styleClass="ui-progressbar-success"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Budget">
|
<p:column headerText="Budget">
|
||||||
<h:outputText value="#{chantier.budget}">
|
<h:outputText value="#{chantier.budget}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,236 +1,236 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Nouveau chantier - BTP Xpress</ui:define>
|
<ui:define name="title">Nouveau chantier - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<!-- En-tête avec breadcrumb -->
|
<!-- En-tête avec breadcrumb -->
|
||||||
<div class="flex align-items-center justify-content-between mb-4">
|
<div class="flex align-items-center justify-content-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-900 font-bold mb-2">Créer un nouveau chantier</h2>
|
<h2 class="text-900 font-bold mb-2">Créer un nouveau chantier</h2>
|
||||||
<p class="text-600 mt-0">Remplissez les informations du chantier à créer</p>
|
<p class="text-600 mt-0">Remplissez les informations du chantier à créer</p>
|
||||||
</div>
|
</div>
|
||||||
<p:commandButton value="Retour à la liste"
|
<p:commandButton value="Retour à la liste"
|
||||||
icon="pi pi-arrow-left"
|
icon="pi pi-arrow-left"
|
||||||
outcome="/chantiers"
|
outcome="/chantiers"
|
||||||
styleClass="ui-button-secondary ui-button-outlined"/>
|
styleClass="ui-button-secondary ui-button-outlined"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p:messages id="messages" showDetail="true" closable="true"/>
|
<p:messages id="messages" showDetail="true" closable="true"/>
|
||||||
|
|
||||||
<h:form id="nouveauChantierForm" styleClass="p-fluid">
|
<h:form id="nouveauChantierForm" styleClass="p-fluid">
|
||||||
|
|
||||||
<!-- SECTION 1: Informations générales -->
|
<!-- SECTION 1: Informations générales -->
|
||||||
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Nom du chantier -->
|
<!-- Nom du chantier -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="nom" class="font-bold">Nom du chantier <span class="text-red-500">*</span></label>
|
<label for="nom" class="font-bold">Nom du chantier <span class="text-red-500">*</span></label>
|
||||||
<p:inputText id="nom"
|
<p:inputText id="nom"
|
||||||
value="#{chantiersView.entity.nom}"
|
value="#{chantiersView.entity.nom}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le nom du chantier est obligatoire"
|
requiredMessage="Le nom du chantier est obligatoire"
|
||||||
placeholder="Ex: Construction Immeuble R+3">
|
placeholder="Ex: Construction Immeuble R+3">
|
||||||
<f:validateLength minimum="3" maximum="200"/>
|
<f:validateLength minimum="3" maximum="200"/>
|
||||||
</p:inputText>
|
</p:inputText>
|
||||||
<small class="text-600">Nom descriptif du projet de construction</small>
|
<small class="text-600">Nom descriptif du projet de construction</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Client -->
|
<!-- Client -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
|
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
|
||||||
<p:inputText id="client"
|
<p:inputText id="client"
|
||||||
value="#{chantiersView.entity.client}"
|
value="#{chantiersView.entity.client}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le client est obligatoire"
|
requiredMessage="Le client est obligatoire"
|
||||||
placeholder="Ex: Société ABC">
|
placeholder="Ex: Société ABC">
|
||||||
<f:validateLength minimum="2" maximum="200"/>
|
<f:validateLength minimum="2" maximum="200"/>
|
||||||
</p:inputText>
|
</p:inputText>
|
||||||
<small class="text-600">Nom du client ou de l'entreprise</small>
|
<small class="text-600">Nom du client ou de l'entreprise</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Adresse complète -->
|
<!-- Adresse complète -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label for="adresse" class="font-bold">Adresse du chantier</label>
|
<label for="adresse" class="font-bold">Adresse du chantier</label>
|
||||||
<p:inputTextarea id="adresse"
|
<p:inputTextarea id="adresse"
|
||||||
value="#{chantiersView.entity.adresse}"
|
value="#{chantiersView.entity.adresse}"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Ex: Quartier Résidentiel, Avenue de la Paix, Lot 245"
|
placeholder="Ex: Quartier Résidentiel, Avenue de la Paix, Lot 245"
|
||||||
autoResize="false">
|
autoResize="false">
|
||||||
<f:validateLength maximum="500"/>
|
<f:validateLength maximum="500"/>
|
||||||
</p:inputTextarea>
|
</p:inputTextarea>
|
||||||
<small class="text-600">Localisation précise du chantier</small>
|
<small class="text-600">Localisation précise du chantier</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Statut -->
|
<!-- Statut -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
|
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
|
||||||
<p:selectOneMenu id="statut"
|
<p:selectOneMenu id="statut"
|
||||||
value="#{chantiersView.entity.statut}"
|
value="#{chantiersView.entity.statut}"
|
||||||
required="true">
|
required="true">
|
||||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
||||||
<f:selectItem itemLabel="Planifié" itemValue="PLANIFIE"/>
|
<f:selectItem itemLabel="Planifié" itemValue="PLANIFIE"/>
|
||||||
<f:selectItem itemLabel="En cours" itemValue="EN_COURS"/>
|
<f:selectItem itemLabel="En cours" itemValue="EN_COURS"/>
|
||||||
<f:selectItem itemLabel="Suspendu" itemValue="SUSPENDU"/>
|
<f:selectItem itemLabel="Suspendu" itemValue="SUSPENDU"/>
|
||||||
<f:selectItem itemLabel="Terminé" itemValue="TERMINE"/>
|
<f:selectItem itemLabel="Terminé" itemValue="TERMINE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Avancement initial -->
|
<!-- Avancement initial -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="avancement" class="font-bold">Avancement (%)</label>
|
<label for="avancement" class="font-bold">Avancement (%)</label>
|
||||||
<p:inputNumber id="avancement"
|
<p:inputNumber id="avancement"
|
||||||
value="#{chantiersView.entity.avancement}"
|
value="#{chantiersView.entity.avancement}"
|
||||||
minValue="0"
|
minValue="0"
|
||||||
maxValue="100"
|
maxValue="100"
|
||||||
suffix=" %"
|
suffix=" %"
|
||||||
decimalPlaces="0">
|
decimalPlaces="0">
|
||||||
</p:inputNumber>
|
</p:inputNumber>
|
||||||
<small class="text-600">Pourcentage de réalisation (0-100%)</small>
|
<small class="text-600">Pourcentage de réalisation (0-100%)</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 2: Planification -->
|
<!-- SECTION 2: Planification -->
|
||||||
<p:panel header="Planification" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Planification" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Date de début -->
|
<!-- Date de début -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="dateDebut" class="font-bold">Date de début <span class="text-red-500">*</span></label>
|
<label for="dateDebut" class="font-bold">Date de début <span class="text-red-500">*</span></label>
|
||||||
<p:calendar id="dateDebut"
|
<p:calendar id="dateDebut"
|
||||||
value="#{chantiersView.entity.dateDebut}"
|
value="#{chantiersView.entity.dateDebut}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="La date de début est obligatoire"
|
requiredMessage="La date de début est obligatoire"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2030"
|
yearRange="2020:2030"
|
||||||
placeholder="Sélectionner une date">
|
placeholder="Sélectionner une date">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date de fin prévue -->
|
<!-- Date de fin prévue -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="dateFinPrevue" class="font-bold">Date de fin prévue <span class="text-red-500">*</span></label>
|
<label for="dateFinPrevue" class="font-bold">Date de fin prévue <span class="text-red-500">*</span></label>
|
||||||
<p:calendar id="dateFinPrevue"
|
<p:calendar id="dateFinPrevue"
|
||||||
value="#{chantiersView.entity.dateFinPrevue}"
|
value="#{chantiersView.entity.dateFinPrevue}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="La date de fin est obligatoire"
|
requiredMessage="La date de fin est obligatoire"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2035"
|
yearRange="2020:2035"
|
||||||
mindate="#{chantiersView.entity.dateDebut}"
|
mindate="#{chantiersView.entity.dateDebut}"
|
||||||
placeholder="Sélectionner une date">
|
placeholder="Sélectionner une date">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
<small class="text-600">Doit être postérieure à la date de début</small>
|
<small class="text-600">Doit être postérieure à la date de début</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Durée estimée (calculée automatiquement) -->
|
<!-- Durée estimée (calculée automatiquement) -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label class="font-bold">Durée estimée</label>
|
<label class="font-bold">Durée estimée</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon">
|
<span class="p-inputgroup-addon">
|
||||||
<i class="pi pi-calendar"></i>
|
<i class="pi pi-calendar"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputText value="Calculée automatiquement"
|
<p:inputText value="Calculée automatiquement"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
styleClass="text-center font-bold"/>
|
styleClass="text-center font-bold"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Basé sur dates début et fin</small>
|
<small class="text-600">Basé sur dates début et fin</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 3: Budget -->
|
<!-- SECTION 3: Budget -->
|
||||||
<p:panel header="Budget et coûts" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Budget et coûts" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Budget total -->
|
<!-- Budget total -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="budget" class="font-bold">Budget total (FCFA) <span class="text-red-500">*</span></label>
|
<label for="budget" class="font-bold">Budget total (FCFA) <span class="text-red-500">*</span></label>
|
||||||
<p:inputNumber id="budget"
|
<p:inputNumber id="budget"
|
||||||
value="#{chantiersView.entity.budget}"
|
value="#{chantiersView.entity.budget}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le budget est obligatoire"
|
requiredMessage="Le budget est obligatoire"
|
||||||
minValue="0"
|
minValue="0"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
placeholder="0">
|
placeholder="0">
|
||||||
</p:inputNumber>
|
</p:inputNumber>
|
||||||
<small class="text-600">Budget total alloué au chantier</small>
|
<small class="text-600">Budget total alloué au chantier</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Coût réel (initialement 0) -->
|
<!-- Coût réel (initialement 0) -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="coutReel" class="font-bold">Coût réel (FCFA)</label>
|
<label for="coutReel" class="font-bold">Coût réel (FCFA)</label>
|
||||||
<p:inputNumber id="coutReel"
|
<p:inputNumber id="coutReel"
|
||||||
value="#{chantiersView.entity.coutReel}"
|
value="#{chantiersView.entity.coutReel}"
|
||||||
minValue="0"
|
minValue="0"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
placeholder="0">
|
placeholder="0">
|
||||||
</p:inputNumber>
|
</p:inputNumber>
|
||||||
<small class="text-600">Coût réel dépensé (actualisé régulièrement)</small>
|
<small class="text-600">Coût réel dépensé (actualisé régulièrement)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Indicateur budgétaire visuel -->
|
<!-- Indicateur budgétaire visuel -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<div class="surface-100 border-round p-3">
|
<div class="surface-100 border-round p-3">
|
||||||
<div class="flex align-items-center justify-content-between mb-2">
|
<div class="flex align-items-center justify-content-between mb-2">
|
||||||
<span class="text-900 font-medium">État budgétaire</span>
|
<span class="text-900 font-medium">État budgétaire</span>
|
||||||
<span class="text-600 text-sm">Budget: #{chantiersView.entity.budget} FCFA | Dépensé: #{chantiersView.entity.coutReel} FCFA</span>
|
<span class="text-600 text-sm">Budget: #{chantiersView.entity.budget} FCFA | Dépensé: #{chantiersView.entity.coutReel} FCFA</span>
|
||||||
</div>
|
</div>
|
||||||
<p:progressBar value="#{chantiersView.entity.coutReel / chantiersView.entity.budget * 100}"
|
<p:progressBar value="#{chantiersView.entity.coutReel / chantiersView.entity.budget * 100}"
|
||||||
displayValue="true"
|
displayValue="true"
|
||||||
labelTemplate="{value}% du budget utilisé"
|
labelTemplate="{value}% du budget utilisé"
|
||||||
styleClass="#{chantiersView.entity.coutReel > chantiersView.entity.budget ? 'bg-red-500' : 'bg-green-500'}"/>
|
styleClass="#{chantiersView.entity.coutReel > chantiersView.entity.budget ? 'bg-red-500' : 'bg-green-500'}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- Boutons d'action -->
|
<!-- Boutons d'action -->
|
||||||
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
|
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-600 text-sm">Les champs marqués d'un </span>
|
<span class="text-600 text-sm">Les champs marqués d'un </span>
|
||||||
<span class="text-red-500 font-bold">*</span>
|
<span class="text-red-500 font-bold">*</span>
|
||||||
<span class="text-600 text-sm"> sont obligatoires</span>
|
<span class="text-600 text-sm"> sont obligatoires</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<p:commandButton value="Annuler"
|
<p:commandButton value="Annuler"
|
||||||
icon="pi pi-times"
|
icon="pi pi-times"
|
||||||
action="/chantiers?faces-redirect=true"
|
action="/chantiers?faces-redirect=true"
|
||||||
styleClass="ui-button-secondary"
|
styleClass="ui-button-secondary"
|
||||||
immediate="true"/>
|
immediate="true"/>
|
||||||
<p:commandButton value="Enregistrer le chantier"
|
<p:commandButton value="Enregistrer le chantier"
|
||||||
icon="pi pi-save"
|
icon="pi pi-save"
|
||||||
action="#{chantiersView.save}"
|
action="#{chantiersView.save}"
|
||||||
update="@form messages"
|
update="@form messages"
|
||||||
oncomplete="if (args && !args.validationFailed) window.location.href='/chantiers.xhtml';"
|
oncomplete="if (args && !args.validationFailed) window.location.href='/chantiers.xhtml';"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,94 +1,94 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Chantiers planifiés - BTP Xpress</ui:define>
|
<ui:define name="title">Chantiers planifiés - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<f:metadata>
|
<f:metadata>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.setFiltreStatut('PLANIFIE')}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.setFiltreStatut('PLANIFIE')}"/>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.init()}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.init()}"/>
|
||||||
</f:metadata>
|
</f:metadata>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Chantiers planifiés</h1>
|
<h1>Chantiers planifiés</h1>
|
||||||
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
||||||
action="#{chantiersView.createNew()}"
|
action="#{chantiersView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
||||||
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="filtreClient" value="Client"/>
|
<h:outputLabel for="filtreClient" value="Client"/>
|
||||||
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
||||||
placeholder="Rechercher par client..." style="width: 100%;"/>
|
placeholder="Rechercher par client..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="chantiersForm"/>
|
<ui:param name="formId" value="chantiersForm"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="var" value="chantier"/>
|
<ui:param name="var" value="chantier"/>
|
||||||
<ui:param name="title" value="Liste des chantiers planifiés"/>
|
<ui:param name="title" value="Liste des chantiers planifiés"/>
|
||||||
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
||||||
<h:outputText value="#{chantier.nom}"/>
|
<h:outputText value="#{chantier.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Client" sortBy="#{chantier.client}">
|
<p:column headerText="Client" sortBy="#{chantier.client}">
|
||||||
<h:outputText value="#{chantier.client}"/>
|
<h:outputText value="#{chantier.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date début prévue" sortBy="#{chantier.dateDebut}">
|
<p:column headerText="Date début prévue" sortBy="#{chantier.dateDebut}">
|
||||||
<h:outputText value="#{chantier.dateDebut}">
|
<h:outputText value="#{chantier.dateDebut}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date fin prévue" sortBy="#{chantier.dateFinPrevue}">
|
<p:column headerText="Date fin prévue" sortBy="#{chantier.dateFinPrevue}">
|
||||||
<h:outputText value="#{chantier.dateFinPrevue}">
|
<h:outputText value="#{chantier.dateFinPrevue}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Budget">
|
<p:column headerText="Budget">
|
||||||
<h:outputText value="#{chantier.budget}">
|
<h:outputText value="#{chantier.budget}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,94 +1,94 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Chantiers terminés - BTP Xpress</ui:define>
|
<ui:define name="title">Chantiers terminés - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<f:metadata>
|
<f:metadata>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.setFiltreStatut('TERMINE')}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.setFiltreStatut('TERMINE')}"/>
|
||||||
<f:event type="preRenderView" listener="#{chantiersView.init()}"/>
|
<f:event type="preRenderView" listener="#{chantiersView.init()}"/>
|
||||||
</f:metadata>
|
</f:metadata>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Chantiers terminés</h1>
|
<h1>Chantiers terminés</h1>
|
||||||
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
<p:commandButton value="Nouveau chantier" icon="pi pi-plus"
|
||||||
action="#{chantiersView.createNew()}"
|
action="#{chantiersView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
<h:outputLabel for="filtreNom" value="Nom du chantier"/>
|
||||||
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{chantiersView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="filtreClient" value="Client"/>
|
<h:outputLabel for="filtreClient" value="Client"/>
|
||||||
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
<p:inputText id="filtreClient" value="#{chantiersView.filtreClient}"
|
||||||
placeholder="Rechercher par client..." style="width: 100%;"/>
|
placeholder="Rechercher par client..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="chantiersForm"/>
|
<ui:param name="formId" value="chantiersForm"/>
|
||||||
<ui:param name="tableId" value="chantiersTable"/>
|
<ui:param name="tableId" value="chantiersTable"/>
|
||||||
<ui:param name="viewBean" value="#{chantiersView}"/>
|
<ui:param name="viewBean" value="#{chantiersView}"/>
|
||||||
<ui:param name="var" value="chantier"/>
|
<ui:param name="var" value="chantier"/>
|
||||||
<ui:param name="title" value="Liste des chantiers terminés"/>
|
<ui:param name="title" value="Liste des chantiers terminés"/>
|
||||||
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
<ui:param name="createPath" value="/chantiers/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
||||||
<h:outputText value="#{chantier.nom}"/>
|
<h:outputText value="#{chantier.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Client" sortBy="#{chantier.client}">
|
<p:column headerText="Client" sortBy="#{chantier.client}">
|
||||||
<h:outputText value="#{chantier.client}"/>
|
<h:outputText value="#{chantier.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
||||||
<h:outputText value="#{chantier.dateDebut}">
|
<h:outputText value="#{chantier.dateDebut}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date fin prévue" sortBy="#{chantier.dateFinPrevue}">
|
<p:column headerText="Date fin prévue" sortBy="#{chantier.dateFinPrevue}">
|
||||||
<h:outputText value="#{chantier.dateFinPrevue}">
|
<h:outputText value="#{chantier.dateFinPrevue}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Budget">
|
<p:column headerText="Budget">
|
||||||
<h:outputText value="#{chantier.budget}">
|
<h:outputText value="#{chantier.budget}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
action="#{chantiersView.viewDetails(chantier.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,95 +1,95 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Clients - BTP Xpress</ui:define>
|
<ui:define name="title">Clients - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Clients</h1>
|
<h1>Gestion des Clients</h1>
|
||||||
<p:commandButton value="Nouveau client" icon="pi pi-user-plus"
|
<p:commandButton value="Nouveau client" icon="pi pi-user-plus"
|
||||||
action="#{clientsView.createNew()}"
|
action="#{clientsView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{clientsView}"/>
|
<ui:param name="viewBean" value="#{clientsView}"/>
|
||||||
<ui:param name="tableId" value="clientsTable"/>
|
<ui:param name="tableId" value="clientsTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNom" value="Nom / Raison sociale"/>
|
<h:outputLabel for="filtreNom" value="Nom / Raison sociale"/>
|
||||||
<p:inputText id="filtreNom" value="#{clientsView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{clientsView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreEmail" value="Email"/>
|
<h:outputLabel for="filtreEmail" value="Email"/>
|
||||||
<p:inputText id="filtreEmail" value="#{clientsView.filtreEmail}"
|
<p:inputText id="filtreEmail" value="#{clientsView.filtreEmail}"
|
||||||
placeholder="Rechercher par email..." style="width: 100%;"/>
|
placeholder="Rechercher par email..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreVille" value="Ville"/>
|
<h:outputLabel for="filtreVille" value="Ville"/>
|
||||||
<p:inputText id="filtreVille" value="#{clientsView.filtreVille}"
|
<p:inputText id="filtreVille" value="#{clientsView.filtreVille}"
|
||||||
placeholder="Rechercher par ville..." style="width: 100%;"/>
|
placeholder="Rechercher par ville..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="clientsForm"/>
|
<ui:param name="formId" value="clientsForm"/>
|
||||||
<ui:param name="tableId" value="clientsTable"/>
|
<ui:param name="tableId" value="clientsTable"/>
|
||||||
<ui:param name="viewBean" value="#{clientsView}"/>
|
<ui:param name="viewBean" value="#{clientsView}"/>
|
||||||
<ui:param name="var" value="client"/>
|
<ui:param name="var" value="client"/>
|
||||||
<ui:param name="title" value="Liste des clients"/>
|
<ui:param name="title" value="Liste des clients"/>
|
||||||
<ui:param name="createPath" value="/clients/nouveau"/>
|
<ui:param name="createPath" value="/clients/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Raison sociale" sortBy="#{client.raisonSociale}">
|
<p:column headerText="Raison sociale" sortBy="#{client.raisonSociale}">
|
||||||
<h:outputText value="#{client.raisonSociale}"/>
|
<h:outputText value="#{client.raisonSociale}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Contact" sortBy="#{client.nomContact}">
|
<p:column headerText="Contact" sortBy="#{client.nomContact}">
|
||||||
<h:outputText value="#{client.nomContact}"/>
|
<h:outputText value="#{client.nomContact}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Email" sortBy="#{client.email}">
|
<p:column headerText="Email" sortBy="#{client.email}">
|
||||||
<h:outputText value="#{client.email}"/>
|
<h:outputText value="#{client.email}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Téléphone">
|
<p:column headerText="Téléphone">
|
||||||
<h:outputText value="#{client.telephone}"/>
|
<h:outputText value="#{client.telephone}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Ville" sortBy="#{client.ville}">
|
<p:column headerText="Ville" sortBy="#{client.ville}">
|
||||||
<h:outputText value="#{client.ville}"/>
|
<h:outputText value="#{client.ville}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Chantiers">
|
<p:column headerText="Chantiers">
|
||||||
<p:tag value="#{client.nombreChantiers}" severity="info"/>
|
<p:tag value="#{client.nombreChantiers}" severity="info"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Chiffre d'affaires">
|
<p:column headerText="Chiffre d'affaires">
|
||||||
<h:outputText value="#{client.chiffreAffairesTotal}">
|
<h:outputText value="#{client.chiffreAffairesTotal}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{clientsView.viewDetails(client.id)}"/>
|
action="#{clientsView.viewDetails(client.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,85 +1,85 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Détails du client - BTP Xpress</ui:define>
|
<ui:define name="title">Détails du client - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<f:metadata>
|
<f:metadata>
|
||||||
<f:viewParam name="id" value="#{clientsView.clientId}"/>
|
<f:viewParam name="id" value="#{clientsView.clientId}"/>
|
||||||
<f:event type="preRenderView" listener="#{clientsView.loadClientById()}"/>
|
<f:event type="preRenderView" listener="#{clientsView.loadClientById()}"/>
|
||||||
</f:metadata>
|
</f:metadata>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Détails du client</h1>
|
<h1>Détails du client</h1>
|
||||||
<p:commandButton value="Retour" icon="pi pi-arrow-left"
|
<p:commandButton value="Retour" icon="pi pi-arrow-left"
|
||||||
outcome="/clients"
|
outcome="/clients"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h:form id="detailsClientForm">
|
<h:form id="detailsClientForm">
|
||||||
<div class="grid" rendered="#{not empty clientsView.selectedItem}">
|
<div class="grid" rendered="#{not empty clientsView.selectedItem}">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p:panel header="Informations générales">
|
<p:panel header="Informations générales">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<p><strong>Raison sociale :</strong> #{clientsView.selectedItem.raisonSociale}</p>
|
<p><strong>Raison sociale :</strong> #{clientsView.selectedItem.raisonSociale}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<p><strong>Nom du contact :</strong> #{clientsView.selectedItem.nomContact}</p>
|
<p><strong>Nom du contact :</strong> #{clientsView.selectedItem.nomContact}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<p><strong>Email :</strong> #{clientsView.selectedItem.email}</p>
|
<p><strong>Email :</strong> #{clientsView.selectedItem.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<p><strong>Téléphone :</strong> #{clientsView.selectedItem.telephone}</p>
|
<p><strong>Téléphone :</strong> #{clientsView.selectedItem.telephone}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<p:panel header="Adresse">
|
<p:panel header="Adresse">
|
||||||
<p><strong>Adresse :</strong> #{clientsView.selectedItem.adresse}</p>
|
<p><strong>Adresse :</strong> #{clientsView.selectedItem.adresse}</p>
|
||||||
<p><strong>Ville :</strong> #{clientsView.selectedItem.ville}</p>
|
<p><strong>Ville :</strong> #{clientsView.selectedItem.ville}</p>
|
||||||
<p><strong>Code postal :</strong> #{clientsView.selectedItem.codePostal}</p>
|
<p><strong>Code postal :</strong> #{clientsView.selectedItem.codePostal}</p>
|
||||||
<p><strong>Pays :</strong> #{clientsView.selectedItem.pays}</p>
|
<p><strong>Pays :</strong> #{clientsView.selectedItem.pays}</p>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<p:panel header="Statistiques">
|
<p:panel header="Statistiques">
|
||||||
<p><strong>Nombre de chantiers :</strong>
|
<p><strong>Nombre de chantiers :</strong>
|
||||||
<p:tag value="#{clientsView.selectedItem.nombreChantiers}" severity="info"/>
|
<p:tag value="#{clientsView.selectedItem.nombreChantiers}" severity="info"/>
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Chiffre d'affaires total :</strong>
|
<p><strong>Chiffre d'affaires total :</strong>
|
||||||
<h:outputText value="#{clientsView.selectedItem.chiffreAffairesTotal}">
|
<h:outputText value="#{clientsView.selectedItem.chiffreAffairesTotal}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Date de création :</strong>
|
<p><strong>Date de création :</strong>
|
||||||
<h:outputText value="#{clientsView.selectedItem.dateCreation}">
|
<h:outputText value="#{clientsView.selectedItem.dateCreation}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy HH:mm"/>
|
<f:convertDateTime pattern="dd/MM/yyyy HH:mm"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p>
|
</p>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p:message rendered="#{empty clientsView.selectedItem}" severity="warn"
|
<p:message rendered="#{empty clientsView.selectedItem}" severity="warn"
|
||||||
summary="Client introuvable"/>
|
summary="Client introuvable"/>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,95 +1,95 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Nouveau client - BTP Xpress</ui:define>
|
<ui:define name="title">Nouveau client - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Créer un nouveau client</h1>
|
<h1>Créer un nouveau client</h1>
|
||||||
<p:commandButton value="Retour" icon="pi pi-arrow-left"
|
<p:commandButton value="Retour" icon="pi pi-arrow-left"
|
||||||
outcome="/clients"
|
outcome="/clients"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h:form id="nouveauClientForm">
|
<h:form id="nouveauClientForm">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="raisonSociale" value="Raison sociale *"/>
|
<h:outputLabel for="raisonSociale" value="Raison sociale *"/>
|
||||||
<p:inputText id="raisonSociale" value="#{clientsView.selectedItem.raisonSociale}"
|
<p:inputText id="raisonSociale" value="#{clientsView.selectedItem.raisonSociale}"
|
||||||
required="true" requiredMessage="La raison sociale est obligatoire"
|
required="true" requiredMessage="La raison sociale est obligatoire"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="nomContact" value="Nom du contact *"/>
|
<h:outputLabel for="nomContact" value="Nom du contact *"/>
|
||||||
<p:inputText id="nomContact" value="#{clientsView.selectedItem.nomContact}"
|
<p:inputText id="nomContact" value="#{clientsView.selectedItem.nomContact}"
|
||||||
required="true" requiredMessage="Le nom du contact est obligatoire"
|
required="true" requiredMessage="Le nom du contact est obligatoire"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="email" value="Email *"/>
|
<h:outputLabel for="email" value="Email *"/>
|
||||||
<p:inputText id="email" value="#{clientsView.selectedItem.email}"
|
<p:inputText id="email" value="#{clientsView.selectedItem.email}"
|
||||||
required="true" requiredMessage="L'email est obligatoire"
|
required="true" requiredMessage="L'email est obligatoire"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
<p:message for="email"/>
|
<p:message for="email"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<h:outputLabel for="telephone" value="Téléphone"/>
|
<h:outputLabel for="telephone" value="Téléphone"/>
|
||||||
<p:inputText id="telephone" value="#{clientsView.selectedItem.telephone}"
|
<p:inputText id="telephone" value="#{clientsView.selectedItem.telephone}"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h:outputLabel for="adresse" value="Adresse"/>
|
<h:outputLabel for="adresse" value="Adresse"/>
|
||||||
<p:inputTextarea id="adresse" value="#{clientsView.selectedItem.adresse}"
|
<p:inputTextarea id="adresse" value="#{clientsView.selectedItem.adresse}"
|
||||||
rows="3" style="width: 100%;"/>
|
rows="3" style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="ville" value="Ville"/>
|
<h:outputLabel for="ville" value="Ville"/>
|
||||||
<p:inputText id="ville" value="#{clientsView.selectedItem.ville}"
|
<p:inputText id="ville" value="#{clientsView.selectedItem.ville}"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="codePostal" value="Code postal"/>
|
<h:outputLabel for="codePostal" value="Code postal"/>
|
||||||
<p:inputText id="codePostal" value="#{clientsView.selectedItem.codePostal}"
|
<p:inputText id="codePostal" value="#{clientsView.selectedItem.codePostal}"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="pays" value="Pays"/>
|
<h:outputLabel for="pays" value="Pays"/>
|
||||||
<p:inputText id="pays" value="#{clientsView.selectedItem.pays}"
|
<p:inputText id="pays" value="#{clientsView.selectedItem.pays}"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="flex justify-content-end gap-2 mt-3">
|
<div class="flex justify-content-end gap-2 mt-3">
|
||||||
<p:commandButton value="Annuler" icon="pi pi-times"
|
<p:commandButton value="Annuler" icon="pi pi-times"
|
||||||
outcome="/clients"
|
outcome="/clients"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
<p:commandButton value="Enregistrer" icon="pi pi-check"
|
<p:commandButton value="Enregistrer" icon="pi pi-check"
|
||||||
action="#{clientsView.saveNew()}"
|
action="#{clientsView.saveNew()}"
|
||||||
update="@form"
|
update="@form"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +1,96 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Recherche de clients - BTP Xpress</ui:define>
|
<ui:define name="title">Recherche de clients - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Recherche avancée de clients</h1>
|
<h1>Recherche avancée de clients</h1>
|
||||||
<p:commandButton value="Retour" icon="pi pi-arrow-left"
|
<p:commandButton value="Retour" icon="pi pi-arrow-left"
|
||||||
outcome="/clients"
|
outcome="/clients"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h:form id="rechercheClientForm">
|
<h:form id="rechercheClientForm">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p:panel header="Critères de recherche">
|
<p:panel header="Critères de recherche">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="rechercheNom" value="Nom / Raison sociale"/>
|
<h:outputLabel for="rechercheNom" value="Nom / Raison sociale"/>
|
||||||
<p:inputText id="rechercheNom" value="#{clientsView.filtreNom}"
|
<p:inputText id="rechercheNom" value="#{clientsView.filtreNom}"
|
||||||
placeholder="Rechercher..." style="width: 100%;"/>
|
placeholder="Rechercher..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="rechercheEmail" value="Email"/>
|
<h:outputLabel for="rechercheEmail" value="Email"/>
|
||||||
<p:inputText id="rechercheEmail" value="#{clientsView.filtreEmail}"
|
<p:inputText id="rechercheEmail" value="#{clientsView.filtreEmail}"
|
||||||
placeholder="Rechercher..." style="width: 100%;"/>
|
placeholder="Rechercher..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="rechercheVille" value="Ville"/>
|
<h:outputLabel for="rechercheVille" value="Ville"/>
|
||||||
<p:inputText id="rechercheVille" value="#{clientsView.filtreVille}"
|
<p:inputText id="rechercheVille" value="#{clientsView.filtreVille}"
|
||||||
placeholder="Rechercher..." style="width: 100%;"/>
|
placeholder="Rechercher..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-content-end gap-2 mt-3">
|
<div class="flex justify-content-end gap-2 mt-3">
|
||||||
<p:commandButton value="Réinitialiser" icon="pi pi-refresh"
|
<p:commandButton value="Réinitialiser" icon="pi pi-refresh"
|
||||||
action="#{clientsView.resetFilters()}"
|
action="#{clientsView.resetFilters()}"
|
||||||
update="@form"
|
update="@form"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
<p:commandButton value="Rechercher" icon="pi pi-search"
|
<p:commandButton value="Rechercher" icon="pi pi-search"
|
||||||
action="#{clientsView.search()}"
|
action="#{clientsView.search()}"
|
||||||
update="@form,clientsTable"
|
update="@form,clientsTable"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="clientsForm"/>
|
<ui:param name="formId" value="clientsForm"/>
|
||||||
<ui:param name="tableId" value="clientsTable"/>
|
<ui:param name="tableId" value="clientsTable"/>
|
||||||
<ui:param name="viewBean" value="#{clientsView}"/>
|
<ui:param name="viewBean" value="#{clientsView}"/>
|
||||||
<ui:param name="var" value="client"/>
|
<ui:param name="var" value="client"/>
|
||||||
<ui:param name="title" value="Résultats de recherche"/>
|
<ui:param name="title" value="Résultats de recherche"/>
|
||||||
<ui:param name="createPath" value=""/>
|
<ui:param name="createPath" value=""/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Raison sociale" sortBy="#{client.raisonSociale}">
|
<p:column headerText="Raison sociale" sortBy="#{client.raisonSociale}">
|
||||||
<h:outputText value="#{client.raisonSociale}"/>
|
<h:outputText value="#{client.raisonSociale}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Contact" sortBy="#{client.nomContact}">
|
<p:column headerText="Contact" sortBy="#{client.nomContact}">
|
||||||
<h:outputText value="#{client.nomContact}"/>
|
<h:outputText value="#{client.nomContact}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Email" sortBy="#{client.email}">
|
<p:column headerText="Email" sortBy="#{client.email}">
|
||||||
<h:outputText value="#{client.email}"/>
|
<h:outputText value="#{client.email}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Téléphone">
|
<p:column headerText="Téléphone">
|
||||||
<h:outputText value="#{client.telephone}"/>
|
<h:outputText value="#{client.telephone}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Ville" sortBy="#{client.ville}">
|
<p:column headerText="Ville" sortBy="#{client.ville}">
|
||||||
<h:outputText value="#{client.ville}"/>
|
<h:outputText value="#{client.ville}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{clientsView.viewDetails(client.id)}"/>
|
action="#{clientsView.viewDetails(client.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,488 +1,488 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Tableau de bord - BTP Xpress</ui:define>
|
<ui:define name="title">Tableau de bord - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Tableau de bord - BTP Xpress</h1>
|
<h1>Tableau de bord - BTP Xpress</h1>
|
||||||
<p>Bean dashboardView disponible: #{not empty dashboardView}</p>
|
<p>Bean dashboardView disponible: #{not empty dashboardView}</p>
|
||||||
<p>Chantiers actifs: #{dashboardView.chantiersActifs}</p>
|
<p>Chantiers actifs: #{dashboardView.chantiersActifs}</p>
|
||||||
<p>Test de contenu simple</p>
|
<p>Test de contenu simple</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ========================================================================
|
<!-- ========================================================================
|
||||||
BARRE D'ALERTES (affichée uniquement si alertes critiques)
|
BARRE D'ALERTES (affichée uniquement si alertes critiques)
|
||||||
======================================================================== -->
|
======================================================================== -->
|
||||||
<p:outputPanel rendered="#{dashboardView.alerteCritique}" styleClass="col-12">
|
<p:outputPanel rendered="#{dashboardView.alerteCritique}" styleClass="col-12">
|
||||||
<div class="notification notification-danger">
|
<div class="notification notification-danger">
|
||||||
<i class="pi pi-exclamation-triangle"></i>
|
<i class="pi pi-exclamation-triangle"></i>
|
||||||
<strong>#{dashboardView.totalAlertes} alertes</strong> nécessitent votre attention immédiate
|
<strong>#{dashboardView.totalAlertes} alertes</strong> nécessitent votre attention immédiate
|
||||||
<span style="margin-left: 1rem; opacity: 0.9;">
|
<span style="margin-left: 1rem; opacity: 0.9;">
|
||||||
Maintenance: #{dashboardView.alertesMaintenanceCount} •
|
Maintenance: #{dashboardView.alertesMaintenanceCount} •
|
||||||
Chantiers: #{dashboardView.alertesChantiersCount} •
|
Chantiers: #{dashboardView.alertesChantiersCount} •
|
||||||
Disponibilités: #{dashboardView.alertesDisponibilitesCount}
|
Disponibilités: #{dashboardView.alertesDisponibilitesCount}
|
||||||
</span>
|
</span>
|
||||||
<p:commandButton value="Rafraîchir"
|
<p:commandButton value="Rafraîchir"
|
||||||
icon="pi pi-refresh"
|
icon="pi pi-refresh"
|
||||||
action="#{dashboardView.rafraichir}"
|
action="#{dashboardView.rafraichir}"
|
||||||
update="@form"
|
update="@form"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
style="float: right;"/>
|
style="float: right;"/>
|
||||||
</div>
|
</div>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
|
|
||||||
<!-- ====================================================================
|
<!-- ====================================================================
|
||||||
KPIs PRINCIPAUX (3 cartes en ligne)
|
KPIs PRINCIPAUX (3 cartes en ligne)
|
||||||
==================================================================== -->
|
==================================================================== -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="grid" style="margin: -0.5rem;">
|
<div class="grid" style="margin: -0.5rem;">
|
||||||
|
|
||||||
<!-- KPI 1: Chantiers Actifs -->
|
<!-- KPI 1: Chantiers Actifs -->
|
||||||
<div class="col-12 md:col-6 xl:col-4">
|
<div class="col-12 md:col-6 xl:col-4">
|
||||||
<div class="card overview-box white">
|
<div class="card overview-box white">
|
||||||
<div class="overview-info">
|
<div class="overview-info">
|
||||||
<h6>Chantiers actifs</h6>
|
<h6>Chantiers actifs</h6>
|
||||||
<h1>#{dashboardView.chantiersActifs}</h1>
|
<h1>#{dashboardView.chantiersActifs}</h1>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
Sur #{dashboardView.nombreChantiers} au total
|
Sur #{dashboardView.nombreChantiers} au total
|
||||||
</p>
|
</p>
|
||||||
<p:progressBar value="#{dashboardView.tauxActiviteChantiers}"
|
<p:progressBar value="#{dashboardView.tauxActiviteChantiers}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
displayValue="#{dashboardView.tauxActiviteChantiers}%"
|
displayValue="#{dashboardView.tauxActiviteChantiers}%"
|
||||||
styleClass="ui-progressbar-info"/>
|
styleClass="ui-progressbar-info"/>
|
||||||
</div>
|
</div>
|
||||||
<i class="pi pi-building"></i>
|
<i class="pi pi-building"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- KPI 2: Équipes Disponibles -->
|
<!-- KPI 2: Équipes Disponibles -->
|
||||||
<div class="col-12 md:col-6 xl:col-4">
|
<div class="col-12 md:col-6 xl:col-4">
|
||||||
<div class="card overview-box blue">
|
<div class="card overview-box blue">
|
||||||
<div class="overview-info">
|
<div class="overview-info">
|
||||||
<h6>Équipes disponibles</h6>
|
<h6>Équipes disponibles</h6>
|
||||||
<h1>#{dashboardView.equipesDisponibles}/#{dashboardView.nombreEquipes}</h1>
|
<h1>#{dashboardView.equipesDisponibles}/#{dashboardView.nombreEquipes}</h1>
|
||||||
<p class="subtitle">Taux de disponibilité</p>
|
<p class="subtitle">Taux de disponibilité</p>
|
||||||
<p:progressBar value="#{dashboardView.tauxDisponibiliteEquipes}"
|
<p:progressBar value="#{dashboardView.tauxDisponibiliteEquipes}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
displayValue="#{dashboardView.tauxDisponibiliteEquipes}%"
|
displayValue="#{dashboardView.tauxDisponibiliteEquipes}%"
|
||||||
style="background: rgba(255,255,255,0.3);"/>
|
style="background: rgba(255,255,255,0.3);"/>
|
||||||
</div>
|
</div>
|
||||||
<i class="pi pi-users"></i>
|
<i class="pi pi-users"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- KPI 3: Maintenances Critiques -->
|
<!-- KPI 3: Maintenances Critiques -->
|
||||||
<div class="col-12 md:col-12 xl:col-4">
|
<div class="col-12 md:col-12 xl:col-4">
|
||||||
<div class="card overview-box #{dashboardView.alerteRetardMaintenance ? 'red' : 'green'}">
|
<div class="card overview-box #{dashboardView.alerteRetardMaintenance ? 'red' : 'green'}">
|
||||||
<div class="overview-info">
|
<div class="overview-info">
|
||||||
<h6>Maintenances en retard</h6>
|
<h6>Maintenances en retard</h6>
|
||||||
<h1>#{dashboardView.maintenancesEnRetard}</h1>
|
<h1>#{dashboardView.maintenancesEnRetard}</h1>
|
||||||
<p class="subtitle">#{dashboardView.maintenancesPlanifiees} planifiées</p>
|
<p class="subtitle">#{dashboardView.maintenancesPlanifiees} planifiées</p>
|
||||||
<p:badge value="#{dashboardView.alerteRetardMaintenance ? 'URGENT' : 'OK'}"
|
<p:badge value="#{dashboardView.alerteRetardMaintenance ? 'URGENT' : 'OK'}"
|
||||||
severity="#{dashboardView.alerteRetardMaintenance ? 'danger' : 'success'}"
|
severity="#{dashboardView.alerteRetardMaintenance ? 'danger' : 'success'}"
|
||||||
style="margin-top: 0.5rem;"/>
|
style="margin-top: 0.5rem;"/>
|
||||||
</div>
|
</div>
|
||||||
<i class="pi pi-wrench"></i>
|
<i class="pi pi-wrench"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ====================================================================
|
<!-- ====================================================================
|
||||||
SECTION CENTRALE : Graphique + KPIs Ressources
|
SECTION CENTRALE : Graphique + KPIs Ressources
|
||||||
==================================================================== -->
|
==================================================================== -->
|
||||||
|
|
||||||
<!-- Colonne gauche: Statistiques chantiers (placeholder pour graphique futur) -->
|
<!-- Colonne gauche: Statistiques chantiers (placeholder pour graphique futur) -->
|
||||||
<div class="col-12 xl:col-8">
|
<div class="col-12 xl:col-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Vue d'ensemble</h6>
|
<h6>Vue d'ensemble</h6>
|
||||||
<p class="subtitle">Statistiques globales</p>
|
<p class="subtitle">Statistiques globales</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<!-- Chantiers actifs avec progression -->
|
<!-- Chantiers actifs avec progression -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="statistic-item" style="padding: 1.5rem; background: var(--blue-50); border-radius: var(--border-radius); border-left: 4px solid var(--blue-500);">
|
<div class="statistic-item" style="padding: 1.5rem; background: var(--blue-50); border-radius: var(--border-radius); border-left: 4px solid var(--blue-500);">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
||||||
<i class="pi pi-building" style="font-size: 2rem; color: var(--blue-500);"></i>
|
<i class="pi pi-building" style="font-size: 2rem; color: var(--blue-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.75rem; color: var(--blue-600);">#{dashboardView.chantiersActifs}</h5>
|
<h5 style="margin: 0; font-size: 1.75rem; color: var(--blue-600);">#{dashboardView.chantiersActifs}</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Chantiers actifs</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Chantiers actifs</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p:progressBar value="#{dashboardView.tauxActiviteChantiers}"
|
<p:progressBar value="#{dashboardView.tauxActiviteChantiers}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
displayValue="#{dashboardView.tauxActiviteChantiers}% d'activité"
|
displayValue="#{dashboardView.tauxActiviteChantiers}% d'activité"
|
||||||
styleClass="ui-progressbar-info"
|
styleClass="ui-progressbar-info"
|
||||||
style="height: 1rem;"/>
|
style="height: 1rem;"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chantiers en retard -->
|
<!-- Chantiers en retard -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="statistic-item" style="padding: 1.5rem; background: var(--orange-50); border-radius: var(--border-radius); border-left: 4px solid var(--orange-500);">
|
<div class="statistic-item" style="padding: 1.5rem; background: var(--orange-50); border-radius: var(--border-radius); border-left: 4px solid var(--orange-500);">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||||
<i class="pi pi-exclamation-triangle" style="font-size: 2rem; color: var(--orange-500);"></i>
|
<i class="pi pi-exclamation-triangle" style="font-size: 2rem; color: var(--orange-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.75rem; color: var(--orange-600);">#{dashboardView.chantiersEnRetardList.size()}</h5>
|
<h5 style="margin: 0; font-size: 1.75rem; color: var(--orange-600);">#{dashboardView.chantiersEnRetardList.size()}</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Chantiers en retard</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Chantiers en retard</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p:outputPanel rendered="#{dashboardView.chantiersEnRetardList.size() > 0}">
|
<p:outputPanel rendered="#{dashboardView.chantiersEnRetardList.size() > 0}">
|
||||||
<small style="display: block; margin-top: 0.75rem; color: var(--orange-700);">
|
<small style="display: block; margin-top: 0.75rem; color: var(--orange-700);">
|
||||||
<i class="pi pi-info-circle"></i> Attention requise
|
<i class="pi pi-info-circle"></i> Attention requise
|
||||||
</small>
|
</small>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Événements aujourd'hui -->
|
<!-- Événements aujourd'hui -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="statistic-item" style="padding: 1.5rem; background: var(--purple-50); border-radius: var(--border-radius); border-left: 4px solid var(--purple-500);">
|
<div class="statistic-item" style="padding: 1.5rem; background: var(--purple-50); border-radius: var(--border-radius); border-left: 4px solid var(--purple-500);">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||||
<i class="pi pi-calendar" style="font-size: 2rem; color: var(--purple-500);"></i>
|
<i class="pi pi-calendar" style="font-size: 2rem; color: var(--purple-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.75rem; color: var(--purple-600);">#{dashboardView.evenementsAujourdhui}</h5>
|
<h5 style="margin: 0; font-size: 1.75rem; color: var(--purple-600);">#{dashboardView.evenementsAujourdhui}</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Événements aujourd'hui</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Événements aujourd'hui</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Documents totaux -->
|
<!-- Documents totaux -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="statistic-item" style="padding: 1.5rem; background: var(--cyan-50); border-radius: var(--border-radius); border-left: 4px solid var(--cyan-500);">
|
<div class="statistic-item" style="padding: 1.5rem; background: var(--cyan-50); border-radius: var(--border-radius); border-left: 4px solid var(--cyan-500);">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||||
<i class="pi pi-file" style="font-size: 2rem; color: var(--cyan-500);"></i>
|
<i class="pi pi-file" style="font-size: 2rem; color: var(--cyan-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.75rem; color: var(--cyan-600);">#{dashboardView.nombreDocuments}</h5>
|
<h5 style="margin: 0; font-size: 1.75rem; color: var(--cyan-600);">#{dashboardView.nombreDocuments}</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Documents totaux</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Documents totaux</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Colonne droite: KPIs Ressources -->
|
<!-- Colonne droite: KPIs Ressources -->
|
||||||
<div class="col-12 xl:col-4">
|
<div class="col-12 xl:col-4">
|
||||||
<div class="card" style="height: 100%;">
|
<div class="card" style="height: 100%;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Ressources</h6>
|
<h6>Ressources</h6>
|
||||||
<p class="subtitle">État actuel des ressources</p>
|
<p class="subtitle">État actuel des ressources</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 1.5rem;">
|
<div style="padding: 1rem; display: flex; flex-direction: column; gap: 1.5rem;">
|
||||||
|
|
||||||
<!-- Employés actifs -->
|
<!-- Employés actifs -->
|
||||||
<div class="statistic-item" style="padding: 1.25rem; background: var(--green-50); border-radius: var(--border-radius); border-left: 4px solid var(--green-500);">
|
<div class="statistic-item" style="padding: 1.25rem; background: var(--green-50); border-radius: var(--border-radius); border-left: 4px solid var(--green-500);">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
||||||
<i class="pi pi-users" style="font-size: 1.75rem; color: var(--green-500);"></i>
|
<i class="pi pi-users" style="font-size: 1.75rem; color: var(--green-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.5rem; color: var(--green-600);">
|
<h5 style="margin: 0; font-size: 1.5rem; color: var(--green-600);">
|
||||||
#{dashboardView.employesActifs}<span style="font-size: 1rem; color: var(--text-color-secondary);">/#{dashboardView.nombreEmployes}</span>
|
#{dashboardView.employesActifs}<span style="font-size: 1rem; color: var(--text-color-secondary);">/#{dashboardView.nombreEmployes}</span>
|
||||||
</h5>
|
</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Employés actifs</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Employés actifs</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p:progressBar value="#{dashboardView.tauxActiviteEmployes}"
|
<p:progressBar value="#{dashboardView.tauxActiviteEmployes}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
displayValue="#{dashboardView.tauxActiviteEmployes}%"
|
displayValue="#{dashboardView.tauxActiviteEmployes}%"
|
||||||
styleClass="ui-progressbar-#{dashboardView.tauxActiviteEmployes > 80 ? 'success' : (dashboardView.tauxActiviteEmployes > 60 ? 'warning' : 'danger')}"
|
styleClass="ui-progressbar-#{dashboardView.tauxActiviteEmployes > 80 ? 'success' : (dashboardView.tauxActiviteEmployes > 60 ? 'warning' : 'danger')}"
|
||||||
style="height: 1rem;"/>
|
style="height: 1rem;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Matériel disponible -->
|
<!-- Matériel disponible -->
|
||||||
<div class="statistic-item" style="padding: 1.25rem; background: var(--teal-50); border-radius: var(--border-radius); border-left: 4px solid var(--teal-500);">
|
<div class="statistic-item" style="padding: 1.25rem; background: var(--teal-50); border-radius: var(--border-radius); border-left: 4px solid var(--teal-500);">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
|
||||||
<i class="pi pi-cog" style="font-size: 1.75rem; color: var(--teal-500);"></i>
|
<i class="pi pi-cog" style="font-size: 1.75rem; color: var(--teal-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.5rem; color: var(--teal-600);">
|
<h5 style="margin: 0; font-size: 1.5rem; color: var(--teal-600);">
|
||||||
#{dashboardView.materielDisponible}<span style="font-size: 1rem; color: var(--text-color-secondary);">/#{dashboardView.nombreMateriel}</span>
|
#{dashboardView.materielDisponible}<span style="font-size: 1rem; color: var(--text-color-secondary);">/#{dashboardView.nombreMateriel}</span>
|
||||||
</h5>
|
</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Matériel disponible</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Matériel disponible</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p:progressBar value="#{dashboardView.tauxDisponibiliteMateriel}"
|
<p:progressBar value="#{dashboardView.tauxDisponibiliteMateriel}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
displayValue="#{dashboardView.tauxDisponibiliteMateriel}%"
|
displayValue="#{dashboardView.tauxDisponibiliteMateriel}%"
|
||||||
styleClass="ui-progressbar-success"
|
styleClass="ui-progressbar-success"
|
||||||
style="height: 1rem;"/>
|
style="height: 1rem;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Taux d'utilisation global -->
|
<!-- Taux d'utilisation global -->
|
||||||
<div class="statistic-item" style="padding: 1.25rem; background: var(--indigo-50); border-radius: var(--border-radius); border-left: 4px solid var(--indigo-500); flex: 1; display: flex; flex-direction: column; justify-content: center;">
|
<div class="statistic-item" style="padding: 1.25rem; background: var(--indigo-50); border-radius: var(--border-radius); border-left: 4px solid var(--indigo-500); flex: 1; display: flex; flex-direction: column; justify-content: center;">
|
||||||
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem;">
|
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem;">
|
||||||
<i class="pi pi-chart-line" style="font-size: 1.75rem; color: var(--indigo-500);"></i>
|
<i class="pi pi-chart-line" style="font-size: 1.75rem; color: var(--indigo-500);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<h5 style="margin: 0; font-size: 1.75rem; color: var(--indigo-600);">#{dashboardView.tauxUtilisationGlobal}%</h5>
|
<h5 style="margin: 0; font-size: 1.75rem; color: var(--indigo-600);">#{dashboardView.tauxUtilisationGlobal}%</h5>
|
||||||
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Taux d'utilisation global</h6>
|
<h6 style="margin: 0.25rem 0 0 0; font-weight: 500; color: var(--text-color-secondary);">Taux d'utilisation global</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small style="display: block; color: var(--text-color-secondary); font-style: italic; padding-left: 2.75rem;">
|
<small style="display: block; color: var(--text-color-secondary); font-style: italic; padding-left: 2.75rem;">
|
||||||
<i class="pi pi-info-circle" style="font-size: 0.875rem;"></i>
|
<i class="pi pi-info-circle" style="font-size: 0.875rem;"></i>
|
||||||
Moyenne chantiers, employés et matériel
|
Moyenne chantiers, employés et matériel
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ====================================================================
|
<!-- ====================================================================
|
||||||
TABLEAU CHANTIERS ACTIFS
|
TABLEAU CHANTIERS ACTIFS
|
||||||
==================================================================== -->
|
==================================================================== -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Chantiers actifs</h6>
|
<h6>Chantiers actifs</h6>
|
||||||
<p class="subtitle">#{dashboardView.chantiersActifsList.size()} chantiers en cours</p>
|
<p class="subtitle">#{dashboardView.chantiersActifsList.size()} chantiers en cours</p>
|
||||||
</div>
|
</div>
|
||||||
<p:commandButton value="Voir tout"
|
<p:commandButton value="Voir tout"
|
||||||
icon="pi pi-arrow-right"
|
icon="pi pi-arrow-right"
|
||||||
outcome="/chantiers"
|
outcome="/chantiers"
|
||||||
styleClass="ui-button-text"/>
|
styleClass="ui-button-text"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p:dataTable value="#{dashboardView.chantiersActifsList}"
|
<p:dataTable value="#{dashboardView.chantiersActifsList}"
|
||||||
var="chantier"
|
var="chantier"
|
||||||
emptyMessage="Aucun chantier actif pour le moment"
|
emptyMessage="Aucun chantier actif pour le moment"
|
||||||
styleClass="p-datatable-sm"
|
styleClass="p-datatable-sm"
|
||||||
paginator="true"
|
paginator="true"
|
||||||
rows="10"
|
rows="10"
|
||||||
paginatorPosition="bottom">
|
paginatorPosition="bottom">
|
||||||
|
|
||||||
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
<p:column headerText="Nom" sortBy="#{chantier.nom}">
|
||||||
<h:outputText value="#{chantier.nom}"/>
|
<h:outputText value="#{chantier.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Client" sortBy="#{chantier.client}">
|
<p:column headerText="Client" sortBy="#{chantier.client}">
|
||||||
<h:outputText value="#{chantier.client}"/>
|
<h:outputText value="#{chantier.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
<p:column headerText="Date début" sortBy="#{chantier.dateDebut}">
|
||||||
<h:outputText value="#{chantier.dateDebutFormatee}"/>
|
<h:outputText value="#{chantier.dateDebutFormatee}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Fin prévue" sortBy="#{chantier.dateFinPrevue}">
|
<p:column headerText="Fin prévue" sortBy="#{chantier.dateFinPrevue}">
|
||||||
<h:outputText value="#{chantier.dateFinPrevueFormatee}"/>
|
<h:outputText value="#{chantier.dateFinPrevueFormatee}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Avancement">
|
<p:column headerText="Avancement">
|
||||||
<p:progressBar value="#{chantier.avancement}"
|
<p:progressBar value="#{chantier.avancement}"
|
||||||
showValue="true"
|
showValue="true"
|
||||||
displayValue="#{chantier.avancement}%"
|
displayValue="#{chantier.avancement}%"
|
||||||
styleClass="ui-progressbar-success"/>
|
styleClass="ui-progressbar-success"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Budget" sortBy="#{chantier.budget}">
|
<p:column headerText="Budget" sortBy="#{chantier.budget}">
|
||||||
<h:outputText value="#{chantier.budget}">
|
<h:outputText value="#{chantier.budget}">
|
||||||
<f:convertNumber type="number" groupingUsed="true"/>
|
<f:convertNumber type="number" groupingUsed="true"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Coût réel" sortBy="#{chantier.coutReel}">
|
<p:column headerText="Coût réel" sortBy="#{chantier.coutReel}">
|
||||||
<h:outputText value="#{chantier.coutReel}"
|
<h:outputText value="#{chantier.coutReel}"
|
||||||
style="#{chantier.depassementBudget ? 'color: var(--red-500); font-weight: bold;' : ''}">
|
style="#{chantier.depassementBudget ? 'color: var(--red-500); font-weight: bold;' : ''}">
|
||||||
<f:convertNumber type="number" groupingUsed="true"/>
|
<f:convertNumber type="number" groupingUsed="true"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
<p:badge value="!" severity="danger"
|
<p:badge value="!" severity="danger"
|
||||||
style="margin-left: 0.5rem;"
|
style="margin-left: 0.5rem;"
|
||||||
rendered="#{chantier.depassementBudget}"/>
|
rendered="#{chantier.depassementBudget}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
<p:column headerText="Statut">
|
<p:column headerText="Statut">
|
||||||
<p:badge value="#{chantier.statut}"
|
<p:badge value="#{chantier.statut}"
|
||||||
severity="#{chantier.statut == 'EN_COURS' ? 'info' : 'success'}"/>
|
severity="#{chantier.statut == 'EN_COURS' ? 'info' : 'success'}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
|
||||||
</p:dataTable>
|
</p:dataTable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ====================================================================
|
<!-- ====================================================================
|
||||||
SECTION BAS : Chantiers en retard + Maintenances en retard
|
SECTION BAS : Chantiers en retard + Maintenances en retard
|
||||||
==================================================================== -->
|
==================================================================== -->
|
||||||
|
|
||||||
<!-- Chantiers en retard -->
|
<!-- Chantiers en retard -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Chantiers en retard</h6>
|
<h6>Chantiers en retard</h6>
|
||||||
<p class="subtitle">#{dashboardView.chantiersEnRetardList.size()} chantiers en retard</p>
|
<p class="subtitle">#{dashboardView.chantiersEnRetardList.size()} chantiers en retard</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui:repeat value="#{dashboardView.chantiersEnRetardList}" var="chantier">
|
<ui:repeat value="#{dashboardView.chantiersEnRetardList}" var="chantier">
|
||||||
<div class="chantier-retard-item" style="padding: 1rem; border-bottom: 1px solid var(--surface-border); background: var(--orange-50);">
|
<div class="chantier-retard-item" style="padding: 1rem; border-bottom: 1px solid var(--surface-border); background: var(--orange-50);">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: start;">
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
||||||
<div>
|
<div>
|
||||||
<h6 style="margin: 0 0 0.5rem 0;">
|
<h6 style="margin: 0 0 0.5rem 0;">
|
||||||
<i class="pi pi-building" style="color: var(--orange-500);"></i>
|
<i class="pi pi-building" style="color: var(--orange-500);"></i>
|
||||||
#{chantier.nom}
|
#{chantier.nom}
|
||||||
</h6>
|
</h6>
|
||||||
<p style="margin: 0.25rem 0; font-size: 0.9rem;">
|
<p style="margin: 0.25rem 0; font-size: 0.9rem;">
|
||||||
<strong>Date fin prévue:</strong> #{chantier.dateFinPrevueFormatee}
|
<strong>Date fin prévue:</strong> #{chantier.dateFinPrevueFormatee}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p:badge value="+#{chantier.joursRetard}j" severity="warning" size="large"/>
|
<p:badge value="+#{chantier.joursRetard}j" severity="warning" size="large"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:repeat>
|
</ui:repeat>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{empty dashboardView.chantiersEnRetardList}">
|
<p:outputPanel rendered="#{empty dashboardView.chantiersEnRetardList}">
|
||||||
<div style="padding: 2rem; text-align: center; color: var(--green-500);">
|
<div style="padding: 2rem; text-align: center; color: var(--green-500);">
|
||||||
<i class="pi pi-check-circle" style="font-size: 3rem;"></i>
|
<i class="pi pi-check-circle" style="font-size: 3rem;"></i>
|
||||||
<p style="margin-top: 1rem;">Tous les chantiers sont dans les temps</p>
|
<p style="margin-top: 1rem;">Tous les chantiers sont dans les temps</p>
|
||||||
</div>
|
</div>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Maintenances en retard -->
|
<!-- Maintenances en retard -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Maintenances en retard</h6>
|
<h6>Maintenances en retard</h6>
|
||||||
<p class="subtitle">#{dashboardView.maintenancesEnRetardList.size()} maintenances urgentes</p>
|
<p class="subtitle">#{dashboardView.maintenancesEnRetardList.size()} maintenances urgentes</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui:repeat value="#{dashboardView.maintenancesEnRetardList}" var="maintenance">
|
<ui:repeat value="#{dashboardView.maintenancesEnRetardList}" var="maintenance">
|
||||||
<div class="maintenance-item" style="padding: 1rem; border-bottom: 1px solid var(--surface-border); background: var(--red-50);">
|
<div class="maintenance-item" style="padding: 1rem; border-bottom: 1px solid var(--surface-border); background: var(--red-50);">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: start;">
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
||||||
<div>
|
<div>
|
||||||
<h6 style="margin: 0 0 0.5rem 0;">
|
<h6 style="margin: 0 0 0.5rem 0;">
|
||||||
<i class="pi pi-wrench" style="color: var(--red-500);"></i>
|
<i class="pi pi-wrench" style="color: var(--red-500);"></i>
|
||||||
#{maintenance.materiel}
|
#{maintenance.materiel}
|
||||||
</h6>
|
</h6>
|
||||||
<p style="margin: 0.25rem 0; font-size: 0.9rem;">
|
<p style="margin: 0.25rem 0; font-size: 0.9rem;">
|
||||||
<strong>Type:</strong> #{maintenance.type} •
|
<strong>Type:</strong> #{maintenance.type} •
|
||||||
<strong>Prévue:</strong> #{maintenance.datePrevueFormatee}
|
<strong>Prévue:</strong> #{maintenance.datePrevueFormatee}
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 0.25rem 0; font-size: 0.85rem; color: var(--text-color-secondary);">
|
<p style="margin: 0.25rem 0; font-size: 0.85rem; color: var(--text-color-secondary);">
|
||||||
#{maintenance.description}
|
#{maintenance.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p:badge value="+#{maintenance.joursRetard}j" severity="danger" size="large"/>
|
<p:badge value="+#{maintenance.joursRetard}j" severity="danger" size="large"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:repeat>
|
</ui:repeat>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{empty dashboardView.maintenancesEnRetardList}">
|
<p:outputPanel rendered="#{empty dashboardView.maintenancesEnRetardList}">
|
||||||
<div style="padding: 2rem; text-align: center; color: var(--green-500);">
|
<div style="padding: 2rem; text-align: center; color: var(--green-500);">
|
||||||
<i class="pi pi-check-circle" style="font-size: 3rem;"></i>
|
<i class="pi pi-check-circle" style="font-size: 3rem;"></i>
|
||||||
<p style="margin-top: 1rem;">Toutes les maintenances sont à jour</p>
|
<p style="margin-top: 1rem;">Toutes les maintenances sont à jour</p>
|
||||||
</div>
|
</div>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ====================================================================
|
<!-- ====================================================================
|
||||||
SECTION BAS 2 : Disponibilités en attente + Documents récents
|
SECTION BAS 2 : Disponibilités en attente + Documents récents
|
||||||
==================================================================== -->
|
==================================================================== -->
|
||||||
|
|
||||||
<!-- Disponibilités en attente -->
|
<!-- Disponibilités en attente -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Disponibilités en attente</h6>
|
<h6>Disponibilités en attente</h6>
|
||||||
<p class="subtitle">#{dashboardView.disponibilitesEnAttenteList.size()} demandes à valider</p>
|
<p class="subtitle">#{dashboardView.disponibilitesEnAttenteList.size()} demandes à valider</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui:repeat value="#{dashboardView.disponibilitesEnAttenteList}" var="dispo">
|
<ui:repeat value="#{dashboardView.disponibilitesEnAttenteList}" var="dispo">
|
||||||
<div class="disponibilite-card" style="padding: 1rem; border-bottom: 1px solid var(--surface-border);">
|
<div class="disponibilite-card" style="padding: 1rem; border-bottom: 1px solid var(--surface-border);">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: start;">
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
||||||
<i class="pi pi-user"></i>
|
<i class="pi pi-user"></i>
|
||||||
<strong>#{dispo.employe}</strong>
|
<strong>#{dispo.employe}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin: 0.5rem 0;">
|
<div style="margin: 0.5rem 0;">
|
||||||
<p:badge value="#{dispo.type}"
|
<p:badge value="#{dispo.type}"
|
||||||
severity="#{dashboardView.getSeveriteDisponibilite(dispo.type)}"/>
|
severity="#{dashboardView.getSeveriteDisponibilite(dispo.type)}"/>
|
||||||
<span style="margin-left: 0.5rem; font-size: 0.9rem;">
|
<span style="margin-left: 0.5rem; font-size: 0.9rem;">
|
||||||
Du #{dispo.dateDebutFormatee} au #{dispo.dateFinFormatee}
|
Du #{dispo.dateDebutFormatee} au #{dispo.dateFinFormatee}
|
||||||
(#{dispo.nombreJours} jours)
|
(#{dispo.nombreJours} jours)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<small style="color: var(--text-color-secondary);">
|
<small style="color: var(--text-color-secondary);">
|
||||||
<strong>Motif:</strong> #{dispo.motif}
|
<strong>Motif:</strong> #{dispo.motif}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:repeat>
|
</ui:repeat>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{empty dashboardView.disponibilitesEnAttenteList}">
|
<p:outputPanel rendered="#{empty dashboardView.disponibilitesEnAttenteList}">
|
||||||
<div style="padding: 2rem; text-align: center; color: var(--text-color-secondary);">
|
<div style="padding: 2rem; text-align: center; color: var(--text-color-secondary);">
|
||||||
<i class="pi pi-inbox" style="font-size: 2rem;"></i>
|
<i class="pi pi-inbox" style="font-size: 2rem;"></i>
|
||||||
<p style="margin-top: 1rem;">Aucune demande de disponibilité en attente</p>
|
<p style="margin-top: 1rem;">Aucune demande de disponibilité en attente</p>
|
||||||
</div>
|
</div>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Documents récents -->
|
<!-- Documents récents -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Documents récents</h6>
|
<h6>Documents récents</h6>
|
||||||
<p class="subtitle">5 derniers documents ajoutés</p>
|
<p class="subtitle">5 derniers documents ajoutés</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="documents-list" style="list-style: none; padding: 0; margin: 0;">
|
<ul class="documents-list" style="list-style: none; padding: 0; margin: 0;">
|
||||||
<ui:repeat value="#{dashboardView.documentsRecentsList}" var="doc">
|
<ui:repeat value="#{dashboardView.documentsRecentsList}" var="doc">
|
||||||
<li style="padding: 1rem; border-bottom: 1px solid var(--surface-border); display: flex; align-items: center; gap: 1rem;">
|
<li style="padding: 1rem; border-bottom: 1px solid var(--surface-border); display: flex; align-items: center; gap: 1rem;">
|
||||||
<i class="#{dashboardView.getIconeDocument(doc.type)}"
|
<i class="#{dashboardView.getIconeDocument(doc.type)}"
|
||||||
style="font-size: 2rem; color: var(--primary-color);"></i>
|
style="font-size: 2rem; color: var(--primary-color);"></i>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<div style="font-weight: 500;">#{doc.nom}</div>
|
<div style="font-weight: 500;">#{doc.nom}</div>
|
||||||
<small style="color: var(--text-color-secondary);">
|
<small style="color: var(--text-color-secondary);">
|
||||||
#{doc.type} • Ajouté le #{doc.dateCreationFormatee}
|
#{doc.type} • Ajouté le #{doc.dateCreationFormatee}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<p:button icon="pi pi-download" styleClass="ui-button-text ui-button-sm"/>
|
<p:button icon="pi pi-download" styleClass="ui-button-text ui-button-sm"/>
|
||||||
</li>
|
</li>
|
||||||
</ui:repeat>
|
</ui:repeat>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p:outputPanel rendered="#{empty dashboardView.documentsRecentsList}">
|
<p:outputPanel rendered="#{empty dashboardView.documentsRecentsList}">
|
||||||
<div style="padding: 2rem; text-align: center; color: var(--text-color-secondary);">
|
<div style="padding: 2rem; text-align: center; color: var(--text-color-secondary);">
|
||||||
<i class="pi pi-file" style="font-size: 2rem;"></i>
|
<i class="pi pi-file" style="font-size: 2rem;"></i>
|
||||||
<p style="margin-top: 1rem;">Aucun document récent</p>
|
<p style="margin-top: 1rem;">Aucun document récent</p>
|
||||||
</div>
|
</div>
|
||||||
</p:outputPanel>
|
</p:outputPanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,112 +1,112 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Devis - BTP Xpress</ui:define>
|
<ui:define name="title">Devis - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Devis</h1>
|
<h1>Gestion des Devis</h1>
|
||||||
<p:commandButton value="Nouveau devis" icon="pi pi-plus"
|
<p:commandButton value="Nouveau devis" icon="pi pi-plus"
|
||||||
action="#{devisView.createNew()}"
|
action="#{devisView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{devisView}"/>
|
<ui:param name="viewBean" value="#{devisView}"/>
|
||||||
<ui:param name="tableId" value="devisTable"/>
|
<ui:param name="tableId" value="devisTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNumero" value="Numéro"/>
|
<h:outputLabel for="filtreNumero" value="Numéro"/>
|
||||||
<p:inputText id="filtreNumero" value="#{devisView.filtreNumero}"
|
<p:inputText id="filtreNumero" value="#{devisView.filtreNumero}"
|
||||||
placeholder="Rechercher par numéro..." style="width: 100%;"/>
|
placeholder="Rechercher par numéro..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreClient" value="Client"/>
|
<h:outputLabel for="filtreClient" value="Client"/>
|
||||||
<p:inputText id="filtreClient" value="#{devisView.filtreClient}"
|
<p:inputText id="filtreClient" value="#{devisView.filtreClient}"
|
||||||
placeholder="Rechercher par client..." style="width: 100%;"/>
|
placeholder="Rechercher par client..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreStatut" value="Statut"/>
|
<h:outputLabel for="filtreStatut" value="Statut"/>
|
||||||
<p:selectOneMenu id="filtreStatut" value="#{devisView.filtreStatut}" style="width: 100%;">
|
<p:selectOneMenu id="filtreStatut" value="#{devisView.filtreStatut}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
||||||
<f:selectItem itemLabel="En attente" itemValue="EN_ATTENTE"/>
|
<f:selectItem itemLabel="En attente" itemValue="EN_ATTENTE"/>
|
||||||
<f:selectItem itemLabel="Accepté" itemValue="ACCEPTE"/>
|
<f:selectItem itemLabel="Accepté" itemValue="ACCEPTE"/>
|
||||||
<f:selectItem itemLabel="Refusé" itemValue="REFUSE"/>
|
<f:selectItem itemLabel="Refusé" itemValue="REFUSE"/>
|
||||||
<f:selectItem itemLabel="Expiré" itemValue="EXPIRE"/>
|
<f:selectItem itemLabel="Expiré" itemValue="EXPIRE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="devisForm"/>
|
<ui:param name="formId" value="devisForm"/>
|
||||||
<ui:param name="tableId" value="devisTable"/>
|
<ui:param name="tableId" value="devisTable"/>
|
||||||
<ui:param name="viewBean" value="#{devisView}"/>
|
<ui:param name="viewBean" value="#{devisView}"/>
|
||||||
<ui:param name="var" value="devis"/>
|
<ui:param name="var" value="devis"/>
|
||||||
<ui:param name="title" value="Liste des devis"/>
|
<ui:param name="title" value="Liste des devis"/>
|
||||||
<ui:param name="createPath" value="/devis/nouveau"/>
|
<ui:param name="createPath" value="/devis/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Numéro" sortBy="#{devis.numero}">
|
<p:column headerText="Numéro" sortBy="#{devis.numero}">
|
||||||
<h:outputText value="#{devis.numero}"/>
|
<h:outputText value="#{devis.numero}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Objet" sortBy="#{devis.objet}">
|
<p:column headerText="Objet" sortBy="#{devis.objet}">
|
||||||
<h:outputText value="#{devis.objet}"/>
|
<h:outputText value="#{devis.objet}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Client" sortBy="#{devis.client}">
|
<p:column headerText="Client" sortBy="#{devis.client}">
|
||||||
<h:outputText value="#{devis.client}"/>
|
<h:outputText value="#{devis.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date émission" sortBy="#{devis.dateEmission}">
|
<p:column headerText="Date émission" sortBy="#{devis.dateEmission}">
|
||||||
<h:outputText value="#{devis.dateEmission}">
|
<h:outputText value="#{devis.dateEmission}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date validité" sortBy="#{devis.dateValidite}">
|
<p:column headerText="Date validité" sortBy="#{devis.dateValidite}">
|
||||||
<h:outputText value="#{devis.dateValidite}">
|
<h:outputText value="#{devis.dateValidite}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Montant HT">
|
<p:column headerText="Montant HT">
|
||||||
<h:outputText value="#{devis.montantHT}">
|
<h:outputText value="#{devis.montantHT}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Montant TTC">
|
<p:column headerText="Montant TTC">
|
||||||
<h:outputText value="#{devis.montantTTC}">
|
<h:outputText value="#{devis.montantTTC}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Statut" sortBy="#{devis.statut}">
|
<p:column headerText="Statut" sortBy="#{devis.statut}">
|
||||||
<p:tag value="#{devis.statut}"
|
<p:tag value="#{devis.statut}"
|
||||||
severity="#{devis.statut == 'ACCEPTE' ? 'success' : (devis.statut == 'REFUSE' ? 'danger' : (devis.statut == 'EXPIRE' ? 'warning' : 'info'))}"/>
|
severity="#{devis.statut == 'ACCEPTE' ? 'success' : (devis.statut == 'REFUSE' ? 'danger' : (devis.statut == 'EXPIRE' ? 'warning' : 'info'))}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{devisView.viewDetails(devis.id)}"/>
|
action="#{devisView.viewDetails(devis.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Devis acceptés - BTP Xpress</ui:define>
|
<ui:define name="title">Devis acceptés - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Devis acceptés</h6>
|
<h6>Devis acceptés</h6>
|
||||||
<p class="subtitle">Devis acceptés par les clients</p>
|
<p class="subtitle">Devis acceptés par les clients</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Attente - DEVIS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Attente</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/devis" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Attente - DEVIS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Attente</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/devis" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Devis expirés - BTP Xpress</ui:define>
|
<ui:define name="title">Devis expirés - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Devis expirés</h6>
|
<h6>Devis expirés</h6>
|
||||||
<p class="subtitle">Devis dont la validité est expirée</p>
|
<p class="subtitle">Devis dont la validité est expirée</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,312 +1,312 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Nouveau devis - BTP Xpress</ui:define>
|
<ui:define name="title">Nouveau devis - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<!-- En-tête avec breadcrumb -->
|
<!-- En-tête avec breadcrumb -->
|
||||||
<div class="flex align-items-center justify-content-between mb-4">
|
<div class="flex align-items-center justify-content-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-900 font-bold mb-2">Créer un nouveau devis</h2>
|
<h2 class="text-900 font-bold mb-2">Créer un nouveau devis</h2>
|
||||||
<p class="text-600 mt-0">Établissez un devis détaillé pour votre client</p>
|
<p class="text-600 mt-0">Établissez un devis détaillé pour votre client</p>
|
||||||
</div>
|
</div>
|
||||||
<p:commandButton value="Retour à la liste"
|
<p:commandButton value="Retour à la liste"
|
||||||
icon="pi pi-arrow-left"
|
icon="pi pi-arrow-left"
|
||||||
outcome="/devis"
|
outcome="/devis"
|
||||||
styleClass="ui-button-secondary ui-button-outlined"/>
|
styleClass="ui-button-secondary ui-button-outlined"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p:messages id="messages" showDetail="true" closable="true"/>
|
<p:messages id="messages" showDetail="true" closable="true"/>
|
||||||
|
|
||||||
<h:form id="nouveauDevisForm" styleClass="p-fluid">
|
<h:form id="nouveauDevisForm" styleClass="p-fluid">
|
||||||
|
|
||||||
<!-- SECTION 1: Informations générales -->
|
<!-- SECTION 1: Informations générales -->
|
||||||
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Numéro (auto-généré) -->
|
<!-- Numéro (auto-généré) -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="numero" class="font-bold">Numéro de devis</label>
|
<label for="numero" class="font-bold">Numéro de devis</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon">
|
<span class="p-inputgroup-addon">
|
||||||
<i class="pi pi-hashtag"></i>
|
<i class="pi pi-hashtag"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputText id="numero"
|
<p:inputText id="numero"
|
||||||
value="#{devisView.entity.numero}"
|
value="#{devisView.entity.numero}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
placeholder="Auto-généré"
|
placeholder="Auto-généré"
|
||||||
styleClass="text-center font-bold"/>
|
styleClass="text-center font-bold"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Généré automatiquement lors de l'enregistrement</small>
|
<small class="text-600">Généré automatiquement lors de l'enregistrement</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Statut -->
|
<!-- Statut -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
|
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
|
||||||
<p:selectOneMenu id="statut"
|
<p:selectOneMenu id="statut"
|
||||||
value="#{devisView.entity.statut}"
|
value="#{devisView.entity.statut}"
|
||||||
required="true">
|
required="true">
|
||||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
||||||
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
||||||
<f:selectItem itemLabel="En attente" itemValue="ATTENTE"/>
|
<f:selectItem itemLabel="En attente" itemValue="ATTENTE"/>
|
||||||
<f:selectItem itemLabel="Accepté" itemValue="ACCEPTE"/>
|
<f:selectItem itemLabel="Accepté" itemValue="ACCEPTE"/>
|
||||||
<f:selectItem itemLabel="Refusé" itemValue="REFUSE"/>
|
<f:selectItem itemLabel="Refusé" itemValue="REFUSE"/>
|
||||||
<f:selectItem itemLabel="Expiré" itemValue="EXPIRE"/>
|
<f:selectItem itemLabel="Expiré" itemValue="EXPIRE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date d'émission -->
|
<!-- Date d'émission -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="dateEmission" class="font-bold">Date d'émission <span class="text-red-500">*</span></label>
|
<label for="dateEmission" class="font-bold">Date d'émission <span class="text-red-500">*</span></label>
|
||||||
<p:calendar id="dateEmission"
|
<p:calendar id="dateEmission"
|
||||||
value="#{devisView.entity.dateEmission}"
|
value="#{devisView.entity.dateEmission}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="La date d'émission est obligatoire"
|
requiredMessage="La date d'émission est obligatoire"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2030"
|
yearRange="2020:2030"
|
||||||
placeholder="Sélectionner une date">
|
placeholder="Sélectionner une date">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Client -->
|
<!-- Client -->
|
||||||
<div class="field col-12 md:col-8">
|
<div class="field col-12 md:col-8">
|
||||||
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
|
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
|
||||||
<p:inputText id="client"
|
<p:inputText id="client"
|
||||||
value="#{devisView.entity.client}"
|
value="#{devisView.entity.client}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le client est obligatoire"
|
requiredMessage="Le client est obligatoire"
|
||||||
placeholder="Ex: Entreprise ABC SARL">
|
placeholder="Ex: Entreprise ABC SARL">
|
||||||
<f:validateLength minimum="2" maximum="200"/>
|
<f:validateLength minimum="2" maximum="200"/>
|
||||||
</p:inputText>
|
</p:inputText>
|
||||||
<small class="text-600">Nom du client ou de l'entreprise</small>
|
<small class="text-600">Nom du client ou de l'entreprise</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date de validité -->
|
<!-- Date de validité -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="dateValidite" class="font-bold">Date de validité <span class="text-red-500">*</span></label>
|
<label for="dateValidite" class="font-bold">Date de validité <span class="text-red-500">*</span></label>
|
||||||
<p:calendar id="dateValidite"
|
<p:calendar id="dateValidite"
|
||||||
value="#{devisView.entity.dateValidite}"
|
value="#{devisView.entity.dateValidite}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="La date de validité est obligatoire"
|
requiredMessage="La date de validité est obligatoire"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2035"
|
yearRange="2020:2035"
|
||||||
mindate="#{devisView.entity.dateEmission}"
|
mindate="#{devisView.entity.dateEmission}"
|
||||||
placeholder="Sélectionner une date">
|
placeholder="Sélectionner une date">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
<small class="text-600">Date limite de validité du devis (généralement 30 jours)</small>
|
<small class="text-600">Date limite de validité du devis (généralement 30 jours)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Objet du devis -->
|
<!-- Objet du devis -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label for="objet" class="font-bold">Objet du devis <span class="text-red-500">*</span></label>
|
<label for="objet" class="font-bold">Objet du devis <span class="text-red-500">*</span></label>
|
||||||
<p:inputTextarea id="objet"
|
<p:inputTextarea id="objet"
|
||||||
value="#{devisView.entity.objet}"
|
value="#{devisView.entity.objet}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="L'objet du devis est obligatoire"
|
requiredMessage="L'objet du devis est obligatoire"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Ex: Construction d'un immeuble R+3 à usage résidentiel"
|
placeholder="Ex: Construction d'un immeuble R+3 à usage résidentiel"
|
||||||
autoResize="false">
|
autoResize="false">
|
||||||
<f:validateLength minimum="10" maximum="500"/>
|
<f:validateLength minimum="10" maximum="500"/>
|
||||||
</p:inputTextarea>
|
</p:inputTextarea>
|
||||||
<small class="text-600">Description détaillée de la prestation</small>
|
<small class="text-600">Description détaillée de la prestation</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 2: Lignes du devis -->
|
<!-- SECTION 2: Lignes du devis -->
|
||||||
<p:panel header="Détail du devis" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Détail du devis" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="surface-100 border-round p-3">
|
<div class="surface-100 border-round p-3">
|
||||||
<div class="flex align-items-center gap-2 mb-2">
|
<div class="flex align-items-center gap-2 mb-2">
|
||||||
<i class="pi pi-info-circle text-blue-500"></i>
|
<i class="pi pi-info-circle text-blue-500"></i>
|
||||||
<span class="text-900 font-medium">Lignes de devis</span>
|
<span class="text-900 font-medium">Lignes de devis</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-600 text-sm mt-0 mb-0">
|
<p class="text-600 text-sm mt-0 mb-0">
|
||||||
Ajoutez les différentes prestations, fournitures et main d'œuvre.
|
Ajoutez les différentes prestations, fournitures et main d'œuvre.
|
||||||
Cette fonctionnalité sera disponible dans une prochaine version.
|
Cette fonctionnalité sera disponible dans une prochaine version.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Placeholder pour table de lignes -->
|
<!-- Placeholder pour table de lignes -->
|
||||||
<div class="surface-50 border-round p-4 text-center">
|
<div class="surface-50 border-round p-4 text-center">
|
||||||
<i class="pi pi-list text-400 mb-3" style="font-size: 3rem;"></i>
|
<i class="pi pi-list text-400 mb-3" style="font-size: 3rem;"></i>
|
||||||
<p class="text-600 mt-0 mb-3">Gestion des lignes de devis en cours de développement</p>
|
<p class="text-600 mt-0 mb-3">Gestion des lignes de devis en cours de développement</p>
|
||||||
<p class="text-500 text-sm">
|
<p class="text-500 text-sm">
|
||||||
Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, TVA, etc.
|
Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, TVA, etc.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 3: Montants et totaux -->
|
<!-- SECTION 3: Montants et totaux -->
|
||||||
<p:panel header="Montants" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Montants" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Montant HT -->
|
<!-- Montant HT -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="montantHT" class="font-bold">Montant HT (FCFA) <span class="text-red-500">*</span></label>
|
<label for="montantHT" class="font-bold">Montant HT (FCFA) <span class="text-red-500">*</span></label>
|
||||||
<p:inputNumber id="montantHT"
|
<p:inputNumber id="montantHT"
|
||||||
value="#{devisView.entity.montantHT}"
|
value="#{devisView.entity.montantHT}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le montant HT est obligatoire"
|
requiredMessage="Le montant HT est obligatoire"
|
||||||
minValue="0"
|
minValue="0"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
placeholder="0">
|
placeholder="0">
|
||||||
</p:inputNumber>
|
</p:inputNumber>
|
||||||
<small class="text-600">Montant hors taxes</small>
|
<small class="text-600">Montant hors taxes</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TVA (calculée) -->
|
<!-- TVA (calculée) -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label class="font-bold">TVA (18%)</label>
|
<label class="font-bold">TVA (18%)</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon">
|
<span class="p-inputgroup-addon">
|
||||||
<i class="pi pi-percentage"></i>
|
<i class="pi pi-percentage"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputNumber value="#{devisView.entity.montantHT * 0.18}"
|
<p:inputNumber value="#{devisView.entity.montantHT * 0.18}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
styleClass="text-center font-medium"/>
|
styleClass="text-center font-medium"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Calculé automatiquement (18% du montant HT)</small>
|
<small class="text-600">Calculé automatiquement (18% du montant HT)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Montant TTC (calculé) -->
|
<!-- Montant TTC (calculé) -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label class="font-bold">Montant TTC (FCFA)</label>
|
<label class="font-bold">Montant TTC (FCFA)</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon bg-primary">
|
<span class="p-inputgroup-addon bg-primary">
|
||||||
<i class="pi pi-dollar text-white"></i>
|
<i class="pi pi-dollar text-white"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputNumber value="#{devisView.entity.montantHT * 1.18}"
|
<p:inputNumber value="#{devisView.entity.montantHT * 1.18}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
styleClass="text-center font-bold text-xl text-primary"/>
|
styleClass="text-center font-bold text-xl text-primary"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Montant toutes taxes comprises (HT + TVA)</small>
|
<small class="text-600">Montant toutes taxes comprises (HT + TVA)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Récapitulatif visuel -->
|
<!-- Récapitulatif visuel -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<div class="surface-100 border-round p-3">
|
<div class="surface-100 border-round p-3">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">Montant HT</span>
|
<span class="text-600 text-sm block mb-2">Montant HT</span>
|
||||||
<div class="text-900 font-bold text-xl">
|
<div class="text-900 font-bold text-xl">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{devisView.entity.montantHT}"/>
|
<ui:param name="amount" value="#{devisView.entity.montantHT}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">TVA (18%)</span>
|
<span class="text-600 text-sm block mb-2">TVA (18%)</span>
|
||||||
<div class="text-orange-600 font-bold text-xl">
|
<div class="text-orange-600 font-bold text-xl">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{devisView.entity.montantHT * 0.18}"/>
|
<ui:param name="amount" value="#{devisView.entity.montantHT * 0.18}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">Total TTC</span>
|
<span class="text-600 text-sm block mb-2">Total TTC</span>
|
||||||
<div class="text-primary font-bold text-2xl">
|
<div class="text-primary font-bold text-2xl">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{devisView.entity.montantHT * 1.18}"/>
|
<ui:param name="amount" value="#{devisView.entity.montantHT * 1.18}"/>
|
||||||
<ui:param name="size" value="large"/>
|
<ui:param name="size" value="large"/>
|
||||||
<ui:param name="bold" value="true"/>
|
<ui:param name="bold" value="true"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 4: Conditions (optionnel) -->
|
<!-- SECTION 4: Conditions (optionnel) -->
|
||||||
<p:panel header="Conditions et remarques" toggleable="true" collapsed="true" class="mb-4">
|
<p:panel header="Conditions et remarques" toggleable="true" collapsed="true" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label for="conditions" class="font-bold">Conditions de paiement</label>
|
<label for="conditions" class="font-bold">Conditions de paiement</label>
|
||||||
<p:inputTextarea id="conditions"
|
<p:inputTextarea id="conditions"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Ex: Paiement en 3 fois : 30% à la commande, 40% à mi-parcours, 30% à la livraison"
|
placeholder="Ex: Paiement en 3 fois : 30% à la commande, 40% à mi-parcours, 30% à la livraison"
|
||||||
autoResize="false">
|
autoResize="false">
|
||||||
</p:inputTextarea>
|
</p:inputTextarea>
|
||||||
<small class="text-600">Détaillez les modalités de paiement</small>
|
<small class="text-600">Détaillez les modalités de paiement</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label for="remarques" class="font-bold">Remarques</label>
|
<label for="remarques" class="font-bold">Remarques</label>
|
||||||
<p:inputTextarea id="remarques"
|
<p:inputTextarea id="remarques"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Toutes remarques ou précisions supplémentaires"
|
placeholder="Toutes remarques ou précisions supplémentaires"
|
||||||
autoResize="false">
|
autoResize="false">
|
||||||
</p:inputTextarea>
|
</p:inputTextarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- Boutons d'action -->
|
<!-- Boutons d'action -->
|
||||||
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
|
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-600 text-sm">Les champs marqués d'un </span>
|
<span class="text-600 text-sm">Les champs marqués d'un </span>
|
||||||
<span class="text-red-500 font-bold">*</span>
|
<span class="text-red-500 font-bold">*</span>
|
||||||
<span class="text-600 text-sm"> sont obligatoires</span>
|
<span class="text-600 text-sm"> sont obligatoires</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<p:commandButton value="Annuler"
|
<p:commandButton value="Annuler"
|
||||||
icon="pi pi-times"
|
icon="pi pi-times"
|
||||||
action="/devis?faces-redirect=true"
|
action="/devis?faces-redirect=true"
|
||||||
styleClass="ui-button-secondary"
|
styleClass="ui-button-secondary"
|
||||||
immediate="true"/>
|
immediate="true"/>
|
||||||
<p:commandButton value="Enregistrer comme brouillon"
|
<p:commandButton value="Enregistrer comme brouillon"
|
||||||
icon="pi pi-save"
|
icon="pi pi-save"
|
||||||
action="#{devisView.save}"
|
action="#{devisView.save}"
|
||||||
update="@form messages"
|
update="@form messages"
|
||||||
oncomplete="if (args && !args.validationFailed) window.location.href='/devis.xhtml';"
|
oncomplete="if (args && !args.validationFailed) window.location.href='/devis.xhtml';"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
<p:commandButton value="Enregistrer et envoyer"
|
<p:commandButton value="Enregistrer et envoyer"
|
||||||
icon="pi pi-send"
|
icon="pi pi-send"
|
||||||
action="#{devisView.save}"
|
action="#{devisView.save}"
|
||||||
update="@form messages"
|
update="@form messages"
|
||||||
oncomplete="if (args && !args.validationFailed) window.location.href='/devis.xhtml';"
|
oncomplete="if (args && !args.validationFailed) window.location.href='/devis.xhtml';"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Documentation - BTP Xpress</ui:define>
|
<ui:define name="title">Documentation - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Documentation</h1>
|
<h1>Documentation</h1>
|
||||||
<p>Documentation de l'application BTP Xpress</p>
|
<p>Documentation de l'application BTP Xpress</p>
|
||||||
<p>Module en cours de développement...</p>
|
<p>Module en cours de développement...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Documents - BTP Xpress</ui:define>
|
<ui:define name="title">Documents - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Documents</h6>
|
<h6>Documents</h6>
|
||||||
<p class="subtitle">Gestion des documents</p>
|
<p class="subtitle">Gestion des documents</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +1,102 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Employés - BTP Xpress</ui:define>
|
<ui:define name="title">Employés - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Employés</h1>
|
<h1>Gestion des Employés</h1>
|
||||||
<p:commandButton value="Nouvel employé" icon="pi pi-user-plus"
|
<p:commandButton value="Nouvel employé" icon="pi pi-user-plus"
|
||||||
action="#{employeView.createNew()}"
|
action="#{employeView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{employeView}"/>
|
<ui:param name="viewBean" value="#{employeView}"/>
|
||||||
<ui:param name="tableId" value="employesTable"/>
|
<ui:param name="tableId" value="employesTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNom" value="Nom"/>
|
<h:outputLabel for="filtreNom" value="Nom"/>
|
||||||
<p:inputText id="filtreNom" value="#{employeView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{employeView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtrePoste" value="Poste"/>
|
<h:outputLabel for="filtrePoste" value="Poste"/>
|
||||||
<p:inputText id="filtrePoste" value="#{employeView.filtrePoste}"
|
<p:inputText id="filtrePoste" value="#{employeView.filtrePoste}"
|
||||||
placeholder="Rechercher par poste..." style="width: 100%;"/>
|
placeholder="Rechercher par poste..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreStatut" value="Statut"/>
|
<h:outputLabel for="filtreStatut" value="Statut"/>
|
||||||
<p:selectOneMenu id="filtreStatut" value="#{employeView.filtreStatut}" style="width: 100%;">
|
<p:selectOneMenu id="filtreStatut" value="#{employeView.filtreStatut}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="Actif" itemValue="ACTIF"/>
|
<f:selectItem itemLabel="Actif" itemValue="ACTIF"/>
|
||||||
<f:selectItem itemLabel="Inactif" itemValue="INACTIF"/>
|
<f:selectItem itemLabel="Inactif" itemValue="INACTIF"/>
|
||||||
<f:selectItem itemLabel="En congé" itemValue="EN_CONGE"/>
|
<f:selectItem itemLabel="En congé" itemValue="EN_CONGE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="employesForm"/>
|
<ui:param name="formId" value="employesForm"/>
|
||||||
<ui:param name="tableId" value="employesTable"/>
|
<ui:param name="tableId" value="employesTable"/>
|
||||||
<ui:param name="viewBean" value="#{employeView}"/>
|
<ui:param name="viewBean" value="#{employeView}"/>
|
||||||
<ui:param name="var" value="employe"/>
|
<ui:param name="var" value="employe"/>
|
||||||
<ui:param name="title" value="Liste des employés"/>
|
<ui:param name="title" value="Liste des employés"/>
|
||||||
<ui:param name="createPath" value="/employes/nouveau"/>
|
<ui:param name="createPath" value="/employes/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom complet" sortBy="#{employe.nomComplet}">
|
<p:column headerText="Nom complet" sortBy="#{employe.nomComplet}">
|
||||||
<h:outputText value="#{employe.nomComplet}"/>
|
<h:outputText value="#{employe.nomComplet}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Email" sortBy="#{employe.email}">
|
<p:column headerText="Email" sortBy="#{employe.email}">
|
||||||
<h:outputText value="#{employe.email}"/>
|
<h:outputText value="#{employe.email}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Téléphone">
|
<p:column headerText="Téléphone">
|
||||||
<h:outputText value="#{employe.telephone}"/>
|
<h:outputText value="#{employe.telephone}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Poste" sortBy="#{employe.poste}">
|
<p:column headerText="Poste" sortBy="#{employe.poste}">
|
||||||
<h:outputText value="#{employe.poste}"/>
|
<h:outputText value="#{employe.poste}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Taux horaire">
|
<p:column headerText="Taux horaire">
|
||||||
<h:outputText value="#{employe.tauxHoraire}">
|
<h:outputText value="#{employe.tauxHoraire}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa/h"/>
|
<h:outputText value=" Fcfa/h"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date embauche" sortBy="#{employe.dateEmbauche}">
|
<p:column headerText="Date embauche" sortBy="#{employe.dateEmbauche}">
|
||||||
<h:outputText value="#{employe.dateEmbauche}">
|
<h:outputText value="#{employe.dateEmbauche}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Statut" sortBy="#{employe.statut}">
|
<p:column headerText="Statut" sortBy="#{employe.statut}">
|
||||||
<p:tag value="#{employe.statut}"
|
<p:tag value="#{employe.statut}"
|
||||||
severity="#{employe.statut == 'ACTIF' ? 'success' : 'warning'}"/>
|
severity="#{employe.statut == 'ACTIF' ? 'success' : 'warning'}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{employeView.viewDetails(employe.id)}"/>
|
action="#{employeView.viewDetails(employe.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Actifs - EMPLOYES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Actifs</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/employes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Actifs - EMPLOYES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Actifs</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/employes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Disponibles - EMPLOYES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Disponibles</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/employes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Disponibles - EMPLOYES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Disponibles</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/employes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">EMPLOYES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>EMPLOYES</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">EMPLOYES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>EMPLOYES</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,93 +1,93 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Équipes - BTP Xpress</ui:define>
|
<ui:define name="title">Équipes - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Équipes</h1>
|
<h1>Gestion des Équipes</h1>
|
||||||
<p:commandButton value="Nouvelle équipe" icon="pi pi-users"
|
<p:commandButton value="Nouvelle équipe" icon="pi pi-users"
|
||||||
action="#{equipeView.createNew()}"
|
action="#{equipeView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{equipeView}"/>
|
<ui:param name="viewBean" value="#{equipeView}"/>
|
||||||
<ui:param name="tableId" value="equipesTable"/>
|
<ui:param name="tableId" value="equipesTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNom" value="Nom de l'équipe"/>
|
<h:outputLabel for="filtreNom" value="Nom de l'équipe"/>
|
||||||
<p:inputText id="filtreNom" value="#{equipeView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{equipeView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreSpecialite" value="Spécialité"/>
|
<h:outputLabel for="filtreSpecialite" value="Spécialité"/>
|
||||||
<p:inputText id="filtreSpecialite" value="#{equipeView.filtreSpecialite}"
|
<p:inputText id="filtreSpecialite" value="#{equipeView.filtreSpecialite}"
|
||||||
placeholder="Rechercher par spécialité..." style="width: 100%;"/>
|
placeholder="Rechercher par spécialité..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreStatut" value="Statut"/>
|
<h:outputLabel for="filtreStatut" value="Statut"/>
|
||||||
<p:selectOneMenu id="filtreStatut" value="#{equipeView.filtreStatut}" style="width: 100%;">
|
<p:selectOneMenu id="filtreStatut" value="#{equipeView.filtreStatut}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="Active" itemValue="ACTIVE"/>
|
<f:selectItem itemLabel="Active" itemValue="ACTIVE"/>
|
||||||
<f:selectItem itemLabel="Inactive" itemValue="INACTIVE"/>
|
<f:selectItem itemLabel="Inactive" itemValue="INACTIVE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="equipesForm"/>
|
<ui:param name="formId" value="equipesForm"/>
|
||||||
<ui:param name="tableId" value="equipesTable"/>
|
<ui:param name="tableId" value="equipesTable"/>
|
||||||
<ui:param name="viewBean" value="#{equipeView}"/>
|
<ui:param name="viewBean" value="#{equipeView}"/>
|
||||||
<ui:param name="var" value="equipe"/>
|
<ui:param name="var" value="equipe"/>
|
||||||
<ui:param name="title" value="Liste des équipes"/>
|
<ui:param name="title" value="Liste des équipes"/>
|
||||||
<ui:param name="createPath" value="/equipes/nouveau"/>
|
<ui:param name="createPath" value="/equipes/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom" sortBy="#{equipe.nom}">
|
<p:column headerText="Nom" sortBy="#{equipe.nom}">
|
||||||
<h:outputText value="#{equipe.nom}"/>
|
<h:outputText value="#{equipe.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Chef d'équipe" sortBy="#{equipe.chef}">
|
<p:column headerText="Chef d'équipe" sortBy="#{equipe.chef}">
|
||||||
<h:outputText value="#{equipe.chef}"/>
|
<h:outputText value="#{equipe.chef}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Spécialité" sortBy="#{equipe.specialite}">
|
<p:column headerText="Spécialité" sortBy="#{equipe.specialite}">
|
||||||
<h:outputText value="#{equipe.specialite}"/>
|
<h:outputText value="#{equipe.specialite}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Nombre de membres">
|
<p:column headerText="Nombre de membres">
|
||||||
<p:tag value="#{equipe.nombreMembres}" severity="info"/>
|
<p:tag value="#{equipe.nombreMembres}" severity="info"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Description">
|
<p:column headerText="Description">
|
||||||
<h:outputText value="#{equipe.description}"/>
|
<h:outputText value="#{equipe.description}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Statut" sortBy="#{equipe.statut}">
|
<p:column headerText="Statut" sortBy="#{equipe.statut}">
|
||||||
<p:tag value="#{equipe.statut}"
|
<p:tag value="#{equipe.statut}"
|
||||||
severity="#{equipe.statut == 'ACTIVE' ? 'success' : 'warning'}"/>
|
severity="#{equipe.statut == 'ACTIVE' ? 'success' : 'warning'}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{equipeView.viewDetails(equipe.id)}"/>
|
action="#{equipeView.viewDetails(equipe.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Disponibles - EQUIPES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Disponibles</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/equipes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Disponibles - EQUIPES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Disponibles</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/equipes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">EQUIPES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>EQUIPES</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">EQUIPES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>EQUIPES</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Specialites - EQUIPES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Specialites</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/equipes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Specialites - EQUIPES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Specialites</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/equipes" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,122 +1,122 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Factures - BTP Xpress</ui:define>
|
<ui:define name="title">Factures - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Factures</h1>
|
<h1>Gestion des Factures</h1>
|
||||||
<p:commandButton value="Nouvelle facture" icon="pi pi-plus"
|
<p:commandButton value="Nouvelle facture" icon="pi pi-plus"
|
||||||
action="#{factureView.createNew()}"
|
action="#{factureView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{factureView}"/>
|
<ui:param name="viewBean" value="#{factureView}"/>
|
||||||
<ui:param name="tableId" value="facturesTable"/>
|
<ui:param name="tableId" value="facturesTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNumero" value="Numéro"/>
|
<h:outputLabel for="filtreNumero" value="Numéro"/>
|
||||||
<p:inputText id="filtreNumero" value="#{factureView.filtreNumero}"
|
<p:inputText id="filtreNumero" value="#{factureView.filtreNumero}"
|
||||||
placeholder="Rechercher par numéro..." style="width: 100%;"/>
|
placeholder="Rechercher par numéro..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreClient" value="Client"/>
|
<h:outputLabel for="filtreClient" value="Client"/>
|
||||||
<p:inputText id="filtreClient" value="#{factureView.filtreClient}"
|
<p:inputText id="filtreClient" value="#{factureView.filtreClient}"
|
||||||
placeholder="Rechercher par client..." style="width: 100%;"/>
|
placeholder="Rechercher par client..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreStatut" value="Statut"/>
|
<h:outputLabel for="filtreStatut" value="Statut"/>
|
||||||
<p:selectOneMenu id="filtreStatut" value="#{factureView.filtreStatut}" style="width: 100%;">
|
<p:selectOneMenu id="filtreStatut" value="#{factureView.filtreStatut}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
||||||
<f:selectItem itemLabel="Émise" itemValue="EMISE"/>
|
<f:selectItem itemLabel="Émise" itemValue="EMISE"/>
|
||||||
<f:selectItem itemLabel="Envoyée" itemValue="ENVOYEE"/>
|
<f:selectItem itemLabel="Envoyée" itemValue="ENVOYEE"/>
|
||||||
<f:selectItem itemLabel="Payée" itemValue="PAYEE"/>
|
<f:selectItem itemLabel="Payée" itemValue="PAYEE"/>
|
||||||
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD"/>
|
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD"/>
|
||||||
<f:selectItem itemLabel="Annulée" itemValue="ANNULEE"/>
|
<f:selectItem itemLabel="Annulée" itemValue="ANNULEE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="facturesForm"/>
|
<ui:param name="formId" value="facturesForm"/>
|
||||||
<ui:param name="tableId" value="facturesTable"/>
|
<ui:param name="tableId" value="facturesTable"/>
|
||||||
<ui:param name="viewBean" value="#{factureView}"/>
|
<ui:param name="viewBean" value="#{factureView}"/>
|
||||||
<ui:param name="var" value="facture"/>
|
<ui:param name="var" value="facture"/>
|
||||||
<ui:param name="title" value="Liste des factures"/>
|
<ui:param name="title" value="Liste des factures"/>
|
||||||
<ui:param name="createPath" value="/factures/nouveau"/>
|
<ui:param name="createPath" value="/factures/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Numéro" sortBy="#{facture.numero}">
|
<p:column headerText="Numéro" sortBy="#{facture.numero}">
|
||||||
<h:outputText value="#{facture.numero}"/>
|
<h:outputText value="#{facture.numero}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Objet" sortBy="#{facture.objet}">
|
<p:column headerText="Objet" sortBy="#{facture.objet}">
|
||||||
<h:outputText value="#{facture.objet}"/>
|
<h:outputText value="#{facture.objet}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Client" sortBy="#{facture.client}">
|
<p:column headerText="Client" sortBy="#{facture.client}">
|
||||||
<h:outputText value="#{facture.client}"/>
|
<h:outputText value="#{facture.client}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date émission" sortBy="#{facture.dateEmission}">
|
<p:column headerText="Date émission" sortBy="#{facture.dateEmission}">
|
||||||
<h:outputText value="#{facture.dateEmission}">
|
<h:outputText value="#{facture.dateEmission}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date échéance" sortBy="#{facture.dateEcheance}">
|
<p:column headerText="Date échéance" sortBy="#{facture.dateEcheance}">
|
||||||
<h:outputText value="#{facture.dateEcheance}">
|
<h:outputText value="#{facture.dateEcheance}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" ⚠️" rendered="#{factureView.isEnRetard(facture)}"
|
<h:outputText value=" ⚠️" rendered="#{factureView.isEnRetard(facture)}"
|
||||||
title="Facture en retard" style="color: red;"/>
|
title="Facture en retard" style="color: red;"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Montant TTC">
|
<p:column headerText="Montant TTC">
|
||||||
<h:outputText value="#{facture.montantTTC}">
|
<h:outputText value="#{facture.montantTTC}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Montant payé">
|
<p:column headerText="Montant payé">
|
||||||
<h:outputText value="#{facture.montantPaye}">
|
<h:outputText value="#{facture.montantPaye}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Reste à payer">
|
<p:column headerText="Reste à payer">
|
||||||
<h:outputText value="#{factureView.getMontantRestant(facture)}">
|
<h:outputText value="#{factureView.getMontantRestant(facture)}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"
|
<h:outputText value=" Fcfa"
|
||||||
style="#{factureView.getMontantRestant(facture) > 0 ? 'color: red; font-weight: bold;' : ''}"/>
|
style="#{factureView.getMontantRestant(facture) > 0 ? 'color: red; font-weight: bold;' : ''}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Statut" sortBy="#{facture.statut}">
|
<p:column headerText="Statut" sortBy="#{facture.statut}">
|
||||||
<p:tag value="#{facture.statut}"
|
<p:tag value="#{facture.statut}"
|
||||||
severity="#{facture.statut == 'PAYEE' ? 'success' : (facture.statut == 'ANNULEE' ? 'danger' : (factureView.isEnRetard(facture) ? 'danger' : 'warning'))}"/>
|
severity="#{facture.statut == 'PAYEE' ? 'success' : (facture.statut == 'ANNULEE' ? 'danger' : (factureView.isEnRetard(facture) ? 'danger' : 'warning'))}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{factureView.viewDetails(facture.id)}"/>
|
action="#{factureView.viewDetails(facture.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Impayees - FACTURES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Impayees</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/factures" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Impayees - FACTURES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Impayees</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/factures" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,392 +1,392 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Nouvelle facture - BTP Xpress</ui:define>
|
<ui:define name="title">Nouvelle facture - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<!-- En-tête avec breadcrumb -->
|
<!-- En-tête avec breadcrumb -->
|
||||||
<div class="flex align-items-center justify-content-between mb-4">
|
<div class="flex align-items-center justify-content-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-900 font-bold mb-2">Créer une nouvelle facture</h2>
|
<h2 class="text-900 font-bold mb-2">Créer une nouvelle facture</h2>
|
||||||
<p class="text-600 mt-0">Émettez une facture pour un chantier ou prestation réalisée</p>
|
<p class="text-600 mt-0">Émettez une facture pour un chantier ou prestation réalisée</p>
|
||||||
</div>
|
</div>
|
||||||
<p:commandButton value="Retour à la liste"
|
<p:commandButton value="Retour à la liste"
|
||||||
icon="pi pi-arrow-left"
|
icon="pi pi-arrow-left"
|
||||||
outcome="/factures"
|
outcome="/factures"
|
||||||
styleClass="ui-button-secondary ui-button-outlined"/>
|
styleClass="ui-button-secondary ui-button-outlined"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p:messages id="messages" showDetail="true" closable="true"/>
|
<p:messages id="messages" showDetail="true" closable="true"/>
|
||||||
|
|
||||||
<h:form id="nouvelleFactureForm" styleClass="p-fluid">
|
<h:form id="nouvelleFactureForm" styleClass="p-fluid">
|
||||||
|
|
||||||
<!-- SECTION 1: Informations générales -->
|
<!-- SECTION 1: Informations générales -->
|
||||||
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Numéro (auto-généré) -->
|
<!-- Numéro (auto-généré) -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="numero" class="font-bold">Numéro de facture</label>
|
<label for="numero" class="font-bold">Numéro de facture</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon bg-primary">
|
<span class="p-inputgroup-addon bg-primary">
|
||||||
<i class="pi pi-hashtag text-white"></i>
|
<i class="pi pi-hashtag text-white"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputText id="numero"
|
<p:inputText id="numero"
|
||||||
value="#{factureView.entity.numero}"
|
value="#{factureView.entity.numero}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
placeholder="Auto-généré"
|
placeholder="Auto-généré"
|
||||||
styleClass="text-center font-bold text-primary"/>
|
styleClass="text-center font-bold text-primary"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Généré automatiquement selon la séquence configurée</small>
|
<small class="text-600">Généré automatiquement selon la séquence configurée</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Statut -->
|
<!-- Statut -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
|
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
|
||||||
<p:selectOneMenu id="statut"
|
<p:selectOneMenu id="statut"
|
||||||
value="#{factureView.entity.statut}"
|
value="#{factureView.entity.statut}"
|
||||||
required="true">
|
required="true">
|
||||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
||||||
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
|
||||||
<f:selectItem itemLabel="Émise" itemValue="EMISE"/>
|
<f:selectItem itemLabel="Émise" itemValue="EMISE"/>
|
||||||
<f:selectItem itemLabel="Payée" itemValue="PAYEE"/>
|
<f:selectItem itemLabel="Payée" itemValue="PAYEE"/>
|
||||||
<f:selectItem itemLabel="Impayée" itemValue="IMPAYEE"/>
|
<f:selectItem itemLabel="Impayée" itemValue="IMPAYEE"/>
|
||||||
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD"/>
|
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD"/>
|
||||||
<f:selectItem itemLabel="Annulée" itemValue="ANNULEE"/>
|
<f:selectItem itemLabel="Annulée" itemValue="ANNULEE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Type de facture -->
|
<!-- Type de facture -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="typeFacture" class="font-bold">Type de facture</label>
|
<label for="typeFacture" class="font-bold">Type de facture</label>
|
||||||
<p:selectOneMenu id="typeFacture">
|
<p:selectOneMenu id="typeFacture">
|
||||||
<f:selectItem itemLabel="Standard" itemValue="STANDARD"/>
|
<f:selectItem itemLabel="Standard" itemValue="STANDARD"/>
|
||||||
<f:selectItem itemLabel="Acompte" itemValue="ACOMPTE"/>
|
<f:selectItem itemLabel="Acompte" itemValue="ACOMPTE"/>
|
||||||
<f:selectItem itemLabel="Solde" itemValue="SOLDE"/>
|
<f:selectItem itemLabel="Solde" itemValue="SOLDE"/>
|
||||||
<f:selectItem itemLabel="Avoir" itemValue="AVOIR"/>
|
<f:selectItem itemLabel="Avoir" itemValue="AVOIR"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
<small class="text-600">Nature de la facturation</small>
|
<small class="text-600">Nature de la facturation</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Client -->
|
<!-- Client -->
|
||||||
<div class="field col-12 md:col-8">
|
<div class="field col-12 md:col-8">
|
||||||
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
|
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
|
||||||
<p:inputText id="client"
|
<p:inputText id="client"
|
||||||
value="#{factureView.entity.client}"
|
value="#{factureView.entity.client}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le client est obligatoire"
|
requiredMessage="Le client est obligatoire"
|
||||||
placeholder="Ex: Entreprise ABC SARL">
|
placeholder="Ex: Entreprise ABC SARL">
|
||||||
<f:validateLength minimum="2" maximum="200"/>
|
<f:validateLength minimum="2" maximum="200"/>
|
||||||
</p:inputText>
|
</p:inputText>
|
||||||
<small class="text-600">Nom du client ou de l'entreprise</small>
|
<small class="text-600">Nom du client ou de l'entreprise</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date d'émission -->
|
<!-- Date d'émission -->
|
||||||
<div class="field col-12 md:col-4">
|
<div class="field col-12 md:col-4">
|
||||||
<label for="dateEmission" class="font-bold">Date d'émission <span class="text-red-500">*</span></label>
|
<label for="dateEmission" class="font-bold">Date d'émission <span class="text-red-500">*</span></label>
|
||||||
<p:calendar id="dateEmission"
|
<p:calendar id="dateEmission"
|
||||||
value="#{factureView.entity.dateEmission}"
|
value="#{factureView.entity.dateEmission}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="La date d'émission est obligatoire"
|
requiredMessage="La date d'émission est obligatoire"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2030"
|
yearRange="2020:2030"
|
||||||
placeholder="Sélectionner une date">
|
placeholder="Sélectionner une date">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Objet -->
|
<!-- Objet -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label for="objet" class="font-bold">Objet de la facture <span class="text-red-500">*</span></label>
|
<label for="objet" class="font-bold">Objet de la facture <span class="text-red-500">*</span></label>
|
||||||
<p:inputTextarea id="objet"
|
<p:inputTextarea id="objet"
|
||||||
value="#{factureView.entity.objet}"
|
value="#{factureView.entity.objet}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="L'objet de la facture est obligatoire"
|
requiredMessage="L'objet de la facture est obligatoire"
|
||||||
rows="2"
|
rows="2"
|
||||||
placeholder="Ex: Travaux de construction immeuble R+3 - Phase gros œuvre"
|
placeholder="Ex: Travaux de construction immeuble R+3 - Phase gros œuvre"
|
||||||
autoResize="false">
|
autoResize="false">
|
||||||
<f:validateLength minimum="10" maximum="500"/>
|
<f:validateLength minimum="10" maximum="500"/>
|
||||||
</p:inputTextarea>
|
</p:inputTextarea>
|
||||||
<small class="text-600">Description des prestations facturées</small>
|
<small class="text-600">Description des prestations facturées</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date d'échéance -->
|
<!-- Date d'échéance -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="dateEcheance" class="font-bold">Date d'échéance <span class="text-red-500">*</span></label>
|
<label for="dateEcheance" class="font-bold">Date d'échéance <span class="text-red-500">*</span></label>
|
||||||
<p:calendar id="dateEcheance"
|
<p:calendar id="dateEcheance"
|
||||||
value="#{factureView.entity.dateEcheance}"
|
value="#{factureView.entity.dateEcheance}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="La date d'échéance est obligatoire"
|
requiredMessage="La date d'échéance est obligatoire"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2035"
|
yearRange="2020:2035"
|
||||||
mindate="#{factureView.entity.dateEmission}"
|
mindate="#{factureView.entity.dateEmission}"
|
||||||
placeholder="Sélectionner une date">
|
placeholder="Sélectionner une date">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
<small class="text-600">Date limite de paiement (généralement 30 jours)</small>
|
<small class="text-600">Date limite de paiement (généralement 30 jours)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Date de paiement (optionnel) -->
|
<!-- Date de paiement (optionnel) -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="datePaiement" class="font-bold">Date de paiement</label>
|
<label for="datePaiement" class="font-bold">Date de paiement</label>
|
||||||
<p:calendar id="datePaiement"
|
<p:calendar id="datePaiement"
|
||||||
value="#{factureView.entity.datePaiement}"
|
value="#{factureView.entity.datePaiement}"
|
||||||
pattern="dd/MM/yyyy"
|
pattern="dd/MM/yyyy"
|
||||||
locale="fr"
|
locale="fr"
|
||||||
showIcon="true"
|
showIcon="true"
|
||||||
showButtonBar="true"
|
showButtonBar="true"
|
||||||
monthNavigator="true"
|
monthNavigator="true"
|
||||||
yearNavigator="true"
|
yearNavigator="true"
|
||||||
yearRange="2020:2030"
|
yearRange="2020:2030"
|
||||||
placeholder="À renseigner après paiement">
|
placeholder="À renseigner après paiement">
|
||||||
</p:calendar>
|
</p:calendar>
|
||||||
<small class="text-600">Date effective du paiement (optionnel)</small>
|
<small class="text-600">Date effective du paiement (optionnel)</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 2: Lignes de facture -->
|
<!-- SECTION 2: Lignes de facture -->
|
||||||
<p:panel header="Détail de la facture" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Détail de la facture" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="surface-100 border-round p-3">
|
<div class="surface-100 border-round p-3">
|
||||||
<div class="flex align-items-center gap-2 mb-2">
|
<div class="flex align-items-center gap-2 mb-2">
|
||||||
<i class="pi pi-info-circle text-blue-500"></i>
|
<i class="pi pi-info-circle text-blue-500"></i>
|
||||||
<span class="text-900 font-medium">Lignes de facturation</span>
|
<span class="text-900 font-medium">Lignes de facturation</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-600 text-sm mt-0 mb-0">
|
<p class="text-600 text-sm mt-0 mb-0">
|
||||||
Ajoutez les différentes prestations, fournitures et quantités facturées.
|
Ajoutez les différentes prestations, fournitures et quantités facturées.
|
||||||
Cette fonctionnalité sera disponible dans une prochaine version.
|
Cette fonctionnalité sera disponible dans une prochaine version.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Placeholder pour table de lignes -->
|
<!-- Placeholder pour table de lignes -->
|
||||||
<div class="surface-50 border-round p-4 text-center">
|
<div class="surface-50 border-round p-4 text-center">
|
||||||
<i class="pi pi-list text-400 mb-3" style="font-size: 3rem;"></i>
|
<i class="pi pi-list text-400 mb-3" style="font-size: 3rem;"></i>
|
||||||
<p class="text-600 mt-0 mb-3">Gestion des lignes de facture en cours de développement</p>
|
<p class="text-600 mt-0 mb-3">Gestion des lignes de facture en cours de développement</p>
|
||||||
<p class="text-500 text-sm">
|
<p class="text-500 text-sm">
|
||||||
Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, remise, etc.
|
Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, remise, etc.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 3: Montants -->
|
<!-- SECTION 3: Montants -->
|
||||||
<p:panel header="Montants et totaux" toggleable="true" collapsed="false" class="mb-4">
|
<p:panel header="Montants et totaux" toggleable="true" collapsed="false" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<!-- Montant HT -->
|
<!-- Montant HT -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="montantHT" class="font-bold">Montant HT (FCFA) <span class="text-red-500">*</span></label>
|
<label for="montantHT" class="font-bold">Montant HT (FCFA) <span class="text-red-500">*</span></label>
|
||||||
<p:inputNumber id="montantHT"
|
<p:inputNumber id="montantHT"
|
||||||
value="#{factureView.entity.montantHT}"
|
value="#{factureView.entity.montantHT}"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le montant HT est obligatoire"
|
requiredMessage="Le montant HT est obligatoire"
|
||||||
minValue="0"
|
minValue="0"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
placeholder="0">
|
placeholder="0">
|
||||||
</p:inputNumber>
|
</p:inputNumber>
|
||||||
<small class="text-600">Montant hors taxes</small>
|
<small class="text-600">Montant hors taxes</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TVA (calculée) -->
|
<!-- TVA (calculée) -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label class="font-bold">TVA (18%)</label>
|
<label class="font-bold">TVA (18%)</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon bg-orange-100">
|
<span class="p-inputgroup-addon bg-orange-100">
|
||||||
<i class="pi pi-percentage text-orange-600"></i>
|
<i class="pi pi-percentage text-orange-600"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputNumber value="#{factureView.entity.montantHT * 0.18}"
|
<p:inputNumber value="#{factureView.entity.montantHT * 0.18}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
styleClass="text-center font-medium text-orange-600"/>
|
styleClass="text-center font-medium text-orange-600"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Calculé automatiquement (18% du montant HT)</small>
|
<small class="text-600">Calculé automatiquement (18% du montant HT)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Montant TTC (calculé) -->
|
<!-- Montant TTC (calculé) -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label class="font-bold">Montant TTC (FCFA)</label>
|
<label class="font-bold">Montant TTC (FCFA)</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon bg-primary">
|
<span class="p-inputgroup-addon bg-primary">
|
||||||
<i class="pi pi-dollar text-white"></i>
|
<i class="pi pi-dollar text-white"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputNumber value="#{factureView.entity.montantHT * 1.18}"
|
<p:inputNumber value="#{factureView.entity.montantHT * 1.18}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
styleClass="text-center font-bold text-xl text-primary"/>
|
styleClass="text-center font-bold text-xl text-primary"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Montant toutes taxes comprises (HT + TVA)</small>
|
<small class="text-600">Montant toutes taxes comprises (HT + TVA)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Montant payé -->
|
<!-- Montant payé -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="montantPaye" class="font-bold">Montant payé (FCFA)</label>
|
<label for="montantPaye" class="font-bold">Montant payé (FCFA)</label>
|
||||||
<p:inputNumber id="montantPaye"
|
<p:inputNumber id="montantPaye"
|
||||||
value="#{factureView.entity.montantPaye}"
|
value="#{factureView.entity.montantPaye}"
|
||||||
minValue="0"
|
minValue="0"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
placeholder="0">
|
placeholder="0">
|
||||||
</p:inputNumber>
|
</p:inputNumber>
|
||||||
<small class="text-600">Montant déjà encaissé</small>
|
<small class="text-600">Montant déjà encaissé</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Montant restant (calculé) -->
|
<!-- Montant restant (calculé) -->
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label class="font-bold">Montant restant (FCFA)</label>
|
<label class="font-bold">Montant restant (FCFA)</label>
|
||||||
<div class="p-inputgroup">
|
<div class="p-inputgroup">
|
||||||
<span class="p-inputgroup-addon">
|
<span class="p-inputgroup-addon">
|
||||||
<i class="pi pi-exclamation-triangle"></i>
|
<i class="pi pi-exclamation-triangle"></i>
|
||||||
</span>
|
</span>
|
||||||
<p:inputNumber value="#{(factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye}"
|
<p:inputNumber value="#{(factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye}"
|
||||||
disabled="true"
|
disabled="true"
|
||||||
decimalPlaces="0"
|
decimalPlaces="0"
|
||||||
thousandSeparator=" "
|
thousandSeparator=" "
|
||||||
suffix=" FCFA"
|
suffix=" FCFA"
|
||||||
styleClass="text-center font-bold #{((factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye) > 0 ? 'text-red-600' : 'text-green-600'}"/>
|
styleClass="text-center font-bold #{((factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye) > 0 ? 'text-red-600' : 'text-green-600'}"/>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-600">Reste à encaisser</small>
|
<small class="text-600">Reste à encaisser</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Récapitulatif visuel -->
|
<!-- Récapitulatif visuel -->
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<div class="surface-100 border-round p-3">
|
<div class="surface-100 border-round p-3">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-3">
|
<div class="col-12 md:col-3">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">Montant HT</span>
|
<span class="text-600 text-sm block mb-2">Montant HT</span>
|
||||||
<div class="text-900 font-bold text-xl">
|
<div class="text-900 font-bold text-xl">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{factureView.entity.montantHT}"/>
|
<ui:param name="amount" value="#{factureView.entity.montantHT}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3">
|
<div class="col-12 md:col-3">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">TVA (18%)</span>
|
<span class="text-600 text-sm block mb-2">TVA (18%)</span>
|
||||||
<div class="text-orange-600 font-bold text-xl">
|
<div class="text-orange-600 font-bold text-xl">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{factureView.entity.montantHT * 0.18}"/>
|
<ui:param name="amount" value="#{factureView.entity.montantHT * 0.18}"/>
|
||||||
<ui:param name="size" value="normal"/>
|
<ui:param name="size" value="normal"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3">
|
<div class="col-12 md:col-3">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">Total TTC</span>
|
<span class="text-600 text-sm block mb-2">Total TTC</span>
|
||||||
<div class="text-primary font-bold text-2xl">
|
<div class="text-primary font-bold text-2xl">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{factureView.entity.montantHT * 1.18}"/>
|
<ui:param name="amount" value="#{factureView.entity.montantHT * 1.18}"/>
|
||||||
<ui:param name="size" value="large"/>
|
<ui:param name="size" value="large"/>
|
||||||
<ui:param name="bold" value="true"/>
|
<ui:param name="bold" value="true"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3">
|
<div class="col-12 md:col-3">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span class="text-600 text-sm block mb-2">Reste à payer</span>
|
<span class="text-600 text-sm block mb-2">Reste à payer</span>
|
||||||
<div class="font-bold text-xl" style="color: #{((factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye) > 0 ? '#EF4444' : '#10B981'}">
|
<div class="font-bold text-xl" style="color: #{((factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye) > 0 ? '#EF4444' : '#10B981'}">
|
||||||
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
|
||||||
<ui:param name="amount" value="#{(factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye}"/>
|
<ui:param name="amount" value="#{(factureView.entity.montantHT * 1.18) - factureView.entity.montantPaye}"/>
|
||||||
<ui:param name="size" value="large"/>
|
<ui:param name="size" value="large"/>
|
||||||
<ui:param name="bold" value="true"/>
|
<ui:param name="bold" value="true"/>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- SECTION 4: Informations de paiement -->
|
<!-- SECTION 4: Informations de paiement -->
|
||||||
<p:panel header="Informations de paiement" toggleable="true" collapsed="true" class="mb-4">
|
<p:panel header="Informations de paiement" toggleable="true" collapsed="true" class="mb-4">
|
||||||
<div class="formgrid grid">
|
<div class="formgrid grid">
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="modePaiement" class="font-bold">Mode de paiement</label>
|
<label for="modePaiement" class="font-bold">Mode de paiement</label>
|
||||||
<p:selectOneMenu id="modePaiement">
|
<p:selectOneMenu id="modePaiement">
|
||||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
|
||||||
<f:selectItem itemLabel="Virement bancaire" itemValue="VIREMENT"/>
|
<f:selectItem itemLabel="Virement bancaire" itemValue="VIREMENT"/>
|
||||||
<f:selectItem itemLabel="Chèque" itemValue="CHEQUE"/>
|
<f:selectItem itemLabel="Chèque" itemValue="CHEQUE"/>
|
||||||
<f:selectItem itemLabel="Espèces" itemValue="ESPECES"/>
|
<f:selectItem itemLabel="Espèces" itemValue="ESPECES"/>
|
||||||
<f:selectItem itemLabel="Carte bancaire" itemValue="CARTE"/>
|
<f:selectItem itemLabel="Carte bancaire" itemValue="CARTE"/>
|
||||||
<f:selectItem itemLabel="Mobile Money" itemValue="MOBILE_MONEY"/>
|
<f:selectItem itemLabel="Mobile Money" itemValue="MOBILE_MONEY"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="field col-12 md:col-6">
|
<div class="field col-12 md:col-6">
|
||||||
<label for="referencePaiement" class="font-bold">Référence de paiement</label>
|
<label for="referencePaiement" class="font-bold">Référence de paiement</label>
|
||||||
<p:inputText id="referencePaiement"
|
<p:inputText id="referencePaiement"
|
||||||
placeholder="Ex: Virement du 15/01/2025"/>
|
placeholder="Ex: Virement du 15/01/2025"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="field col-12">
|
<div class="field col-12">
|
||||||
<label for="conditionsPaiement" class="font-bold">Conditions de paiement</label>
|
<label for="conditionsPaiement" class="font-bold">Conditions de paiement</label>
|
||||||
<p:inputTextarea id="conditionsPaiement"
|
<p:inputTextarea id="conditionsPaiement"
|
||||||
rows="3"
|
rows="3"
|
||||||
placeholder="Ex: Paiement à 30 jours fin de mois, escompte 2% si paiement sous 8 jours"
|
placeholder="Ex: Paiement à 30 jours fin de mois, escompte 2% si paiement sous 8 jours"
|
||||||
autoResize="false">
|
autoResize="false">
|
||||||
</p:inputTextarea>
|
</p:inputTextarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
|
||||||
<!-- Boutons d'action -->
|
<!-- Boutons d'action -->
|
||||||
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
|
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-600 text-sm">Les champs marqués d'un </span>
|
<span class="text-600 text-sm">Les champs marqués d'un </span>
|
||||||
<span class="text-red-500 font-bold">*</span>
|
<span class="text-red-500 font-bold">*</span>
|
||||||
<span class="text-600 text-sm"> sont obligatoires</span>
|
<span class="text-600 text-sm"> sont obligatoires</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<p:commandButton value="Annuler"
|
<p:commandButton value="Annuler"
|
||||||
icon="pi pi-times"
|
icon="pi pi-times"
|
||||||
action="/factures?faces-redirect=true"
|
action="/factures?faces-redirect=true"
|
||||||
styleClass="ui-button-secondary"
|
styleClass="ui-button-secondary"
|
||||||
immediate="true"/>
|
immediate="true"/>
|
||||||
<p:commandButton value="Enregistrer comme brouillon"
|
<p:commandButton value="Enregistrer comme brouillon"
|
||||||
icon="pi pi-save"
|
icon="pi pi-save"
|
||||||
action="#{factureView.save}"
|
action="#{factureView.save}"
|
||||||
update="@form messages"
|
update="@form messages"
|
||||||
oncomplete="if (args && !args.validationFailed) window.location.href='/factures.xhtml';"
|
oncomplete="if (args && !args.validationFailed) window.location.href='/factures.xhtml';"
|
||||||
styleClass="ui-button-secondary"/>
|
styleClass="ui-button-secondary"/>
|
||||||
<p:commandButton value="Émettre la facture"
|
<p:commandButton value="Émettre la facture"
|
||||||
icon="pi pi-send"
|
icon="pi pi-send"
|
||||||
action="#{factureView.save}"
|
action="#{factureView.save}"
|
||||||
update="@form messages"
|
update="@form messages"
|
||||||
oncomplete="if (args && !args.validationFailed) window.location.href='/factures.xhtml';"
|
oncomplete="if (args && !args.validationFailed) window.location.href='/factures.xhtml';"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Payees - FACTURES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Payees</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/factures" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Payees - FACTURES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Payees</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/factures" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Retard - FACTURES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Retard</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/factures" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Retard - FACTURES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Retard</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/factures" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Fournisseurs - BTP Xpress</ui:define>
|
<ui:define name="title">Fournisseurs - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Fournisseurs</h6>
|
<h6>Fournisseurs</h6>
|
||||||
<p class="subtitle">Gestion des fournisseurs</p>
|
<p class="subtitle">Gestion des fournisseurs</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
58
src/main/resources/META-INF/resources/index.xhtml
Normal file
58
src/main/resources/META-INF/resources/index.xhtml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
|
xmlns:p="http://primefaces.org/ui"
|
||||||
|
lang="fr">
|
||||||
|
|
||||||
|
<h:head>
|
||||||
|
<f:facet name="first">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||||
|
<link rel="icon" href="#{request.contextPath}/resources/freya-layout/images/favicon.ico" type="image/x-icon"/>
|
||||||
|
</f:facet>
|
||||||
|
<title>BTP Xpress - Plateforme de Gestion BTP</title>
|
||||||
|
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
|
||||||
|
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
|
||||||
|
<h:outputStylesheet name="css/layout-light.css" library="freya-layout" />
|
||||||
|
<h:outputStylesheet name="css/freya-purple-light.css" library="freya-layout" />
|
||||||
|
</h:head>
|
||||||
|
|
||||||
|
<h:body>
|
||||||
|
<div class="layout-wrapper">
|
||||||
|
<!-- Redirection vers login ou dashboard selon l'état de connexion -->
|
||||||
|
<div class="grid" style="min-height: 100vh; align-items: center; justify-content: center;">
|
||||||
|
<div class="col-12 md:col-8 lg:col-6">
|
||||||
|
<div class="card" style="text-align: center; padding: 3rem;">
|
||||||
|
<h1 style="color: var(--primary-color); margin-bottom: 1rem;">
|
||||||
|
<i class="pi pi-building" style="font-size: 3rem; margin-bottom: 1rem;"></i><br/>
|
||||||
|
BTP Xpress
|
||||||
|
</h1>
|
||||||
|
<h2 style="color: var(--text-color); margin-bottom: 2rem;">
|
||||||
|
Plateforme de Gestion BTP
|
||||||
|
</h2>
|
||||||
|
<p style="color: var(--text-color-secondary); margin-bottom: 2rem; line-height: 1.8;">
|
||||||
|
Gestion complète de vos chantiers, équipes, matériels et facturation.
|
||||||
|
Optimisez votre activité BTP avec une solution moderne et intuitive.
|
||||||
|
</p>
|
||||||
|
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
|
||||||
|
<p:commandButton value="Se connecter" icon="pi pi-sign-in"
|
||||||
|
action="login.xhtml?faces-redirect=true"
|
||||||
|
styleClass="ui-button-primary" style="min-width: 150px;"/>
|
||||||
|
<p:commandButton value="En savoir plus" icon="pi pi-info-circle"
|
||||||
|
action="aide.xhtml?faces-redirect=true"
|
||||||
|
styleClass="ui-button-secondary" style="min-width: 150px;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer activé uniquement sur la page d'accueil publique -->
|
||||||
|
<ui:include src="WEB-INF/footer.xhtml"/>
|
||||||
|
</div>
|
||||||
|
</h:body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -1,85 +1,85 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
lang="fr">
|
lang="fr">
|
||||||
|
|
||||||
<h:head>
|
<h:head>
|
||||||
<f:facet name="first">
|
<f:facet name="first">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||||
<link rel="icon" href="#{request.contextPath}/resources/freya-layout/images/favicon.ico" type="image/x-icon"/>
|
<link rel="icon" href="#{request.contextPath}/resources/freya-layout/images/favicon.ico" type="image/x-icon"/>
|
||||||
</f:facet>
|
</f:facet>
|
||||||
<title>Connexion - BTP Xpress</title>
|
<title>Connexion - BTP Xpress</title>
|
||||||
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
|
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
|
||||||
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
|
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
|
||||||
<h:outputStylesheet name="css/layout-light.css" library="freya-layout" />
|
<h:outputStylesheet name="css/layout-light.css" library="freya-layout" />
|
||||||
<h:outputStylesheet name="css/freya-purple-light.css" library="freya-layout" />
|
<h:outputStylesheet name="css/freya-purple-light.css" library="freya-layout" />
|
||||||
</h:head>
|
</h:head>
|
||||||
|
|
||||||
<h:body class="login-body">
|
<h:body class="login-body">
|
||||||
<div class="login-wrapper">
|
<div class="login-wrapper">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-left">
|
<div class="login-left">
|
||||||
<div class="login-content">
|
<div class="login-content">
|
||||||
<h1>BTP Xpress</h1>
|
<h1>BTP Xpress</h1>
|
||||||
<p class="subtitle">Votre plateforme de gestion BTP</p>
|
<p class="subtitle">Votre plateforme de gestion BTP</p>
|
||||||
<p>Gérez vos projets, clients, matériels et équipes en toute simplicité.</p>
|
<p>Gérez vos projets, clients, matériels et équipes en toute simplicité.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-right">
|
<div class="login-right">
|
||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
<h2>Connexion</h2>
|
<h2>Connexion</h2>
|
||||||
<p class="login-subtitle">Connectez-vous à votre compte</p>
|
<p class="login-subtitle">Connectez-vous à votre compte</p>
|
||||||
|
|
||||||
<h:form id="loginForm">
|
<h:form id="loginForm">
|
||||||
<div class="login-input-group">
|
<div class="login-input-group">
|
||||||
<label for="username">Nom d'utilisateur ou email</label>
|
<label for="username">Nom d'utilisateur ou email</label>
|
||||||
<p:inputText id="username"
|
<p:inputText id="username"
|
||||||
value="#{loginView.username}"
|
value="#{loginView.username}"
|
||||||
placeholder="Votre nom d'utilisateur"
|
placeholder="Votre nom d'utilisateur"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le nom d'utilisateur est requis"
|
requiredMessage="Le nom d'utilisateur est requis"
|
||||||
styleClass="ui-input-filled"
|
styleClass="ui-input-filled"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login-input-group">
|
<div class="login-input-group">
|
||||||
<label for="password">Mot de passe</label>
|
<label for="password">Mot de passe</label>
|
||||||
<p:password id="password"
|
<p:password id="password"
|
||||||
value="#{loginView.password}"
|
value="#{loginView.password}"
|
||||||
placeholder="Votre mot de passe"
|
placeholder="Votre mot de passe"
|
||||||
required="true"
|
required="true"
|
||||||
requiredMessage="Le mot de passe est requis"
|
requiredMessage="Le mot de passe est requis"
|
||||||
feedback="false"
|
feedback="false"
|
||||||
toggleMask="true"
|
toggleMask="true"
|
||||||
styleClass="ui-input-filled"
|
styleClass="ui-input-filled"
|
||||||
style="width: 100%;"/>
|
style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="login-options">
|
<div class="login-options">
|
||||||
<p:selectBooleanCheckbox id="rememberMe"
|
<p:selectBooleanCheckbox id="rememberMe"
|
||||||
value="#{loginView.rememberMe}"
|
value="#{loginView.rememberMe}"
|
||||||
label="Se souvenir de moi"/>
|
label="Se souvenir de moi"/>
|
||||||
<a href="#" style="text-decoration: none; color: var(--primary-color);">Mot de passe oublié ?</a>
|
<a href="#" style="text-decoration: none; color: var(--primary-color);">Mot de passe oublié ?</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p:commandButton value="Se connecter"
|
<p:commandButton value="Se connecter"
|
||||||
icon="pi pi-sign-in"
|
icon="pi pi-sign-in"
|
||||||
action="#{loginView.login()}"
|
action="#{loginView.login()}"
|
||||||
style="width: 100%; margin-top: 1rem;"
|
style="width: 100%; margin-top: 1rem;"
|
||||||
update="@form"
|
update="@form"
|
||||||
process="@form"/>
|
process="@form"/>
|
||||||
|
|
||||||
<p:messages id="messages" showDetail="true" closable="true"/>
|
<p:messages id="messages" showDetail="true" closable="true"/>
|
||||||
</h:form>
|
</h:form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</h:body>
|
</h:body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Maintenance - BTP Xpress</ui:define>
|
<ui:define name="title">Maintenance - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Gestion de la Maintenance</h1>
|
<h1>Gestion de la Maintenance</h1>
|
||||||
<p>Module en cours de développement...</p>
|
<p>Module en cours de développement...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Corrective - MAINTENANCE - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Corrective</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/maintenance" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Corrective - MAINTENANCE - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Corrective</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/maintenance" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">MAINTENANCE - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>MAINTENANCE</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">MAINTENANCE - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>MAINTENANCE</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Maintenance préventive - BTP Xpress</ui:define>
|
<ui:define name="title">Maintenance préventive - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Maintenance préventive</h6>
|
<h6>Maintenance préventive</h6>
|
||||||
<p class="subtitle">Maintenances préventives planifiées</p>
|
<p class="subtitle">Maintenances préventives planifiées</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Urgente - MAINTENANCE - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Urgente</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/maintenance" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Urgente - MAINTENANCE - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Urgente</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/maintenance" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,111 +1,111 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Matériels - BTP Xpress</ui:define>
|
<ui:define name="title">Matériels - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex align-items-center justify-content-between mb-3">
|
<div class="flex align-items-center justify-content-between mb-3">
|
||||||
<h1>Gestion des Matériels</h1>
|
<h1>Gestion des Matériels</h1>
|
||||||
<p:commandButton value="Nouveau matériel" icon="pi pi-wrench"
|
<p:commandButton value="Nouveau matériel" icon="pi pi-wrench"
|
||||||
action="#{materielView.createNew()}"
|
action="#{materielView.createNew()}"
|
||||||
styleClass="ui-button-primary"/>
|
styleClass="ui-button-primary"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
<ui:include src="/WEB-INF/components/liste-filters.xhtml">
|
||||||
<ui:param name="formId" value="filtresForm"/>
|
<ui:param name="formId" value="filtresForm"/>
|
||||||
<ui:param name="viewBean" value="#{materielView}"/>
|
<ui:param name="viewBean" value="#{materielView}"/>
|
||||||
<ui:param name="tableId" value="materielsTable"/>
|
<ui:param name="tableId" value="materielsTable"/>
|
||||||
<ui:define name="filter-fields">
|
<ui:define name="filter-fields">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreNom" value="Nom"/>
|
<h:outputLabel for="filtreNom" value="Nom"/>
|
||||||
<p:inputText id="filtreNom" value="#{materielView.filtreNom}"
|
<p:inputText id="filtreNom" value="#{materielView.filtreNom}"
|
||||||
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
placeholder="Rechercher par nom..." style="width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreType" value="Type"/>
|
<h:outputLabel for="filtreType" value="Type"/>
|
||||||
<p:selectOneMenu id="filtreType" value="#{materielView.filtreType}" style="width: 100%;">
|
<p:selectOneMenu id="filtreType" value="#{materielView.filtreType}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="Engin" itemValue="ENGIN"/>
|
<f:selectItem itemLabel="Engin" itemValue="ENGIN"/>
|
||||||
<f:selectItem itemLabel="Outil" itemValue="OUTIL"/>
|
<f:selectItem itemLabel="Outil" itemValue="OUTIL"/>
|
||||||
<f:selectItem itemLabel="Véhicule" itemValue="VEHICULE"/>
|
<f:selectItem itemLabel="Véhicule" itemValue="VEHICULE"/>
|
||||||
<f:selectItem itemLabel="Équipement" itemValue="EQUIPEMENT"/>
|
<f:selectItem itemLabel="Équipement" itemValue="EQUIPEMENT"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-4">
|
<div class="col-12 md:col-4">
|
||||||
<h:outputLabel for="filtreStatut" value="Statut"/>
|
<h:outputLabel for="filtreStatut" value="Statut"/>
|
||||||
<p:selectOneMenu id="filtreStatut" value="#{materielView.filtreStatut}" style="width: 100%;">
|
<p:selectOneMenu id="filtreStatut" value="#{materielView.filtreStatut}" style="width: 100%;">
|
||||||
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
<f:selectItem itemLabel="Tous" itemValue="TOUS"/>
|
||||||
<f:selectItem itemLabel="Disponible" itemValue="DISPONIBLE"/>
|
<f:selectItem itemLabel="Disponible" itemValue="DISPONIBLE"/>
|
||||||
<f:selectItem itemLabel="En service" itemValue="EN_SERVICE"/>
|
<f:selectItem itemLabel="En service" itemValue="EN_SERVICE"/>
|
||||||
<f:selectItem itemLabel="En maintenance" itemValue="EN_MAINTENANCE"/>
|
<f:selectItem itemLabel="En maintenance" itemValue="EN_MAINTENANCE"/>
|
||||||
<f:selectItem itemLabel="Hors service" itemValue="HORS_SERVICE"/>
|
<f:selectItem itemLabel="Hors service" itemValue="HORS_SERVICE"/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
<ui:include src="/WEB-INF/components/liste-table.xhtml">
|
||||||
<ui:param name="formId" value="materielsForm"/>
|
<ui:param name="formId" value="materielsForm"/>
|
||||||
<ui:param name="tableId" value="materielsTable"/>
|
<ui:param name="tableId" value="materielsTable"/>
|
||||||
<ui:param name="viewBean" value="#{materielView}"/>
|
<ui:param name="viewBean" value="#{materielView}"/>
|
||||||
<ui:param name="var" value="materiel"/>
|
<ui:param name="var" value="materiel"/>
|
||||||
<ui:param name="title" value="Liste des matériels"/>
|
<ui:param name="title" value="Liste des matériels"/>
|
||||||
<ui:param name="createPath" value="/materiels/nouveau"/>
|
<ui:param name="createPath" value="/materiels/nouveau"/>
|
||||||
<ui:define name="columns">
|
<ui:define name="columns">
|
||||||
<p:column headerText="Nom" sortBy="#{materiel.nom}">
|
<p:column headerText="Nom" sortBy="#{materiel.nom}">
|
||||||
<h:outputText value="#{materiel.nom}"/>
|
<h:outputText value="#{materiel.nom}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Type" sortBy="#{materiel.type}">
|
<p:column headerText="Type" sortBy="#{materiel.type}">
|
||||||
<p:tag value="#{materiel.type}" severity="info"/>
|
<p:tag value="#{materiel.type}" severity="info"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Marque" sortBy="#{materiel.marque}">
|
<p:column headerText="Marque" sortBy="#{materiel.marque}">
|
||||||
<h:outputText value="#{materiel.marque}"/>
|
<h:outputText value="#{materiel.marque}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Modèle">
|
<p:column headerText="Modèle">
|
||||||
<h:outputText value="#{materiel.modele}"/>
|
<h:outputText value="#{materiel.modele}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="N° série">
|
<p:column headerText="N° série">
|
||||||
<h:outputText value="#{materiel.numeroSerie}"/>
|
<h:outputText value="#{materiel.numeroSerie}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Valeur d'achat">
|
<p:column headerText="Valeur d'achat">
|
||||||
<h:outputText value="#{materiel.valeurAchat}">
|
<h:outputText value="#{materiel.valeurAchat}">
|
||||||
<f:converter converterId="fcfaConverter"/>
|
<f:converter converterId="fcfaConverter"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
<h:outputText value=" Fcfa"/>
|
<h:outputText value=" Fcfa"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Date achat" sortBy="#{materiel.dateAchat}">
|
<p:column headerText="Date achat" sortBy="#{materiel.dateAchat}">
|
||||||
<h:outputText value="#{materiel.dateAchat}">
|
<h:outputText value="#{materiel.dateAchat}">
|
||||||
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
<f:convertDateTime pattern="dd/MM/yyyy"/>
|
||||||
</h:outputText>
|
</h:outputText>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Statut" sortBy="#{materiel.statut}">
|
<p:column headerText="Statut" sortBy="#{materiel.statut}">
|
||||||
<p:tag value="#{materiel.statut}"
|
<p:tag value="#{materiel.statut}"
|
||||||
severity="#{materiel.statut == 'DISPONIBLE' ? 'success' : (materiel.statut == 'HORS_SERVICE' ? 'danger' : 'warning')}"/>
|
severity="#{materiel.statut == 'DISPONIBLE' ? 'success' : (materiel.statut == 'HORS_SERVICE' ? 'danger' : 'warning')}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Actions" style="width: 150px;">
|
<p:column headerText="Actions" style="width: 150px;">
|
||||||
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
<p:commandButton icon="pi pi-eye" title="Voir les détails"
|
||||||
styleClass="ui-button-text"
|
styleClass="ui-button-text"
|
||||||
action="#{materielView.viewDetails(materiel.id)}"/>
|
action="#{materielView.viewDetails(materiel.id)}"/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:include>
|
</ui:include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Messages - BTP Xpress</ui:define>
|
<ui:define name="title">Messages - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Messages</h1>
|
<h1>Messages</h1>
|
||||||
<p>Module en cours de développement...</p>
|
<p>Module en cours de développement...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Archives - MESSAGES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Archives</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/messages" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Archives - MESSAGES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Archives</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/messages" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Envoyes - MESSAGES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Envoyes</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/messages" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Envoyes - MESSAGES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Envoyes</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/messages" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">MESSAGES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>MESSAGES</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">MESSAGES - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>MESSAGES</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Notifications - BTP Xpress</ui:define>
|
<ui:define name="title">Notifications - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Notifications</h1>
|
<h1>Notifications</h1>
|
||||||
<p>Module en cours de développement...</p>
|
<p>Module en cours de développement...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Non Lues - NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Non Lues</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/notifications" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Non Lues - NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Non Lues</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/notifications" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>NOTIFICATIONS</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>NOTIFICATIONS</h1><p>Module en cours de développement...</p></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Recentes - NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Recentes</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/notifications" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Recentes - NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Recentes</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/notifications" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Statistiques - NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Statistiques</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/notifications" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" template="/WEB-INF/template.xhtml"><ui:define name="title">Statistiques - NOTIFICATIONS - BTP Xpress</ui:define><ui:define name="content"><div class="layout-dashboard"><div class="grid"><div class="col-12"><div class="card"><h1>Statistiques</h1><p>Module en cours de développement...</p><p:commandButton value="Retour" icon="pi pi-arrow-left" outcome="/notifications" styleClass="ui-button-secondary"/></div></div></div></div></ui:define></ui:composition>
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Paramètres - BTP Xpress</ui:define>
|
<ui:define name="title">Paramètres - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h6>Paramètres</h6>
|
<h6>Paramètres</h6>
|
||||||
<p class="subtitle">Configuration de l'application</p>
|
<p class="subtitle">Configuration de l'application</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Page en développement</p>
|
<p>Page en développement</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xmlns:h="http://java.sun.com/jsf/html"
|
xmlns:h="http://java.sun.com/jsf/html"
|
||||||
xmlns:f="http://java.sun.com/jsf/core"
|
xmlns:f="http://java.sun.com/jsf/core"
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
template="/WEB-INF/template.xhtml">
|
template="/WEB-INF/template.xhtml">
|
||||||
|
|
||||||
<ui:define name="title">Planning - BTP Xpress</ui:define>
|
<ui:define name="title">Planning - BTP Xpress</ui:define>
|
||||||
|
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<div class="layout-dashboard">
|
<div class="layout-dashboard">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>Planning</h1>
|
<h1>Planning</h1>
|
||||||
<p>Module en cours de développement...</p>
|
<p>Module en cours de développement...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user