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,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd"
version="4.0">
<name>Lions User Manager</name>
<application>
<locale-config>
<default-locale>fr</default-locale>
<supported-locale>fr</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
</application>
<navigation-rule>
<from-view-id>*</from-view-id>
<!-- ================================================================
DASHBOARD & ACCUEIL
================================================================ -->
<navigation-case>
<description>Page d'accueil / Dashboard</description>
<from-outcome>userManagerDashboardPage</from-outcome>
<to-view-id>/pages/user-manager/dashboard.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers dashboard</description>
<from-outcome>/pages/user-manager/dashboard</from-outcome>
<to-view-id>/pages/user-manager/dashboard.xhtml</to-view-id>
<redirect />
</navigation-case>
<!-- ================================================================
GESTION DES UTILISATEURS
================================================================ -->
<navigation-case>
<description>Page de liste des utilisateurs</description>
<from-outcome>userListPage</from-outcome>
<to-view-id>/pages/user-manager/users/list.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers liste utilisateurs</description>
<from-outcome>/pages/user-manager/users/list</from-outcome>
<to-view-id>/pages/user-manager/users/list.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Page de création d'utilisateur</description>
<from-outcome>userCreatePage</from-outcome>
<to-view-id>/pages/user-manager/users/create.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers création utilisateur</description>
<from-outcome>/pages/user-manager/users/create</from-outcome>
<to-view-id>/pages/user-manager/users/create.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Page de profil utilisateur</description>
<from-outcome>userProfilePage</from-outcome>
<to-view-id>/pages/user-manager/users/profile.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers profil utilisateur</description>
<from-outcome>/pages/user-manager/users/profile</from-outcome>
<to-view-id>/pages/user-manager/users/profile.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Page d'édition utilisateur</description>
<from-outcome>userEditPage</from-outcome>
<to-view-id>/pages/user-manager/users/edit.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers édition utilisateur</description>
<from-outcome>/pages/user-manager/users/edit</from-outcome>
<to-view-id>/pages/user-manager/users/edit.xhtml</to-view-id>
<redirect />
</navigation-case>
<!-- ================================================================
GESTION DES RÔLES
================================================================ -->
<navigation-case>
<description>Page de liste des rôles</description>
<from-outcome>roleListPage</from-outcome>
<to-view-id>/pages/user-manager/roles/list.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers liste rôles</description>
<from-outcome>/pages/user-manager/roles/list</from-outcome>
<to-view-id>/pages/user-manager/roles/list.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Page d'attribution de rôles</description>
<from-outcome>roleAssignPage</from-outcome>
<to-view-id>/pages/user-manager/roles/assign.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers attribution rôles</description>
<from-outcome>/pages/user-manager/roles/assign</from-outcome>
<to-view-id>/pages/user-manager/roles/assign.xhtml</to-view-id>
<redirect />
</navigation-case>
<!-- ================================================================
AUDIT
================================================================ -->
<navigation-case>
<description>Page de journal d'audit</description>
<from-outcome>auditLogsPage</from-outcome>
<to-view-id>/pages/user-manager/audit/logs.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers journal d'audit</description>
<from-outcome>/pages/user-manager/audit/logs</from-outcome>
<to-view-id>/pages/user-manager/audit/logs.xhtml</to-view-id>
<redirect />
</navigation-case>
<!-- ================================================================
SYNCHRONISATION
================================================================ -->
<navigation-case>
<description>Page de dashboard synchronisation</description>
<from-outcome>syncDashboardPage</from-outcome>
<to-view-id>/pages/user-manager/sync/dashboard.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers dashboard synchronisation</description>
<from-outcome>/pages/user-manager/sync/dashboard</from-outcome>
<to-view-id>/pages/user-manager/sync/dashboard.xhtml</to-view-id>
<redirect />
</navigation-case>
<!-- ================================================================
PARAMÈTRES & PROFIL
================================================================ -->
<navigation-case>
<description>Page de paramètres utilisateur</description>
<from-outcome>settingsPage</from-outcome>
<to-view-id>/pages/user-manager/settings.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers paramètres</description>
<from-outcome>/pages/user-manager/settings</from-outcome>
<to-view-id>/pages/user-manager/settings.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
</faces-config>

View File

@@ -0,0 +1,3 @@
# Configuration Quarkus pour PrimeFaces
# Exclusion de classes obsolètes de PrimeFaces 14.0.5

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html 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"
lang="fr">
<h:head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lions User Manager - Gestion des Utilisateurs Keycloak</title>
<!-- PrimeFaces Freya Theme -->
<h:outputStylesheet name="primefaces-freya/theme.css" />
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
</h:head>
<h:body>
<div class="flex align-items-center justify-content-center" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<div class="card" style="width: 90%; max-width: 600px; text-align: center;">
<div class="flex flex-column align-items-center gap-3 p-5">
<i class="pi pi-users text-6xl text-primary"></i>
<h1 class="text-4xl font-bold m-0">Lions User Manager</h1>
<p class="text-xl text-600 m-0">Gestion centralisée des utilisateurs Keycloak</p>
<div class="flex flex-column gap-2 mt-4" style="width: 100%;">
<h:link outcome="/pages/user-manager/users/list" styleClass="no-underline">
<p:commandButton value="Accéder à la Gestion des Utilisateurs"
icon="pi pi-users"
styleClass="w-full p-button-lg" />
</h:link>
<h:link outcome="/pages/user-manager/roles/list" styleClass="no-underline">
<p:commandButton value="Gestion des Rôles"
icon="pi pi-shield"
styleClass="w-full p-button-lg p-button-outlined" />
</h:link>
<h:link outcome="/pages/user-manager/audit/logs" styleClass="no-underline">
<p:commandButton value="Journal d'Audit"
icon="pi pi-history"
styleClass="w-full p-button-lg p-button-outlined" />
</h:link>
</div>
<div class="mt-4 text-600">
<p class="m-0">Version 1.0.0</p>
<p class="m-0 text-sm">Module réutilisable pour l'écosystème LionsDev</p>
</div>
</div>
</div>
</div>
</h:body>
</html>

View File

