Update - Lions User Manager - Client (Quarkus PrimeFaces Freya)

This commit is contained in:
dahoud
2025-12-06 22:07:05 +00:00
parent 53ea02ccad
commit 51d087e5be
189 changed files with 38558 additions and 1 deletions

View File

@@ -0,0 +1,399 @@
# 📦 Composants Réutilisables - Lions User Manager
**Version**: 1.0.0
**Date**: 2025-01-29
**Pattern**: WOU/DRY (Write Once Use / Don't Repeat Yourself)
---
## 🎯 Vue d'Ensemble
Ce répertoire contient tous les composants UI réutilisables pour le module **lions-user-manager**. Ces composants suivent les patterns établis dans **unionflow** et peuvent être utilisés dans n'importe quel projet de l'écosystème **lionsdev**.
---
## 📂 Structure des Composants
```
templates/components/
├── user-management/ # Composants spécifiques utilisateurs
│ ├── user-card.xhtml
│ ├── user-form.xhtml
│ ├── user-search-bar.xhtml
│ ├── user-actions.xhtml
│ └── user-role-badge.xhtml
├── role-management/ # Composants spécifiques rôles
│ ├── role-card.xhtml
│ ├── role-form.xhtml
│ └── role-assignment.xhtml
├── audit/ # Composants audit
│ ├── audit-log-row.xhtml
│ └── audit-stats-card.xhtml
└── shared/ # Composants génériques réutilisables
├── buttons/
│ └── button-user-action.xhtml
├── cards/
│ └── user-stat-card.xhtml
├── forms/
│ └── user-form-field.xhtml
└── tables/
└── user-data-table.xhtml
```
---
## 📋 Liste Complète des Composants
### 👤 User Management (5 composants)
#### 1. `user-card.xhtml`
**Description**: Carte utilisateur avec informations principales et actions
**Paramètres principaux**:
- `user`: UserDTO (requis)
- `showActions`: Boolean (défaut: true)
- `showRoles`: Boolean (défaut: true)
- `clickable`: Boolean (défaut: true)
- `outcome`: String (optionnel)
**Exemple**:
```xhtml
<ui:include src="/templates/components/user-management/user-card.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="showActions" value="true" />
</ui:include>
```
#### 2. `user-form.xhtml`
**Description**: Formulaire complet pour créer/modifier un utilisateur
**Paramètres principaux**:
- `user`: UserDTO (requis)
- `mode`: String (défaut: "create") - "create" ou "edit"
- `showRealmSelector`: Boolean (défaut: false)
- `showPasswordFields`: Boolean (défaut: true)
- `readonly`: Boolean (défaut: false)
**Exemple**:
```xhtml
<ui:include src="/templates/components/user-management/user-form.xhtml">
<ui:param name="user" value="#{userBean.newUser}" />
<ui:param name="mode" value="create" />
<ui:param name="submitAction" value="#{userBean.createUser}" />
</ui:include>
```
#### 3. `user-search-bar.xhtml`
**Description**: Barre de recherche avancée pour utilisateurs
**Paramètres principaux**:
- `searchCriteria`: UserSearchCriteriaDTO (requis)
- `searchAction`: String (requis)
- `showAdvanced`: Boolean (défaut: false)
- `showRealmFilter`: Boolean (défaut: true)
- `showStatusFilter`: Boolean (défaut: true)
**Exemple**:
```xhtml
<ui:include src="/templates/components/user-management/user-search-bar.xhtml">
<ui:param name="searchCriteria" value="#{userBean.searchCriteria}" />
<ui:param name="searchAction" value="#{userBean.search}" />
<ui:param name="update" value="userTable" />
</ui:include>
```
#### 4. `user-actions.xhtml`
**Description**: Boutons d'action pour un utilisateur
**Paramètres principaux**:
- `user`: UserDTO (requis)
- `showView`: Boolean (défaut: true)
- `showEdit`: Boolean (défaut: true)
- `showDelete`: Boolean (défaut: true)
- `showActivate`: Boolean (défaut: true)
- `layout`: String (défaut: "horizontal") - "horizontal", "vertical" ou "dropdown"
**Exemple**:
```xhtml
<ui:include src="/templates/components/user-management/user-actions.xhtml">
<ui:param name="user" value="#{user}" />
<ui:param name="layout" value="dropdown" />
<ui:param name="update" value="userTable" />
</ui:include>
```
#### 5. `user-role-badge.xhtml`
**Description**: Badge pour un rôle utilisateur
**Paramètres principaux**:
- `roleName`: String (requis)
- `roleType`: String (optionnel) - "REALM_ROLE", "CLIENT_ROLE", "COMPOSITE_ROLE"
- `severity`: String (optionnel) - "success", "info", "warning", "danger"
- `clickable`: Boolean (défaut: false)
**Exemple**:
```xhtml
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
<ui:param name="roleName" value="ADMIN" />
<ui:param name="roleType" value="REALM_ROLE" />
</ui:include>
```
---
### 🛡️ Role Management (3 composants)
#### 6. `role-card.xhtml`
**Description**: Carte rôle avec informations principales
**Paramètres principaux**:
- `role`: RoleDTO (requis)
- `showActions`: Boolean (défaut: true)
- `showDescription`: Boolean (défaut: true)
- `showCompositeInfo`: Boolean (défaut: true)
**Exemple**:
```xhtml
<ui:include src="/templates/components/role-management/role-card.xhtml">
<ui:param name="role" value="#{roleBean.selectedRole}" />
</ui:include>
```
#### 7. `role-form.xhtml`
**Description**: Formulaire pour créer/modifier un rôle
**Paramètres principaux**:
- `role`: RoleDTO (requis)
- `mode`: String (défaut: "create")
- `showRealmSelector`: Boolean (défaut: true)
- `showClientSelector`: Boolean (défaut: false)
- `showCompositeOptions`: Boolean (défaut: true)
**Exemple**:
```xhtml
<ui:include src="/templates/components/role-management/role-form.xhtml">
<ui:param name="role" value="#{roleBean.newRole}" />
<ui:param name="mode" value="create" />
<ui:param name="submitAction" value="#{roleBean.createRole}" />
</ui:include>
```
#### 8. `role-assignment.xhtml`
**Description**: Interface pour attribuer/révoquer des rôles
**Paramètres principaux**:
- `user`: UserDTO (requis)
- `availableRoles`: List<RoleDTO> (requis)
- `userRoles`: List<RoleDTO> (requis)
- `assignAction`: String (requis)
- `revokeAction`: String (requis)
**Exemple**:
```xhtml
<ui:include src="/templates/components/role-management/role-assignment.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="availableRoles" value="#{roleBean.availableRoles}" />
<ui:param name="userRoles" value="#{userBean.userRoles}" />
<ui:param name="assignAction" value="#{roleBean.assignRole}" />
<ui:param name="revokeAction" value="#{roleBean.revokeRole}" />
</ui:include>
```
---
### 📊 Audit (2 composants)
#### 9. `audit-log-row.xhtml`
**Description**: Ligne de log d'audit avec informations détaillées
**Paramètres principaux**:
- `auditLog`: AuditLogDTO (requis)
- `showDetails`: Boolean (défaut: false)
- `showActions`: Boolean (défaut: false)
- `compact`: Boolean (défaut: false)
**Exemple**:
```xhtml
<ui:include src="/templates/components/audit/audit-log-row.xhtml">
<ui:param name="auditLog" value="#{log}" />
<ui:param name="showDetails" value="true" />
</ui:include>
```
#### 10. `audit-stats-card.xhtml`
**Description**: Carte statistiques d'audit
**Paramètres principaux**:
- `title`: String (requis)
- `value`: Number (requis)
- `icon`: String (requis)
- `iconColor`: String (requis)
- `trend`: Number (optionnel)
- `trendLabel`: String (optionnel)
**Exemple**:
```xhtml
<ui:include src="/templates/components/audit/audit-stats-card.xhtml">
<ui:param name="title" value="Total Actions" />
<ui:param name="value" value="#{auditBean.totalActions}" />
<ui:param name="icon" value="pi-history" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
```
---
### 🔧 Shared Components (4 composants)
#### 11. `button-user-action.xhtml`
**Description**: Bouton générique pour actions utilisateur
**Paramètres principaux**:
- `value`: String (requis)
- `icon`: String (optionnel)
- `action`: String (optionnel)
- `outcome`: String (optionnel)
- `severity`: String (défaut: "primary")
- `size`: String (défaut: "normal")
**Exemple**:
```xhtml
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Créer Utilisateur" />
<ui:param name="icon" value="pi-user-plus" />
<ui:param name="action" value="#{userBean.createUser}" />
</ui:include>
```
#### 12. `user-stat-card.xhtml`
**Description**: Carte statistique utilisateur
**Paramètres principaux**:
- `title`: String (requis)
- `value`: String/Number (requis)
- `icon`: String (requis)
- `iconColor`: String (requis)
- `trend`: Number (optionnel)
- `clickable`: Boolean (défaut: false)
**Exemple**:
```xhtml
<ui:include src="/templates/components/shared/cards/user-stat-card.xhtml">
<ui:param name="title" value="Total Utilisateurs" />
<ui:param name="value" value="#{userBean.totalUsers}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
```
#### 13. `user-form-field.xhtml`
**Description**: Champ de formulaire générique
**Paramètres principaux**:
- `id`: String (requis)
- `label`: String (requis)
- `value`: Object (requis)
- `type`: String (défaut: "text") - "text", "email", "password", "number", "textarea", "select", "checkbox", "calendar"
- `required`: Boolean (défaut: false)
- `readonly`: Boolean (défaut: false)
**Exemple**:
```xhtml
<ui:include src="/templates/components/shared/forms/user-form-field.xhtml">
<ui:param name="id" value="username" />
<ui:param name="label" value="Nom d'utilisateur" />
<ui:param name="value" value="#{user.username}" />
<ui:param name="required" value="true" />
</ui:include>
```
#### 14. `user-data-table.xhtml`
**Description**: Tableau de données pour utilisateurs
**Paramètres principaux**:
- `users`: List<UserDTO> (requis)
- `var`: String (défaut: "user")
- `paginator`: Boolean (défaut: true)
- `rows`: Number (défaut: 20)
- `showActions`: Boolean (défaut: true)
- `showRoles`: Boolean (défaut: true)
- `showSelection`: Boolean (défaut: false)
**Exemple**:
```xhtml
<ui:include src="/templates/components/shared/tables/user-data-table.xhtml">
<ui:param name="users" value="#{userBean.users}" />
<ui:param name="update" value="userTable" />
</ui:include>
```
---
## 🎨 Patterns et Conventions
### Documentation Inline
Chaque composant contient une documentation complète en commentaire avec:
- Description du composant
- Liste des paramètres avec types et valeurs par défaut
- Exemples d'utilisation
### Paramètres Optionnels
Tous les paramètres optionnels ont des valeurs par défaut définies avec `<c:set>`.
### Pattern WOU/DRY
- **Write Once Use**: Chaque composant est écrit une fois et réutilisé partout
- **Don't Repeat Yourself**: Pas de duplication de code
### Naming Convention
- Noms de fichiers en `kebab-case`
- Paramètres en `camelCase`
- IDs de composants avec préfixe cohérent
---
## 🚀 Utilisation dans d'Autres Projets
Ces composants peuvent être utilisés dans n'importe quel projet de l'écosystème **lionsdev** en ajoutant la dépendance Maven:
```xml
<dependency>
<groupId>dev.lions.user.manager</groupId>
<artifactId>lions-user-manager-client-quarkus-primefaces-freya</artifactId>
<version>1.0.0</version>
</dependency>
```
Puis inclure les composants dans vos pages XHTML:
```xhtml
<ui:include src="/templates/components/user-management/user-card.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
</ui:include>
```
---
## 📝 Notes Importantes
1. **Dépendances**: Tous les composants nécessitent PrimeFaces 14.0.5+ et Quarkus PrimeFaces 3.13.3+
2. **Thème**: Les composants utilisent le thème Freya (compatible avec unionflow)
3. **Validation**: Les composants de formulaire incluent la validation JSF
4. **Accessibilité**: Les composants suivent les bonnes pratiques d'accessibilité
---
## 🔄 Maintenance
Pour ajouter un nouveau composant:
1. Créer le fichier dans le répertoire approprié
2. Suivre le pattern de documentation inline
3. Ajouter des exemples d'utilisation
4. Mettre à jour ce README
---
**Dernière mise à jour**: 2025-01-29
**Version**: 1.0.0
**Auteur**: Lions User Manager Team

View File

@@ -0,0 +1,110 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Ligne de Log d'Audit (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Affiche une ligne de log d'audit avec informations détaillées
Paramètres:
- auditLog: AuditLogDTO (requis) - Le log d'audit à afficher
- showDetails: Boolean (défaut: false) - Afficher les détails complets
- showActions: Boolean (défaut: false) - Afficher les actions possibles
- compact: Boolean (défaut: false) - Mode compact
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemples d'utilisation:
1. Ligne simple:
<ui:include src="/templates/components/audit/audit-log-row.xhtml">
<ui:param name="auditLog" value="#{log}" />
</ui:include>
2. Ligne avec détails:
<ui:include src="/templates/components/audit/audit-log-row.xhtml">
<ui:param name="auditLog" value="#{log}" />
<ui:param name="showDetails" value="true" />
</ui:include>
-->
<c:set var="showDetails" value="#{empty showDetails ? false : showDetails}" />
<c:set var="showActions" value="#{empty showActions ? false : showActions}" />
<c:set var="compact" value="#{empty compact ? false : compact}" />
<!-- Déterminer la severity selon le succès -->
<c:set var="severity" value="#{auditLog.succes ? 'success' : 'danger'}" />
<c:set var="icon" value="#{auditLog.succes ? 'pi-check-circle' : 'pi-times-circle'}" />
<div class="audit-log-row flex align-items-center gap-3 p-3 border-round surface-border border-1 #{styleClass}"
style="#{compact ? 'padding: 0.5rem;' : ''}">
<!-- Icône de statut -->
<div class="flex align-items-center justify-content-center"
style="width: 2.5rem; height: 2.5rem; border-radius: 50%; background: var(--#{severity}-100);">
<i class="pi #{icon} text-#{severity}-600"></i>
</div>
<!-- Informations principales -->
<div class="flex-1">
<div class="flex align-items-center gap-2 mb-1">
<strong class="text-900">#{auditLog.typeAction}</strong>
<p:tag
value="#{auditLog.succes ? 'Succès' : 'Échec'}"
severity="#{severity}"
styleClass="text-xs" />
</div>
<div class="text-color-secondary text-sm">
<c:if test="#{not empty auditLog.acteurUsername}">
<span><i class="pi pi-user mr-1"></i>#{auditLog.acteurUsername}</span>
</c:if>
<c:if test="#{not empty auditLog.ressourceType}">
<span class="ml-3"><i class="pi pi-database mr-1"></i>#{auditLog.ressourceType}</span>
</c:if>
<c:if test="#{not empty auditLog.dateAction}">
<span class="ml-3"><i class="pi pi-calendar mr-1"></i>#{auditLog.dateAction}</span>
</c:if>
</div>
<!-- Détails (si affichés) -->
<c:if test="#{showDetails}">
<div class="mt-2 text-color-secondary text-xs">
<c:if test="#{not empty auditLog.ressourceId}">
<div><strong>Ressource ID:</strong> #{auditLog.ressourceId}</div>
</c:if>
<c:if test="#{not empty auditLog.details}">
<div><strong>Détails:</strong> #{auditLog.details}</div>
</c:if>
<c:if test="#{not empty auditLog.adresseIp}">
<div><strong>IP:</strong> #{auditLog.adresseIp}</div>
</c:if>
<c:if test="#{not empty auditLog.userAgent}">
<div><strong>User Agent:</strong> #{auditLog.userAgent}</div>
</c:if>
<c:if test="#{not empty auditLog.messageErreur}">
<div class="text-red-600"><strong>Erreur:</strong> #{auditLog.messageErreur}</div>
</c:if>
</div>
</c:if>
</div>
<!-- Actions (si affichées) -->
<c:if test="#{showActions}">
<div class="flex gap-1">
<p:commandButton
icon="pi pi-eye"
styleClass="p-button-text p-button-sm"
title="Voir les détails"
onclick="PF('auditLogDetailsDialog').show()" />
</div>
</c:if>
</div>
</ui:composition>

View File

@@ -0,0 +1,120 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Carte Statistiques Audit (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Affiche des statistiques d'audit dans une carte
Paramètres:
- title: String (requis) - Titre de la carte
- value: Number (requis) - Valeur à afficher
- icon: String (requis) - Classe d'icône PrimeIcons
- iconColor: String (requis) - Couleur de l'icône
- subtitle: String (optionnel) - Sous-titre
- trend: Number (optionnel) - Tendance (pourcentage)
- trendLabel: String (optionnel) - Libellé de la tendance
- colSize: String (défaut: "col-12 md:col-6 lg:col-3") - Taille de colonne
- clickable: Boolean (défaut: false) - Rendre la carte cliquable
- clickAction: String (optionnel) - Action au clic
Exemples d'utilisation:
1. Carte simple:
<ui:include src="/templates/components/audit/audit-stats-card.xhtml">
<ui:param name="title" value="Total Actions" />
<ui:param name="value" value="#{auditBean.totalActions}" />
<ui:param name="icon" value="pi-history" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
2. Carte avec tendance:
<ui:include src="/templates/components/audit/audit-stats-card.xhtml">
<ui:param name="title" value="Actions Réussies" />
<ui:param name="value" value="#{auditBean.successCount}" />
<ui:param name="icon" value="pi-check-circle" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="trend" value="#{auditBean.successTrend}" />
<ui:param name="trendLabel" value="vs mois dernier" />
</ui:include>
-->
<c:set var="colSize" value="#{empty colSize ? 'col-12 md:col-6 lg:col-3' : colSize}" />
<c:set var="clickable" value="#{empty clickable ? false : clickable}" />
<div class="field #{colSize}">
<c:choose>
<c:when test="#{clickable and not empty clickAction}">
<p:commandLink
styleClass="card-link"
action="#{clickAction}">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200 p-4">
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">#{title}</span>
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
style="width: 2.5rem; height: 2.5rem;">
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
</div>
</div>
<div class="text-900 font-bold text-2xl mb-2">#{value}</div>
<c:if test="#{not empty subtitle}">
<div class="text-500 text-xs mb-2">#{subtitle}</div>
</c:if>
<c:if test="#{not empty trend}">
<div class="flex align-items-center">
<i class="pi pi-arrow-#{trend >= 0 ? 'up' : 'down'} text-#{trend >= 0 ? 'green' : 'red'}-500 text-sm mr-2"></i>
<span class="text-#{trend >= 0 ? 'green' : 'red'}-600 font-semibold text-sm mr-2">
#{trend >= 0 ? '+' : ''}#{trend}%
</span>
<c:if test="#{not empty trendLabel}">
<span class="text-500 text-xs">#{trendLabel}</span>
</c:if>
</div>
</c:if>
</div>
</p:commandLink>
</c:when>
<c:otherwise>
<div class="card surface-0 border-round-lg p-4">
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">#{title}</span>
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
style="width: 2.5rem; height: 2.5rem;">
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
</div>
</div>
<div class="text-900 font-bold text-2xl mb-2">#{value}</div>
<c:if test="#{not empty subtitle}">
<div class="text-500 text-xs mb-2">#{subtitle}</div>
</c:if>
<c:if test="#{not empty trend}">
<div class="flex align-items-center">
<i class="pi pi-arrow-#{trend >= 0 ? 'up' : 'down'} text-#{trend >= 0 ? 'green' : 'red'}-500 text-sm mr-2"></i>
<span class="text-#{trend >= 0 ? 'green' : 'red'}-600 font-semibold text-sm mr-2">
#{trend >= 0 ? '+' : ''}#{trend}%
</span>
<c:if test="#{not empty trendLabel}">
<span class="text-500 text-xs">#{trendLabel}</span>
</c:if>
</div>
</c:if>
</div>
</c:otherwise>
</c:choose>
</div>
</ui:composition>

View File

@@ -0,0 +1,28 @@
<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:p="http://primefaces.org/ui">
<!--
Composant réutilisable: Footer (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Pied de page avec informations
-->
<div class="layout-footer">
<div class="grid">
<div class="col-12">
<div class="footer-bottom text-center">
<h4>Lions User Manager</h4>
<h6>Copyright © Lions Dev Team - Gestion centralisée des utilisateurs Keycloak</h6>
<p class="text-600 text-sm mt-2">Version 1.0.0</p>
</div>
</div>
</div>
</div>
</ui:composition>

View File

@@ -0,0 +1,58 @@
<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:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya">
<!--
Composant réutilisable: Menu Navigation (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Menu de navigation latéral pour Lions User Manager
-->
<div class="menu-wrapper">
<div class="sidebar-logo">
<a href="/pages/user-manager/dashboard">
<p:graphicImage name="images/logo-freya-single.svg" library="freya-layout" />
</a>
<a href="#" class="sidebar-pin" title="Toggle Menu">
<span class="pin"></span>
</a>
</div>
<div class="layout-menu-container">
<h:form id="menuform">
<fr:menu widgetVar="FreyaMenuWidget">
<!-- Dashboard -->
<p:menuitem id="m_dashboard" value="Tableau de Bord" icon="pi pi-home" outcome="/pages/user-manager/dashboard" />
<!-- Gestion Utilisateurs -->
<p:submenu id="m_users" label="Gestion Utilisateurs" icon="pi pi-users">
<p:menuitem id="m_users_list" value="Liste des Utilisateurs" icon="pi pi-list" outcome="/pages/user-manager/users/list" />
<p:menuitem id="m_users_create" value="Nouvel Utilisateur" icon="pi pi-user-plus" outcome="/pages/user-manager/users/create" />
</p:submenu>
<!-- Gestion Rôles -->
<p:submenu id="m_roles" label="Gestion Rôles" icon="pi pi-shield">
<p:menuitem id="m_roles_list" value="Liste des Rôles" icon="pi pi-list" outcome="/pages/user-manager/roles/list" />
<p:menuitem id="m_roles_assign" value="Attribution Rôles" icon="pi pi-key" outcome="/pages/user-manager/roles/assign" />
</p:submenu>
<!-- Audit -->
<p:submenu id="m_audit" label="Audit" icon="pi pi-history">
<p:menuitem id="m_audit_logs" value="Journal d'Audit" icon="pi pi-file-o" outcome="/pages/user-manager/audit/logs" />
</p:submenu>
<!-- Synchronisation -->
<p:submenu id="m_sync" label="Synchronisation" icon="pi pi-sync">
<p:menuitem id="m_sync_dashboard" value="Dashboard" icon="pi pi-dashboard" outcome="/pages/user-manager/sync/dashboard" />
</p:submenu>
</fr:menu>
</h:form>
</div>
</div>
</ui:composition>

View File

@@ -0,0 +1,52 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant en-tête de page réutilisable (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: En-tête de page avec icône, titre, description et actions
Paramètres:
- icon: String (optionnel) - Classe d'icône PrimeIcons (ex: "pi pi-users text-blue-500")
- title: String (requis) - Titre de la page
- description: String (optionnel) - Description de la page
Usage:
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-users text-blue-500" />
<ui:param name="title" value="Gestion des Utilisateurs" />
<ui:param name="description" value="Gestion centralisée des utilisateurs Keycloak" />
<ui:define name="actions">
Boutons d'action ici
</ui:define>
</ui:include>
-->
<div class="grid mb-4">
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between">
<div>
<h3 class="mb-2">
<c:if test="#{not empty icon}">
<i class="#{icon} mr-2"></i>
</c:if>
#{title}
</h3>
<p class="text-600 m-0" rendered="#{not empty description}">#{description}</p>
</div>
<div>
<ui:insert name="actions" />
</div>
</div>
</div>
</div>
</div>
</ui:composition>

View File

@@ -0,0 +1,117 @@
<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:p="http://primefaces.org/ui">
<!--
Composant réutilisable: Topbar (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Barre supérieure avec logo, menu et profil utilisateur
-->
<div class="layout-topbar">
<div class="layout-topbar-wrapper">
<div class="layout-topbar-left">
<a href="#" class="menu-button">
<i class="pi pi-bars"/>
</a>
<h:link id="logolink" outcome="/pages/user-manager/dashboard" styleClass="layout-topbar-logo">
<p:graphicImage name="images/#{guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}" library="freya-layout" />
</h:link>
</div>
<ui:include src="/templates/components/layout/menu.xhtml" />
<div class="layout-topbar-right">
<ul class="layout-topbar-actions">
<li class="topbar-item user-profile">
<a href="#" class="user-profile-link">
<div class="bg-primary text-white border-circle flex align-items-center justify-content-center user-avatar"
style="width: 36px; height: 36px; font-size: 14px; font-weight: bold; margin-right: 0.75rem;">
#{userSessionBean.initials}
</div>
<span class="user-info">
<span class="user-name">
#{userSessionBean.fullName}
<span class="user-status online"></span>
<span class="user-separator">|</span>
<span class="user-role">#{userSessionBean.primaryRole}</span>
</span>
<span class="user-email">#{userSessionBean.email}</span>
</span>
</a>
<ul class="user-dropdown-menu">
<!-- En-tête du menu avec infos utilisateur -->
<li class="user-dropdown-header">
<div class="user-dropdown-avatar">
<div class="bg-primary text-white border-circle flex align-items-center justify-content-center"
style="width: 48px; height: 48px; font-size: 20px; font-weight: bold;">
#{userSessionBean.initials}
</div>
<span class="user-status-indicator online"></span>
</div>
<div class="user-dropdown-info">
<div class="user-dropdown-name text-900 font-semibold">#{userSessionBean.fullName}</div>
<div class="user-dropdown-email text-600 text-sm">#{userSessionBean.email}</div>
<div class="user-dropdown-role text-500 text-xs">#{userSessionBean.primaryRole}</div>
</div>
</li>
<!-- Séparateur -->
<li class="user-dropdown-divider"></li>
<!-- Actions principales -->
<li class="user-dropdown-section">
<div class="section-title">Mon Compte</div>
<div class="section-items">
<h:link outcome="/pages/user-manager/users/profile" styleClass="dropdown-item">
<i class="pi pi-user"></i>
<span>Mon Profil</span>
<i class="pi pi-angle-right item-arrow"></i>
</h:link>
<h:link outcome="/pages/user-manager/settings" styleClass="dropdown-item">
<i class="pi pi-cog"></i>
<span>Paramètres</span>
<i class="pi pi-angle-right item-arrow"></i>
</h:link>
<a href="#" class="dropdown-item">
<i class="pi pi-shield"></i>
<span>Sécurité</span>
<i class="pi pi-angle-right item-arrow"></i>
</a>
</div>
</li>
<!-- Séparateur -->
<li class="user-dropdown-divider"></li>
<!-- Actions système -->
<li class="user-dropdown-section">
<div class="section-items">
<a href="#" class="dropdown-item">
<i class="pi pi-question-circle"></i>
<span>Aide &amp; Support</span>
</a>
<h:form>
<p:commandLink action="#{userSessionBean.logout}" styleClass="dropdown-item logout-item">
<i class="pi pi-sign-out"></i>
<span>Déconnexion</span>
</p:commandLink>
</h:form>
</div>
</li>
</ul>
</li>
</ul>
<a href="#" class="layout-rightpanel-button">
<i class="pi pi-arrow-left"/>
</a>
</div>
</div>
</div>
</ui:composition>

View File

@@ -0,0 +1,183 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Attribution de Rôles (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Interface pour attribuer/révoquer des rôles à un utilisateur
Paramètres:
- user: UserDTO (requis) - L'utilisateur concerné
- availableRoles: List&lt;RoleDTO&gt; (requis) - Liste des rôles disponibles
- userRoles: List&lt;RoleDTO&gt; (requis) - Liste des rôles de l'utilisateur
- roleBean: String (optionnel) - Nom du bean pour les actions (défaut: "roleGestionBean")
- update: String (défaut: "@form") - Composants à mettre à jour
- showRealmRoles: Boolean (défaut: true) - Afficher les rôles Realm
- showClientRoles: Boolean (défaut: true) - Afficher les rôles Client
- formId: String (défaut: "roleAssignmentForm") - ID du formulaire
Exemples d'utilisation:
1. Attribution simple:
<ui:include src="/templates/components/role-management/role-assignment.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="availableRoles" value="#{roleBean.availableRoles}" />
<ui:param name="userRoles" value="#{userBean.userRoles}" />
<ui:param name="assignAction" value="#{roleBean.assignRole}" />
<ui:param name="revokeAction" value="#{roleBean.revokeRole}" />
</ui:include>
-->
<c:set var="formId" value="#{empty formId ? 'roleAssignmentForm' : formId}" />
<c:set var="update" value="#{empty update ? '@form' : update}" />
<c:set var="showRealmRoles" value="#{empty showRealmRoles ? true : showRealmRoles}" />
<c:set var="showClientRoles" value="#{empty showClientRoles ? true : showClientRoles}" />
<c:set var="roleBeanName" value="#{empty roleBean ? 'roleGestionBean' : roleBean}" />
<h:form id="#{formId}">
<p:panel header="Attribution de rôles - #{user.username}" styleClass="w-full">
<!-- Rôles actuels de l'utilisateur -->
<h3>Rôles actuels</h3>
<div class="flex flex-wrap gap-2 mb-4">
<c:forEach var="userRole" items="#{userRoles}">
<p:tag
value="#{userRole.name}"
severity="info"
icon="pi pi-shield">
<p:commandButton
icon="pi pi-times"
styleClass="p-button-text p-button-sm p-button-danger ml-2"
action="#{roleGestionBean.revokeRoleFromParams}"
update="#{update}"
title="Révoquer le rôle">
<f:param name="userId" value="#{user.id}" />
<f:param name="roleName" value="#{userRole.name}" />
</p:commandButton>
</p:tag>
</c:forEach>
<c:if test="#{empty userRoles}">
<span class="text-color-secondary">Aucun rôle attribué</span>
</c:if>
</div>
<p:separator />
<!-- Rôles disponibles -->
<h3>Rôles disponibles</h3>
<!-- Rôles Realm -->
<c:if test="#{showRealmRoles}">
<p:panel header="Rôles Realm" styleClass="mb-3">
<div class="flex flex-wrap gap-2">
<c:forEach var="role" items="#{availableRoles}">
<c:if test="#{role.typeRole == 'REALM_ROLE'}">
<c:set var="isAssigned" value="false" />
<c:forEach var="userRole" items="#{userRoles}">
<c:if test="#{userRole.id == role.id}">
<c:set var="isAssigned" value="true" />
</c:if>
</c:forEach>
<c:choose>
<c:when test="#{isAssigned}">
<p:tag
value="#{role.name}"
severity="success"
icon="pi pi-check" />
</c:when>
<c:otherwise>
<p:tag
value="#{role.name}"
severity="info"
icon="pi pi-shield">
<p:commandButton
icon="pi pi-plus"
styleClass="p-button-text p-button-sm p-button-success ml-2"
action="#{roleGestionBean.assignRoleFromParams}"
update="#{update}"
title="Attribuer le rôle">
<f:param name="userId" value="#{user.id}" />
<f:param name="roleName" value="#{role.name}" />
</p:commandButton>
</p:tag>
</c:otherwise>
</c:choose>
</c:if>
</c:forEach>
</div>
</p:panel>
</c:if>
<!-- Rôles Client -->
<c:if test="#{showClientRoles}">
<p:panel header="Rôles Client" styleClass="mb-3">
<div class="flex flex-wrap gap-2">
<c:forEach var="role" items="#{availableRoles}">
<c:if test="#{role.typeRole == 'CLIENT_ROLE'}">
<c:set var="isAssigned" value="false" />
<c:forEach var="userRole" items="#{userRoles}">
<c:if test="#{userRole.id == role.id}">
<c:set var="isAssigned" value="true" />
</c:if>
</c:forEach>
<c:choose>
<c:when test="#{isAssigned}">
<p:tag
value="#{role.name}"
severity="success"
icon="pi pi-check" />
</c:when>
<c:otherwise>
<p:tag
value="#{role.name}"
severity="info"
icon="pi pi-shield">
<p:commandButton
icon="pi pi-plus"
styleClass="p-button-text p-button-sm p-button-success ml-2"
action="#{roleGestionBean.assignRoleFromParams}"
update="#{update}"
title="Attribuer le rôle">
<f:param name="userId" value="#{user.id}" />
<f:param name="roleName" value="#{role.name}" />
</p:commandButton>
</p:tag>
</c:otherwise>
</c:choose>
</c:if>
</c:forEach>
</div>
</p:panel>
</c:if>
<!-- Recherche de rôles -->
<p:separator />
<h3>Rechercher un rôle</h3>
<div class="flex gap-2 mb-3">
<p:inputText
value="#{roleGestionBean.roleSearchText}"
placeholder="Rechercher un rôle..."
styleClass="flex-1">
<p:ajax event="keyup"
delay="300"
update="@parent" />
</p:inputText>
</div>
<div id="roleSearchResults" class="flex flex-wrap gap-2">
<!-- Résultats de recherche -->
</div>
</p:panel>
</h:form>
</ui:composition>

View File

@@ -0,0 +1,155 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
<!--
Composant réutilisable: Carte Rôle (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Affiche une carte rôle avec informations principales et actions
Paramètres:
- role: RoleDTO (requis) - Le rôle à afficher
- showActions: Boolean (défaut: true) - Afficher les boutons d'action
- showDescription: Boolean (défaut: true) - Afficher la description
- showCompositeInfo: Boolean (défaut: true) - Afficher les infos de rôle composite
- clickable: Boolean (défaut: true) - Rendre la carte cliquable
- outcome: String (optionnel) - Page de destination au clic
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemples d'utilisation:
1. Carte simple:
<ui:include src="/templates/components/role-management/role-card.xhtml">
<ui:param name="role" value="#{roleBean.selectedRole}" />
</ui:include>
2. Carte avec actions:
<ui:include src="/templates/components/role-management/role-card.xhtml">
<ui:param name="role" value="#{roleBean.selectedRole}" />
<ui:param name="showActions" value="true" />
<ui:param name="outcome" value="/pages/user-manager/roles/details" />
</ui:include>
-->
<c:set var="showActions" value="#{empty showActions ? true : showActions}" />
<c:set var="showDescription" value="#{empty showDescription ? true : showDescription}" />
<c:set var="showCompositeInfo" value="#{empty showCompositeInfo ? true : showCompositeInfo}" />
<c:set var="clickable" value="#{empty clickable ? true : clickable}" />
<p:card styleClass="role-card #{styleClass}" rendered="#{not empty role}">
<f:facet name="header">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-shield text-2xl text-primary"></i>
<div class="flex flex-column">
<h3 class="m-0">#{role.name}</h3>
<span class="text-color-secondary text-sm">
<c:choose>
<c:when test="#{role.typeRole == 'REALM_ROLE'}">Rôle Realm</c:when>
<c:when test="#{role.typeRole == 'CLIENT_ROLE'}">Rôle Client</c:when>
<c:when test="#{role.typeRole == 'COMPOSITE_ROLE'}">Rôle Composite</c:when>
<c:otherwise>Rôle</c:otherwise>
</c:choose>
</span>
</div>
</div>
<p:tag
value="#{role.typeRole != null ? role.typeRole : 'ROLE'}"
severity="#{role.typeRole == 'REALM_ROLE' ? 'success' : role.typeRole == 'CLIENT_ROLE' ? 'info' : 'warning'}" />
</div>
</f:facet>
<div class="role-card-content">
<!-- Description -->
<c:if test="#{showDescription and not empty role.description}">
<p class="text-color-secondary mb-3">#{role.description}</p>
</c:if>
<!-- Informations -->
<div class="flex flex-column gap-2 mb-3">
<c:if test="#{not empty role.realmName}">
<div class="flex align-items-center gap-2">
<i class="pi pi-globe text-color-secondary"></i>
<span>Realm: <strong>#{role.realmName}</strong></span>
</div>
</c:if>
<c:if test="#{not empty role.clientId}">
<div class="flex align-items-center gap-2">
<i class="pi pi-desktop text-color-secondary"></i>
<span>Client: <strong>#{role.clientId}</strong></span>
</div>
</c:if>
<!-- Rôle composite -->
<c:if test="#{showCompositeInfo and role.composite}">
<div class="flex align-items-center gap-2">
<i class="pi pi-sitemap text-color-secondary"></i>
<span class="text-warning">Rôle composite</span>
</div>
</c:if>
</div>
</div>
<f:facet name="footer">
<c:if test="#{showActions}">
<div class="flex gap-2 justify-content-end">
<p:commandButton
icon="pi pi-eye"
title="Voir les détails"
styleClass="p-button-text p-button-sm"
outcome="#{not empty outcome ? outcome : '/pages/user-manager/roles/details'}"
rendered="#{clickable}">
<f:param name="roleId" value="#{role.id}" />
</p:commandButton>
<p:commandButton
icon="pi pi-pencil"
title="Modifier"
styleClass="p-button-text p-button-sm p-button-warning"
outcome="/pages/user-manager/roles/edit">
<f:param name="roleId" value="#{role.id}" />
</p:commandButton>
<p:commandButton
icon="pi pi-trash"
title="Supprimer"
styleClass="p-button-text p-button-sm p-button-danger"
onclick="PF('confirmDeleteRoleDialog_#{fn:replace(fn:replace(role.id != null ? role.id : role.name, '-', '_'), ':', '_')}').show()" />
</div>
</c:if>
</f:facet>
</p:card>
<!-- Dialog de confirmation de suppression avec ID unique basé sur l'ID ou le nom du rôle -->
<c:if test="#{not empty role.name}">
<c:set var="dialogId" value="confirmDeleteRoleDialog_#{fn:replace(fn:replace(role.id != null ? role.id : role.name, '-', '_'), ':', '_')}" />
<p:confirmDialog
id="#{dialogId}"
widgetVar="#{dialogId}"
message="Êtes-vous sûr de vouloir supprimer le rôle #{role.name} ?"
header="Confirmation de suppression"
severity="warn">
<p:commandButton
value="Oui"
icon="pi pi-check"
styleClass="p-button-danger"
action="#{roleGestionBean.deleteRealmRole(role.name)}"
update="@form"
oncomplete="PF('#{dialogId}').hide()" />
<p:commandButton
value="Non"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('#{dialogId}').hide()" />
</p:confirmDialog>
</c:if>
</ui:composition>

View File

@@ -0,0 +1,131 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<p:panel header="#{mode == 'create' ? 'Nouveau Rôle' : 'Modifier Rôle'}"
styleClass="w-full">
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
<!-- Nom du rôle -->
<p:outputLabel for="roleName" value="Nom du rôle *" />
<p:inputText id="roleName"
value="#{role.name}"
required="true"
readonly="#{readonly}"
placeholder="ADMIN, USER, MODERATOR..."
styleClass="w-full">
<f:validateLength minimum="2" maximum="100" />
<f:validateRegex pattern="^[A-Z_]+$" />
</p:inputText>
<!-- Description -->
<p:outputLabel for="description" value="Description" />
<p:inputTextarea id="description"
value="#{role.description}"
readonly="#{readonly}"
placeholder="Description du rôle..."
rows="3"
styleClass="w-full" />
<!-- Type de rôle -->
<p:outputLabel for="typeRole" value="Type de rôle *" />
<p:selectOneMenu id="typeRole"
value="#{role.typeRole}"
required="true"
readonly="#{readonly or mode == 'edit'}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItem itemLabel="Rôle Realm" itemValue="REALM_ROLE" />
<f:selectItem itemLabel="Rôle Client" itemValue="CLIENT_ROLE" />
<f:selectItem itemLabel="Rôle Composite" itemValue="COMPOSITE_ROLE" />
</p:selectOneMenu>
<!-- Realm (si affiché) -->
<c:if test="#{showRealmSelector}">
<p:outputLabel for="realmName" value="Realm *" />
<p:selectOneMenu id="realmName"
value="#{role.realmName}"
required="#{showRealmSelector}"
readonly="#{readonly}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{roleBean.availableRealms}" />
</p:selectOneMenu>
</c:if>
<!-- Client (si affiché) -->
<c:if test="#{showClientSelector}">
<p:outputLabel for="clientId" value="Client *" />
<p:selectOneMenu id="clientId"
value="#{role.clientId}"
required="#{showClientSelector}"
readonly="#{readonly}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{roleBean.availableClients}" />
</p:selectOneMenu>
</c:if>
<!-- Rôle composite -->
<c:if test="#{showCompositeOptions}">
<p:outputLabel for="composite" value="Rôle composite" />
<p:selectBooleanCheckbox id="composite"
value="#{role.composite}"
readonly="#{readonly}" />
</c:if>
</p:panelGrid>
<!-- Boutons d'action -->
<f:facet name="footer">
<div class="flex gap-2 justify-content-end">
<c:if test="#{not readonly}">
<c:choose>
<!-- Si hasSubmitAction est explicitement défini à true, utiliser action -->
<c:when test="#{hasSubmitAction == true}">
<p:commandButton
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
icon="pi pi-check"
styleClass="p-button-success"
action="#{submitAction}"
update="#{not empty update ? update : '@form'}"
process="@form" />
</c:when>
<!-- Si submitOutcome est fourni, utiliser outcome -->
<c:when test="#{not empty submitOutcome}">
<p:commandButton
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
icon="pi pi-check"
styleClass="p-button-success"
outcome="#{submitOutcome}"
update="#{not empty update ? update : '@form'}"
process="@form" />
</c:when>
<!-- Sinon, essayer d'utiliser submitAction si fourni -->
<c:otherwise>
<p:commandButton
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
icon="pi pi-check"
styleClass="p-button-success"
action="#{submitAction}"
update="#{not empty update ? update : '@form'}"
process="@form" />
</c:otherwise>
</c:choose>
</c:if>
<p:commandButton
value="Annuler"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('createRealmRoleDialog').hide(); PF('createClientRoleDialog').hide();"
immediate="true" />
</div>
</f:facet>
</p:panel>
</ui:composition>

View File

@@ -0,0 +1,92 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Formulaire Rôle (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Formulaire complet pour créer/modifier un rôle
Paramètres:
- role: RoleDTO (requis) - Le rôle à éditer (peut être null pour création)
- formId: String (défaut: "roleForm") - ID du formulaire
- mode: String (défaut: "create") - Mode: "create" ou "edit"
- showRealmSelector: Boolean (défaut: true) - Afficher le sélecteur de realm
- showClientSelector: Boolean (défaut: false) - Afficher le sélecteur de client
- showCompositeOptions: Boolean (défaut: true) - Afficher les options composite
- readonly: Boolean (défaut: false) - Mode lecture seule
- submitAction: String (optionnel) - Action à exécuter à la soumission
- submitOutcome: String (optionnel) - Page de redirection après soumission
- update: String (optionnel) - Composants à mettre à jour après soumission
Exemples d'utilisation:
1. Formulaire de création rôle Realm:
<ui:include src="/templates/components/role-management/role-form.xhtml">
<ui:param name="role" value="#{roleBean.newRole}" />
<ui:param name="mode" value="create" />
<ui:param name="showClientSelector" value="false" />
<ui:param name="submitAction" value="#{roleBean.createRole}" />
</ui:include>
2. Formulaire de création rôle Client:
<ui:include src="/templates/components/role-management/role-form.xhtml">
<ui:param name="role" value="#{roleBean.newRole}" />
<ui:param name="mode" value="create" />
<ui:param name="showClientSelector" value="true" />
<ui:param name="submitAction" value="#{roleBean.createClientRole}" />
</ui:include>
-->
<c:set var="formId" value="#{empty formId ? 'roleForm' : formId}" />
<c:set var="mode" value="#{empty mode ? 'create' : mode}" />
<c:set var="showRealmSelector" value="#{empty showRealmSelector ? true : showRealmSelector}" />
<c:set var="showClientSelector" value="#{empty showClientSelector ? false : showClientSelector}" />
<c:set var="showCompositeOptions" value="#{empty showCompositeOptions ? true : showCompositeOptions}" />
<c:set var="readonly" value="#{empty readonly ? false : readonly}" />
<c:set var="useParentForm" value="#{empty useParentForm ? false : useParentForm}" />
<c:choose>
<c:when test="#{useParentForm}">
<!-- Utiliser le formulaire parent (pas de h:form ici) -->
<ui:include src="/templates/components/role-management/role-form-content.xhtml">
<ui:param name="role" value="#{role}" />
<ui:param name="mode" value="#{mode}" />
<ui:param name="showRealmSelector" value="#{showRealmSelector}" />
<ui:param name="showClientSelector" value="#{showClientSelector}" />
<ui:param name="showCompositeOptions" value="#{showCompositeOptions}" />
<ui:param name="readonly" value="#{readonly}" />
<ui:param name="hasSubmitAction" value="#{hasSubmitAction}" />
<ui:param name="submitAction" value="#{submitAction}" />
<ui:param name="submitOutcome" value="#{submitOutcome}" />
<ui:param name="update" value="#{update}" />
<ui:param name="cancelOutcome" value="#{cancelOutcome}" />
</ui:include>
</c:when>
<c:otherwise>
<!-- Créer son propre formulaire -->
<h:form id="#{formId}">
<ui:include src="/templates/components/role-management/role-form-content.xhtml">
<ui:param name="role" value="#{role}" />
<ui:param name="mode" value="#{mode}" />
<ui:param name="showRealmSelector" value="#{showRealmSelector}" />
<ui:param name="showClientSelector" value="#{showClientSelector}" />
<ui:param name="showCompositeOptions" value="#{showCompositeOptions}" />
<ui:param name="readonly" value="#{readonly}" />
<ui:param name="hasSubmitAction" value="#{hasSubmitAction}" />
<ui:param name="submitAction" value="#{submitAction}" />
<ui:param name="submitOutcome" value="#{submitOutcome}" />
<ui:param name="update" value="#{update}" />
<ui:param name="cancelOutcome" value="#{cancelOutcome}" />
</ui:include>
</h:form>
</c:otherwise>
</c:choose>
</ui:composition>

View File

@@ -0,0 +1,240 @@
# 📊 Composants KPI Réutilisables - Écosystème LionsDev
**Version**: 2.0.0
**Date**: 2025-01-29
**Pattern**: WOU/DRY (Write Once Use / Don't Repeat Yourself)
---
## 🎯 Vue d'Ensemble
Ces composants KPI (Indicateurs de Performance) sont conçus pour être **100% réutilisables** dans tous les projets de l'écosystème **lionsdev**. Ils suivent les meilleures pratiques de PrimeFaces Freya et offrent une expérience utilisateur cohérente.
---
## 📦 Composants Disponibles
### 1. **kpi-card.xhtml** - Carte KPI Individuelle
Composant principal pour afficher un indicateur de performance unique.
**Localisation**: `/templates/components/shared/cards/kpi-card.xhtml`
**Paramètres principaux**:
- `title` (requis) - Titre du KPI
- `value` (requis) - Valeur à afficher
- `icon` (requis) - Icône PrimeIcons
- `iconColor` (requis) - Couleur de l'icône
- `growthValue` (optionnel) - Valeur de croissance
- `growthLabel` (optionnel) - Libellé de croissance
- `progressValue` (optionnel) - Barre de progression 0-100
- `statusIcon`, `statusLabel`, `statusValue` (optionnel) - Mode statut
- `clickable` (défaut: false) - Rendre cliquable
- `clickOutcome` (optionnel) - Redirection au clic
**Exemple simple**:
```xhtml
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Total Utilisateurs" />
<ui:param name="value" value="#{bean.totalUsers}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
```
**Exemple avec croissance**:
```xhtml
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Utilisateurs Actifs" />
<ui:param name="value" value="#{bean.activeUsers}" />
<ui:param name="icon" value="pi-user-check" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="growthValue" value="#{bean.growthPercent}" />
<ui:param name="growthLabel" value="ce mois" />
<ui:param name="progressValue" value="#{bean.progressPercent}" />
</ui:include>
```
**Exemple avec statut**:
```xhtml
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Sessions Actives" />
<ui:param name="value" value="#{bean.activeSessions}" />
<ui:param name="icon" value="pi-sign-in" />
<ui:param name="iconColor" value="purple-600" />
<ui:param name="statusIcon" value="pi-check-circle" />
<ui:param name="statusLabel" value="En ligne" />
<ui:param name="statusValue" value="#{bean.onlineUsers} actifs" />
</ui:include>
```
---
### 2. **kpi-group.xhtml** - Groupe de KPI
Composant composite pour organiser plusieurs KPI dans une grille.
**Localisation**: `/templates/components/shared/dashboard/kpi-group.xhtml`
**Paramètres**:
- `title` (optionnel) - Titre de la section
- `columns` (défaut: 4) - Nombre de colonnes (1-6)
- `colSize` (optionnel) - Taille personnalisée
**Exemple**:
```xhtml
<ui:include src="/templates/components/shared/dashboard/kpi-group.xhtml">
<ui:param name="title" value="Statistiques Utilisateurs" />
<ui:param name="columns" value="4" />
<ui:define name="kpi-content">
<!-- KPI 1 -->
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Total" />
<ui:param name="value" value="#{bean.total}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
<!-- KPI 2, 3, 4... -->
</ui:define>
</ui:include>
```
---
### 3. **dashboard-section.xhtml** - Section Dashboard
Composant composite pour créer des sections de dashboard réutilisables.
**Localisation**: `/templates/components/shared/dashboard/dashboard-section.xhtml`
**Paramètres**:
- `title` (requis) - Titre de la section
- `description` (optionnel) - Description
- `icon` (optionnel) - Icône
- `colSize` (défaut: "col-12") - Taille de colonne
- `showCard` (défaut: true) - Envelopper dans une carte
**Exemple**:
```xhtml
<ui:include src="/templates/components/shared/dashboard/dashboard-section.xhtml">
<ui:param name="title" value="Actions Rapides" />
<ui:param name="icon" value="pi-bolt" />
<ui:param name="colSize" value="col-12 lg:col-6" />
<ui:define name="section-content">
<!-- Contenu de la section -->
<div class="grid">
<div class="col-12 md:col-6">
<p:commandButton value="Action 1" />
</div>
</div>
</ui:define>
</ui:include>
```
---
### 4. **user-stat-card.xhtml** - Wrapper de Compatibilité
Wrapper de compatibilité ascendante qui délègue à `kpi-card.xhtml`.
**Note**: Pour de nouvelles implémentations, utilisez directement `kpi-card.xhtml`.
---
## 🎨 Couleurs Disponibles
Utilisez les couleurs PrimeFlex pour `iconColor`:
- `blue-600`, `blue-500`, `blue-400`
- `green-600`, `green-500`, `green-400`
- `purple-600`, `purple-500`, `purple-400`
- `orange-600`, `orange-500`, `orange-400`
- `red-600`, `red-500`, `red-400`
- `cyan-600`, `cyan-500`, `cyan-400`
- `pink-600`, `pink-500`, `pink-400`
---
## 📐 Tailles de Colonnes
Utilisez les classes PrimeFlex pour `colSize`:
- `col-12` - Pleine largeur
- `col-12 md:col-6` - 2 colonnes sur tablette+
- `col-12 md:col-6 lg:col-3` - 4 colonnes sur desktop (défaut)
- `col-12 md:col-4` - 3 colonnes
- `col-12 md:col-6 lg:col-2` - 6 colonnes
---
## 🔄 Réutilisabilité dans l'Écosystème LionsDev
Ces composants peuvent être utilisés dans:
-**lions-user-manager** (module actuel)
-**unionflow** (via dépendance Maven)
-**btpxpress** (via dépendance Maven)
-**Tout autre projet lionsdev** (via dépendance Maven)
**Avantages**:
- Code DRY (Don't Repeat Yourself)
- Interface utilisateur cohérente
- Maintenance centralisée
- Évolutivité garantie
---
## 📝 Bonnes Pratiques
1. **Toujours utiliser `kpi-card.xhtml`** pour de nouveaux KPI
2. **Utiliser `kpi-group.xhtml`** pour organiser plusieurs KPI
3. **Utiliser `dashboard-section.xhtml`** pour structurer les dashboards
4. **Respecter les conventions de couleurs** PrimeFlex
5. **Documenter les paramètres personnalisés** dans votre code
---
## 🚀 Exemple Complet de Dashboard
```xhtml
<div class="grid">
<!-- En-tête -->
<div class="col-12">
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="title" value="Tableau de Bord" />
</ui:include>
</div>
<!-- KPIs -->
<div class="col-12">
<div class="grid">
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Total" />
<ui:param name="value" value="#{bean.total}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
<!-- Autres KPI... -->
</div>
</div>
<!-- Sections -->
<ui:include src="/templates/components/shared/dashboard/dashboard-section.xhtml">
<ui:param name="title" value="Actions Rapides" />
<ui:define name="section-content">
<!-- Contenu -->
</ui:define>
</ui:include>
</div>
```
---
## 📚 Documentation Complète
Pour plus de détails, consultez:
- `/templates/components/README.md` - Documentation générale
- Code source des composants avec commentaires JSDoc
---
**Auteur**: Lions User Manager Team
**Licence**: Écosystème LionsDev

View File

@@ -0,0 +1,132 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Bouton Action Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Bouton générique pour actions utilisateur
Paramètres:
- value: String (requis) - Texte du bouton
- icon: String (optionnel) - Classe d'icône PrimeIcons
- hasAction: Boolean (défaut: false) - Indique si une action est fournie
- action: MethodExpression (optionnel) - Action à exécuter (requis si hasAction=true)
- hasOutcome: Boolean (défaut: false) - Indique si un outcome est fourni
- outcome: String (optionnel) - Page de redirection (requis si hasOutcome=true)
- onclick: String (optionnel) - Code JavaScript à exécuter au clic
- severity: String (défaut: "primary") - Severity: "primary", "success", "warning", "danger", "info", "secondary"
- size: String (défaut: "normal") - Taille: "small", "normal", "large"
- disabled: Boolean (défaut: false) - Désactiver le bouton
- update: String (optionnel) - Composants à mettre à jour
- process: String (défaut: "@this") - Composants à traiter
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemples d'utilisation:
1. Bouton simple:
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Créer Utilisateur" />
<ui:param name="icon" value="pi-user-plus" />
<ui:param name="action" value="#{userBean.createUser}" />
</ui:include>
2. Bouton avec redirection:
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Voir Profil" />
<ui:param name="icon" value="pi-eye" />
<ui:param name="outcome" value="/pages/user-manager/users/profile" />
</ui:include>
-->
<c:set var="severity" value="#{empty severity ? 'primary' : severity}" />
<c:set var="size" value="#{empty size ? 'normal' : size}" />
<c:set var="disabled" value="#{empty disabled ? false : disabled}" />
<c:set var="process" value="#{empty process ? '@this' : process}" />
<c:set var="hasAction" value="#{empty hasAction ? false : hasAction}" />
<c:set var="hasOutcome" value="#{empty hasOutcome ? false : hasOutcome}" />
<!-- Déterminer la classe selon la severity -->
<c:choose>
<c:when test="#{severity == 'primary'}">
<c:set var="buttonClass" value="p-button-primary" />
</c:when>
<c:when test="#{severity == 'success'}">
<c:set var="buttonClass" value="p-button-success" />
</c:when>
<c:when test="#{severity == 'warning'}">
<c:set var="buttonClass" value="p-button-warning" />
</c:when>
<c:when test="#{severity == 'danger'}">
<c:set var="buttonClass" value="p-button-danger" />
</c:when>
<c:when test="#{severity == 'info'}">
<c:set var="buttonClass" value="p-button-info" />
</c:when>
<c:otherwise>
<c:set var="buttonClass" value="p-button-secondary" />
</c:otherwise>
</c:choose>
<!-- Ajouter la taille -->
<c:if test="#{size == 'small'}">
<c:set var="buttonClass" value="#{buttonClass} p-button-sm" />
</c:if>
<c:if test="#{size == 'large'}">
<c:set var="buttonClass" value="#{buttonClass} p-button-lg" />
</c:if>
<!-- Ajouter les classes personnalisées -->
<c:if test="#{not empty styleClass}">
<c:set var="buttonClass" value="#{buttonClass} #{styleClass}" />
</c:if>
<c:choose>
<c:when test="#{hasAction}">
<p:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
disabled="#{disabled}"
action="#{action}"
update="#{not empty update ? update : '@form'}"
process="#{process}"
onclick="#{not empty onclick ? onclick : ''}" />
</c:when>
<c:when test="#{hasOutcome}">
<p:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
disabled="#{disabled}"
outcome="#{outcome}"
update="#{not empty update ? update : '@form'}"
process="#{process}"
onclick="#{not empty onclick ? onclick : ''}" />
</c:when>
<c:when test="#{not empty onclick}">
<p:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
disabled="#{disabled}"
type="button"
onclick="#{onclick}" />
</c:when>
<c:otherwise>
<p:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
disabled="true"
title="Aucune action définie" />
</c:otherwise>
</c:choose>
</ui:composition>

View File

@@ -0,0 +1,115 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
<div class="p-4" style="min-height: 9rem;">
<!-- Header: Titre et Icône -->
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">#{title}</span>
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
style="width: 2.5rem; height: 2.5rem;">
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
</div>
</div>
<!-- Valeur principale -->
<div class="text-900 font-bold text-2xl mb-2">
<c:choose>
<c:when test="#{not empty value}">
<c:set var="valueStr" value="#{String.valueOf(value)}" />
<c:choose>
<c:when test="#{fn:startsWith(valueStr, '-') and fn:length(valueStr) == 1}">0</c:when>
<c:when test="#{fn:startsWith(valueStr, '...')}">0</c:when>
<c:otherwise>#{value}</c:otherwise>
</c:choose>
</c:when>
<c:otherwise>0</c:otherwise>
</c:choose>
</div>
<!-- Sous-titre -->
<c:if test="#{not empty subtitle}">
<div class="text-500 text-xs mb-2">#{subtitle}</div>
</c:if>
<!-- Section Croissance ou Statut -->
<c:choose>
<!-- Mode Statut (statusIcon fourni) -->
<c:when test="#{not empty statusIcon}">
<c:choose>
<c:when test="#{not empty statusValue and statusValue != '0' and statusValue != '0.0'}">
<div class="flex align-items-center mb-2">
<i class="pi #{statusIcon} text-green-500 text-sm mr-2"></i>
<span class="text-green-600 font-semibold text-sm mr-2">#{statusLabel}</span>
<span class="text-500 text-xs">#{statusValue}</span>
</div>
</c:when>
<c:otherwise>
<div class="text-500 text-xs mb-2">Aucun #{statusLabel}</div>
</c:otherwise>
</c:choose>
</c:when>
<!-- Mode Croissance -->
<c:otherwise>
<c:choose>
<!-- Croissance en nombre -->
<c:when test="#{growthType == 'number'}">
<c:choose>
<c:when test="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
<div class="flex align-items-center mb-2">
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}</span>
<c:if test="#{not empty growthLabel}">
<span class="text-500 text-xs">#{growthLabel}</span>
</c:if>
</div>
</c:when>
<c:otherwise>
<div class="text-500 text-xs mb-2">#{noDataLabel}</div>
</c:otherwise>
</c:choose>
</c:when>
<!-- Croissance en pourcentage (défaut) -->
<c:otherwise>
<c:choose>
<c:when test="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
<div class="flex align-items-center mb-2">
<c:choose>
<c:when test="#{growthValue >= 0}">
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}%</span>
</c:when>
<c:otherwise>
<i class="pi pi-arrow-down text-red-500 text-sm mr-2"></i>
<span class="text-red-600 font-semibold text-sm mr-2">#{growthValue}%</span>
</c:otherwise>
</c:choose>
<c:if test="#{not empty growthLabel}">
<span class="text-500 text-xs">#{growthLabel}</span>
</c:if>
</div>
</c:when>
<c:otherwise>
<div class="text-500 text-xs mb-2">#{noDataLabel}</div>
</c:otherwise>
</c:choose>
</c:otherwise>
</c:choose>
</c:otherwise>
</c:choose>
<!-- Barre de progression -->
<c:if test="#{showProgress and not empty progressValue}">
<p:progressBar value="#{progressValue}"
showValue="false"
styleClass="surface-200"
style="height: 0.5rem; width: 100%;" />
</c:if>
</div>
</ui:composition>

View File

@@ -0,0 +1,95 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Carte KPI (Indicateur de Performance) - Écosystème LionsDev
Auteur: Lions User Manager
Version: 2.1.0
Description: Carte KPI générique et réutilisable pour tous les projets de l'écosystème lionsdev
-->
<c:set var="colSize" value="#{empty colSize ? 'col-12 md:col-6 lg:col-3' : colSize}" />
<c:set var="clickable" value="#{empty clickable ? false : clickable}" />
<c:set var="growthType" value="#{empty growthType ? 'percentage' : growthType}" />
<c:set var="showGrowth" value="#{empty showGrowth ? (not empty growthValue) : showGrowth}" />
<c:set var="showProgress" value="#{empty showProgress ? (not empty progressValue) : showProgress}" />
<c:set var="noDataLabel" value="#{empty noDataLabel ? 'Données non disponibles' : noDataLabel}" />
<div class="#{colSize}">
<c:choose>
<c:when test="#{clickable and not empty clickOutcome}">
<p:commandLink styleClass="card-link w-full #{styleClass}" outcome="#{clickOutcome}">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<ui:include src="/templates/components/shared/cards/kpi-card-content.xhtml">
<ui:param name="title" value="#{title}" />
<ui:param name="value" value="#{value}" />
<ui:param name="icon" value="#{icon}" />
<ui:param name="iconColor" value="#{iconColor}" />
<ui:param name="subtitle" value="#{subtitle}" />
<ui:param name="growthValue" value="#{growthValue}" />
<ui:param name="growthLabel" value="#{growthLabel}" />
<ui:param name="growthType" value="#{growthType}" />
<ui:param name="showGrowth" value="#{showGrowth}" />
<ui:param name="noDataLabel" value="#{noDataLabel}" />
<ui:param name="progressValue" value="#{progressValue}" />
<ui:param name="showProgress" value="#{showProgress}" />
<ui:param name="statusIcon" value="#{statusIcon}" />
<ui:param name="statusLabel" value="#{statusLabel}" />
<ui:param name="statusValue" value="#{statusValue}" />
</ui:include>
</div>
</p:commandLink>
</c:when>
<c:when test="#{clickable and not empty clickAction}">
<p:commandLink styleClass="card-link w-full #{styleClass}" action="#{clickAction}">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<ui:include src="/templates/components/shared/cards/kpi-card-content.xhtml">
<ui:param name="title" value="#{title}" />
<ui:param name="value" value="#{value}" />
<ui:param name="icon" value="#{icon}" />
<ui:param name="iconColor" value="#{iconColor}" />
<ui:param name="subtitle" value="#{subtitle}" />
<ui:param name="growthValue" value="#{growthValue}" />
<ui:param name="growthLabel" value="#{growthLabel}" />
<ui:param name="growthType" value="#{growthType}" />
<ui:param name="showGrowth" value="#{showGrowth}" />
<ui:param name="noDataLabel" value="#{noDataLabel}" />
<ui:param name="progressValue" value="#{progressValue}" />
<ui:param name="showProgress" value="#{showProgress}" />
<ui:param name="statusIcon" value="#{statusIcon}" />
<ui:param name="statusLabel" value="#{statusLabel}" />
<ui:param name="statusValue" value="#{statusValue}" />
</ui:include>
</div>
</p:commandLink>
</c:when>
<c:otherwise>
<div class="card surface-0 border-round-lg #{styleClass}">
<ui:include src="/templates/components/shared/cards/kpi-card-content.xhtml">
<ui:param name="title" value="#{title}" />
<ui:param name="value" value="#{value}" />
<ui:param name="icon" value="#{icon}" />
<ui:param name="iconColor" value="#{iconColor}" />
<ui:param name="subtitle" value="#{subtitle}" />
<ui:param name="growthValue" value="#{growthValue}" />
<ui:param name="growthLabel" value="#{growthLabel}" />
<ui:param name="growthType" value="#{growthType}" />
<ui:param name="showGrowth" value="#{showGrowth}" />
<ui:param name="noDataLabel" value="#{noDataLabel}" />
<ui:param name="progressValue" value="#{progressValue}" />
<ui:param name="showProgress" value="#{showProgress}" />
<ui:param name="statusIcon" value="#{statusIcon}" />
<ui:param name="statusLabel" value="#{statusLabel}" />
<ui:param name="statusValue" value="#{statusValue}" />
</ui:include>
</div>
</c:otherwise>
</c:choose>
</div>
</ui:composition>

View File

@@ -0,0 +1,67 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Carte Statistique Utilisateur (WOU/DRY Pattern)
Version 2.0.0 - Utilise maintenant kpi-card.xhtml en interne
Auteur: Lions User Manager
Description: Wrapper autour de kpi-card.xhtml pour compatibilité ascendante
Paramètres:
- title: String (requis) - Titre de la carte
- value: String/Number (requis) - Valeur à afficher
- icon: String (requis) - Classe d'icône PrimeIcons
- iconColor: String (requis) - Couleur de l'icône
- subtitle: String (optionnel) - Sous-titre
- trend: Number (optionnel) - Tendance (pourcentage) - Mappé vers growthValue
- trendLabel: String (optionnel) - Libellé de la tendance - Mappé vers growthLabel
- colSize: String (défaut: "col-12 md:col-6 lg:col-3") - Taille de colonne
- clickable: Boolean (défaut: false) - Rendre la carte cliquable
- clickOutcome: String (optionnel) - Page de redirection au clic
Note: Ce composant est un wrapper de compatibilité. Pour de nouvelles implémentations,
utilisez directement kpi-card.xhtml qui offre plus de fonctionnalités.
Exemples d'utilisation:
1. Carte simple:
<ui:include src="/templates/components/shared/cards/user-stat-card.xhtml">
<ui:param name="title" value="Total Utilisateurs" />
<ui:param name="value" value="#{userBean.totalUsers}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
2. Carte avec tendance:
<ui:include src="/templates/components/shared/cards/user-stat-card.xhtml">
<ui:param name="title" value="Utilisateurs Actifs" />
<ui:param name="value" value="#{userBean.activeUsers}" />
<ui:param name="icon" value="pi-user-check" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="trend" value="#{userBean.activeUsersTrend}" />
<ui:param name="trendLabel" value="ce mois" />
</ui:include>
-->
<!-- Déléguer à kpi-card.xhtml -->
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="#{title}" />
<ui:param name="value" value="#{value}" />
<ui:param name="icon" value="#{icon}" />
<ui:param name="iconColor" value="#{iconColor}" />
<ui:param name="subtitle" value="#{subtitle}" />
<ui:param name="growthValue" value="#{trend}" />
<ui:param name="growthLabel" value="#{trendLabel}" />
<ui:param name="growthType" value="percentage" />
<ui:param name="colSize" value="#{colSize}" />
<ui:param name="clickable" value="#{clickable}" />
<ui:param name="clickOutcome" value="#{clickOutcome}" />
</ui:include>
</ui:composition>

View File

@@ -0,0 +1,77 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Section Dashboard - Écosystème LionsDev
Auteur: Lions User Manager
Version: 1.0.0
Description: Composant composite pour créer des sections de dashboard réutilisables
Paramètres:
- title: String (requis) - Titre de la section
- description: String (optionnel) - Description de la section
- icon: String (optionnel) - Icône PrimeIcons
- colSize: String (défaut: "col-12") - Taille de colonne
- showCard: Boolean (défaut: true) - Envelopper dans une carte
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemple:
ui:include src="/templates/components/shared/dashboard/dashboard-section.xhtml"
ui:param name="title" value="Actions Rapides"
ui:param name="icon" value="pi-bolt"
ui:param name="colSize" value="col-12 lg:col-6"
ui:define name="section-content"
Contenu de la section
ui:define
ui:include
-->
<c:set var="colSize" value="#{empty colSize ? 'col-12' : colSize}" />
<c:set var="showCard" value="#{empty showCard ? true : showCard}" />
<div class="#{colSize}">
<c:choose>
<c:when test="#{showCard}">
<div class="card #{styleClass}">
<c:if test="#{not empty title}">
<div class="flex align-items-center mb-3">
<c:if test="#{not empty icon}">
<i class="pi #{icon} mr-2 text-primary"></i>
</c:if>
<h5 class="font-semibold mb-0">#{title}</h5>
</div>
</c:if>
<c:if test="#{not empty description}">
<p class="text-600 text-sm mb-3">#{description}</p>
</c:if>
<ui:insert name="section-content">
<!-- Contenu de la section -->
</ui:insert>
</div>
</c:when>
<c:otherwise>
<c:if test="#{not empty title}">
<div class="flex align-items-center mb-3">
<c:if test="#{not empty icon}">
<i class="pi #{icon} mr-2 text-primary"></i>
</c:if>
<h5 class="font-semibold mb-0">#{title}</h5>
</div>
</c:if>
<c:if test="#{not empty description}">
<p class="text-600 text-sm mb-3">#{description}</p>
</c:if>
<ui:insert name="section-content">
<!-- Contenu de la section -->
</ui:insert>
</c:otherwise>
</c:choose>
</div>
</ui:composition>

View File

@@ -0,0 +1,78 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Groupe de KPI (Composite) - Écosystème LionsDev
Auteur: Lions User Manager
Version: 1.0.0
Description: Composant composite pour afficher un groupe de KPI dans une grille
Paramètres:
- title: String (optionnel) - Titre de la section
- columns: Integer (défaut: 4) - Nombre de colonnes (1-12)
- colSize: String (optionnel) - Taille de colonne personnalisée (ex: "col-12 md:col-6 lg:col-3")
- showTitle: Boolean (défaut: true si title fourni) - Afficher le titre
- styleClass: String (optionnel) - Classes CSS supplémentaires
Utilisation:
Ce composant doit être utilisé avec des ui:param pour passer les KPI individuels.
Chaque KPI doit être inclus avec kpi-card.xhtml.
Exemple:
<ui:include src="/templates/components/shared/dashboard/kpi-group.xhtml">
<ui:param name="title" value="Statistiques Utilisateurs" />
<ui:param name="columns" value="4" />
<ui:define name="kpi-content">
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Total" />
<ui:param name="value" value="#{bean.total}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
Autres KPI à ajouter ici
</ui:define>
</ui:include>
-->
<c:set var="columns" value="#{empty columns ? 4 : columns}" />
<c:set var="showTitle" value="#{empty showTitle ? (not empty title) : showTitle}" />
<c:choose>
<c:when test="#{columns == 1}">
<c:set var="colSize" value="col-12" />
</c:when>
<c:when test="#{columns == 2}">
<c:set var="colSize" value="col-12 md:col-6" />
</c:when>
<c:when test="#{columns == 3}">
<c:set var="colSize" value="col-12 md:col-6 lg:col-4" />
</c:when>
<c:when test="#{columns == 4}">
<c:set var="colSize" value="col-12 md:col-6 lg:col-3" />
</c:when>
<c:when test="#{columns == 6}">
<c:set var="colSize" value="col-12 md:col-6 lg:col-2" />
</c:when>
<c:otherwise>
<c:set var="colSize" value="#{empty colSize ? 'col-12 md:col-6 lg:col-3' : colSize}" />
</c:otherwise>
</c:choose>
<div class="mb-4 #{styleClass}">
<c:if test="#{showTitle}">
<h5 class="font-semibold mb-3">#{title}</h5>
</c:if>
<div class="grid">
<ui:insert name="kpi-content">
<!-- Les KPI seront insérés ici -->
</ui:insert>
</div>
</div>
</ui:composition>

View File

@@ -0,0 +1,163 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Champ Formulaire Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Champ de formulaire générique pour utilisateur
Paramètres:
- id: String (requis) - ID du champ
- label: String (requis) - Label du champ
- value: Object (requis) - Valeur du champ
- type: String (défaut: "text") - Type: "text", "email", "password", "number", "textarea", "select", "checkbox", "calendar"
- required: Boolean (défaut: false) - Champ requis
- readonly: Boolean (défaut: false) - Mode lecture seule
- placeholder: String (optionnel) - Placeholder
- helpText: String (optionnel) - Texte d'aide
- styleClass: String (optionnel) - Classes CSS supplémentaires
- selectItems: List (optionnel) - Items pour select
- rows: Number (optionnel, défaut: 3) - Nombre de lignes pour textarea
Exemples d'utilisation:
1. Champ texte:
<ui:include src="/templates/components/shared/forms/user-form-field.xhtml">
<ui:param name="id" value="username" />
<ui:param name="label" value="Nom d'utilisateur" />
<ui:param name="value" value="#{user.username}" />
<ui:param name="required" value="true" />
</ui:include>
2. Champ email:
<ui:include src="/templates/components/shared/forms/user-form-field.xhtml">
<ui:param name="id" value="email" />
<ui:param name="label" value="Email" />
<ui:param name="value" value="#{user.email}" />
<ui:param name="type" value="email" />
<ui:param name="required" value="true" />
</ui:include>
3. Champ select:
<ui:include src="/templates/components/shared/forms/user-form-field.xhtml">
<ui:param name="id" value="statut" />
<ui:param name="label" value="Statut" />
<ui:param name="value" value="#{user.statut}" />
<ui:param name="type" value="select" />
<ui:param name="selectItems" value="#{userBean.statutOptions}" />
</ui:include>
-->
<c:set var="type" value="#{empty type ? 'text' : type}" />
<c:set var="required" value="#{empty required ? false : required}" />
<c:set var="readonly" value="#{empty readonly ? false : readonly}" />
<c:set var="rows" value="#{empty rows ? 3 : rows}" />
<div class="field">
<p:outputLabel for="#{id}" value="#{label}#{required ? ' *' : ''}" />
<c:choose>
<!-- Champ texte -->
<c:when test="#{type == 'text' or type == 'email'}">
<p:inputText
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
placeholder="#{placeholder}"
type="#{type == 'email' ? 'email' : 'text'}"
styleClass="w-full #{styleClass}" />
</c:when>
<!-- Champ mot de passe -->
<c:when test="#{type == 'password'}">
<p:password
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
placeholder="#{placeholder}"
feedback="#{not empty feedback ? feedback : false}"
styleClass="w-full #{styleClass}" />
</c:when>
<!-- Champ nombre -->
<c:when test="#{type == 'number'}">
<p:inputNumber
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
placeholder="#{placeholder}"
styleClass="w-full #{styleClass}" />
</c:when>
<!-- Champ textarea -->
<c:when test="#{type == 'textarea'}">
<p:inputTextarea
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
placeholder="#{placeholder}"
rows="#{rows}"
styleClass="w-full #{styleClass}" />
</c:when>
<!-- Champ select -->
<c:when test="#{type == 'select'}">
<p:selectOneMenu
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
styleClass="w-full #{styleClass}">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{selectItems}" />
</p:selectOneMenu>
</c:when>
<!-- Champ checkbox -->
<c:when test="#{type == 'checkbox'}">
<p:selectBooleanCheckbox
id="#{id}"
value="#{value}"
readonly="#{readonly}" />
</c:when>
<!-- Champ calendar -->
<c:when test="#{type == 'calendar'}">
<p:calendar
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
pattern="dd/MM/yyyy"
styleClass="w-full #{styleClass}" />
</c:when>
<!-- Par défaut: champ texte -->
<c:otherwise>
<p:inputText
id="#{id}"
value="#{value}"
required="#{required}"
readonly="#{readonly}"
placeholder="#{placeholder}"
styleClass="w-full #{styleClass}" />
</c:otherwise>
</c:choose>
<c:if test="#{not empty helpText}">
<small class="text-color-secondary text-xs">#{helpText}</small>
</c:if>
</div>
</ui:composition>

View File

@@ -0,0 +1,191 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:lum="http://xmlns.jcp.org/jsf/composite/components">
<!--
Composant réutilisable: Tableau Utilisateurs (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Tableau de données pour afficher une liste d'utilisateurs
Paramètres:
- users: List&lt;UserDTO&gt; (requis) - Liste des utilisateurs
- var: String (défaut: "user") - Nom de la variable pour itération
- tableId: String (défaut: "userTable") - ID du tableau
- paginator: Boolean (défaut: true) - Activer la pagination
- rows: Number (défaut: 20) - Nombre de lignes par page
- showActions: Boolean (défaut: true) - Afficher la colonne actions
- showRoles: Boolean (défaut: true) - Afficher la colonne rôles
- showEmail: Boolean (défaut: true) - Afficher la colonne email
- showStatus: Boolean (défaut: true) - Afficher la colonne statut
- showSelection: Boolean (défaut: false) - Activer la sélection
- selection: UserDTO (optionnel) - Utilisateur sélectionné
- selectionMode: String (défaut: "single") - Mode: "single" ou "multiple"
- totalRecords: Long (optionnel) - Nombre total d'enregistrements pour l'affichage
- hasOnPageChange: Boolean (défaut: false) - Indique si un gestionnaire de pagination est fourni
- onPageChange: MethodExpression (optionnel) - Méthode à appeler lors du changement de page (requis si hasOnPageChange=true)
- lazy: Boolean (défaut: false) - Activer le chargement paresseux
- update: String (optionnel) - Composants à mettre à jour
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemples d'utilisation:
1. Tableau simple:
<ui:include src="/templates/components/shared/tables/user-data-table.xhtml">
<ui:param name="users" value="#{userBean.users}" />
</ui:include>
2. Tableau avec sélection:
<ui:include src="/templates/components/shared/tables/user-data-table.xhtml">
<ui:param name="users" value="#{userBean.users}" />
<ui:param name="showSelection" value="true" />
<ui:param name="selection" value="#{userBean.selectedUser}" />
</ui:include>
-->
<c:set var="varName" value="#{empty var ? 'user' : var}" />
<c:set var="tableId" value="#{empty tableId ? 'userTable' : tableId}" />
<c:set var="paginator" value="#{empty paginator ? true : paginator}" />
<c:set var="rows" value="#{empty rows ? 20 : rows}" />
<c:set var="showActions" value="#{empty showActions ? true : showActions}" />
<c:set var="showRoles" value="#{empty showRoles ? true : showRoles}" />
<c:set var="showEmail" value="#{empty showEmail ? true : showEmail}" />
<c:set var="showStatus" value="#{empty showStatus ? true : showStatus}" />
<c:set var="showSelection" value="#{empty showSelection ? false : showSelection}" />
<c:set var="selectionMode" value="#{empty selectionMode ? 'single' : selectionMode}" />
<c:set var="hasOnPageChange" value="#{empty hasOnPageChange ? false : hasOnPageChange}" />
<p:dataTable
id="#{tableId}"
value="#{users}"
var="user"
rowKey="#{user.id}"
paginator="#{paginator}"
rows="#{rows}"
rowCount="#{not empty totalRecords ? totalRecords : (users != null ? users.size() : 0)}"
selection="#{selection}"
selectionMode="#{selectionMode}"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped w-full #{styleClass}"
rowStyleClass="p-datatable-row"
widgetVar="#{tableId}Widget"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="10,20,50,100"
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
emptyMessage="Aucun utilisateur trouvé"
reflow="true"
responsiveLayout="scroll"
lazy="#{not empty lazy and lazy}">
<f:facet name="header">
<div class="flex align-items-center justify-content-between">
<span class="text-900 font-semibold text-xl">Utilisateurs</span>
<c:if test="#{not empty totalRecords}">
<span class="text-600 text-sm">Total: #{totalRecords}</span>
</c:if>
</div>
</f:facet>
<!-- Gestionnaire d'événements pour la pagination -->
<c:if test="#{hasOnPageChange}">
<p:ajax event="page" listener="#{onPageChange}" update="#{not empty update ? update : tableId}" />
</c:if>
<!-- Colonne de sélection -->
<c:if test="#{showSelection}">
<p:column selectionMode="#{selectionMode}" style="width: 50px" />
</c:if>
<!-- Colonne Username -->
<p:column headerText="Nom d'utilisateur" sortBy="#{user.username}" style="width: 200px">
<div class="flex align-items-center py-2">
<div class="border-circle overflow-hidden mr-2 flex-shrink-0" style="width: 36px; height: 36px;">
<div class="bg-primary text-white flex align-items-center justify-content-center w-full h-full">
<span style="font-size: 0.875rem; font-weight: bold;">
#{user.prenom != null ? user.prenom.substring(0,1) : 'U'}#{user.nom != null ? user.nom.substring(0,1) : ''}
</span>
</div>
</div>
<span class="font-semibold text-900">#{user.username}</span>
</div>
</p:column>
<!-- Colonne Nom complet -->
<p:column headerText="Nom complet" sortBy="#{user.nom}" style="width: 220px">
<div class="flex flex-column py-2">
<span class="font-medium text-900">#{user.prenom} #{user.nom}</span>
<c:if test="#{not empty user.fonction}">
<small class="text-600 text-xs mt-1">#{user.fonction}</small>
</c:if>
</div>
</p:column>
<!-- Colonne Email -->
<c:if test="#{showEmail}">
<p:column headerText="Email" sortBy="#{user.email}" style="width: 250px">
<div class="flex align-items-center py-2">
<i class="pi pi-envelope text-500 mr-2"></i>
<span class="text-900">#{user.email}</span>
<c:if test="#{user.emailVerified}">
<i class="pi pi-check-circle text-green-500 ml-2" title="Email vérifié"></i>
</c:if>
</div>
</p:column>
</c:if>
<!-- Colonne Statut -->
<c:if test="#{showStatus}">
<p:column headerText="Statut" sortBy="#{user.statut}" style="width: 130px">
<div class="flex align-items-center py-2">
<p:tag
value="#{user.statut != null ? user.statut : 'INCONNU'}"
severity="#{user.enabled ? 'success' : 'danger'}" />
</div>
</p:column>
</c:if>
<!-- Colonne Rôles -->
<c:if test="#{showRoles}">
<p:column headerText="Rôles" style="width: 200px">
<div class="flex flex-wrap gap-1 py-2 align-items-center">
<c:choose>
<c:when test="#{user.realmRoles != null and !user.realmRoles.isEmpty()}">
<c:forEach var="role" items="#{user.realmRoles}" varStatus="status">
<c:if test="#{status.index &lt; 3}">
<p:tag value="#{role}" severity="info" styleClass="text-xs" />
</c:if>
</c:forEach>
<c:if test="#{user.realmRoles.size() &gt; 3}">
<p:tag value="+#{user.realmRoles.size() - 3}" severity="secondary" styleClass="text-xs" />
</c:if>
</c:when>
<c:otherwise>
<span class="text-500 text-xs">Aucun rôle</span>
</c:otherwise>
</c:choose>
</div>
</p:column>
</c:if>
<!-- Colonne Actions -->
<c:if test="#{showActions}">
<p:column headerText="Actions" style="width: 100px" exportable="false">
<div class="flex justify-content-center align-items-center" style="min-height: 3rem;">
<lum:user-action-dropdown
userId="#{user.id}"
userEnabled="#{user.enabled}"
update="#{not empty update ? update : tableId}"
activateAction="#{activateAction}"
deactivateAction="#{deactivateAction}"
deleteAction="#{deleteAction}" />
</div>
</p:column>
</c:if>
</p:dataTable>
</ui:composition>

View File

@@ -0,0 +1,387 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Actions Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Boutons d'action pour un utilisateur (activate, deactivate, delete, etc.)
Paramètres:
- user: UserDTO (requis) - L'utilisateur concerné
- showView: Boolean (défaut: true) - Afficher le bouton "Voir"
- showEdit: Boolean (défaut: true) - Afficher le bouton "Modifier"
- showDelete: Boolean (défaut: true) - Afficher le bouton "Supprimer"
- showActivate: Boolean (défaut: true) - Afficher le bouton "Activer"
- showDeactivate: Boolean (défaut: true) - Afficher le bouton "Désactiver"
- showResetPassword: Boolean (défaut: true) - Afficher le bouton "Réinitialiser mot de passe"
- showLogoutSessions: Boolean (défaut: false) - Afficher le bouton "Déconnecter toutes les sessions"
- viewAction: String (optionnel) - Action pour "Voir"
- editAction: String (optionnel) - Action pour "Modifier"
- deleteAction: String (optionnel) - Action pour "Supprimer"
- activateAction: String (optionnel) - Action pour "Activer"
- deactivateAction: String (optionnel) - Action pour "Désactiver"
- resetPasswordAction: String (optionnel) - Action pour "Réinitialiser mot de passe"
- logoutSessionsAction: String (optionnel) - Action pour "Déconnecter sessions"
- viewOutcome: String (optionnel) - Page pour "Voir"
- editOutcome: String (optionnel) - Page pour "Modifier"
- update: String (défaut: "@form") - Composants à mettre à jour
- layout: String (défaut: "horizontal") - Layout: "horizontal" ou "vertical" ou "dropdown"
Exemples d'utilisation:
1. Actions horizontales (défaut):
<ui:include src="/templates/components/user-management/user-actions.xhtml">
<ui:param name="user" value="#{user}" />
<ui:param name="update" value="userTable" />
</ui:include>
2. Actions en dropdown:
<ui:include src="/templates/components/user-management/user-actions.xhtml">
<ui:param name="user" value="#{user}" />
<ui:param name="layout" value="dropdown" />
<ui:param name="update" value="userTable" />
</ui:include>
3. Actions limitées:
<ui:include src="/templates/components/user-management/user-actions.xhtml">
<ui:param name="user" value="#{user}" />
<ui:param name="showDelete" value="false" />
<ui:param name="showResetPassword" value="false" />
</ui:include>
-->
<c:set var="update" value="#{empty update ? '@form' : update}" />
<c:set var="layout" value="#{empty layout ? 'horizontal' : layout}" />
<c:set var="showView" value="#{empty showView ? true : showView}" />
<c:set var="showEdit" value="#{empty showEdit ? true : showEdit}" />
<c:set var="showDelete" value="#{empty showDelete ? true : showDelete}" />
<c:set var="showActivate" value="#{empty showActivate ? true : showActivate}" />
<c:set var="showDeactivate" value="#{empty showDeactivate ? true : showDeactivate}" />
<c:set var="showResetPassword" value="#{empty showResetPassword ? true : showResetPassword}" />
<c:set var="showLogoutSessions" value="#{empty showLogoutSessions ? false : showLogoutSessions}" />
<!-- Définir les actions par défaut si non fournies -->
<c:set var="defaultActivateAction" value="#{userBean.activateUser(user.id)}" />
<c:set var="defaultDeactivateAction" value="#{userBean.deactivateUser(user.id)}" />
<c:set var="defaultDeleteAction" value="#{userBean.deleteUser(user.id)}" />
<c:set var="defaultResetPasswordAction" value="#{userBean.resetPassword(user.id)}" />
<c:set var="defaultLogoutSessionsAction" value="#{userBean.logoutAllSessions(user.id)}" />
<c:choose>
<!-- Layout Dropdown -->
<c:when test="#{layout == 'dropdown'}">
<p:commandButton
icon="pi pi-ellipsis-v"
styleClass="p-button-text p-button-sm p-button-rounded p-button-plain"
type="button"
title="Actions"
style="width: 2rem; height: 2rem; padding: 0; margin: 0;">
<p:menu styleClass="w-12rem">
<c:if test="#{showView}">
<p:menuitem
value="Voir le profil"
icon="pi pi-eye"
outcome="#{not empty viewOutcome ? viewOutcome : '/pages/user-manager/users/profile'}">
<f:param name="userId" value="#{user.id}" />
</p:menuitem>
</c:if>
<c:if test="#{showEdit}">
<p:menuitem
value="Modifier"
icon="pi pi-pencil"
outcome="#{not empty editOutcome ? editOutcome : '/pages/user-manager/users/edit'}">
<f:param name="userId" value="#{user.id}" />
</p:menuitem>
</c:if>
<c:if test="#{showResetPassword}">
<p:menuitem
value="Réinitialiser mot de passe"
icon="pi pi-key"
onclick="PF('resetPasswordDialog').show()" />
</c:if>
<p:separator />
<c:if test="#{showActivate and not user.enabled}">
<c:choose>
<c:when test="#{not empty activateAction}">
<p:menuitem
value="Activer"
icon="pi pi-check"
styleClass="text-green-600"
action="#{activateAction}"
update="#{update}" />
</c:when>
<c:otherwise>
<p:menuitem
value="Activer"
icon="pi pi-check"
styleClass="text-green-600"
action="#{userBean.activateUser(user.id)}"
update="#{update}" />
</c:otherwise>
</c:choose>
</c:if>
<c:if test="#{showDeactivate and user.enabled}">
<c:choose>
<c:when test="#{not empty deactivateAction}">
<p:menuitem
value="Désactiver"
icon="pi pi-times"
styleClass="text-orange-600"
action="#{deactivateAction}"
update="#{update}" />
</c:when>
<c:otherwise>
<p:menuitem
value="Désactiver"
icon="pi pi-times"
styleClass="text-orange-600"
action="#{userBean.deactivateUser(user.id)}"
update="#{update}" />
</c:otherwise>
</c:choose>
</c:if>
<c:if test="#{showLogoutSessions}">
<c:choose>
<c:when test="#{not empty logoutSessionsAction}">
<p:menuitem
value="Déconnecter toutes les sessions"
icon="pi pi-sign-out"
styleClass="text-blue-600"
action="#{logoutSessionsAction}"
update="#{update}" />
</c:when>
<c:otherwise>
<p:menuitem
value="Déconnecter toutes les sessions"
icon="pi pi-sign-out"
styleClass="text-blue-600"
action="#{userBean.logoutAllSessions(user.id)}"
update="#{update}" />
</c:otherwise>
</c:choose>
</c:if>
<c:if test="#{showDelete}">
<p:separator />
<p:menuitem
value="Supprimer"
icon="pi pi-trash"
styleClass="text-red-600"
onclick="PF('confirmDeleteDialog').show()" />
</c:if>
</p:menu>
</p:commandButton>
</c:when>
<!-- Layout Horizontal (défaut) -->
<c:otherwise>
<div class="flex gap-1">
<c:if test="#{showView}">
<p:commandButton
icon="pi pi-eye"
title="Voir le profil"
styleClass="p-button-text p-button-sm p-button-info"
outcome="#{not empty viewOutcome ? viewOutcome : '/pages/user-manager/users/profile'}">
<f:param name="userId" value="#{user.id}" />
</p:commandButton>
</c:if>
<c:if test="#{showEdit}">
<p:commandButton
icon="pi pi-pencil"
title="Modifier"
styleClass="p-button-text p-button-sm p-button-warning"
outcome="#{not empty editOutcome ? editOutcome : '/pages/user-manager/users/edit'}">
<f:param name="userId" value="#{user.id}" />
</p:commandButton>
</c:if>
<c:if test="#{showResetPassword}">
<p:commandButton
icon="pi pi-key"
title="Réinitialiser mot de passe"
styleClass="p-button-text p-button-sm p-button-help"
onclick="PF('resetPasswordDialog').show()" />
</c:if>
<c:if test="#{showActivate and not user.enabled}">
<c:choose>
<c:when test="#{not empty activateAction}">
<p:commandButton
icon="pi pi-check"
title="Activer"
styleClass="p-button-text p-button-sm p-button-success"
action="#{activateAction}"
update="#{update}" />
</c:when>
<c:otherwise>
<p:commandButton
icon="pi pi-check"
title="Activer"
styleClass="p-button-text p-button-sm p-button-success"
action="#{userBean.activateUser(user.id)}"
update="#{update}" />
</c:otherwise>
</c:choose>
</c:if>
<c:if test="#{showDeactivate and user.enabled}">
<c:choose>
<c:when test="#{not empty deactivateAction}">
<p:commandButton
icon="pi pi-times"
title="Désactiver"
styleClass="p-button-text p-button-sm p-button-warning"
action="#{deactivateAction}"
update="#{update}" />
</c:when>
<c:otherwise>
<p:commandButton
icon="pi pi-times"
title="Désactiver"
styleClass="p-button-text p-button-sm p-button-warning"
action="#{userBean.deactivateUser(user.id)}"
update="#{update}" />
</c:otherwise>
</c:choose>
</c:if>
<c:if test="#{showLogoutSessions}">
<c:choose>
<c:when test="#{not empty logoutSessionsAction}">
<p:commandButton
icon="pi pi-sign-out"
title="Déconnecter toutes les sessions"
styleClass="p-button-text p-button-sm p-button-info"
action="#{logoutSessionsAction}"
update="#{update}" />
</c:when>
<c:otherwise>
<p:commandButton
icon="pi pi-sign-out"
title="Déconnecter toutes les sessions"
styleClass="p-button-text p-button-sm p-button-info"
action="#{userBean.logoutAllSessions(user.id)}"
update="#{update}" />
</c:otherwise>
</c:choose>
</c:if>
<c:if test="#{showDelete}">
<p:commandButton
icon="pi pi-trash"
title="Supprimer"
styleClass="p-button-text p-button-sm p-button-danger"
onclick="PF('confirmDeleteDialog').show()" />
</c:if>
</div>
</c:otherwise>
</c:choose>
<!-- Dialog de confirmation de suppression -->
<p:confirmDialog
id="confirmDeleteDialog"
widgetVar="confirmDeleteDialog"
message="Êtes-vous sûr de vouloir supprimer l'utilisateur #{user.username} ?"
header="Confirmation de suppression"
severity="warn">
<c:choose>
<c:when test="#{not empty deleteAction}">
<p:commandButton
value="Oui"
icon="pi pi-check"
styleClass="p-button-danger"
action="#{deleteAction}"
update="#{update}"
oncomplete="PF('confirmDeleteDialog').hide()" />
</c:when>
<c:otherwise>
<p:commandButton
value="Oui"
icon="pi pi-check"
styleClass="p-button-danger"
action="#{userBean.deleteUser(user.id)}"
update="#{update}"
oncomplete="PF('confirmDeleteDialog').hide()" />
</c:otherwise>
</c:choose>
<p:commandButton
value="Non"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('confirmDeleteDialog').hide()" />
</p:confirmDialog>
<!-- Dialog de réinitialisation de mot de passe -->
<p:dialog
id="resetPasswordDialog"
widgetVar="resetPasswordDialog"
header="Réinitialiser le mot de passe"
modal="true"
styleClass="w-full md:w-4">
<h:form>
<p:panelGrid columns="2" styleClass="w-full">
<p:outputLabel for="newPassword" value="Nouveau mot de passe *" />
<p:password id="newPassword"
value="#{userBean.newPassword}"
feedback="true"
required="true"
styleClass="w-full">
<f:validateLength minimum="8" maximum="100" />
</p:password>
<p:outputLabel for="newPasswordConfirm" value="Confirmer *" />
<p:password id="newPasswordConfirm"
value="#{userBean.newPasswordConfirm}"
required="true"
styleClass="w-full" />
</p:panelGrid>
<f:facet name="footer">
<div class="flex gap-2 justify-content-end">
<c:choose>
<c:when test="#{not empty resetPasswordAction}">
<p:commandButton
value="Réinitialiser"
icon="pi pi-check"
styleClass="p-button-primary"
action="#{resetPasswordAction}"
update="#{update}"
oncomplete="PF('resetPasswordDialog').hide()"
process="@form" />
</c:when>
<c:otherwise>
<p:commandButton
value="Réinitialiser"
icon="pi pi-check"
styleClass="p-button-primary"
action="#{userBean.resetPassword(user.id)}"
update="#{update}"
oncomplete="PF('resetPasswordDialog').hide()"
process="@form" />
</c:otherwise>
</c:choose>
<p:commandButton
value="Annuler"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('resetPasswordDialog').hide()" />
</div>
</f:facet>
</h:form>
</p:dialog>
</ui:composition>

View File

@@ -0,0 +1,130 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Carte Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Affiche une carte utilisateur avec informations principales et actions
Paramètres:
- user: UserDTO (requis) - L'utilisateur à afficher
- showActions: Boolean (défaut: true) - Afficher les boutons d'action
- showRoles: Boolean (défaut: true) - Afficher les rôles de l'utilisateur
- clickable: Boolean (défaut: true) - Rendre la carte cliquable
- outcome: String (optionnel) - Page de destination au clic
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemples d'utilisation:
1. Carte simple:
<ui:include src="/templates/components/user-management/user-card.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
</ui:include>
2. Carte avec actions:
<ui:include src="/templates/components/user-management/user-card.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="showActions" value="true" />
<ui:param name="outcome" value="/pages/user-manager/users/profile" />
</ui:include>
3. Carte sans rôles:
<ui:include src="/templates/components/user-management/user-card.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="showRoles" value="false" />
</ui:include>
-->
<c:set var="showActions" value="#{empty showActions ? true : showActions}" />
<c:set var="showRoles" value="#{empty showRoles ? true : showRoles}" />
<c:set var="clickable" value="#{empty clickable ? true : clickable}" />
<p:card styleClass="user-card #{styleClass}" rendered="#{not empty user}">
<f:facet name="header">
<div class="flex align-items-center gap-2">
<p:avatar
label="#{user.prenom != null ? user.prenom.substring(0,1) : 'U'}#{user.nom != null ? user.nom.substring(0,1) : ''}"
styleClass="user-avatar"
size="large" />
<div class="flex flex-column">
<h3 class="m-0">#{user.prenom} #{user.nom}</h3>
<span class="text-color-secondary text-sm">@#{user.username}</span>
</div>
</div>
</f:facet>
<div class="user-card-content">
<!-- Informations principales -->
<div class="flex flex-column gap-2 mb-3">
<div class="flex align-items-center gap-2">
<i class="pi pi-envelope text-color-secondary"></i>
<span>#{user.email}</span>
</div>
<c:if test="#{not empty user.telephone}">
<div class="flex align-items-center gap-2">
<i class="pi pi-phone text-color-secondary"></i>
<span>#{user.telephone}</span>
</div>
</c:if>
<!-- Statut -->
<div class="flex align-items-center gap-2">
<i class="pi pi-circle-fill text-color-secondary"></i>
<p:tag
value="#{user.statut != null ? user.statut : 'INCONNU'}"
severity="#{user.enabled ? 'success' : 'danger'}" />
</div>
</div>
<!-- Rôles -->
<c:if test="#{showRoles and not empty user.roles}">
<div class="flex flex-column gap-2 mb-3">
<h5 class="m-0">Rôles</h5>
<div class="flex flex-wrap gap-1">
<c:forEach var="role" items="#{user.roles}">
<p:tag value="#{role.name}" severity="info" />
</c:forEach>
</div>
</div>
</c:if>
</div>
<f:facet name="footer">
<c:if test="#{showActions}">
<div class="flex gap-2 justify-content-end">
<p:commandButton
icon="pi pi-eye"
title="Voir le profil"
styleClass="p-button-text p-button-sm"
outcome="#{not empty outcome ? outcome : '/pages/user-manager/users/profile'}"
rendered="#{clickable}">
<f:param name="userId" value="#{user.id}" />
</p:commandButton>
<p:commandButton
icon="pi pi-pencil"
title="Modifier"
styleClass="p-button-text p-button-sm p-button-warning"
outcome="/pages/user-manager/users/edit">
<f:param name="userId" value="#{user.id}" />
</p:commandButton>
<p:commandButton
icon="pi pi-trash"
title="Supprimer"
styleClass="p-button-text p-button-sm p-button-danger"
onclick="PF('confirmDeleteDialog').show()" />
</div>
</c:if>
</f:facet>
</p:card>
</ui:composition>

View File

@@ -0,0 +1,216 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<p:panel header="#{mode == 'create' ? 'Nouvel Utilisateur' : 'Modifier Utilisateur'}"
styleClass="w-full">
<!-- Informations de base -->
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
<!-- Username -->
<p:outputLabel for="username" value="Nom d'utilisateur *" />
<p:inputText id="username"
value="#{user.username}"
required="true"
readonly="#{readonly or mode == 'edit'}"
placeholder="jdupont"
styleClass="w-full">
<f:validateLength minimum="3" maximum="100" />
<f:validateRegex pattern="^[a-zA-Z0-9._-]+$" />
</p:inputText>
<!-- Email -->
<p:outputLabel for="email" value="Email *" />
<p:inputText id="email"
value="#{user.email}"
required="true"
readonly="#{readonly}"
placeholder="jean.dupont@lions.dev"
styleClass="w-full">
<f:validateLength minimum="5" maximum="255" />
</p:inputText>
<!-- Prénom -->
<p:outputLabel for="prenom" value="Prénom *" />
<p:inputText id="prenom"
value="#{user.prenom}"
required="true"
readonly="#{readonly}"
placeholder="Jean"
styleClass="w-full">
<f:validateLength minimum="2" maximum="100" />
</p:inputText>
<!-- Nom -->
<p:outputLabel for="nom" value="Nom *" />
<p:inputText id="nom"
value="#{user.nom}"
required="true"
readonly="#{readonly}"
placeholder="Dupont"
styleClass="w-full">
<f:validateLength minimum="2" maximum="100" />
</p:inputText>
<!-- Téléphone -->
<p:outputLabel for="telephone" value="Téléphone" />
<p:inputText id="telephone"
value="#{user.telephone}"
readonly="#{readonly}"
placeholder="+225 07 12 34 56 78"
styleClass="w-full" />
<!-- Organisation -->
<p:outputLabel for="organisation" value="Organisation" />
<p:inputText id="organisation"
value="#{user.organisation}"
readonly="#{readonly}"
placeholder="Lions Dev"
styleClass="w-full" />
<!-- Département -->
<p:outputLabel for="departement" value="Département" />
<p:inputText id="departement"
value="#{user.departement}"
readonly="#{readonly}"
placeholder="IT"
styleClass="w-full" />
<!-- Fonction -->
<p:outputLabel for="fonction" value="Fonction" />
<p:inputText id="fonction"
value="#{user.fonction}"
readonly="#{readonly}"
placeholder="Développeur Senior"
styleClass="w-full" />
<!-- Ville -->
<p:outputLabel for="ville" value="Ville" />
<p:inputText id="ville"
value="#{user.ville}"
readonly="#{readonly}"
placeholder="Abidjan"
styleClass="w-full" />
<!-- Pays -->
<p:outputLabel for="pays" value="Pays" />
<p:inputText id="pays"
value="#{user.pays}"
readonly="#{readonly}"
placeholder="Côte d'Ivoire"
styleClass="w-full" />
<!-- Statut -->
<p:outputLabel for="statut" value="Statut" />
<p:selectOneMenu id="statut"
value="#{user.statut}"
readonly="#{readonly}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{userBean.statutOptions}" />
</p:selectOneMenu>
<!-- Enabled -->
<p:outputLabel for="enabled" value="Compte activé" />
<p:selectBooleanCheckbox id="enabled"
value="#{user.enabled}"
readonly="#{readonly}" />
<!-- Email vérifié -->
<p:outputLabel for="emailVerified" value="Email vérifié" />
<p:selectBooleanCheckbox id="emailVerified"
value="#{user.emailVerified}"
readonly="#{readonly}" />
<!-- Realm (si affiché) -->
<c:if test="#{showRealmSelector}">
<p:outputLabel for="realmName" value="Realm *" />
<p:selectOneMenu id="realmName"
value="#{user.realmName}"
required="#{showRealmSelector}"
readonly="#{readonly}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{userBean.availableRealms}" />
</p:selectOneMenu>
</c:if>
</p:panelGrid>
<!-- Champs mot de passe (si affichés) -->
<c:if test="#{showPasswordFields and mode == 'create'}">
<p:separator />
<h3>Mot de passe</h3>
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
<p:outputLabel for="password" value="Mot de passe *" />
<p:password id="password"
value="#{userBean.password}"
required="true"
feedback="true"
placeholder="Minimum 8 caractères"
styleClass="w-full">
<f:validateLength minimum="8" maximum="100" />
</p:password>
<p:outputLabel for="passwordConfirm" value="Confirmer le mot de passe *" />
<p:password id="passwordConfirm"
value="#{userBean.passwordConfirm}"
required="true"
placeholder="Répétez le mot de passe"
styleClass="w-full" />
</p:panelGrid>
</c:if>
<!-- Boutons d'action -->
<f:facet name="footer">
<div class="flex gap-2 justify-content-end">
<c:if test="#{not readonly}">
<c:choose>
<!-- Si hasSubmitAction est explicitement défini à true, utiliser action -->
<c:when test="#{hasSubmitAction == true}">
<p:commandButton
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
icon="pi pi-check"
styleClass="p-button-success"
action="#{submitAction}"
update="#{not empty update ? update : '@form'}"
process="@form" />
</c:when>
<!-- Si submitOutcome est fourni, utiliser outcome -->
<c:when test="#{not empty submitOutcome}">
<p:commandButton
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
icon="pi pi-check"
styleClass="p-button-success"
outcome="#{submitOutcome}"
update="#{not empty update ? update : '@form'}"
process="@form" />
</c:when>
<!-- Sinon, essayer d'utiliser submitAction si fourni -->
<c:otherwise>
<p:commandButton
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
icon="pi pi-check"
styleClass="p-button-success"
action="#{submitAction}"
update="#{not empty update ? update : '@form'}"
process="@form" />
</c:otherwise>
</c:choose>
</c:if>
<p:commandButton
value="Annuler"
icon="pi pi-times"
styleClass="p-button-secondary"
outcome="#{not empty cancelOutcome ? cancelOutcome : '/pages/user-manager/users/list'}"
immediate="true" />
</div>
</f:facet>
</p:panel>
</ui:composition>

View File

@@ -0,0 +1,95 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Formulaire Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Formulaire complet pour créer/modifier un utilisateur
Paramètres:
- user: UserDTO (requis) - L'utilisateur à éditer (peut être null pour création)
- formId: String (défaut: "userForm") - ID du formulaire
- mode: String (défaut: "create") - Mode: "create" ou "edit"
- showRealmSelector: Boolean (défaut: false) - Afficher le sélecteur de realm
- showPasswordFields: Boolean (défaut: true) - Afficher les champs mot de passe
- readonly: Boolean (défaut: false) - Mode lecture seule
- submitAction: String (optionnel) - Expression de méthode JSF à exécuter (ex: "#{bean.method}")
- submitOutcome: String (optionnel) - Page de redirection après soumission
- update: String (optionnel) - Composants à mettre à jour après soumission
- hasSubmitAction: Boolean (optionnel) - Indicateur si submitAction est fourni (pour éviter l'évaluation)
- useParentForm: Boolean (défaut: false) - Utiliser le formulaire parent au lieu de créer un nouveau formulaire
Exemples d'utilisation:
1. Formulaire de création:
<ui:include src="/templates/components/user-management/user-form.xhtml">
<ui:param name="user" value="#{userBean.newUser}" />
<ui:param name="mode" value="create" />
<ui:param name="submitAction" value="#{userBean.createUser}" />
</ui:include>
2. Formulaire d'édition:
<ui:include src="/templates/components/user-management/user-form.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="mode" value="edit" />
<ui:param name="showPasswordFields" value="false" />
<ui:param name="submitAction" value="#{userBean.updateUser}" />
</ui:include>
3. Formulaire en lecture seule:
<ui:include src="/templates/components/user-management/user-form.xhtml">
<ui:param name="user" value="#{userBean.selectedUser}" />
<ui:param name="readonly" value="true" />
</ui:include>
-->
<c:set var="formId" value="#{empty formId ? 'userForm' : formId}" />
<c:set var="mode" value="#{empty mode ? 'create' : mode}" />
<c:set var="showRealmSelector" value="#{empty showRealmSelector ? false : showRealmSelector}" />
<c:set var="showPasswordFields" value="#{empty showPasswordFields ? true : showPasswordFields}" />
<c:set var="readonly" value="#{empty readonly ? false : readonly}" />
<c:set var="useParentForm" value="#{empty useParentForm ? false : useParentForm}" />
<c:choose>
<c:when test="#{useParentForm}">
<!-- Utiliser le formulaire parent - pas de formulaire ici -->
<ui:include src="/templates/components/user-management/user-form-content.xhtml">
<ui:param name="user" value="#{user}" />
<ui:param name="mode" value="#{mode}" />
<ui:param name="showRealmSelector" value="#{showRealmSelector}" />
<ui:param name="showPasswordFields" value="#{showPasswordFields}" />
<ui:param name="readonly" value="#{readonly}" />
<ui:param name="submitAction" value="#{submitAction}" />
<ui:param name="submitOutcome" value="#{submitOutcome}" />
<ui:param name="update" value="#{update}" />
<ui:param name="hasSubmitAction" value="#{hasSubmitAction}" />
<ui:param name="cancelOutcome" value="#{cancelOutcome}" />
</ui:include>
</c:when>
<c:otherwise>
<!-- Créer son propre formulaire -->
<h:form id="#{formId}">
<ui:include src="/templates/components/user-management/user-form-content.xhtml">
<ui:param name="user" value="#{user}" />
<ui:param name="mode" value="#{mode}" />
<ui:param name="showRealmSelector" value="#{showRealmSelector}" />
<ui:param name="showPasswordFields" value="#{showPasswordFields}" />
<ui:param name="readonly" value="#{readonly}" />
<ui:param name="submitAction" value="#{submitAction}" />
<ui:param name="submitOutcome" value="#{submitOutcome}" />
<ui:param name="update" value="#{update}" />
<ui:param name="hasSubmitAction" value="#{hasSubmitAction}" />
<ui:param name="cancelOutcome" value="#{cancelOutcome}" />
</ui:include>
</h:form>
</c:otherwise>
</c:choose>
</ui:composition>

View File

@@ -0,0 +1,105 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Badge de Rôle Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Affiche un badge pour un rôle utilisateur avec icône et couleur
Paramètres:
- roleName: String (requis) - Nom du rôle
- roleType: String (optionnel) - Type de rôle: "REALM_ROLE", "CLIENT_ROLE", "COMPOSITE_ROLE"
- severity: String (optionnel) - Severity PrimeFaces: "success", "info", "warning", "danger" (défaut: "info")
- showIcon: Boolean (défaut: true) - Afficher l'icône
- icon: String (optionnel) - Classe d'icône PrimeIcons (défaut: "pi-shield")
- size: String (optionnel) - Taille: "small", "normal", "large" (défaut: "normal")
- clickable: Boolean (défaut: false) - Rendre le badge cliquable
- clickAction: String (optionnel) - Action au clic
- styleClass: String (optionnel) - Classes CSS supplémentaires
Exemples d'utilisation:
1. Badge simple:
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
<ui:param name="roleName" value="ADMIN" />
</ui:include>
2. Badge avec type:
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
<ui:param name="roleName" value="USER" />
<ui:param name="roleType" value="REALM_ROLE" />
<ui:param name="severity" value="success" />
</ui:include>
3. Badge cliquable:
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
<ui:param name="roleName" value="MODERATOR" />
<ui:param name="clickable" value="true" />
<ui:param name="clickAction" value="#{roleBean.viewRole(roleName)}" />
</ui:include>
-->
<c:set var="showIcon" value="#{empty showIcon ? true : showIcon}" />
<c:set var="size" value="#{empty size ? 'normal' : size}" />
<c:set var="clickable" value="#{empty clickable ? false : clickable}" />
<c:set var="severity" value="#{empty severity ? 'info' : severity}" />
<c:set var="icon" value="#{empty icon ? 'pi-shield' : icon}" />
<!-- Déterminer la severity selon le type de rôle -->
<c:choose>
<c:when test="#{roleType == 'REALM_ROLE'}">
<c:set var="severity" value="success" />
</c:when>
<c:when test="#{roleType == 'CLIENT_ROLE'}">
<c:set var="severity" value="info" />
</c:when>
<c:when test="#{roleType == 'COMPOSITE_ROLE'}">
<c:set var="severity" value="warning" />
</c:when>
</c:choose>
<!-- Déterminer la taille -->
<c:choose>
<c:when test="#{size == 'small'}">
<c:set var="tagStyleClass" value="text-xs" />
</c:when>
<c:when test="#{size == 'large'}">
<c:set var="tagStyleClass" value="text-base" />
</c:when>
<c:otherwise>
<c:set var="tagStyleClass" value="text-sm" />
</c:otherwise>
</c:choose>
<c:choose>
<!-- Badge cliquable -->
<c:when test="#{clickable and not empty clickAction}">
<p:commandLink
styleClass="role-badge-link"
action="#{clickAction}">
<p:tag
value="#{roleName}"
severity="#{severity}"
icon="#{showIcon ? icon : ''}"
styleClass="#{tagStyleClass} #{styleClass}" />
</p:commandLink>
</c:when>
<!-- Badge simple -->
<c:otherwise>
<p:tag
value="#{roleName}"
severity="#{severity}"
icon="#{showIcon ? icon : ''}"
styleClass="#{tagStyleClass} #{styleClass}" />
</c:otherwise>
</c:choose>
</ui:composition>

View File

@@ -0,0 +1,181 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<p:panel styleClass="w-full mb-3">
<f:facet name="header">
<div class="flex align-items-center justify-content-between">
<span>Recherche d'utilisateurs</span>
<p:commandButton
icon="pi pi-filter"
styleClass="p-button-text p-button-sm"
onclick="PF('advancedSearchDialog').toggle()"
rendered="#{showAdvanced}"
title="Options avancées" />
</div>
</f:facet>
<!-- Recherche rapide -->
<div class="flex gap-2 align-items-end">
<div class="flex-1">
<p:outputLabel for="searchText" value="Rechercher" />
<p:inputText id="searchText"
value="#{searchCriteria.searchTerm}"
placeholder="Nom, prénom, email, username..."
styleClass="w-full">
<p:ajax event="keyup"
delay="500"
listener="#{searchAction}"
update="#{update}" />
</p:inputText>
</div>
<!-- Filtre Realm -->
<c:if test="#{showRealmFilter}">
<div style="width: 200px;">
<p:outputLabel for="realmFilter" value="Realm" />
<p:selectOneMenu id="realmFilter"
value="#{searchCriteria.realmName}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les realms" itemValue="" />
<f:selectItems value="#{userBean.availableRealms}" />
</p:selectOneMenu>
</div>
</c:if>
<!-- Filtre Statut -->
<c:if test="#{showStatusFilter}">
<div style="width: 180px;">
<p:outputLabel for="statusFilter" value="Statut" />
<p:selectOneMenu id="statusFilter"
value="#{searchCriteria.statut}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les statuts" itemValue="" />
<f:selectItems value="#{userBean.statutOptions}" />
</p:selectOneMenu>
</div>
</c:if>
<!-- Bouton Rechercher -->
<div>
<p:commandButton
value="Rechercher"
icon="pi pi-search"
styleClass="p-button-primary"
action="#{searchAction}"
update="#{update}"
process="@form" />
</div>
<!-- Bouton Réinitialiser -->
<div>
<p:commandButton
value="Réinitialiser"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{userBean.resetSearch}"
update="#{update}"
process="@form" />
</div>
</div>
</p:panel>
<!-- Options avancées (Dialog) -->
<c:if test="#{showAdvanced}">
<p:dialog id="advancedSearchDialog"
header="Options de recherche avancées"
widgetVar="advancedSearchDialog"
modal="true"
resizable="false"
styleClass="w-full md:w-6">
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
<!-- Email -->
<p:outputLabel for="emailFilter" value="Email" />
<p:inputText id="emailFilter"
value="#{searchCriteria.email}"
placeholder="email@example.com"
styleClass="w-full" />
<!-- Téléphone -->
<p:outputLabel for="phoneFilter" value="Téléphone" />
<p:inputText id="phoneFilter"
value="#{searchCriteria.telephone}"
placeholder="+225 07 12 34 56 78"
styleClass="w-full" />
<!-- Organisation -->
<p:outputLabel for="orgFilter" value="Organisation" />
<p:inputText id="orgFilter"
value="#{searchCriteria.organisation}"
placeholder="Nom de l'organisation"
styleClass="w-full" />
<!-- Ville -->
<p:outputLabel for="cityFilter" value="Ville" />
<p:inputText id="cityFilter"
value="#{searchCriteria.ville}"
placeholder="Nom de la ville"
styleClass="w-full" />
<!-- Rôle (si affiché) -->
<c:if test="#{showRoleFilter}">
<p:outputLabel for="roleFilter" value="Rôle Realm" />
<p:selectManyMenu id="roleFilter"
value="#{searchCriteria.realmRoles}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les rôles" itemValue="" />
<f:selectItems value="#{userBean.availableRoles}" />
</p:selectManyMenu>
</c:if>
<!-- Date de création (début) -->
<p:outputLabel for="dateDebut" value="Date de création (début)" />
<p:calendar id="dateDebut"
value="#{searchCriteria.dateCreationMin}"
pattern="dd/MM/yyyy"
styleClass="w-full" />
<!-- Date de création (fin) -->
<p:outputLabel for="dateFin" value="Date de création (fin)" />
<p:calendar id="dateFin"
value="#{searchCriteria.dateCreationMax}"
pattern="dd/MM/yyyy"
styleClass="w-full" />
<!-- Enabled -->
<p:outputLabel for="enabledFilter" value="Compte activé" />
<p:selectOneMenu id="enabledFilter"
value="#{searchCriteria.enabled}"
styleClass="w-full">
<f:selectItem itemLabel="Tous" itemValue="" />
<f:selectItem itemLabel="Activé" itemValue="true" />
<f:selectItem itemLabel="Désactivé" itemValue="false" />
</p:selectOneMenu>
</p:panelGrid>
<f:facet name="footer">
<div class="flex gap-2 justify-content-end">
<p:commandButton
value="Appliquer"
icon="pi pi-check"
styleClass="p-button-primary"
action="#{searchAction}"
update="#{update}"
onclick="PF('advancedSearchDialog').hide()"
process="@form" />
<p:commandButton
value="Annuler"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('advancedSearchDialog').hide()" />
</div>
</f:facet>
</p:dialog>
</c:if>
</ui:composition>

View File

@@ -0,0 +1,83 @@
<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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant réutilisable: Barre de Recherche Utilisateur (WOU/DRY Pattern)
Auteur: Lions User Manager
Version: 1.0.0
Description: Barre de recherche avancée pour utilisateurs
Paramètres:
- searchCriteria: UserSearchCriteriaDTO (requis) - Critères de recherche
- searchAction: String (requis) - Action à exécuter lors de la recherche
- update: String (défaut: "@form") - Composants à mettre à jour
- showAdvanced: Boolean (défaut: false) - Afficher les options avancées
- showRealmFilter: Boolean (défaut: true) - Afficher le filtre realm
- showStatusFilter: Boolean (défaut: true) - Afficher le filtre statut
- showRoleFilter: Boolean (défaut: true) - Afficher le filtre rôle
- formId: String (défaut: "searchForm") - ID du formulaire
- useParentForm: Boolean (défaut: false) - Si true, n'crée pas de formulaire (utilise le formulaire parent)
Exemples d'utilisation:
1. Recherche simple:
<ui:include src="/templates/components/user-management/user-search-bar.xhtml">
<ui:param name="searchCriteria" value="#{userBean.searchCriteria}" />
<ui:param name="searchAction" value="#{userBean.search}" />
<ui:param name="update" value="userTable" />
</ui:include>
2. Recherche avec formulaire parent:
<h:form id="formUsers">
<ui:include src="/templates/components/user-management/user-search-bar.xhtml">
<ui:param name="searchCriteria" value="#{userBean.searchCriteria}" />
<ui:param name="searchAction" value="#{userBean.search}" />
<ui:param name="update" value="userTable" />
<ui:param name="useParentForm" value="true" />
</ui:include>
</h:form>
-->
<c:set var="formId" value="#{empty formId ? 'searchForm' : formId}" />
<c:set var="update" value="#{empty update ? '@form' : update}" />
<c:set var="showAdvanced" value="#{empty showAdvanced ? false : showAdvanced}" />
<c:set var="showRealmFilter" value="#{empty showRealmFilter ? true : showRealmFilter}" />
<c:set var="showStatusFilter" value="#{empty showStatusFilter ? true : showStatusFilter}" />
<c:set var="showRoleFilter" value="#{empty showRoleFilter ? true : showRoleFilter}" />
<c:set var="useParentForm" value="#{empty useParentForm ? false : useParentForm}" />
<c:choose>
<c:when test="#{useParentForm}">
<!-- Utiliser le formulaire parent - pas de formulaire ici -->
<ui:include src="/templates/components/user-management/user-search-bar-content.xhtml">
<ui:param name="searchCriteria" value="#{searchCriteria}" />
<ui:param name="searchAction" value="#{searchAction}" />
<ui:param name="update" value="#{update}" />
<ui:param name="showAdvanced" value="#{showAdvanced}" />
<ui:param name="showRealmFilter" value="#{showRealmFilter}" />
<ui:param name="showStatusFilter" value="#{showStatusFilter}" />
<ui:param name="showRoleFilter" value="#{showRoleFilter}" />
</ui:include>
</c:when>
<c:otherwise>
<!-- Créer son propre formulaire -->
<h:form id="#{formId}">
<ui:include src="/templates/components/user-management/user-search-bar-content.xhtml">
<ui:param name="searchCriteria" value="#{searchCriteria}" />
<ui:param name="searchAction" value="#{searchAction}" />
<ui:param name="update" value="#{update}" />
<ui:param name="showAdvanced" value="#{showAdvanced}" />
<ui:param name="showRealmFilter" value="#{showRealmFilter}" />
<ui:param name="showStatusFilter" value="#{showStatusFilter}" />
<ui:param name="showRoleFilter" value="#{showRoleFilter}" />
</ui:include>
</h:form>
</c:otherwise>
</c:choose>
</ui:composition>