@@ -0,0 +1,272 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{auditConsultationBean}"/>
<ui:define name="title">Journal d'Audit - Lions User Manager</ui:define>
<ui:define name="content">
<!-- En-tête -->
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-history text-orange-500" />
<ui:param name="title" value="Journal d'Audit" />
<ui:param name="description" value="Consultation des logs d'audit et statistiques" />
<ui:define name="actions">
<h:form id="formActionsAudit">
<div class="flex gap-2">
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Exporter CSV" />
<ui:param name="icon" value="pi pi-download" />
<ui:param name="hasAction" value="true" />
<ui:param name="action" value="#{auditConsultationBean.exportToCSV}" />
<ui:param name="severity" value="success" />
</ui:include>
</div>
</h:form>
</ui:define>
</ui:include>
<!-- Statistiques avec composants réutilisables -->
<ui:include src="/templates/components/shared/dashboard/kpi-group.xhtml">
<ui:param name="title" value="Statistiques d'Audit" />
<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 Actions" />
<ui:param name="value" value="#{auditConsultationBean.totalRecords}" />
<ui:param name="icon" value="pi-history" />
<ui:param name="iconColor" value="blue-600" />
</ui:include>
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Actions Réussies" />
<ui:param name="value" value="#{auditConsultationBean.successCount}" />
<ui:param name="icon" value="pi-check-circle" />
<ui:param name="iconColor" value="green-600" />
</ui:include>
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Actions Échouées" />
<ui:param name="value" value="#{auditConsultationBean.failureCount}" />
<ui:param name="icon" value="pi-times-circle" />
<ui:param name="iconColor" value="red-600" />
</ui:include>
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Taux de Réussite" />
<ui:param name="value" value="#{auditConsultationBean.totalRecords > 0 ? (auditConsultationBean.successCount * 100 / auditConsultationBean.totalRecords) : 0}%" />
<ui:param name="icon" value="pi-percentage" />
<ui:param name="iconColor" value="purple-600" />
</ui:include>
</ui:define>
</ui:include>
<!-- Filtres de recherche -->
<div class="card mb-3">
<h:form id="formFilters">
<p:panelGrid columns="3" styleClass="w-full" columnClasses="col-12 md:col-4">
<p:outputLabel for="acteurFilter" value="Acteur" />
<p:inputText id="acteurFilter"
value="#{auditConsultationBean.acteurUsername}"
placeholder="Nom d'utilisateur..."
styleClass="w-full" />
<p:outputLabel for="typeActionFilter" value="Type d'action" />
<p:selectOneMenu id="typeActionFilter"
value="#{auditConsultationBean.selectedTypeAction}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItems value="#{auditConsultationBean.typeActionOptions}" />
</p:selectOneMenu>
<p:outputLabel for="succesFilter" value="Résultat" />
<p:selectOneMenu id="succesFilter"
value="#{auditConsultationBean.succes}"
styleClass="w-full">
<f:selectItem itemLabel="Tous" itemValue="" />
<f:selectItem itemLabel="Succès" itemValue="true" />
<f:selectItem itemLabel="Échec" itemValue="false" />
</p:selectOneMenu>
<p:outputLabel for="dateDebutFilter" value="Date début" />
<p:calendar id="dateDebutFilter"
value="#{auditConsultationBean.dateDebut}"
pattern="dd/MM/yyyy"
styleClass="w-full" />
<p:outputLabel for="dateFinFilter" value="Date fin" />
<p:calendar id="dateFinFilter"
value="#{auditConsultationBean.dateFin}"
pattern="dd/MM/yyyy"
styleClass="w-full" />
<p:outputLabel for="ressourceFilter" value="Type ressource" />
<p:inputText id="ressourceFilter"
value="#{auditConsultationBean.ressourceType}"
placeholder="USER, ROLE..."
styleClass="w-full" />
</p:panelGrid>
<div class="flex gap-2 justify-content-end mt-3">
<p:commandButton
value="Rechercher"
icon="pi pi-search"
styleClass="p-button-primary"
action="#{auditConsultationBean.searchLogs}"
update="formAuditLogs:auditLogsTable" />
<p:commandButton
value="Réinitialiser"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{auditConsultationBean.resetFilters}"
update="formAuditLogs:auditLogsTable @form" />
</div>
</h:form>
</div>
<!-- Liste des logs avec p:dataTable -->
<div class="card">
<h:form id="formAuditLogs">
<h5>Logs d'Audit</h5>
<p:dataTable
id="auditLogsTable"
value="#{auditConsultationBean.auditLogs}"
var="log"
rowKey="#{log.id}"
paginator="true"
rows="20"
rowsPerPageTemplate="10,20,50,100"
emptyMessage="Aucun log d'audit trouvé"
styleClass="w-full">
<!-- Colonne Statut -->
<p:column headerText="Statut" style="width: 5rem">
<p:tag
value="#{log.succes ? 'Succès' : 'Échec'}"
severity="#{log.succes ? 'success' : 'danger'}"
styleClass="text-xs" />
</p:column>
<!-- Colonne Type d'action -->
<p:column headerText="Type d'action" sortBy="#{log.typeAction}" style="width: 15%">
<strong class="text-900">#{log.typeAction}</strong>
</p:column>
<!-- Colonne Acteur -->
<p:column headerText="Acteur" sortBy="#{log.acteurUsername}" style="width: 15%">
<div class="flex align-items-center gap-2">
<i class="pi pi-user text-color-secondary"></i>
<span>#{log.acteurUsername}</span>
</div>
</p:column>
<!-- Colonne Ressource -->
<p:column headerText="Ressource" style="width: 12%">
<div class="flex align-items-center gap-2">
<i class="pi pi-database text-color-secondary"></i>
<span>#{log.ressourceType}</span>
</div>
</p:column>
<!-- Colonne Date -->
<p:column headerText="Date" sortBy="#{log.dateAction}" style="width: 15%">
<div class="flex align-items-center gap-2">
<i class="pi pi-calendar text-color-secondary"></i>
<span>#{log.dateAction}</span>
</div>
</p:column>
<!-- Colonne Détails -->
<p:column headerText="Détails" style="width: 20%">
<c:if test="#{not empty log.details}">
<span class="text-color-secondary text-sm">#{log.details}</span>
</c:if>
<c:if test="#{empty log.details}">
<span class="text-color-secondary text-sm">-</span>
</c:if>
</p:column>
<!-- Colonne IP -->
<p:column headerText="IP" style="width: 10%">
<c:if test="#{not empty log.adresseIp}">
<span class="text-color-secondary text-sm">#{log.adresseIp}</span>
</c:if>
<c:if test="#{empty log.adresseIp}">
<span class="text-color-secondary text-sm">-</span>
</c:if>
</p:column>
<!-- Colonne Actions -->
<p:column headerText="Actions" style="width: 8%">
<p:commandButton
icon="pi pi-eye"
styleClass="p-button-text p-button-sm"
title="Voir les détails"
onclick="PF('auditLogDetailsDialog').show()">
<f:setPropertyActionListener target="#{auditConsultationBean.selectedLog}" value="#{log}" />
</p:commandButton>
</p:column>
</p:dataTable>
</h:form>
</div>
<!-- Dialog de détails -->
<p:dialog
id="auditLogDetailsDialog"
widgetVar="auditLogDetailsDialog"
header="Détails du Log d'Audit"
modal="true"
resizable="false"
styleClass="w-full md:w-30rem">
<h:form id="formAuditLogDetails">
<c:if test="#{not empty auditConsultationBean.selectedLog}">
<div class="flex flex-column gap-3">
<div>
<strong>Type d'action:</strong>
<p>#{auditConsultationBean.selectedLog.typeAction}</p>
</div>
<div>
<strong>Acteur:</strong>
<p>#{auditConsultationBean.selectedLog.acteurUsername}</p>
</div>
<div>
<strong>Ressource:</strong>
<p>#{auditConsultationBean.selectedLog.ressourceType} - #{auditConsultationBean.selectedLog.ressourceId}</p>
</div>
<div>
<strong>Date:</strong>
<p>#{auditConsultationBean.selectedLog.dateAction}</p>
</div>
<c:if test="#{not empty auditConsultationBean.selectedLog.details}">
<div>
<strong>Détails:</strong>
<p>#{auditConsultationBean.selectedLog.details}</p>
</div>
</c:if>
<c:if test="#{not empty auditConsultationBean.selectedLog.adresseIp}">
<div>
<strong>Adresse IP:</strong>
<p>#{auditConsultationBean.selectedLog.adresseIp}</p>
</div>
</c:if>
<c:if test="#{not empty auditConsultationBean.selectedLog.userAgent}">
<div>
<strong>User Agent:</strong>
<p>#{auditConsultationBean.selectedLog.userAgent}</p>
</div>
</c:if>
<c:if test="#{not empty auditConsultationBean.selectedLog.messageErreur}">
<div>
<strong class="text-red-600">Message d'erreur:</strong>
<p class="text-red-600">#{auditConsultationBean.selectedLog.messageErreur}</p>
</div>
</c:if>
</div>
</c:if>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,175 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:define name="title">Tableau de Bord - Lions User Manager</ui:define>
<ui:define name="content">
<h:form id="formDashboard">
<div class="grid">
<!-- En-tête -->
<div class="col-12">
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-home text-blue-500" />
<ui:param name="title" value="Tableau de Bord" />
<ui:param name="description" value="Vue d'ensemble de la gestion des utilisateurs Keycloak" />
<ui:define name="actions">
<h:form id="formRefresh">
<p:commandButton
value="Rafraîchir"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{dashboardBean.refreshStatistics}"
update=":formDashboard" />
</h:form>
</ui:define>
</ui:include>
</div>
<!-- KPIs Principaux avec composant réutilisable -->
<div class="col-12">
<ui:include src="/templates/components/shared/dashboard/kpi-group.xhtml">
<ui:param name="title" value="Statistiques Principales" />
<ui:param name="columns" value="4" />
<ui:define name="kpi-content">
<!-- KPI 1: Utilisateurs Actifs -->
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Utilisateurs Actifs" />
<ui:param name="value" value="#{dashboardBean.totalUsersDisplay}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
<ui:param name="subtitle" value="Total utilisateurs" />
<ui:param name="clickable" value="true" />
<ui:param name="clickOutcome" value="/pages/user-manager/users/list" />
</ui:include>
<!-- KPI 2: Rôles Realm -->
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Rôles Realm" />
<ui:param name="value" value="#{dashboardBean.totalRolesDisplay}" />
<ui:param name="icon" value="pi-shield" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="subtitle" value="Rôles configurés" />
<ui:param name="clickable" value="true" />
<ui:param name="clickOutcome" value="/pages/user-manager/roles/list" />
</ui:include>
<!-- KPI 3: Actions Récentes -->
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Actions Récentes" />
<ui:param name="value" value="#{dashboardBean.recentActionsDisplay}" />
<ui:param name="icon" value="pi-history" />
<ui:param name="iconColor" value="orange-600" />
<ui:param name="subtitle" value="Dernières 24h" />
<ui:param name="clickable" value="true" />
<ui:param name="clickOutcome" value="/pages/user-manager/audit/logs" />
</ui:include>
<!-- KPI 4: Rôles Client -->
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
<ui:param name="title" value="Rôles Client" />
<ui:param name="value" value="-" />
<ui:param name="icon" value="pi-key" />
<ui:param name="iconColor" value="purple-600" />
<ui:param name="subtitle" value="Rôles clients configurés" />
<ui:param name="clickable" value="true" />
<ui:param name="clickOutcome" value="/pages/user-manager/roles/list" />
</ui:include>
</ui:define>
</ui:include>
</div>
<!-- Actions Rapides -->
<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">
<div class="grid">
<div class="col-12 md:col-6">
<h:form>
<p:commandButton
value="Nouvel Utilisateur"
icon="pi pi-user-plus"
styleClass="w-full p-button-success"
outcome="/pages/user-manager/users/create" />
</h:form>
</div>
<div class="col-12 md:col-6">
<h:form>
<p:commandButton
value="Liste des Utilisateurs"
icon="pi pi-users"
styleClass="w-full p-button-primary"
outcome="/pages/user-manager/users/list" />
</h:form>
</div>
<div class="col-12 md:col-6">
<h:form>
<p:commandButton
value="Gestion des Rôles"
icon="pi pi-shield"
styleClass="w-full p-button-info"
outcome="/pages/user-manager/roles/list" />
</h:form>
</div>
<div class="col-12 md:col-6">
<h:form>
<p:commandButton
value="Journal d'Audit"
icon="pi pi-history"
styleClass="w-full p-button-help"
outcome="/pages/user-manager/audit/logs" />
</h:form>
</div>
</div>
</ui:define>
</ui:include>
<!-- Informations Système -->
<ui:include src="/templates/components/shared/dashboard/dashboard-section.xhtml">
<ui:param name="title" value="Informations Système" />
<ui:param name="icon" value="pi-info-circle" />
<ui:param name="colSize" value="col-12 lg:col-6" />
<ui:define name="section-content">
<div class="flex flex-column gap-2">
<div class="flex align-items-center justify-content-between">
<span class="text-600">Version</span>
<span class="font-semibold">1.0.0</span>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Realm Keycloak</span>
<span class="font-semibold">lions-user-manager</span>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Statut</span>
<p:tag value="Opérationnel" severity="success" />
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Application</span>
<span class="font-semibold">Lions User Manager</span>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Environnement</span>
<span class="font-semibold">Développement</span>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Base de données</span>
<span class="font-semibold">Keycloak Admin API</span>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Framework</span>
<span class="font-semibold">Quarkus, PrimeFaces Freya</span>
</div>
</div>
</ui:define>
</ui:include>
</div>
</h:form>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{userProfilBean}"/>
<ui:define name="title">Attribution de Rôles - Lions User Manager</ui:define>
<ui:define name="content">
<!-- En-tête -->
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-key text-purple-500" />
<ui:param name="title" value="Attribution de Rôles" />
<ui:param name="description" value="Gérer les rôles de l'utilisateur" />
</ui:include>
<!-- Attribution de rôles -->
<div class="card">
<ui:include src="/templates/components/role-management/role-assignment.xhtml">
<ui:param name="user" value="#{userProfilBean.user}" />
<ui:param name="availableRoles" value="#{roleGestionBean.allRoles}" />
<ui:param name="userRoles" value="#{roleGestionBean.getUserRolesDTOs(userProfilBean.user)}" />
<ui:param name="update" value="@form" />
</ui:include>
</div>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,166 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{roleGestionBean}"/>
<ui:define name="title">Gestion des Rôles - Lions User Manager</ui:define>
<ui:define name="content">
<!-- En-tête -->
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-shield text-purple-500" />
<ui:param name="title" value="Gestion des Rôles" />
<ui:param name="description" value="Gestion des rôles Realm et Client Keycloak" />
<ui:define name="actions">
<h:form id="formActionsRoles">
<div class="flex gap-2">
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Nouveau Rôle Realm" />
<ui:param name="icon" value="pi pi-plus" />
<ui:param name="hasAction" value="false" />
<ui:param name="hasOutcome" value="false" />
<ui:param name="onclick" value="PF('createRealmRoleDialog').show()" />
<ui:param name="severity" value="success" />
</ui:include>
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Nouveau Rôle Client" />
<ui:param name="icon" value="pi pi-plus-circle" />
<ui:param name="hasAction" value="false" />
<ui:param name="hasOutcome" value="false" />
<ui:param name="onclick" value="PF('createClientRoleDialog').show()" />
<ui:param name="severity" value="info" />
</ui:include>
</div>
</h:form>
</ui:define>
</ui:include>
<!-- Filtres -->
<div class="card mb-3">
<h:form id="formFilters">
<p:panelGrid columns="3" styleClass="w-full" columnClasses="col-12 md:col-4">
<p:outputLabel for="realmFilter" value="Realm" />
<p:selectOneMenu id="realmFilter"
value="#{roleGestionBean.realmName}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{roleGestionBean.availableRealms}" />
<p:ajax event="change"
listener="#{roleGestionBean.loadRealmRoles}"
update=":formRealmRoles:realmRolesPanel :formClientRoles:clientRolesPanel" />
</p:selectOneMenu>
<p:outputLabel for="clientFilter" value="Client" />
<p:selectOneMenu id="clientFilter"
value="#{roleGestionBean.clientName}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItems value="#{roleGestionBean.availableClients}" />
<p:ajax event="change"
listener="#{roleGestionBean.loadClientRoles}"
update=":formClientRoles:clientRolesPanel" />
</p:selectOneMenu>
<p:outputLabel for="typeFilter" value="Type" />
<p:selectOneMenu id="typeFilter"
value="#{roleGestionBean.selectedTypeRole}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItems value="#{roleGestionBean.typeRoleOptions}" />
</p:selectOneMenu>
</p:panelGrid>
</h:form>
</div>
<!-- Rôles Realm -->
<div class="card mb-3">
<h:form id="formRealmRoles">
<p:panel id="realmRolesPanel" header="Rôles Realm" toggleable="true" collapsed="false">
<div class="grid">
<c:forEach var="role" items="#{roleGestionBean.realmRoles}">
<div class="col-12 md:col-6 lg:col-4">
<ui:include src="/templates/components/role-management/role-card.xhtml">
<ui:param name="role" value="#{role}" />
<ui:param name="showActions" value="true" />
</ui:include>
</div>
</c:forEach>
<c:if test="#{empty roleGestionBean.realmRoles}">
<div class="col-12">
<p class="text-center text-color-secondary">Aucun rôle Realm trouvé</p>
</div>
</c:if>
</div>
</p:panel>
</h:form>
</div>
<!-- Rôles Client -->
<div class="card">
<h:form id="formClientRoles">
<p:panel id="clientRolesPanel" header="Rôles Client" toggleable="true" collapsed="false">
<div class="grid">
<c:forEach var="role" items="#{roleGestionBean.clientRoles}">
<div class="col-12 md:col-6 lg:col-4">
<ui:include src="/templates/components/role-management/role-card.xhtml">
<ui:param name="role" value="#{role}" />
<ui:param name="showActions" value="true" />
</ui:include>
</div>
</c:forEach>
<c:if test="#{empty roleGestionBean.clientRoles}">
<div class="col-12">
<p class="text-center text-color-secondary">Aucun rôle Client trouvé</p>
</div>
</c:if>
</div>
</p:panel>
</h:form>
</div>
<!-- Dialog Création Rôle Realm -->
<p:dialog id="createRealmRoleDialog"
header="Nouveau Rôle Realm"
widgetVar="createRealmRoleDialog"
modal="true"
styleClass="w-full md:w-6">
<h:form id="formCreateRealmRole">
<ui:include src="/templates/components/role-management/role-form.xhtml">
<ui:param name="role" value="#{roleGestionBean.newRole}" />
<ui:param name="mode" value="create" />
<ui:param name="showClientSelector" value="false" />
<ui:param name="submitAction" value="#{roleGestionBean.createRealmRole}" />
<ui:param name="hasSubmitAction" value="true" />
<ui:param name="update" value=":formRealmRoles:realmRolesPanel" />
<ui:param name="useParentForm" value="true" />
</ui:include>
</h:form>
</p:dialog>
<!-- Dialog Création Rôle Client -->
<p:dialog id="createClientRoleDialog"
header="Nouveau Rôle Client"
widgetVar="createClientRoleDialog"
modal="true"
styleClass="w-full md:w-6">
<h:form id="formCreateClientRole">
<ui:include src="/templates/components/role-management/role-form.xhtml">
<ui:param name="role" value="#{roleGestionBean.newRole}" />
<ui:param name="mode" value="create" />
<ui:param name="showClientSelector" value="true" />
<ui:param name="submitAction" value="#{roleGestionBean.createClientRole}" />
<ui:param name="hasSubmitAction" value="true" />
<ui:param name="update" value=":formClientRoles:clientRolesPanel" />
<ui:param name="useParentForm" value="true" />
</ui:include>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{settingsBean}"/>
<ui:define name="title">Paramètres - Lions User Manager</ui:define>
<ui:define name="content">
<!-- En-tête -->
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-cog text-blue-500" />
<ui:param name="title" value="Paramètres" />
<ui:param name="description" value="Gérer vos préférences et paramètres de compte" />
</ui:include>
<div class="grid">
<!-- Informations du compte -->
<div class="col-12 lg:col-8">
<div class="card">
<h5>Informations du compte</h5>
<h:form id="formAccountInfo">
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-4, col-12 md:col-8">
<p:outputLabel for="username" value="Nom d'utilisateur" />
<p:inputText id="username"
value="#{userSessionBean.username}"
readonly="true"
styleClass="w-full" />
<p:outputLabel for="email" value="Email" />
<p:inputText id="email"
value="#{userSessionBean.email}"
readonly="true"
styleClass="w-full" />
<p:outputLabel for="fullName" value="Nom complet" />
<p:inputText id="fullName"
value="#{userSessionBean.fullName}"
readonly="true"
styleClass="w-full" />
<p:outputLabel for="mainRole" value="Rôle principal" />
<p:inputText id="mainRole"
value="#{userSessionBean.mainRole}"
readonly="true"
styleClass="w-full" />
</p:panelGrid>
</h:form>
</div>
</div>
<!-- Préférences -->
<div class="col-12 lg:col-4">
<div class="card">
<h5>Préférences</h5>
<h:form id="formPreferences">
<div class="flex flex-column gap-3">
<div class="flex align-items-center justify-content-between">
<span class="text-600">Thème des composants</span>
<p:selectOneMenu value="#{guestPreferences.componentTheme}"
styleClass="w-12rem">
<f:selectItems value="#{guestPreferences.componentThemes}"
var="theme"
itemLabel="#{theme.name}"
itemValue="#{theme.file}" />
<p:ajax event="change" update="@form" />
</p:selectOneMenu>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Mode sombre</span>
<p:selectOneMenu value="#{guestPreferences.darkMode}"
styleClass="w-12rem">
<f:selectItem itemLabel="Clair" itemValue="light" />
<f:selectItem itemLabel="Sombre" itemValue="dark" />
<p:ajax event="change" update="@form" />
</p:selectOneMenu>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Style d'input</span>
<p:selectOneMenu value="#{guestPreferences.inputStyle}"
styleClass="w-12rem">
<f:selectItem itemLabel="Outlined" itemValue="outlined" />
<f:selectItem itemLabel="Filled" itemValue="filled" />
<p:ajax event="change" update="@form" />
</p:selectOneMenu>
</div>
</div>
</h:form>
</div>
</div>
<!-- Actions -->
<div class="col-12">
<div class="card">
<h5>Actions</h5>
<div class="flex gap-2">
<h:form>
<p:commandButton
value="Rafraîchir les informations"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{userSessionBean.loadUserInfo}"
update="formAccountInfo" />
</h:form>
<h:form>
<p:commandButton
value="Changer le mot de passe"
icon="pi pi-key"
styleClass="p-button-info"
outcome="/pages/user-manager/users/profile" />
</h:form>
<h:form>
<p:commandButton
value="Sauvegarder les préférences"
icon="pi pi-save"
styleClass="p-button-success"
action="#{settingsBean.savePreferences}"
update="@form" />
</h:form>
</div>
</div>
</div>
</div>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:define name="title">Synchronisation Keycloak - Lions User Manager</ui:define>
<ui:define name="content">
<!-- En-tête -->
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-sync text-blue-500" />
<ui:param name="title" value="Synchronisation Keycloak" />
<ui:param name="description" value="Synchronisation et vérification de l'état de Keycloak" />
</ui:include>
<!-- Health Checks -->
<div class="grid mb-4">
<div class="col-12 md:col-6">
<div class="card">
<h5>État de Keycloak</h5>
<p:outputLabel value="Vérification de la connexion..." />
<!-- TODO: Intégrer SyncServiceClient pour afficher le statut -->
</div>
</div>
<div class="col-12 md:col-6">
<div class="card">
<h5>Actions de Synchronisation</h5>
<div class="flex flex-column gap-2">
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Synchroniser Utilisateurs" />
<ui:param name="icon" value="pi pi-users" />
<ui:param name="severity" value="primary" />
</ui:include>
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
<ui:param name="value" value="Synchroniser Rôles" />
<ui:param name="icon" value="pi pi-shield" />
<ui:param name="severity" value="info" />
</ui:include>
</div>
</div>
</div>
</div>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,333 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{userCreationBean}"/>
<ui:define name="title">Nouvel Utilisateur - Lions User Manager</ui:define>
<ui:define name="content">
<div class="grid">
<!-- ================================================================
EN-TÊTE DE LA PAGE
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-user-plus text-green-500" style="font-size: 2rem"></i>
<div>
<h3 class="m-0 mb-1">Nouvel Utilisateur</h3>
<p class="text-600 m-0">Créer un nouvel utilisateur dans Keycloak</p>
</div>
</div>
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-text">
<i class="pi pi-arrow-left mr-2"></i>
Retour à la liste
</h:link>
</div>
</div>
</div>
<!-- ================================================================
FORMULAIRE DE CRÉATION
================================================================ -->
<h:form id="formUserCreation">
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-info-circle text-blue-500"></i>
Informations de l'Utilisateur
</h3>
<div class="grid">
<!-- Colonne gauche: Informations de base -->
<div class="col-12 lg:col-6">
<div class="surface-50 border-round p-3 mb-3">
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
<i class="pi pi-user text-blue-500"></i>
<span>Informations de Base</span>
</h4>
<!-- Nom d'utilisateur -->
<div class="field mb-3">
<label for="username" class="block text-900 font-medium mb-2">
Nom d'utilisateur <span class="text-red-500">*</span>
</label>
<p:inputText id="username"
value="#{userCreationBean.newUser.username}"
styleClass="w-full"
required="true"
placeholder="ex: jdupont">
<f:validateLength minimum="3" maximum="50" />
</p:inputText>
<small class="text-500">Identifiant unique de connexion</small>
</div>
<!-- Email -->
<div class="field mb-3">
<label for="email" class="block text-900 font-medium mb-2">
Adresse email <span class="text-red-500">*</span>
</label>
<p:inputText id="email"
value="#{userCreationBean.newUser.email}"
styleClass="w-full"
required="true"
type="email"
placeholder="ex: jean.dupont@example.com">
<f:validateRegex pattern="^[A-Za-z0-9+_.-]+@(.+)$" />
</p:inputText>
</div>
<!-- Prénom -->
<div class="field mb-3">
<label for="prenom" class="block text-900 font-medium mb-2">
Prénom <span class="text-red-500">*</span>
</label>
<p:inputText id="prenom"
value="#{userCreationBean.newUser.prenom}"
styleClass="w-full"
required="true"
placeholder="ex: Jean">
<f:validateLength minimum="2" maximum="100" />
</p:inputText>
</div>
<!-- Nom -->
<div class="field mb-0">
<label for="nom" class="block text-900 font-medium mb-2">
Nom <span class="text-red-500">*</span>
</label>
<p:inputText id="nom"
value="#{userCreationBean.newUser.nom}"
styleClass="w-full"
required="true"
placeholder="ex: Dupont">
<f:validateLength minimum="2" maximum="100" />
</p:inputText>
</div>
</div>
</div>
<!-- Colonne droite: Mot de passe et Configuration -->
<div class="col-12 lg:col-6">
<!-- Section Mot de passe -->
<div class="surface-50 border-round p-3 mb-3">
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
<i class="pi pi-key text-orange-500"></i>
<span>Mot de Passe</span>
</h4>
<!-- Mot de passe -->
<div class="field mb-3">
<label for="password" class="block text-900 font-medium mb-2">
Mot de passe <span class="text-red-500">*</span>
</label>
<p:password id="password"
value="#{userCreationBean.password}"
styleClass="w-full"
required="true"
feedback="true"
toggleMask="true"
placeholder="Minimum 8 caractères">
<f:validateLength minimum="8" maximum="100" />
</p:password>
<small class="text-500">Au moins 8 caractères</small>
</div>
<!-- Confirmation mot de passe -->
<div class="field mb-0">
<label for="passwordConfirm" class="block text-900 font-medium mb-2">
Confirmer le mot de passe <span class="text-red-500">*</span>
</label>
<p:password id="passwordConfirm"
value="#{userCreationBean.passwordConfirm}"
styleClass="w-full"
required="true"
feedback="false"
toggleMask="true"
placeholder="Confirmer le mot de passe">
</p:password>
</div>
</div>
<!-- Section Configuration -->
<div class="surface-50 border-round p-3">
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
<i class="pi pi-cog text-purple-500"></i>
<span>Configuration</span>
</h4>
<!-- Realm -->
<div class="field mb-3">
<label for="realm" class="block text-900 font-medium mb-2">
Realm Keycloak
</label>
<p:selectOneMenu id="realm"
value="#{userCreationBean.realmName}"
styleClass="w-full">
<f:selectItems value="#{userCreationBean.availableRealms}"
var="realm"
itemLabel="#{realm}"
itemValue="#{realm}" />
</p:selectOneMenu>
</div>
<!-- Options de configuration -->
<div class="flex flex-column gap-2">
<!-- Compte activé -->
<div class="field-checkbox mb-0">
<p:selectBooleanCheckbox id="enabled"
value="#{userCreationBean.newUser.enabled}">
</p:selectBooleanCheckbox>
<label for="enabled" class="ml-2">Compte activé</label>
</div>
<!-- Email vérifié -->
<div class="field-checkbox mb-0">
<p:selectBooleanCheckbox id="emailVerified"
value="#{userCreationBean.newUser.emailVerified}">
</p:selectBooleanCheckbox>
<label for="emailVerified" class="ml-2">Email vérifié</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================
ACTIONS
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-check-circle text-green-500"></i>
Actions
</h3>
<div class="flex flex-wrap gap-2 align-items-center">
<!-- Bouton Créer -->
<p:commandButton value="Créer l'utilisateur"
icon="pi pi-check"
styleClass="p-button-success"
action="#{userCreationBean.createUser}"
update=":formUserCreation"
validateClient="true">
</p:commandButton>
<!-- Bouton Réinitialiser -->
<p:commandButton value="Réinitialiser"
icon="pi pi-refresh"
styleClass="p-button-outlined p-button-secondary"
action="#{userCreationBean.resetForm}"
update=":formUserCreation"
immediate="true">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment réinitialiser le formulaire ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<!-- Bouton Annuler -->
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="p-button-outlined"
action="#{userCreationBean.cancel}"
immediate="true">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment annuler la création ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<div class="flex-grow-1"></div>
<!-- Aide -->
<p:commandButton value="Aide"
icon="pi pi-question-circle"
styleClass="p-button-outlined p-button-help"
type="button"
onclick="PF('helpDialog').show();">
</p:commandButton>
</div>
<!-- Message d'information -->
<p:messages id="messages" showDetail="true" closable="true" styleClass="mt-3">
<p:autoUpdate />
</p:messages>
</div>
</div>
</h:form>
</div>
<!-- ================================================================
DIALOG DE CONFIRMATION
================================================================ -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="400">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
icon="pi pi-times" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-primary"
icon="pi pi-check" />
</p:confirmDialog>
<!-- ================================================================
DIALOG D'AIDE
================================================================ -->
<p:dialog header="Aide - Création d'Utilisateur"
widgetVar="helpDialog"
modal="true"
responsive="true"
width="600"
showEffect="fade"
hideEffect="fade">
<div class="grid">
<div class="col-12">
<h4 class="text-900 font-semibold flex align-items-center gap-2 mb-3">
<i class="pi pi-info-circle text-blue-500"></i>
Informations Requises
</h4>
<ul class="text-700 line-height-3 mb-4">
<li><strong>Nom d'utilisateur</strong> : Identifiant unique (3-50 caractères)</li>
<li><strong>Email</strong> : Adresse email valide</li>
<li><strong>Mot de passe</strong> : Au moins 8 caractères</li>
</ul>
<h4 class="text-900 font-semibold flex align-items-center gap-2 mb-3">
<i class="pi pi-shield text-purple-500"></i>
Sécurité
</h4>
<ul class="text-700 line-height-3 mb-4">
<li>Le mot de passe doit contenir au moins 8 caractères</li>
<li>Utilisez une combinaison de lettres, chiffres et caractères spéciaux</li>
<li>L'utilisateur pourra modifier son mot de passe après la première connexion</li>
</ul>
<h4 class="text-900 font-semibold flex align-items-center gap-2 mb-3">
<i class="pi pi-cog text-orange-500"></i>
Configuration
</h4>
<ul class="text-700 line-height-3 mb-0">
<li><strong>Compte activé</strong> : L'utilisateur peut se connecter immédiatement</li>
<li><strong>Email vérifié</strong> : L'email est considéré comme vérifié</li>
<li><strong>Realm</strong> : Espace d'administration Keycloak</li>
</ul>
</div>
</div>
<f:facet name="footer">
<p:commandButton value="Fermer"
icon="pi pi-times"
styleClass="p-button-text"
onclick="PF('helpDialog').hide();"
type="button" />
</f:facet>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{userProfilBean}"/>
<ui:define name="title">Modifier Utilisateur - Lions User Manager</ui:define>
<ui:define name="content">
<!-- En-tête -->
<ui:include src="/templates/components/layout/page-header.xhtml">
<ui:param name="icon" value="pi pi-pencil text-warning-500" />
<ui:param name="title" value="Modifier Utilisateur" />
<ui:param name="description" value="Modifier les informations de l'utilisateur" />
</ui:include>
<!-- Formulaire d'édition -->
<div class="card">
<ui:include src="/templates/components/user-management/user-form.xhtml">
<ui:param name="user" value="#{userProfilBean.user}" />
<ui:param name="mode" value="edit" />
<ui:param name="showPasswordFields" value="false" />
<ui:param name="submitAction" value="#{userProfilBean.updateUser}" />
<ui:param name="cancelOutcome" value="/pages/user-manager/users/list" />
</ui:include>
</div>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,429 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{userListBean}"/>
<ui:define name="title">Liste des Utilisateurs - Lions User Manager</ui:define>
<ui:define name="content">
<h:form id="formUserList">
<div class="grid">
<!-- ================================================================
EN-TÊTE DE LA PAGE
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-users text-blue-500" style="font-size: 2rem"></i>
<div>
<h3 class="m-0 mb-1">Gestion des Utilisateurs</h3>
<p class="text-600 m-0">Gestion centralisée des utilisateurs Keycloak - Recherche, création, modification et suppression</p>
</div>
</div>
<div class="flex gap-2">
<p:commandButton
value="Rafraîchir"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{userListBean.refreshData}"
update=":formUserList"
process="@this" />
<p:commandButton
value="Nouvel Utilisateur"
icon="pi pi-user-plus"
styleClass="p-button-success"
outcome="/pages/user-manager/users/create" />
</div>
</div>
</div>
</div>
<!-- ================================================================
STATISTIQUES KPI (4 CARTES)
================================================================ -->
<div class="col-12">
<h5 class="mb-3">Statistiques des Utilisateurs</h5>
</div>
<!-- KPI 1: Total Utilisateurs -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Total Utilisateurs</div>
<div class="text-900 font-bold text-2xl">#{userListBean.totalRecords}</div>
</div>
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-users text-blue-600 text-xl"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-database text-600"></i>
<span class="ml-2">Utilisateurs dans le realm</span>
</div>
</div>
</div>
<!-- KPI 2: Utilisateurs Actifs -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Utilisateurs Actifs</div>
<div class="text-900 font-bold text-2xl">#{userListBean.activeUsersCount}</div>
</div>
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-check-circle text-green-600 text-xl"></i>
</div>
</div>
<div class="flex align-items-center gap-2">
<span class="text-green-600 font-semibold">
<i class="pi pi-arrow-up text-xs"></i>
#{userListBean.activeUsersPercentage}%
</span>
<span class="text-500 text-sm">Taux d'activation</span>
</div>
<p:progressBar value="#{userListBean.activeUsersPercentage}"
styleClass="mt-2"
style="height: 4px"
showValue="false" />
</div>
</div>
<!-- KPI 3: Utilisateurs Désactivés -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Utilisateurs Désactivés</div>
<div class="text-900 font-bold text-2xl">#{userListBean.disabledUsersCount}</div>
</div>
<div class="flex align-items-center justify-content-center bg-red-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-times-circle text-red-600 text-xl"></i>
</div>
</div>
<div class="flex align-items-center gap-2">
<span class="text-red-600 font-semibold">
<i class="pi pi-arrow-down text-xs"></i>
#{userListBean.disabledUsersPercentage}%
</span>
<span class="text-500 text-sm">Taux de désactivation</span>
</div>
<p:progressBar value="#{userListBean.disabledUsersPercentage}"
styleClass="mt-2"
style="height: 4px"
showValue="false" />
</div>
</div>
<!-- KPI 4: Realm Actuel -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div style="max-width: 150px;">
<div class="text-500 font-medium mb-1">Realm Actuel</div>
<div class="text-900 font-bold text-xl" style="word-break: break-word;">#{userListBean.realmName}</div>
</div>
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-globe text-purple-600 text-xl"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-server text-600"></i>
<span class="ml-2">Realm Keycloak</span>
</div>
</div>
</div>
<!-- ================================================================
SECTION RECHERCHE ET FILTRES
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center gap-2 mb-3">
<i class="pi pi-search text-blue-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Recherche et Filtres</h5>
</div>
<div class="grid">
<div class="col-12 md:col-6 lg:col-4">
<label for="searchText" class="block text-900 font-medium mb-2">Recherche</label>
<p:inputText id="searchText"
value="#{userListBean.searchText}"
placeholder="Nom, email..."
styleClass="w-full">
<p:ajax event="keyup"
delay="500"
update=":formUserList:userTable"
listener="#{userListBean.search}" />
</p:inputText>
</div>
<div class="col-12 md:col-6 lg:col-3">
<label for="realmSelect" class="block text-900 font-medium mb-2">Realm</label>
<p:selectOneMenu id="realmSelect"
value="#{userListBean.realmName}"
styleClass="w-full">
<f:selectItem itemLabel="lions-user-manager" itemValue="lions-user-manager" />
<f:selectItem itemLabel="master" itemValue="master" />
<p:ajax update=":formUserList:userTable"
listener="#{userListBean.search}" />
</p:selectOneMenu>
</div>
<div class="col-12 md:col-6 lg:col-3">
<label for="statusSelect" class="block text-900 font-medium mb-2">Statut</label>
<p:selectOneMenu id="statusSelect"
value="#{userListBean.selectedStatut}"
styleClass="w-full">
<f:selectItem itemLabel="Tous" itemValue="#{null}" />
<f:selectItems value="#{userListBean.statutOptions}" />
<p:ajax update=":formUserList:userTable"
listener="#{userListBean.search}" />
</p:selectOneMenu>
</div>
<div class="col-12 lg:col-2 flex align-items-end">
<p:commandButton
value="Réinitialiser"
icon="pi pi-refresh"
styleClass="p-button-secondary w-full"
action="#{userListBean.resetSearch}"
update=":formUserList:userTable @form" />
</div>
</div>
</div>
</div>
<!-- ================================================================
TABLEAU DES UTILISATEURS
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between mb-3">
<div class="flex align-items-center gap-2">
<i class="pi pi-list text-blue-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Liste des Utilisateurs</h5>
</div>
<p:tag value="#{userListBean.totalRecords} utilisateur(s)"
severity="info"
icon="pi pi-users" />
</div>
<p:dataTable
id="userTable"
value="#{userListBean.users}"
var="user"
rowKey="#{user.id}"
paginator="true"
rows="#{userListBean.pageSize}"
rowsPerPageTemplate="10,20,50"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
styleClass="w-full"
emptyMessage="Aucun utilisateur trouvé"
reflow="true">
<p:ajax event="page" listener="#{userListBean.onPageChange}" update=":formUserList:userTable" />
<!-- Colonne Avatar + Username -->
<p:column headerText="Utilisateur" sortBy="#{user.username}" style="width: 250px">
<div class="flex align-items-center gap-3">
<div class="border-circle bg-primary text-white flex align-items-center justify-content-center"
style="width: 42px; height: 42px; flex-shrink: 0;">
<span class="font-bold">
#{user.prenom != null ? user.prenom.substring(0,1).toUpperCase() : 'U'}#{user.nom != null ? user.nom.substring(0,1).toUpperCase() : 'U'}
</span>
</div>
<div class="flex flex-column">
<span class="font-semibold text-900">#{user.username}</span>
<span class="text-600 text-sm">#{user.prenom} #{user.nom}</span>
</div>
</div>
</p:column>
<!-- Colonne Email -->
<p:column headerText="Email" sortBy="#{user.email}" style="width: 250px">
<div class="flex align-items-center gap-2">
<i class="pi pi-envelope text-500"></i>
<span class="text-900">#{user.email}</span>
<p:outputPanel rendered="#{user.emailVerified}">
<i class="pi pi-check-circle text-green-500" title="Email vérifié"></i>
</p:outputPanel>
</div>
</p:column>
<!-- Colonne Statut -->
<p:column headerText="Statut" sortBy="#{user.enabled}" style="width: 120px; text-align: center">
<p:tag value="#{user.enabled ? 'ACTIF' : 'INACTIF'}"
severity="#{user.enabled ? 'success' : 'danger'}" />
</p:column>
<!-- Colonne Rôles -->
<p:column headerText="Rôles" style="width: 250px">
<div class="flex flex-wrap gap-1">
<h:outputText value="Aucun rôle" styleClass="text-500 text-sm"
rendered="#{user.realmRoles == null or user.realmRoles.size() == 0}" />
<ui:fragment rendered="#{user.realmRoles != null and user.realmRoles.size() > 0}">
<ui:repeat value="#{user.realmRoles}" var="role" varStatus="status">
<p:tag value="#{role}" severity="info"
rendered="#{status.index lt 3}"
styleClass="mr-1" />
</ui:repeat>
<p:tag value="+#{user.realmRoles.size() - 3}" severity="secondary"
rendered="#{user.realmRoles.size() > 3}" />
</ui:fragment>
</div>
</p:column>
<!-- Colonne Actions -->
<p:column headerText="Actions" style="width: 180px; text-align: center">
<div class="flex gap-1 justify-content-center">
<!-- Bouton Modifier -->
<p:commandButton icon="pi pi-pencil"
styleClass="p-button-rounded p-button-text p-button-sm"
title="Modifier"
outcome="/pages/user-manager/users/edit">
<f:param name="userId" value="#{user.id}" />
</p:commandButton>
<!-- Bouton Désactiver (si actif) -->
<p:commandButton icon="pi pi-ban"
styleClass="p-button-rounded p-button-text p-button-sm p-button-warning"
title="Désactiver"
action="#{userListBean.deactivateUser(user.id)}"
update=":formUserList"
rendered="#{user.enabled}">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment désactiver cet utilisateur ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<!-- Bouton Activer (si inactif) -->
<p:commandButton icon="pi pi-check"
styleClass="p-button-rounded p-button-text p-button-sm p-button-success"
title="Activer"
action="#{userListBean.activateUser(user.id)}"
update=":formUserList"
rendered="#{not user.enabled}" />
<!-- Bouton Supprimer -->
<p:commandButton icon="pi pi-trash"
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
title="Supprimer"
action="#{userListBean.deleteUser(user.id)}"
update=":formUserList">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment supprimer cet utilisateur ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</div>
</p:column>
</p:dataTable>
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="350">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
onclick="PF('confirmDialog').hide()" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-primary"
onclick="PF('confirmDialog').hide()" />
</p:confirmDialog>
</div>
</div>
<!-- ================================================================
ACTIONS RAPIDES
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center gap-2 mb-3">
<i class="pi pi-bolt text-orange-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Actions Rapides</h5>
</div>
<div class="grid">
<div class="col-12 md:col-6 lg:col-3">
<p:commandButton
value="Créer un Utilisateur"
icon="pi pi-user-plus"
styleClass="w-full p-button-success"
outcome="/pages/user-manager/users/create" />
</div>
<div class="col-12 md:col-6 lg:col-3">
<p:commandButton
value="Exporter la Liste"
icon="pi pi-download"
styleClass="w-full p-button-secondary"
action="#{userListBean.exportToCSV}"
ajax="false" />
</div>
<div class="col-12 md:col-6 lg:col-3">
<p:commandButton
value="Importer des Utilisateurs"
icon="pi pi-upload"
styleClass="w-full p-button-info"
onclick="PF('importUsersDialog').show()"
type="button" />
</div>
<div class="col-12 md:col-6 lg:col-3">
<p:commandButton
value="Gestion des Rôles"
icon="pi pi-shield"
styleClass="w-full p-button-primary"
outcome="/pages/user-manager/roles/list" />
</div>
</div>
</div>
</div>
</div>
</h:form>
<!-- ================================================================
DIALOG D'IMPORT
================================================================ -->
<p:dialog id="importUsersDialog"
widgetVar="importUsersDialog"
header="Importer des Utilisateurs"
modal="true"
resizable="false"
styleClass="w-full md:w-30rem">
<h:form id="formImportUsers">
<div class="flex flex-column gap-3">
<p class="text-600">
Importez des utilisateurs depuis un fichier CSV ou JSON.
</p>
<p:fileUpload mode="simple"
skinSimple="true"
accept=".csv,.json"
label="Sélectionner un fichier" />
<div class="flex justify-content-end gap-2 mt-3">
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('importUsersDialog').hide()"
type="button" />
<p:commandButton value="Importer"
icon="pi pi-upload"
styleClass="p-button-success"
action="#{userListBean.importUsers}"
update=":formUserList"
oncomplete="PF('importUsersDialog').hide()" />
</div>
</div>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,421 @@
<!DOCTYPE html>
<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"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{userSessionBean}"/>
<ui:define name="title">Mon Profil - Lions User Manager</ui:define>
<ui:define name="content">
<div class="grid">
<!-- ================================================================
EN-TÊTE DE LA PAGE
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between">
<h2 class="text-900 font-semibold text-xl m-0">
<i class="pi pi-user text-blue-500 mr-2"></i>
Mon Profil
</h2>
<h:link outcome="/pages/user-manager/dashboard" styleClass="p-button p-button-text">
<i class="pi pi-arrow-left mr-2"></i>
Retour au tableau de bord
</h:link>
</div>
</div>
</div>
<!-- ================================================================
CARTE PROFIL PRINCIPAL
================================================================ -->
<div class="col-12">
<div class="card">
<div class="grid">
<!-- Photo de profil et informations principales -->
<div class="col-12 md:col-4">
<div class="text-center mb-4">
<!-- Avatar avec gradient -->
<div style="width: 140px; height: 140px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--primary-600, #387FE9)); display: flex; align-items: center; justify-content: center; margin: 0 auto 1.5rem auto; font-size: 3.5rem; font-weight: bold; color: white; box-shadow: 0 8px 24px rgba(0,0,0,0.12);">
#{userSessionBean.initials}
</div>
<!-- Nom complet -->
<h3 class="text-900 font-semibold text-2xl mb-2">#{userSessionBean.fullName}</h3>
<!-- Email -->
<p class="text-600 mb-3 flex align-items-center justify-content-center gap-2">
<i class="pi pi-envelope"></i>
#{userSessionBean.email}
</p>
<!-- Badge de statut -->
<div class="inline-flex align-items-center justify-content-center gap-2">
<span class="inline-flex align-items-center gap-2 bg-green-100 text-green-700 px-3 py-2 border-round font-semibold" style="font-size: 1rem;">
<i class="pi pi-circle-fill" style="font-size: 0.5rem; animation: pulse 2s ease-in-out infinite;"></i>
<span>Connecté</span>
</span>
</div>
<!-- Badge du rôle principal -->
<div class="mt-3 flex justify-content-center">
<span class="inline-flex align-items-center bg-blue-100 text-blue-700 px-3 py-1 border-round font-semibold text-sm" style="text-transform: uppercase; letter-spacing: 0.5px;">
#{userSessionBean.primaryRole}
</span>
</div>
</div>
</div>
<!-- Informations détaillées -->
<div class="col-12 md:col-8">
<div class="grid">
<!-- Colonne gauche: Informations personnelles -->
<div class="col-12 md:col-6">
<h4 class="text-900 font-semibold text-lg mb-3 flex align-items-center gap-2">
<i class="pi pi-user text-blue-500"></i>
Informations Personnelles
</h4>
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-1 text-sm">Nom d'utilisateur</label>
<p class="text-900 font-semibold m-0">#{userSessionBean.username}</p>
</div>
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-1 text-sm">Nom complet</label>
<p class="text-900 font-semibold m-0">#{userSessionBean.fullName}</p>
</div>
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-1 text-sm">Adresse email</label>
<div class="flex align-items-center gap-2">
<p class="text-900 font-semibold m-0">#{userSessionBean.email}</p>
<i class="pi pi-check-circle text-green-500" title="Email vérifié"></i>
</div>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Prénom</label>
<p class="text-900 font-semibold m-0">#{userSessionBean.firstName}</p>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Nom</label>
<p class="text-900 font-semibold m-0">#{userSessionBean.lastName}</p>
</div>
</div>
<!-- Colonne droite: Rôles et permissions -->
<div class="col-12 md:col-6">
<h4 class="text-900 font-semibold text-lg mb-3 flex align-items-center gap-2">
<i class="pi pi-shield text-purple-500"></i>
Rôles et Permissions
</h4>
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-2 text-sm">Rôles assignés</label>
<div class="flex flex-wrap gap-2">
<ui:repeat value="#{userSessionBean.roles}" var="role">
<p:badge value="#{role}" severity="info" styleClass="text-sm"></p:badge>
</ui:repeat>
</div>
</div>
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-2 text-sm">Rôle principal</label>
<div class="flex align-items-center">
<p:badge value="#{userSessionBean.primaryRole}"
severity="success"
styleClass="text-sm">
</p:badge>
</div>
</div>
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-1 text-sm">Niveau d'accès</label>
<p class="text-900 font-semibold m-0">
<h:outputText value="Administrateur système" rendered="#{userSessionBean.hasRole('admin')}" />
<h:outputText value="Gestionnaire utilisateurs" rendered="#{userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('admin')}" />
<h:outputText value="Consultation utilisateurs" rendered="#{userSessionBean.hasRole('user_viewer') and not userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('admin')}" />
<h:outputText value="Auditeur" rendered="#{userSessionBean.hasRole('auditor') and not userSessionBean.hasRole('user_viewer') and not userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('admin')}" />
<h:outputText value="Utilisateur standard" rendered="#{not userSessionBean.hasRole('admin') and not userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('user_viewer') and not userSessionBean.hasRole('auditor')}" />
</p>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-2 text-sm">Statut du compte</label>
<div class="flex align-items-center">
<p:badge value="Actif" severity="success" styleClass="text-sm"></p:badge>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================
INFORMATIONS DE SESSION OIDC
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-shield text-orange-500"></i>
Informations de Session OIDC
</h3>
<div class="grid">
<!-- Colonne gauche: Token Information -->
<div class="col-12 md:col-6">
<h4 class="text-900 font-semibold mb-3">Informations du Token</h4>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Issuer (Émetteur)</label>
<p class="text-700 m-0 text-sm font-mono bg-bluegray-50 p-2 border-round">
#{userSessionBean.issuer}
</p>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Subject (Identifiant)</label>
<p class="text-700 m-0 text-sm font-mono bg-bluegray-50 p-2 border-round">
#{userSessionBean.subject}
</p>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Audience</label>
<p class="text-700 m-0 text-sm font-mono bg-bluegray-50 p-2 border-round">
account
</p>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-2 text-sm">Token Type</label>
<div class="flex align-items-center">
<p:badge value="Bearer" severity="info" styleClass="text-sm"></p:badge>
</div>
</div>
</div>
<!-- Colonne droite: Session Details -->
<div class="col-12 md:col-6">
<h4 class="text-900 font-semibold mb-3">Détails de la Session</h4>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Expiration du token</label>
<div class="flex align-items-center gap-2">
<i class="pi pi-calendar text-orange-500"></i>
<p class="text-700 m-0 text-sm">
<h:outputText value="#{userSessionBean.expirationTime}">
<f:convertDateTime pattern="dd/MM/yyyy à HH:mm:ss" timeZone="Europe/Paris" type="both"/>
</h:outputText>
</p>
</div>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Émis le</label>
<div class="flex align-items-center gap-2">
<i class="pi pi-clock text-blue-500"></i>
<p class="text-700 m-0 text-sm">
<h:outputText value="#{userSessionBean.issuedAt}">
<f:convertDateTime pattern="dd/MM/yyyy à HH:mm:ss" timeZone="Europe/Paris" type="both"/>
</h:outputText>
</p>
</div>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Realm Keycloak</label>
<div class="flex align-items-center gap-2">
<i class="pi pi-globe text-purple-500"></i>
<p class="text-700 m-0 text-sm font-semibold">
lions-user-manager
</p>
</div>
</div>
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Durée de validité</label>
<div class="flex align-items-center gap-2">
<i class="pi pi-hourglass text-green-500"></i>
<p class="text-700 m-0 text-sm">
Session active
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================
STATISTIQUES D'ACTIVITÉ
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-chart-line text-green-500"></i>
Statistiques d'Activité
</h3>
<div class="grid">
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between mb-2">
<span class="text-600 font-medium text-sm">Connexions</span>
<i class="pi pi-sign-in text-blue-500"></i>
</div>
<p class="text-900 font-bold text-2xl m-0">--</p>
<small class="text-500">Total des connexions</small>
</div>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between mb-2">
<span class="text-600 font-medium text-sm">Dernière connexion</span>
<i class="pi pi-clock text-green-500"></i>
</div>
<p class="text-900 font-bold text-xl m-0">Aujourd'hui</p>
<small class="text-500">Session en cours</small>
</div>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between mb-2">
<span class="text-600 font-medium text-sm">Actions</span>
<i class="pi pi-history text-orange-500"></i>
</div>
<p class="text-900 font-bold text-2xl m-0">--</p>
<small class="text-500">Actions effectuées</small>
</div>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between mb-2">
<span class="text-600 font-medium text-sm">Sessions</span>
<i class="pi pi-desktop text-purple-500"></i>
</div>
<p class="text-900 font-bold text-2xl m-0">1</p>
<small class="text-500">Session active</small>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================
ACTIONS
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-cog text-gray-500"></i>
Actions Rapides
</h3>
<h:form id="formProfileActions">
<div class="grid">
<!-- Gestion du Profil -->
<div class="col-12 md:col-6">
<div class="surface-50 border-round p-3 h-full">
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
<i class="pi pi-user text-blue-500"></i>
<span>Gestion du Profil</span>
</h4>
<div class="flex flex-column gap-2">
<p:commandButton value="Modifier mon profil"
icon="pi pi-pencil"
styleClass="p-button-outlined w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité gérée par Keycloak"/>
</p:commandButton>
<p:commandButton value="Changer mon mot de passe"
icon="pi pi-key"
styleClass="p-button-outlined w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Utilisez le portail Keycloak"/>
</p:commandButton>
<p:commandButton value="Paramètres de sécurité"
icon="pi pi-shield"
styleClass="p-button-outlined w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
</p:commandButton>
</div>
</div>
</div>
<!-- Gestion des Sessions -->
<div class="col-12 md:col-6">
<div class="surface-50 border-round p-3 h-full">
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
<i class="pi pi-desktop text-purple-500"></i>
<span>Sessions et Sécurité</span>
</h4>
<div class="flex flex-column gap-2">
<p:commandButton value="Voir mes sessions actives"
icon="pi pi-desktop"
styleClass="p-button-outlined p-button-info w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
</p:commandButton>
<p:commandButton value="Historique des connexions"
icon="pi pi-history"
styleClass="p-button-outlined p-button-secondary w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
</p:commandButton>
<p:commandButton value="Se déconnecter"
icon="pi pi-sign-out"
styleClass="p-button-danger w-full justify-content-start"
action="#{userSessionBean.logout}">
<p:confirm header="Confirmation de déconnexion"
message="Êtes-vous sûr de vouloir vous déconnecter ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</div>
</div>
</div>
</div>
</h:form>
</div>
</div>
</div>
<!-- ================================================================
DIALOG DE CONFIRMATION
================================================================ -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="400">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
icon="pi pi-times" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-danger"
icon="pi pi-check" />
</p:confirmDialog>
<!-- Animation CSS pour le badge "Connecté" -->
<style>
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface>
<composite:attribute name="userId" type="java.lang.String" required="true"/>
<composite:attribute name="userEnabled" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="update" type="java.lang.String" required="false" default="@form"/>
<composite:attribute name="showView" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="showEdit" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="showDelete" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="showActivate" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="showDeactivate" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="showResetPassword" type="java.lang.Boolean" required="false" default="true"/>
<composite:attribute name="viewPage" type="java.lang.String" required="false" default="/pages/user-manager/users/profile"/>
<composite:attribute name="editPage" type="java.lang.String" required="false" default="/pages/user-manager/users/edit"/>
<composite:attribute name="activateAction"
method-signature="void activateAction(jakarta.faces.event.ActionEvent)"
required="false"/>
<composite:attribute name="deactivateAction"
method-signature="void deactivateAction(jakarta.faces.event.ActionEvent)"
required="false"/>
<composite:attribute name="deleteAction"
method-signature="void deleteAction(jakarta.faces.event.ActionEvent)"
required="false"/>
</composite:interface>
<composite:implementation>
<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="#{cc.attrs.showView}">
<p:menuitem
value="Voir le profil"
icon="pi pi-eye"
outcome="#{cc.attrs.viewPage}">
<f:param name="userId" value="#{cc.attrs.userId}" />
</p:menuitem>
</c:if>
<c:if test="#{cc.attrs.showEdit}">
<p:menuitem
value="Modifier"
icon="pi pi-pencil"
outcome="#{cc.attrs.editPage}">
<f:param name="userId" value="#{cc.attrs.userId}" />
</p:menuitem>
</c:if>
<c:if test="#{cc.attrs.showResetPassword}">
<p:menuitem
value="Réinitialiser mot de passe"
icon="pi pi-key"
onclick="PF('resetPasswordDialog').show()" />
</c:if>
<p:separator />
<c:if test="#{cc.attrs.showActivate and !cc.attrs.userEnabled}">
<c:if test="#{not empty cc.attrs.activateAction}">
<p:menuitem
value="Activer"
icon="pi pi-check"
styleClass="text-green-600"
actionListener="#{cc.attrs.activateAction}">
<f:attribute name="userId" value="#{cc.attrs.userId}" />
<p:ajax update="#{cc.attrs.update}" />
</p:menuitem>
</c:if>
</c:if>
<c:if test="#{cc.attrs.showDeactivate and cc.attrs.userEnabled}">
<c:if test="#{not empty cc.attrs.deactivateAction}">
<p:menuitem
value="Désactiver"
icon="pi pi-times"
styleClass="text-orange-600"
actionListener="#{cc.attrs.deactivateAction}">
<f:attribute name="userId" value="#{cc.attrs.userId}" />
<p:ajax update="#{cc.attrs.update}" />
</p:menuitem>
</c:if>
</c:if>
<c:if test="#{cc.attrs.showDelete}">
<c:if test="#{not empty cc.attrs.deleteAction}">
<p:separator />
<p:menuitem
value="Supprimer"
icon="pi pi-trash"
styleClass="text-red-600"
actionListener="#{cc.attrs.deleteAction}">
<f:attribute name="userId" value="#{cc.attrs.userId}" />
<p:ajax update="#{cc.attrs.update}" />
</p:menuitem>
</c:if>
</c:if>
</p:menu>
</p:commandButton>
</composite:implementation>
</html>

View File

@@ -0,0 +1,625 @@
/* ============================================================================
Lions User Manager - Enhanced Custom Topbar Styles
Auteur: Lions User Manager
Version: 2.0.0
Description: Styles améliorés pour la topbar avec intégration intelligente
des patterns Freya layout pour un rendu parfait
Intégrations:
- Freya Layout Variables & Patterns
- Support Dark/Light Theme
- Animations fluides (fadeInDown, modal-in)
- PrimeFlex utility classes
- Responsive design
============================================================================ */
/* ----------------------------------------------------------------------------
BASE TOPBAR LAYOUT OVERRIDES
Améliore la structure de base de la topbar Freya
---------------------------------------------------------------------------- */
.layout-topbar {
position: fixed;
top: 0;
z-index: 999;
width: 100%;
height: 62px;
transition: width 0.2s, box-shadow 0.3s ease;
}
.layout-topbar .layout-topbar-wrapper {
height: 100%;
display: flex;
align-items: center;
}
.layout-topbar .layout-topbar-wrapper .layout-topbar-right {
height: 100%;
flex-grow: 1;
padding: 0 16px 0 12px;
display: flex;
align-items: center;
justify-content: space-between;
}
.layout-topbar .layout-topbar-wrapper .layout-topbar-right .layout-topbar-actions {
display: flex;
align-items: center;
justify-content: flex-end;
flex-grow: 1;
list-style-type: none;
margin: 0;
padding: 0;
height: 100%;
}
.layout-topbar .layout-topbar-wrapper .layout-topbar-right .layout-topbar-actions > li {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
/* ----------------------------------------------------------------------------
USER PROFILE LINK - Enhanced with Freya patterns
---------------------------------------------------------------------------- */
.layout-topbar .user-profile-link {
display: flex !important;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
transition: all 0.2s cubic-bezier(0.05, 0.74, 0.2, 0.99);
text-decoration: none;
cursor: pointer;
position: relative;
overflow: hidden;
}
.layout-topbar .user-profile-link::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
opacity: 0;
transition: opacity 0.2s ease;
}
.layout-topbar .user-profile-link:hover::before {
opacity: 1;
}
.layout-topbar .user-profile-link:hover {
background-color: rgba(255, 255, 255, 0.12);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* User Avatar - Integration with Freya avatar patterns */
.layout-topbar .user-profile-link .user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.layout-topbar .user-profile-link:hover .user-avatar {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* User Info Container */
.layout-topbar .user-info {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: 1.2;
min-width: 0;
}
/* User Name - Enhanced typography */
.layout-topbar .user-name {
font-weight: 600;
font-size: 0.875rem;
color: var(--text-color);
margin-bottom: 0.125rem;
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
/* User Email */
.layout-topbar .user-email {
font-size: 0.75rem;
color: var(--text-color-secondary);
opacity: 0.85;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
/* User Role Badge */
.layout-topbar .user-role {
font-size: 0.7rem;
color: var(--primary-color);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
background: rgba(var(--primary-color-rgb, 79, 142, 236), 0.1);
padding: 0.125rem 0.375rem;
border-radius: 4px;
white-space: nowrap;
}
.layout-topbar .user-separator {
color: var(--text-color-secondary);
opacity: 0.5;
font-weight: 300;
margin: 0 0.25rem;
}
/* Online Status Indicator */
.layout-topbar .user-status {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
position: relative;
}
.layout-topbar .user-status.online {
background-color: #4CAF50;
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
animation: pulse-online 2s ease-in-out infinite;
}
@keyframes pulse-online {
0%, 100% {
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
}
50% {
box-shadow: 0 0 0 4px rgba(76, 175, 80, 0.2);
}
}
/* ----------------------------------------------------------------------------
USER DROPDOWN MENU - Enhanced with Freya dropdown patterns
---------------------------------------------------------------------------- */
.layout-topbar .user-dropdown-menu {
display: none;
position: absolute;
top: 62px;
right: 0;
min-width: 280px;
max-width: 320px;
padding: 0;
margin: 0;
list-style-type: none;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.08);
border: 1px solid var(--surface-border);
background: var(--surface-card);
overflow: hidden;
z-index: 1000;
animation-duration: 0.2s;
animation-timing-function: cubic-bezier(0.05, 0.74, 0.2, 0.99);
animation-fill-mode: forwards;
}
/* Show dropdown when parent is active */
.layout-topbar .user-profile.active-topmenuitem > .user-dropdown-menu {
display: block;
animation-name: fadeInDown;
}
/* Dropdown Header - Integration with Freya gradient patterns */
.user-dropdown-header {
padding: 1.25rem 1rem;
background: linear-gradient(135deg, var(--primary-color), var(--primary-600, #387FE9));
color: white;
display: flex;
align-items: center;
gap: 0.75rem;
position: relative;
overflow: hidden;
}
.user-dropdown-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-20%, -20%); }
}
/* Dropdown Avatar */
.user-dropdown-avatar {
position: relative;
flex-shrink: 0;
}
.user-dropdown-avatar > div {
width: 48px;
height: 48px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 700;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.user-status-indicator {
position: absolute;
bottom: 2px;
right: 2px;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid white;
}
.user-status-indicator.online {
background-color: #4CAF50;
animation: pulse-indicator 2s ease-in-out infinite;
}
@keyframes pulse-indicator {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
/* Dropdown User Info */
.user-dropdown-info {
flex: 1;
min-width: 0;
}
.user-dropdown-name {
font-weight: 600;
font-size: 1rem;
margin-bottom: 0.25rem;
color: white;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-dropdown-email {
font-size: 0.875rem;
opacity: 0.95;
margin-bottom: 0.25rem;
color: rgba(255, 255, 255, 0.95);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-dropdown-role {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
background: rgba(255, 255, 255, 0.25);
padding: 0.25rem 0.5rem;
border-radius: 12px;
display: inline-block;
color: white;
backdrop-filter: blur(10px);
}
/* Dividers */
.user-dropdown-divider {
height: 1px;
background-color: var(--surface-border);
margin: 0;
}
/* Menu Sections */
.user-dropdown-section {
padding: 0.75rem 0;
}
.section-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-color-secondary);
padding: 0 1rem 0.5rem 1rem;
margin-bottom: 0.25rem;
}
.section-items {
display: flex;
flex-direction: column;
}
/* Dropdown Items - Enhanced with Freya interaction patterns */
.dropdown-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
color: var(--text-color);
text-decoration: none;
transition: all 0.2s cubic-bezier(0.05, 0.74, 0.2, 0.99);
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
position: relative;
overflow: hidden;
}
.dropdown-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 100%;
background: var(--primary-color);
transform: scaleX(0);
transition: transform 0.2s ease;
}
.dropdown-item:hover::before {
transform: scaleX(1);
}
.dropdown-item:hover {
background-color: var(--surface-hover);
color: var(--primary-color);
padding-left: 1.25rem;
}
.dropdown-item:active {
background-color: var(--surface-ground);
transform: scale(0.98);
}
.dropdown-item i {
width: 1.25rem;
text-align: center;
color: var(--text-color-secondary);
transition: all 0.2s ease;
font-size: 1rem;
}
.dropdown-item:hover i {
color: var(--primary-color);
transform: scale(1.1);
}
.dropdown-item span {
flex: 1;
}
.item-arrow {
margin-left: auto;
opacity: 0;
transition: opacity 0.2s ease, transform 0.2s ease;
font-size: 0.75rem;
}
.dropdown-item:hover .item-arrow {
opacity: 1;
transform: translateX(4px);
}
/* Logout Item - Enhanced danger state */
.logout-item {
color: var(--red-500) !important;
margin-top: 0.25rem;
}
.logout-item:hover {
background-color: var(--red-50) !important;
color: var(--red-600) !important;
}
.logout-item i {
color: var(--red-500) !important;
}
.logout-item:hover i {
color: var(--red-600) !important;
transform: scale(1.1) rotate(-5deg);
}
/* ----------------------------------------------------------------------------
ANIMATIONS - Integration with Freya animation patterns
---------------------------------------------------------------------------- */
@keyframes dropdownFadeIn {
0% {
opacity: 0;
transform: translateY(-10px) scale(0.95);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.user-dropdown-menu {
animation: dropdownFadeIn 0.3s ease-out;
transform-origin: top right;
}
/* ----------------------------------------------------------------------------
DARK MODE SUPPORT - Integration with Freya dark theme
---------------------------------------------------------------------------- */
.layout-wrapper.layout-topbar-dark .layout-topbar {
background-color: #293241;
box-shadow: 0 10px 40px 0 rgba(0, 0, 0, 0.2);
}
.layout-wrapper.layout-topbar-dark .user-profile-link:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.layout-wrapper.layout-topbar-dark .user-dropdown-menu {
background: var(--surface-900, #1E1E1E);
border-color: var(--surface-700, #383838);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.3);
}
.layout-wrapper.layout-topbar-dark .user-dropdown-divider {
background-color: var(--surface-700, #383838);
}
.layout-wrapper.layout-topbar-dark .section-title {
color: var(--text-color-secondary);
opacity: 0.8;
}
.layout-wrapper.layout-topbar-dark .dropdown-item {
color: var(--text-color);
}
.layout-wrapper.layout-topbar-dark .dropdown-item:hover {
background-color: var(--surface-800, #2A2A2A);
}
.layout-wrapper.layout-topbar-dark .logout-item:hover {
background-color: rgba(211, 47, 47, 0.1) !important;
}
/* ----------------------------------------------------------------------------
RESPONSIVE DESIGN - Integration with Freya responsive patterns
---------------------------------------------------------------------------- */
@media (max-width: 991px) {
.layout-topbar .user-dropdown-menu {
left: 10px;
right: 10px;
position: fixed;
top: 62px;
max-width: none;
}
}
@media (max-width: 768px) {
.layout-topbar .user-dropdown-menu {
min-width: 260px;
max-width: 280px;
}
.user-dropdown-header {
padding: 1rem 0.75rem;
}
.dropdown-item {
padding: 0.625rem 0.75rem;
font-size: 0.8125rem;
}
.section-title {
padding: 0 0.75rem 0.5rem 0.75rem;
}
/* Hide user info on mobile */
.layout-topbar .user-info {
display: none;
}
.layout-topbar .user-profile-link {
padding: 0.5rem;
}
}
@media (max-width: 576px) {
.layout-topbar .user-dropdown-menu {
left: 8px;
right: 8px;
border-radius: 12px;
}
}
/* ----------------------------------------------------------------------------
ACCESSIBILITY ENHANCEMENTS
---------------------------------------------------------------------------- */
.dropdown-item:focus,
.user-profile-link:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.layout-topbar .user-profile-link,
.dropdown-item,
.user-dropdown-menu,
.user-avatar,
.item-arrow {
animation: none;
transition: none;
}
}
/* ----------------------------------------------------------------------------
UTILITY CLASSES - PrimeFlex integration
---------------------------------------------------------------------------- */
.layout-topbar .flex {
display: flex !important;
}
.layout-topbar .align-items-center {
align-items: center !important;
}
.layout-topbar .justify-content-center {
justify-content: center !important;
}
.layout-topbar .gap-2 {
gap: 0.5rem !important;
}
.layout-topbar .text-white {
color: white !important;
}
.layout-topbar .border-circle {
border-radius: 50% !important;
}
.layout-topbar .bg-primary {
background-color: var(--primary-color) !important;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
$primaryColor:lighten(#2170E7, 5%);
$primaryTextColor:#ffffff;
@import '../../sass/variables/layout/_layout_dark';
@import '../../sass/layout/_layout';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
$primaryColor:#2170E7;
$primaryTextColor:#ffffff;
@import '../../sass/variables/layout/_layout_light';
@import '../../sass/layout/_layout';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,9 @@
<svg width="17" height="20" viewBox="0 0 17 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H6.00019V3.82345L17 1.66667V6.66667L6.00019 8.82345V10.4901L17 8.33333V13.3333L6.00019 15.4901V20H0V0Z" fill="url(#paint0_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="3.33335" y1="3.08442e-08" x2="8.49995" y2="20" gradientUnits="userSpaceOnUse">
<stop stop-color="#297FFF"/>
<stop offset="1" stop-color="#7A0EE7"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 469 B

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="209px" height="60px" viewBox="0 0 209 60" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>logo-freya-white</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo-freya-white" fill="#FFFFFF" fill-rule="nonzero">
<polygon id="Path" points="0 0 15.8827941 0 15.8827941 10.1321425 45 4.41666667 45 17.6666667 15.8827941 23.3821425 15.8827941 27.7988092 45 22.0833333 45 35.3333333 15.8827941 41.0488092 15.8827941 53 0 53"></polygon>
<path d="M84,20.8302387 L77.2821159,20.8302387 L77.2821159,18.9204244 C77.2821159,17.4350133 78.1284635,16.5862069 79.6095718,16.5862069 L83.9471033,16.5862069 L83.9471033,9 L77.070529,9 C71.0403023,9 67.8664987,12.3421751 67.8664987,18.1246684 L67.8664987,20.8302387 L63,20.8302387 L63.0528967,28.5755968 L67.7607053,28.5755968 L67.7607053,49 L77.5994962,49 L77.5994962,28.5755968 L84,28.5755968 L84,20.8302387 Z" id="Path"></path>
<path d="M105.301136,21 C101.846591,21 99.3636364,21.9630996 97.3125,24.3173432 L95.7471591,21.1070111 L89,21.1070111 L89,50 L99.0397727,50 L99.0397727,35.3394834 C99.0397727,31.6476015 100.551136,30.095941 104.059659,30.095941 L108,30.095941 L108,21 L105.301136,21 Z" id="Path"></path>
<path d="M141,35.2034783 C141,26.6852174 134.662692,20 125.473595,20 C116.495741,20 110,26.5773913 110,35.4730435 C110,44.3686957 116.548552,51 125.473595,51 C132.603066,51 138.412266,46.8486957 140.41908,40.2173913 L130.596252,40.2173913 C129.698467,41.9426087 127.744463,42.9669565 125.473595,42.9669565 C122.357751,42.9669565 120.298126,41.2417391 119.664395,37.8991304 L140.841567,37.8991304 C140.947189,36.9826087 141,36.12 141,35.2034783 Z M125.473595,27.8713043 C128.431005,27.8713043 130.49063,29.4347826 131.335605,32.346087 L119.822828,32.346087 C120.614991,29.4347826 122.621806,27.8713043 125.473595,27.8713043 Z" id="Shape"></path>
<path d="M165.423077,21 L159.764423,36.6842105 L153.682692,21 L143,21 L154.793269,47.1052632 C153.471154,50.6315789 152.360577,51.6315789 148.605769,51.6315789 L145.855769,51.6315789 L145.855769,60 L149.240385,60 C156.644231,60 160.134615,56.8947368 163.995192,48.1578947 L176,21 L165.423077,21 Z" id="Path"></path>
<path d="M201.660066,20.8641115 L200.656766,23.1324042 C198.333333,21.1341463 195.323432,20 191.943894,20 C183.231023,20 177,26.4268293 177,35.445993 C177,44.5191638 183.231023,51 191.943894,51 C195.270627,51 198.227723,49.9198606 200.551155,47.9756098 L201.39604,50.0278746 L209,50.0278746 L209,20.8641115 L201.660066,20.8641115 Z M193.264026,42.1428571 C189.567657,42.1428571 186.874587,39.2804878 186.874587,35.445993 C186.874587,31.6655052 189.567657,28.8571429 193.264026,28.8571429 C196.960396,28.8571429 199.653465,31.6655052 199.653465,35.445993 C199.653465,39.2804878 196.960396,42.1428571 193.264026,42.1428571 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="446px" height="129px" viewBox="0 0 446 129" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>logo-freya</title>
<defs>
<linearGradient x1="28.4476672%" y1="1.54220833e-07%" x2="49.999861%" y2="100%" id="linearGradient-1">
<stop stop-color="#297FFF" offset="0%"></stop>
<stop stop-color="#7A0EE7" offset="100%"></stop>
</linearGradient>
<linearGradient x1="75.0238871%" y1="-21.5732317%" x2="40.5406822%" y2="100.001105%" id="linearGradient-2">
<stop stop-color="#297FFF" offset="0%"></stop>
<stop stop-color="#7616E8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="37.9032258%" y1="-69.125861%" x2="-21.2207927%" y2="100.001538%" id="linearGradient-3">
<stop stop-color="#297FFF" offset="0%"></stop>
<stop stop-color="#7616E8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-55.9426462%" y1="-59.1651756%" x2="-141.121522%" y2="95.4614408%" id="linearGradient-4">
<stop stop-color="#297FFF" offset="0%"></stop>
<stop stop-color="#7616E8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-89.5003908%" y1="-50.8316014%" x2="-145.407143%" y2="72.8756185%" id="linearGradient-5">
<stop stop-color="#297FFF" offset="0%"></stop>
<stop stop-color="#7616E8" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-319.994499%" y1="-50.1302194%" x2="-402.50275%" y2="91.589373%" id="linearGradient-6">
<stop stop-color="#297FFF" offset="0%"></stop>
<stop stop-color="#7616E8" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo-freya" fill-rule="nonzero">
<polygon id="Path" fill="url(#linearGradient-1)" points="0 0 33.8832941 0 33.8832941 21.793665 96 9.5 96 38 33.8832941 50.293665 33.8832941 59.793665 96 47.5 96 76 33.8832941 88.293665 33.8832941 114 0 114"></polygon>
<path d="M181,45.4350133 L166.604534,45.4350133 L166.604534,41.3289125 C166.604534,38.1352785 168.418136,36.3103448 171.59194,36.3103448 L180.88665,36.3103448 L180.88665,20 L166.151134,20 C153.229219,20 146.428212,27.1856764 146.428212,39.6180371 L146.428212,45.4350133 L136,45.4350133 L136.11335,62.0875332 L146.201511,62.0875332 L146.201511,106 L167.284635,106 L167.284635,62.0875332 L181,62.0875332 L181,45.4350133 Z" id="Path" fill="url(#linearGradient-2)"></path>
<path d="M225.318182,44 C218.045455,44 212.818182,46.0590406 208.5,51.0922509 L205.204545,44.2287823 L191,44.2287823 L191,106 L212.136364,106 L212.136364,74.6568266 C212.136364,66.7638376 215.318182,63.4464945 222.704545,63.4464945 L231,63.4464945 L231,44 L225.318182,44 Z" id="Path" fill="url(#linearGradient-3)"></path>
<path d="M304,73.8782609 C304,56.0173913 290.507666,42 270.943782,42 C251.829642,42 238,55.7913043 238,74.4434783 C238,93.0956522 251.942078,107 270.943782,107 C286.122658,107 298.49063,98.2956522 302.763203,84.3913043 L281.850085,84.3913043 C279.938671,88.0086957 275.778535,90.1565217 270.943782,90.1565217 C264.310051,90.1565217 259.925043,86.5391304 258.575809,79.5304348 L303.662692,79.5304348 C303.887564,77.6086957 304,75.8 304,73.8782609 Z M270.943782,58.5043478 C277.240204,58.5043478 281.625213,61.7826087 283.424191,67.8869565 L258.913118,67.8869565 C260.599659,61.7826087 264.872232,58.5043478 270.943782,58.5043478 Z" id="Shape" fill="url(#linearGradient-4)"></path>
<path d="M353.24359,44 L341.06891,78.1835358 L327.983974,44 L305,44 L330.373397,100.896086 C327.528846,108.581646 325.139423,110.761134 317.060897,110.761134 L311.144231,110.761134 L311.144231,129 L318.426282,129 C334.355769,129 341.865385,122.232119 350.171474,103.190283 L376,44 L353.24359,44 Z" id="Path" fill="url(#linearGradient-5)"></path>
<path d="M430.173267,43.8118467 L428.009901,48.5679443 C423,44.3780488 416.509901,42 409.222772,42 C390.435644,42 377,55.4756098 377,74.3867596 C377,93.4111498 390.435644,107 409.222772,107 C416.39604,107 422.772277,104.735192 427.782178,100.658537 L429.60396,104.961672 L446,104.961672 L446,43.8118467 L430.173267,43.8118467 Z M412.069307,88.4285714 C404.09901,88.4285714 398.292079,82.4268293 398.292079,74.3867596 C398.292079,66.4599303 404.09901,60.5714286 412.069307,60.5714286 C420.039604,60.5714286 425.846535,66.4599303 425.846535,74.3867596 C425.846535,82.4268293 420.039604,88.4285714 412.069307,88.4285714 Z" id="Shape" fill="url(#linearGradient-6)"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 493 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 273 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 825 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View File

@@ -0,0 +1,879 @@
/**
* PrimeFaces Freya Layout
*/
PrimeFaces.widget.Freya = PrimeFaces.widget.BaseWidget.extend({
init: function(cfg) {
this._super(cfg);
this.wrapper = $(document.body).children('.layout-wrapper');
var $this = this;
$(function() {
$this._init();
});
this.restoreMenuState();
this.expandedMenuitems = this.expandedMenuitems||[];
},
_init: function() {
this.contentWrapper = this.wrapper.children('.layout-main');
this.topbar = this.wrapper.find('.layout-topbar');
this.topbarItems = this.topbar.find('.layout-topbar-actions > li.topbar-item');
this.topbarLinks = this.topbarItems.children('a');
this.topbarSearchItemMenu = this.topbar.find('.search-item');
this.menuWrapper = this.wrapper.find('.menu-wrapper');
this.sidebarPin = this.menuWrapper.find('.sidebar-logo > .sidebar-pin');
this.menu = this.menuWrapper.find('.layout-menu');
this.menuButton = this.topbar.find('.menu-button');
this.menulinks = this.menu.find('a');
this.rightpanel = this.wrapper.find('.layout-rightpanel');
this.rightpanelButton = this.topbar.find('.layout-rightpanel-button');
this.rightpanelExitButton = this.rightpanel.find('.rightpanel-exit-button');
this.configButton = $('#layout-config-button');
this.configurator = this.wrapper.children('.layout-config');
this.bindEvents();
},
toggleClass: function(el, className) {
if (el.hasClass(className)) {
el.removeClass(className);
}
else {
el.addClass(className);
}
},
bindEvents: function() {
var $this = this;
this.bindTopbarEvents();
this.bindMenuEvents();
this.bindRightPanelEvents();
this.bindConfigEvents();
$(document.body).off('click.layoutBody').on('click.layoutBody', function() {
if (!$this.menuClick) {
$this.wrapper.removeClass('layout-sidebar-active layout-mobile-active');
$(document.body).removeClass('blocked-scroll');
if ($this.isHorizontal() || $this.isSlim()) {
$this.menu.find('.active-menuitem').removeClass('active-menuitem');
$this.menu.find('ul:visible').hide();
$this.menuActive = false;
}
}
if (!$this.topbarItemClicked) {
$this.removeTopbarClassFromAllItems(null, 'active-topmenuitem', $this.topbarItems.filter('.active-topmenuitem'));
}
if (!$this.rightpanelClicked) {
$this.wrapper.removeClass('layout-rightpanel-active');
}
if (!$this.configClicked && $this.configurator.hasClass('layout-config-active')) {
$this.configurator.removeClass('layout-config-active');
}
$this.horizontalMenuClick = false;
$this.topbarItemClicked = false;
$this.rightpanelClicked = false;
$this.menuClick = false;
$this.configClicked = false;
});
},
bindConfigEvents: function() {
var $this = this;
this.configButton.off('click.configbutton').on('click.configbutton', function(e) {
$this.configurator.toggleClass('layout-config-active');
$this.configClicked = true;
});
this.configurator.off('click.config').on('click.config', function() {
$this.configClicked = true;
});
},
bindMenuEvents: function() {
var $this = this;
this.menuButton.off('click.menu').on('click.menu', function(e) {
$this.menuClick = true;
if ($this.isMobile()) {
if ($this.wrapper.hasClass('layout-mobile-active')) {
$this.wrapper.removeClass('layout-mobile-active');
$(document.body).removeClass('blocked-scroll');
}
else {
$this.wrapper.addClass('layout-mobile-active');
$(document.body).addClass('blocked-scroll');
}
}
e.preventDefault();
});
this.menuWrapper.off('click.menuWrapper mouseenter.menuWrapper mouseleave.menuWrapper')
.on('click.menuWrapper', function() {
$this.menuClick = true;
})
.on('mouseenter.menuWrapper', function(e) {
if(!$this.wrapper.hasClass('layout-sidebar-static')) {
if($this.hideTimeout) {
clearTimeout($this.hideTimeout);
}
$this.menuWrapper.addClass('layout-sidebar-active');
}
if(!$this.wrapper.hasClass('layout-sidebar')) {
if($this.hideTimeout) {
clearTimeout($this.hideTimeout);
}
$this.menuWrapper.removeClass('layout-sidebar-active');
}
})
.on('mouseleave.menuWrapper', function(e) {
if(!$this.wrapper.hasClass('layout-sidebar-static')) {
$this.hideTimeout = setTimeout(function() {
$this.menuWrapper.removeClass('layout-sidebar-active');
}, $this.cfg.closeDelay);
}
});
this.sidebarPin.off('click.menuWrapper').on('click.menuWrapper', function(e) {
$this.wrapper.removeClass('layout-static-restore');
$this.wrapper.toggleClass('layout-static');
$this.saveMenuState();
e.preventDefault();
});
this.menulinks.off('click.menuWrapper').on('click.menuWrapper', function(e) {
var link = $(this),
item = link.parent(),
submenu = item.children('ul');
horizontal = $this.isHorizontal();
slim = $this.isSlim();
$this.menuClick = true;
if (horizontal) {
$this.horizontalMenuClick = true;
}
if(item.hasClass('active-menuitem')) {
if(submenu.length) {
$this.removeMenuitem(item.attr('id'));
item.removeClass('active-menuitem');
if(horizontal || slim) {
if(item.parent().is($this.jq)) {
$this.menuActive = false;
}
submenu.hide();
$this.removeMenuitem(item.attr('id'));
item.removeClass('active-menuitem');
}
else {
submenu.slideUp(function() {
$this.removeMenuitem(item.attr('id'));
item.removeClass('active-menuitem');
});
}
}
}
else {
$this.addMenuitem(item.attr('id'));
if(horizontal || slim) {
$this.deactivateItems(item.siblings());
item.addClass('active-menuitem');
$this.menuActive = true;
submenu.show();
}
else {
$this.deactivateItems(item.siblings(), true);
$this.activate(item);
}
}
if(submenu.length) {
e.preventDefault();
}
});
this.menu.find('> li').off('mouseenter.menu').on('mouseenter.menu', function(e) {
if ($this.isHorizontal() || $this.isSlim()) {
var item = $(this);
if(!item.hasClass('active-menuitem')) {
$this.menu.find('.active-menuitem').removeClass('active-menuitem');
$this.menu.find('ul:visible').hide();
if($this.menuActive) {
item.addClass('active-menuitem');
item.children('ul').show();
}
}
}
});
},
bindTopbarEvents: function() {
var $this = this;
this.topbarLinks.off('click.topbar').on('click.topbar', function(e) {
var link = $(this),
item = link.parent(),
submenu = item.children('ul');
if ($this.isMobile()) {
$this.removeTopbarClassFromAllItems(null, 'active-topmenuitem', $this.topbarItems.filter('.active-topmenuitem').not(item));
}
else {
$this.removeTopbarClassFromAllItems(item, 'active-topmenuitem');
}
$this.addTopbarClass(item, 'active-topmenuitem');
$this.topbarItemClicked = true;
if (submenu.length) {
e.preventDefault();
}
});
this.topbarSearchItemMenu.off('click.topbar').on('click.topbar', function(e) {
$this.topbarItemClicked = true;
});
},
bindRightPanelEvents: function() {
var $this = this;
var changeRightpanelState = function(e) {
this.toggleClass(this.wrapper, 'layout-rightpanel-active');
this.rightpanelClicked = true;
e.preventDefault();
};
this.rightpanelButton.off('click.rightpanel').on('click.rightpanel', changeRightpanelState.bind(this));
this.rightpanelExitButton.off('click.rightpanel').on('click.rightpanel', changeRightpanelState.bind(this));
this.rightpanel.off('click.rightpanel').on('click.rightpanel', function() {
$this.rightpanelClicked = true;
});
},
activate: function(item) {
var submenu = item.children('ul');
item.addClass('active-menuitem');
if(submenu.length) {
submenu.slideDown();
}
},
deactivate: function(item) {
var submenu = item.children('ul');
item.removeClass('active-menuitem');
if(submenu.length) {
submenu.hide();
}
},
deactivateItems: function(items, animate) {
var $this = this;
for(var i = 0; i < items.length; i++) {
var item = items.eq(i),
submenu = item.children('ul');
if(submenu.length) {
if(item.hasClass('active-menuitem')) {
var activeSubItems = item.find('.active-menuitem');
item.removeClass('active-menuitem');
if(animate) {
submenu.slideUp('normal', function() {
$(this).parent().find('.active-menuitem').each(function() {
$this.deactivate($(this));
});
});
}
else {
item.find('.active-menuitem').each(function() {
$this.deactivate($(this));
});
}
$this.removeMenuitem(item.attr('id'));
activeSubItems.each(function() {
$this.removeMenuitem($(this).attr('id'));
});
}
else {
item.find('.active-menuitem').each(function() {
var subItem = $(this);
$this.deactivate(subItem);
$this.removeMenuitem(subItem.attr('id'));
});
}
}
else if(item.hasClass('active-menuitem')) {
$this.deactivate(item);
$this.removeMenuitem(item.attr('id'));
}
}
},
removeMenuitem: function (id) {
this.expandedMenuitems = $.grep(this.expandedMenuitems, function (value) {
return value !== id;
});
this.saveMenuState();
},
addMenuitem: function (id) {
if ($.inArray(id, this.expandedMenuitems) === -1) {
this.expandedMenuitems.push(id);
}
this.saveMenuState();
},
saveMenuState: function() {
if(this.wrapper.hasClass('layout-static'))
$.cookie('freya_menu_static', 'freya_menu_static', {path: '/'});
else
$.removeCookie('freya_menu_static', {path: '/'});
$.cookie('freya_expandeditems', this.expandedMenuitems.join(','), {path: '/'});
},
clearMenuState: function() {
this.expandedMenuitems = [];
$.removeCookie('freya_expandeditems', {path: '/'});
$.removeCookie('freya_menu_static', {path: '/'});
},
clearActiveItems: function() {
var activeItems = this.jq.find('li.active-menuitem'),
subContainers = activeItems.children('ul');
activeItems.removeClass('active-menuitem');
if(subContainers && subContainers.length) {
subContainers.hide();
}
},
clearLayoutState: function() {
this.clearMenuState();
this.clearActiveItems();
},
restoreMenuState: function() {
var menuCookie = $.cookie('freya_expandeditems');
if (!this.isSlim() && !this.isHorizontal() && menuCookie) {
this.expandedMenuitems = menuCookie.split(',');
for (var i = 0; i < this.expandedMenuitems.length; i++) {
var id = this.expandedMenuitems[i];
if (id) {
var menuitem = $("#" + this.expandedMenuitems[i].replace(/:/g, "\\:"));
menuitem.addClass('active-menuitem');
var submenu = menuitem.children('ul');
if(submenu.length) {
submenu.show();
}
}
}
}
var sidebarCookie = $.cookie('freya_menu_static');
if(sidebarCookie) {
this.wrapper.addClass('layout-static');
}
},
removeTopbarClassFromAllItems: function(item, className, items) {
var activeItems = item != null ? item.siblings('.' + className) : items;
activeItems.removeClass(className);
activeItems.children('ul').removeClass('fadeInDown');
},
addTopbarClass: function(item, className) {
var submenu = item.children('ul');
if (submenu.length) {
if (item.hasClass(className)) {
submenu.removeClass('fadeInDown').addClass('fadeOutUp');
setTimeout(function() {
item.removeClass(className);
submenu.removeClass('fadeOutUp');
}, 100);
}
else {
item.addClass(className);
submenu.addClass('fadeInDown');
}
}
},
hideTopBar: function() {
var $this = this;
this.topbarMenu.addClass('fadeOutUp');
setTimeout(function() {
$this.topbarMenu.removeClass('fadeOutUp topbar-menu-visible');
},500);
},
isMobile: function() {
return window.innerWidth < 992;
},
isHorizontal: function() {
return this.wrapper.hasClass('layout-horizontal') && !this.isMobile();
},
isSlim: function() {
return this.wrapper.hasClass('layout-slim') && !this.isMobile();
},
isStatic: function() {
return this.wrapper.hasClass('layout-static') && !this.isMobile();
}
});
PrimeFaces.FreyaConfigurator = {
changeLayout: function( componentTheme, darkMode ) {
this.changeLayoutsTheme(darkMode);
this.changeDemo(darkMode);
this.changeComponentsTheme(componentTheme, darkMode);
this.changeSectionTheme( darkMode, 'layout-menu');
this.changeSectionTheme( darkMode , 'layout-topbar');
},
changeLayoutsTheme: function(darkMode) {
newLayout = '-' + darkMode;
var linkElement = $('link[href*="layout-"]');
var href = linkElement.attr('href');
var startIndexOf = href.indexOf('layout-') + 6;
var endIndexOf = href.indexOf('.css');
var currentColor = href.substring(startIndexOf, endIndexOf);
this.replaceLink(linkElement, href.replace(currentColor, newLayout));
},
changeDemo: function(darkMode) {
newLayout = '-' + darkMode;
var linkElement = $('link[href*="demo-"]');
var href = linkElement.attr('href');
var startIndexOf = href.indexOf('demo-') + 4;
var endIndexOf = href.indexOf('.css');
var currentColor = href.substring(startIndexOf, endIndexOf);
this.replaceLink(linkElement, href.replace(currentColor, newLayout));
},
changeComponentsTheme: function(themeColor, darkMode) {
theme = this.getColor(themeColor, darkMode);
var library = 'primefaces-freya';
var linkElement = $('link[href*="theme.css"]');
var href = linkElement.attr('href');
var index = href.indexOf(library) + 1;
var currentTheme = href.substring(index + library.length);
this.replaceLink(linkElement, href.replace(currentTheme, theme));
},
changeSectionTheme: function(theme, section) {
var wrapperElement = $('.layout-wrapper');
var styleClass = wrapperElement.attr('class');
var tokens = styleClass.split(' ');
var sectionClass;
for (var i = 0; i < tokens.length; i++) {
if (tokens[i].indexOf(section + '-') > -1) {
sectionClass = tokens[i];
break;
}
}
wrapperElement.attr('class', styleClass.replace(sectionClass, section + '-' + theme));
},
changeMenuMode: function(menuMode) {
var wrapper = $(document.body).children('.layout-wrapper');
switch (menuMode) {
case 'layout-sidebar':
wrapper.addClass('layout-sidebar').removeClass('layout-slim layout-horizontal ');
this.clearLayoutState();
break;
case 'layout-horizontal':
wrapper.addClass('layout-horizontal').removeClass('layout-static layout-slim layout-sidebar');
this.clearLayoutState();
break;
case 'layout-slim':
wrapper.addClass('layout-slim').removeClass('layout-static layout-horizontal layout-sidebar');
this.clearLayoutState();
break;
default:
wrapper.addClass('layout-sidebar').removeClass('layout-slim layout-horizontal ');
this.clearLayoutState();
break;
}
},
beforeResourceChange: function() {
PrimeFaces.ajax.RESOURCE = null; //prevent resource append
},
replaceLink: function(linkElement, href) {
PrimeFaces.ajax.RESOURCE = 'javax.faces.Resource';
var isIE = this.isIE();
if (isIE) {
linkElement.attr('href', href);
}
else {
var cloneLinkElement = linkElement.clone(false);
cloneLinkElement.attr('href', href);
linkElement.after(cloneLinkElement);
cloneLinkElement.off('load').on('load', function() {
linkElement.remove();
});
// for dashboard
setTimeout(function() {
if (window['redrawChart']) {
window.redrawChart();
}
}, 100);
}
},
getColor: function(name, darkMode) {
return name + '-' + darkMode;
},
isIE: function() {
return /(MSIE|Trident\/|Edge\/)/i.test(navigator.userAgent);
},
clearLayoutState: function() {
var menu = PF('FreyaMenuWidget');
if (menu) {
menu.clearLayoutState();
}
},
updateInputStyle: function(value) {
if (value === 'filled')
$(document.body).addClass('ui-input-filled');
else
$(document.body).removeClass('ui-input-filled');
}
};
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2006, 2014 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD (Register as an anonymous module)
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch (e) { }
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (arguments.length > 1 && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setMilliseconds(t.getMilliseconds() + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {},
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
cookies = document.cookie ? document.cookie.split('; ') : [],
i = 0,
l = cookies.length;
for (; i < l; i++) {
var parts = cookies[i].split('='),
name = decode(parts.shift()),
cookie = parts.join('=');
if (key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));
if (PrimeFaces.widget.InputSwitch) {
PrimeFaces.widget.InputSwitch = PrimeFaces.widget.InputSwitch.extend({
init: function (cfg) {
this._super(cfg);
if (this.input.prop('checked')) {
this.jq.addClass('ui-inputswitch-checked');
}
},
check: function () {
var $this = this;
this.input.prop('checked', true).trigger('change');
setTimeout(function () {
$this.jq.addClass('ui-inputswitch-checked');
}, 100);
},
uncheck: function () {
var $this = this;
this.input.prop('checked', false).trigger('change');
setTimeout(function () {
$this.jq.removeClass('ui-inputswitch-checked');
}, 100);
}
});
}
if (PrimeFaces.widget.AccordionPanel) {
PrimeFaces.widget.AccordionPanel = PrimeFaces.widget.AccordionPanel.extend({
init: function (cfg) {
this._super(cfg);
this.headers.last().addClass('ui-accordion-header-last');
}
});
}
/* Issue #924 is fixed for 5.3+ and 6.0. (compatibility with 5.3) */
if(window['PrimeFaces'] && window['PrimeFaces'].widget.Dialog) {
PrimeFaces.widget.Dialog = PrimeFaces.widget.Dialog.extend({
enableModality: function() {
this._super();
$(document.body).children(this.jqId + '_modal').addClass('ui-dialog-mask');
},
syncWindowResize: function() {}
});
}
if (PrimeFaces.widget.SelectOneMenu) {
PrimeFaces.widget.SelectOneMenu = PrimeFaces.widget.SelectOneMenu.extend({
init: function (cfg) {
this._super(cfg);
var $this = this;
if (this.jq.parent().hasClass('ui-float-label')) {
this.m_panel = $(this.jqId + '_panel');
this.m_focusInput = $(this.jqId + '_focus');
this.m_panel.addClass('ui-input-overlay-panel');
this.jq.addClass('ui-inputwrapper');
if (this.input.val() != '') {
this.jq.addClass('ui-inputwrapper-filled');
}
this.input.off('change').on('change', function () {
$this.inputValueControl($(this));
});
this.m_focusInput.on('focus.ui-selectonemenu', function () {
$this.jq.addClass('ui-inputwrapper-focus');
})
.on('blur.ui-selectonemenu', function () {
$this.jq.removeClass('ui-inputwrapper-focus');
});
if (this.cfg.editable) {
this.label.on('input', function (e) {
$this.inputValueControl($(this));
}).on('focus', function () {
$this.jq.addClass('ui-inputwrapper-focus');
}).on('blur', function () {
$this.jq.removeClass('ui-inputwrapper-focus');
$this.inputValueControl($(this));
});
}
}
},
inputValueControl: function (input) {
if (input.val() != '')
this.jq.addClass('ui-inputwrapper-filled');
else
this.jq.removeClass('ui-inputwrapper-filled');
}
});
}
if (PrimeFaces.widget.Chips) {
PrimeFaces.widget.Chips = PrimeFaces.widget.Chips.extend({
init: function (cfg) {
this._super(cfg);
var $this = this;
if (this.jq.parent().hasClass('ui-float-label')) {
this.jq.addClass('ui-inputwrapper');
if ($this.jq.find('.ui-chips-token').length !== 0) {
this.jq.addClass('ui-inputwrapper-filled');
}
this.input.on('focus.ui-chips', function () {
$this.jq.addClass('ui-inputwrapper-focus');
}).on('input.ui-chips', function () {
$this.inputValueControl();
}).on('blur.ui-chips', function () {
$this.jq.removeClass('ui-inputwrapper-focus');
$this.inputValueControl();
});
}
},
inputValueControl: function () {
if (this.jq.find('.ui-chips-token').length !== 0 || this.input.val() != '')
this.jq.addClass('ui-inputwrapper-filled');
else
this.jq.removeClass('ui-inputwrapper-filled');
}
});
}
if (PrimeFaces.widget.DatePicker) {
PrimeFaces.widget.DatePicker = PrimeFaces.widget.DatePicker.extend({
init: function (cfg) {
this._super(cfg);
var $this = this;
if (this.jq.parent().hasClass('ui-float-label') && !this.cfg.inline) {
if (this.input.val() != '') {
this.jq.addClass('ui-inputwrapper-filled');
}
this.jqEl.off('focus.ui-datepicker blur.ui-datepicker change.ui-datepicker')
.on('focus.ui-datepicker', function () {
$this.jq.addClass('ui-inputwrapper-focus');
})
.on('blur.ui-datepicker', function () {
$this.jq.removeClass('ui-inputwrapper-focus');
})
.on('change.ui-datepicker', function () {
$this.inputValueControl($(this));
});
}
},
inputValueControl: function (input) {
if (input.val() != '')
this.jq.addClass('ui-inputwrapper-filled');
else
this.jq.removeClass('ui-inputwrapper-filled');
}
});
}

File diff suppressed because one or more lines are too long

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>

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html 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"
lang="fr">
<h:head>
<f:facet name="first">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<meta name="mobile-web-app-capable" content="yes" />
<link rel="icon" href="#{request.contextPath}/resources/freya-layout/images/favicon.ico" type="image/x-icon"></link>
</f:facet>
<title><ui:insert name="title">Lions User Manager</ui:insert></title>
<h:outputScript name="js/layout.js" library="freya-layout" />
<h:outputScript name="js/prism.js" library="freya-layout"/>
<ui:insert name="head"/>
</h:head>
<h:body styleClass="#{guestPreferences.inputStyleClass}">
<div class="layout-wrapper layout-topbar-#{guestPreferences.topbarTheme} layout-menu-#{guestPreferences.menuTheme} #{guestPreferences.menuMode}" >
<ui:include src="/templates/components/layout/topbar.xhtml"/>
<div class="layout-main">
<div class="layout-content">
<p:messages id="messages" showDetail="true" closable="true" />
<ui:insert name="content"/>
</div>
<ui:include src="/templates/components/layout/footer.xhtml"/>
</div>
<p:ajaxStatus style="width:32px;height:32px;position:fixed;right:7px;bottom:7px">
<f:facet name="start">
<i class="pi pi-spin pi-spinner ajax-loader" aria-hidden="true"/>
</f:facet>
<f:facet name="complete">
<h:outputText value="" />
</f:facet>
</p:ajaxStatus>
<div class="layout-mask modal-in"></div>
</div>
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
<h:outputStylesheet name="css/layout-#{guestPreferences.layout}.css" library="freya-layout" />
<h:outputStylesheet name="primefaces-freya-#{guestPreferences.componentTheme}/theme.css" />
<h:outputStylesheet name="css/custom-topbar.css" />
</h:body>
</html>