Refactoring

This commit is contained in:
dahoud
2026-03-01 22:00:28 +00:00
parent c0e2c4da45
commit 6b28cf751e
469 changed files with 26866 additions and 14768 deletions

View File

@@ -17,6 +17,11 @@
<exception-handler-factory>
dev.lions.unionflow.client.exception.ViewExpiredExceptionHandlerFactory
</exception-handler-factory>
<!-- ApplicationFactory personnalisé pour configurer l'ELResolverBuilder -->
<application-factory>
dev.lions.unionflow.client.config.QuarkusApplicationFactory
</application-factory>
</factory>
<application>
@@ -25,6 +30,10 @@
<supported-locale>fr</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<!-- ELResolver personnalisé pour Quarkus Arc -->
<!-- Ce resolver remplace le resolver CDI par défaut qui n'est pas supporté par Arc -->
<el-resolver>dev.lions.unionflow.client.el.QuarkusArcELResolver</el-resolver>
</application>
<navigation-rule>

View File

@@ -53,12 +53,18 @@
</ul>
</div>
<div class="landing-topbar-right">
<ui:include src="/templates/components/buttons/button-primary.xhtml">
<ui:param name="value" value="Accéder" />
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="outcome" value="/pages/secure/dashboard" />
<ui:param name="styleClass" value="landing-button" />
</ui:include>
<a href="#{request.contextPath}/pages/secure/dashboard.xhtml"
style="display:inline-flex;align-items:center;gap:.45rem;
padding:.5rem 1.25rem;border-radius:6px;
border:2px solid rgba(255,255,255,.75);
color:#fff;font-size:.875rem;font-weight:700;
text-decoration:none;letter-spacing:.02em;
transition:background .2s,border-color .2s;"
onmouseover="this.style.background='rgba(255,255,255,.18)';this.style.borderColor='#fff'"
onmouseout="this.style.background='transparent';this.style.borderColor='rgba(255,255,255,.75)'">
Accéder
<i class="pi pi-arrow-right" style="font-size:.78rem;"></i>
</a>
<a href="#" id="landing-menu-button">
<i class="pi pi-bars"> </i>
</a>
@@ -71,12 +77,20 @@
<span class="title">UnionFlow</span>
<h3>Plateforme de Gestion Intégrée pour Mutuelles, Associations et Clubs<br/>
Simplifiez la gestion de votre organisation avec une solution complète et moderne</h3>
<ui:include src="/templates/components/buttons/button-primary.xhtml">
<ui:param name="value" value="Accéder à la plateforme" />
<ui:param name="icon" value="pi pi-sign-in" />
<ui:param name="outcome" value="/pages/secure/dashboard" />
<ui:param name="styleClass" value="landing-button" />
</ui:include>
<a href="#{request.contextPath}/pages/secure/dashboard.xhtml"
style="display:inline-flex;align-items:center;gap:.6rem;
padding:1rem 2.5rem;border-radius:8px;
background:#fff;
color:#1e1e2e;
font-size:1.05rem;font-weight:800;
text-decoration:none;letter-spacing:.01em;
box-shadow:0 6px 24px rgba(0,0,0,.25);
transition:transform .25s,box-shadow .25s;"
onmouseover="this.style.transform='translateY(-3px)';this.style.boxShadow='0 12px 36px rgba(0,0,0,.3)'"
onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='0 6px 24px rgba(0,0,0,.25)'">
<i class="pi pi-sign-in" style="font-size:.95rem;"></i>
Accéder à la plateforme
</a>
</div>
</div>
@@ -175,57 +189,246 @@
<div id="benefits" class="landing-pricing">
<div class="section-header">
<span class="title">Pourquoi choisir UnionFlow ?</span>
<h3>Une solution pensée pour les mutuelles, associations, clubs et organisations similaires avec sécurité avancée, multi-plateforme et synchronisation temps réel.</h3>
<h3>Une plateforme robuste et moderne bâtie pour les organisations du monde entier —<br/>
avec une sensibilité particulière pour les réalités africaines.</h3>
</div>
<!-- Grille des avantages -->
<div class="grid">
<div class="col-12 lg:col-4">
<div class="pricing-card">
<h2>Sécurité</h2>
<span class="price">100%</span>
<span class="time">Sécurisé</span>
<ul>
<li>Connexion sécurisée et centralisée</li>
<li>Contrôle d'accès basé sur les rôles</li>
<li>Protection des données sensibles</li>
<li>Chiffrement des communications</li>
<!-- Sécurité de niveau entreprise -->
<div class="col-12 md:col-6 lg:col-4 flex">
<div style="background:var(--surface-0);border:1px solid var(--surface-200);border-radius:14px;padding:2rem;box-shadow:0 2px 10px rgba(0,0,0,.06);transition:box-shadow .25s,transform .25s;width:100%;"
onmouseover="this.style.boxShadow='0 10px 30px rgba(0,0,0,.12)';this.style.transform='translateY(-3px)'"
onmouseout="this.style.boxShadow='0 2px 10px rgba(0,0,0,.06)';this.style.transform='translateY(0)'">
<div style="width:3rem;height:3rem;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;background:var(--blue-50);">
<i class="pi pi-shield" style="color:var(--blue-600);font-size:1.25rem;"></i>
</div>
<h3 style="color:var(--text-color);font-size:1.1rem;font-weight:700;margin:0 0 .75rem 0;">
Sécurité de niveau entreprise
</h3>
<p style="color:var(--text-color-secondary);line-height:1.7;margin:0 0 1.25rem 0;font-size:.9rem;">
Authentification centralisée SSO, gestion des accès par rôle et traçabilité
complète de toutes les actions sur la plateforme.
</p>
<ul style="list-style:none;padding:0;margin:0;">
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
OpenID Connect via Keycloak
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Contrôle d'accès granulaire par rôle
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Chiffrement de bout en bout
</li>
</ul>
</div>
</div>
<div class="col-12 lg:col-4 preferred">
<div class="pricing-card pro">
<span class="preferred-tag">RECOMMANDÉ</span>
<h2>Multi-Plateforme</h2>
<span class="price">24/7</span>
<span class="time">Disponible</span>
<ul>
<li>Application web responsive</li>
<li>Application mobile Flutter</li>
<li>iOS et Android</li>
<li>Accès depuis n'importe où</li>
<!-- Multi-plateforme -->
<div class="col-12 md:col-6 lg:col-4 flex">
<div style="background:var(--surface-0);border:1px solid var(--surface-200);border-radius:14px;padding:2rem;box-shadow:0 2px 10px rgba(0,0,0,.06);transition:box-shadow .25s,transform .25s;width:100%;"
onmouseover="this.style.boxShadow='0 10px 30px rgba(0,0,0,.12)';this.style.transform='translateY(-3px)'"
onmouseout="this.style.boxShadow='0 2px 10px rgba(0,0,0,.06)';this.style.transform='translateY(0)'">
<div style="width:3rem;height:3rem;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;background:var(--green-50);">
<i class="pi pi-mobile" style="color:var(--green-600);font-size:1.25rem;"></i>
</div>
<h3 style="color:var(--text-color);font-size:1.1rem;font-weight:700;margin:0 0 .75rem 0;">
Accessible partout, tout le temps
</h3>
<p style="color:var(--text-color-secondary);line-height:1.7;margin:0 0 1.25rem 0;font-size:.9rem;">
Une application web responsive et une application mobile native pour
que vos gestionnaires et membres restent connectés en toutes circonstances.
</p>
<ul style="list-style:none;padding:0;margin:0;">
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Web responsive (tous navigateurs)
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Application mobile iOS &amp; Android
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Disponible 24h/24, 7j/7
</li>
</ul>
</div>
</div>
<div class="col-12 lg:col-4">
<div class="pricing-card enterprise">
<h2>Cloud</h2>
<span class="price">Cloud</span>
<span class="time">Moderne</span>
<ul>
<li>Architecture cloud-native</li>
<li>Synchronisation temps réel</li>
<li>Sauvegarde automatique</li>
<li>Scalabilité illimitée</li>
<!-- Cloud-Native -->
<div class="col-12 md:col-6 lg:col-4 flex">
<div style="background:var(--surface-0);border:1px solid var(--surface-200);border-radius:14px;padding:2rem;box-shadow:0 2px 10px rgba(0,0,0,.06);transition:box-shadow .25s,transform .25s;width:100%;"
onmouseover="this.style.boxShadow='0 10px 30px rgba(0,0,0,.12)';this.style.transform='translateY(-3px)'"
onmouseout="this.style.boxShadow='0 2px 10px rgba(0,0,0,.06)';this.style.transform='translateY(0)'">
<div style="width:3rem;height:3rem;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;background:var(--purple-50);">
<i class="pi pi-server" style="color:var(--purple-600);font-size:1.25rem;"></i>
</div>
<h3 style="color:var(--text-color);font-size:1.1rem;font-weight:700;margin:0 0 .75rem 0;">
Architecture cloud-native
</h3>
<p style="color:var(--text-color-secondary);line-height:1.7;margin:0 0 1.25rem 0;font-size:.9rem;">
Bâtie sur Quarkus et une architecture microservices, la plateforme monte en
charge automatiquement et garantit une haute disponibilité.
</p>
<ul style="list-style:none;padding:0;margin:0;">
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Microservices Quarkus haute performance
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Scalabilité élastique
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Sauvegardes automatisées
</li>
</ul>
</div>
</div>
<!-- Pilotage par la donnée -->
<div class="col-12 md:col-6 lg:col-4 flex">
<div style="background:var(--surface-0);border:1px solid var(--surface-200);border-radius:14px;padding:2rem;box-shadow:0 2px 10px rgba(0,0,0,.06);transition:box-shadow .25s,transform .25s;width:100%;"
onmouseover="this.style.boxShadow='0 10px 30px rgba(0,0,0,.12)';this.style.transform='translateY(-3px)'"
onmouseout="this.style.boxShadow='0 2px 10px rgba(0,0,0,.06)';this.style.transform='translateY(0)'">
<div style="width:3rem;height:3rem;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;background:var(--orange-50);">
<i class="pi pi-chart-bar" style="color:var(--orange-600);font-size:1.25rem;"></i>
</div>
<h3 style="color:var(--text-color);font-size:1.1rem;font-weight:700;margin:0 0 .75rem 0;">
Pilotage par la donnée
</h3>
<p style="color:var(--text-color-secondary);line-height:1.7;margin:0 0 1.25rem 0;font-size:.9rem;">
Des tableaux de bord interactifs et des KPIs en temps réel pour prendre
les bonnes décisions au bon moment.
</p>
<ul style="list-style:none;padding:0;margin:0;">
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Dashboards et KPIs temps réel
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Rapports exportables (PDF, Excel)
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Analyse des tendances et historiques
</li>
</ul>
</div>
</div>
<!-- Adapté à votre contexte — carte mise en avant -->
<div class="col-12 md:col-6 lg:col-4 flex">
<div style="background:linear-gradient(135deg,var(--primary-600) 0%,var(--primary-800) 100%);border-radius:14px;padding:2rem;box-shadow:0 8px 24px rgba(0,0,0,.18);transition:box-shadow .25s,transform .25s;width:100%;position:relative;overflow:hidden;"
onmouseover="this.style.transform='translateY(-3px)'"
onmouseout="this.style.transform='translateY(0)'">
<div style="position:absolute;top:-20px;right:-20px;width:100px;height:100px;border-radius:50%;background:rgba(255,255,255,.07);"></div>
<div style="position:absolute;bottom:-30px;right:20px;width:60px;height:60px;border-radius:50%;background:rgba(255,255,255,.05);"></div>
<div style="width:3rem;height:3rem;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;background:rgba(255,255,255,.15);">
<i class="pi pi-globe" style="color:#fff;font-size:1.25rem;"></i>
</div>
<h3 style="color:#fff;font-size:1.1rem;font-weight:700;margin:0 0 .75rem 0;">
Conçu pour les organisations du monde
</h3>
<p style="color:rgba(255,255,255,.82);line-height:1.7;margin:0 0 1.25rem 0;font-size:.9rem;">
Mutuelles, clubs Lions, associations sportives, fédérations professionnelles —
UnionFlow s'adapte à toute structure, sur tous les continents.
</p>
<ul style="list-style:none;padding:0;margin:0;">
<li style="display:flex;align-items:center;gap:.5rem;color:rgba(255,255,255,.82);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:rgba(255,255,255,.9);"></i>
Mutuelles &amp; caisses de solidarité
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:rgba(255,255,255,.82);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:rgba(255,255,255,.9);"></i>
Clubs Lions, Rotary, associations civiques
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:rgba(255,255,255,.82);font-size:.85rem;">
<i class="pi pi-check" style="color:rgba(255,255,255,.9);"></i>
Fédérations &amp; réseaux multi-niveaux
</li>
</ul>
</div>
</div>
<!-- Déploiement flexible -->
<div class="col-12 md:col-6 lg:col-4 flex">
<div style="background:var(--surface-0);border:1px solid var(--surface-200);border-radius:14px;padding:2rem;box-shadow:0 2px 10px rgba(0,0,0,.06);transition:box-shadow .25s,transform .25s;width:100%;"
onmouseover="this.style.boxShadow='0 10px 30px rgba(0,0,0,.12)';this.style.transform='translateY(-3px)'"
onmouseout="this.style.boxShadow='0 2px 10px rgba(0,0,0,.06)';this.style.transform='translateY(0)'">
<div style="width:3rem;height:3rem;border-radius:10px;display:flex;align-items:center;justify-content:center;margin-bottom:1.25rem;background:var(--indigo-50);">
<i class="pi pi-cog" style="color:var(--indigo-600);font-size:1.25rem;"></i>
</div>
<h3 style="color:var(--text-color);font-size:1.1rem;font-weight:700;margin:0 0 .75rem 0;">
Déploiement selon vos contraintes
</h3>
<p style="color:var(--text-color-secondary);line-height:1.7;margin:0 0 1.25rem 0;font-size:.9rem;">
Hébergez la plateforme chez vous ou laissez-nous la gérer. L'API ouverte
s'intègre facilement à vos outils existants.
</p>
<ul style="list-style:none;padding:0;margin:0;">
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
SaaS cloud géré ou on-premise
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);margin-bottom:.4rem;font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
API REST ouverte &amp; documentée
</li>
<li style="display:flex;align-items:center;gap:.5rem;color:var(--text-color-secondary);font-size:.85rem;">
<i class="pi pi-check" style="color:var(--green-500);"></i>
Évolutions et support continus
</li>
</ul>
</div>
</div>
</div>
<!-- Bande de chiffres clés -->
<div style="background:linear-gradient(135deg,var(--primary-700),var(--primary-900));border-radius:16px;padding:2.5rem 2rem;margin-top:3rem;">
<div class="grid text-center" style="color:#fff;">
<div class="col-6 md:col-3" style="padding:1rem;">
<div style="font-size:2.5rem;font-weight:800;line-height:1;">6+</div>
<div style="font-size:.85rem;opacity:.8;margin-top:.5rem;line-height:1.4;">Types d'organisations<br/>supportés</div>
</div>
<div class="col-6 md:col-3" style="padding:1rem;">
<div style="font-size:2.5rem;font-weight:800;line-height:1;">100%</div>
<div style="font-size:.85rem;opacity:.8;margin-top:.5rem;line-height:1.4;">Données sécurisées<br/>et chiffrées</div>
</div>
<div class="col-6 md:col-3" style="padding:1rem;">
<div style="font-size:2.5rem;font-weight:800;line-height:1;">24/7</div>
<div style="font-size:.85rem;opacity:.8;margin-top:.5rem;line-height:1.4;">Disponibilité<br/>garantie</div>
</div>
<div class="col-6 md:col-3" style="padding:1rem;">
<div style="font-size:2.5rem;font-weight:800;line-height:1;"></div>
<div style="font-size:.85rem;opacity:.8;margin-top:.5rem;line-height:1.4;">Scalabilité<br/>cloud-native</div>
</div>
</div>
</div>
<div class="text-center mt-5">
<ui:include src="/templates/components/buttons/button-primary.xhtml">
<ui:param name="value" value="Découvrir toutes les fonctionnalités" />
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="outcome" value="/pages/secure/dashboard" />
<ui:param name="styleClass" value="landing-button" />
</ui:include>
<a href="#{request.contextPath}/pages/secure/dashboard.xhtml"
style="display:inline-flex;align-items:center;gap:.6rem;
padding:.95rem 2.25rem;border-radius:8px;
background:var(--primary-color,#6366f1);
color:var(--primary-color-text,#fff);
font-size:1rem;font-weight:700;
text-decoration:none;letter-spacing:.02em;
box-shadow:0 4px 18px rgba(99,102,241,.35);
transition:transform .25s,box-shadow .25s,opacity .2s;"
onmouseover="this.style.transform='translateY(-3px)';this.style.boxShadow='0 10px 30px rgba(99,102,241,.5)'"
onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='0 4px 18px rgba(99,102,241,.35)'">
Accéder à la plateforme
<i class="pi pi-arrow-right" style="font-size:.9rem;"></i>
</a>
</div>
</div>
@@ -240,16 +443,16 @@
<li><a href="#home">Accueil</a></li>
<li><a href="#features">Fonctionnalités</a></li>
<li><a href="#benefits">Avantages</a></li>
<li><a href="/pages/secure/dashboard">Tableau de Bord</a></li>
<li><a href="/pages/secure/dashboard.xhtml">Tableau de Bord</a></li>
</ul>
</div>
<div class="col-6">
<span class="footer-menutitle">FONCTIONNALITÉS</span>
<ul>
<li><a href="/pages/secure/membre/liste">Membres</a></li>
<li><a href="/pages/secure/cotisation/historique">Cotisations</a></li>
<li><a href="/pages/secure/evenement/calendrier">Événements</a></li>
<li><a href="/pages/secure/aide/documentation">Aide</a></li>
<li><a href="/pages/secure/membre/liste.xhtml">Membres</a></li>
<li><a href="/pages/secure/cotisation/historique.xhtml">Cotisations</a></li>
<li><a href="/pages/secure/evenement/calendrier.xhtml">Événements</a></li>
<li><a href="/pages/secure/aide/documentation.xhtml">Aide</a></li>
</ul>
</div>
</div>
@@ -258,22 +461,34 @@
<span class="footer-menutitle">CONTACT</span>
<ul>
<li>support@unionflow.dev</li>
<li>Abidjan, Côte d'Ivoire</li>
<li>Afrique &amp; International</li>
<li>Plateforme de Gestion Intégrée</li>
</ul>
</div>
<div class="col-12 md:col-6 lg:col-5">
<span class="footer-menutitle">NEWSLETTER</span>
<span class="footer-subtitle">Rejoignez notre newsletter pour être informé des nouvelles fonctionnalités.</span>
<h:form>
<div class="newsletter-input">
<p:inputText placeholder="adresse email" />
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="S'abonner" />
<ui:param name="outlined" value="false" />
</ui:include>
</div>
</h:form>
<div class="newsletter-input">
<input type="email"
placeholder="adresse email"
style="flex:1;padding:.6rem 1rem;border-radius:6px;
border:1px solid rgba(255,255,255,.25);
background:rgba(255,255,255,.1);
color:#fff;font-size:.875rem;outline:none;"
onfocus="this.style.borderColor='rgba(255,255,255,.6)'"
onblur="this.style.borderColor='rgba(255,255,255,.25)'" />
<button type="button"
style="display:inline-flex;align-items:center;gap:.4rem;
padding:.6rem 1.25rem;border-radius:6px;
background:#fff;color:#1e1e2e;
font-size:.875rem;font-weight:700;
border:none;cursor:pointer;white-space:nowrap;
transition:opacity .2s;"
onmouseover="this.style.opacity='.88'"
onmouseout="this.style.opacity='1'">
S'abonner
</button>
</div>
</div>
<div class="col-12">
<div class="footer-bottom">

View File

@@ -1,398 +0,0 @@
<!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">Gestion des Utilisateurs - UnionFlow</ui:define>
<ui:define name="content">
<div class="ui-fluid">
<!-- En-tête avec disposition Freya stricte -->
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4">
<div class="formgrid grid">
<div class="field col-12 lg:col-8">
<h2 class="text-primary font-bold mb-2">
<i class="pi pi-users text-blue-500 mr-2"></i>
Gestion des Utilisateurs
</h2>
<p class="text-600 mt-0">Administration des comptes et permissions utilisateurs</p>
</div>
<div class="field col-12 lg:col-4 text-right">
<h:form id="formActionsEntete">
<div class="formgrid grid">
<div class="field col-12 md:col-4">
<p:commandButton value="Nouvel Utilisateur"
icon="pi pi-user-plus"
styleClass="ui-button-success ui-button-sm w-full"
onclick="PF('dlgNouvelUtilisateur').show();" />
</div>
<div class="field col-12 md:col-4">
<p:commandButton value="Importer"
icon="pi pi-upload"
styleClass="ui-button-info ui-button-outlined ui-button-sm w-full"
onclick="PF('dlgImporterUtilisateurs').show();" />
</div>
<div class="field col-12 md:col-4">
<p:commandButton value="Exporter"
icon="pi pi-download"
styleClass="ui-button-secondary ui-button-outlined ui-button-sm w-full"
action="#{utilisateursBean.exporterUtilisateurs}" />
</div>
</div>
</h:form>
</div>
</div>
</div>
</div>
<!-- Statistiques utilisateurs avec Freya stricte -->
<div class="formgrid grid">
<div class="field col-12 md:col-6 lg:col-3">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4" style="min-height: 9rem;">
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">Total Utilisateurs</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 pi-users text-blue-600 text-lg"></i>
</div>
</div>
<div class="text-900 font-bold text-2xl mb-3">#{utilisateursBean.statistiques.totalUtilisateurs}</div>
<div class="flex align-items-center mb-2">
<div class="bg-blue-500 border-circle mr-2" style="width: 8px; height: 8px;"></div>
<span class="text-500 text-xs">Comptes actifs</span>
</div>
</div>
</div>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4" style="min-height: 9rem;">
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">Connectés</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 pi-circle-fill text-green-600 text-lg"></i>
</div>
</div>
<div class="text-900 font-bold text-2xl mb-3">#{utilisateursBean.statistiques.utilisateursConnectes}</div>
<div class="flex align-items-center mb-2">
<div class="bg-green-500 border-circle mr-2" style="width: 8px; height: 8px;"></div>
<span class="text-500 text-xs">En ligne maintenant</span>
</div>
</div>
</div>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4" style="min-height: 9rem;">
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">Administrateurs</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 pi-shield text-orange-600 text-lg"></i>
</div>
</div>
<div class="text-900 font-bold text-2xl mb-3">#{utilisateursBean.statistiques.administrateurs}</div>
<div class="flex align-items-center mb-2">
<div class="bg-orange-500 border-circle mr-2" style="width: 8px; height: 8px;"></div>
<span class="text-500 text-xs">Privilèges élevés</span>
</div>
</div>
</div>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4" style="min-height: 9rem;">
<div class="flex align-items-center justify-content-between mb-3">
<span class="block text-600 font-medium text-sm">Désactivés</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 pi-ban text-red-600 text-lg"></i>
</div>
</div>
<div class="text-900 font-bold text-2xl mb-3">#{utilisateursBean.statistiques.utilisateursDesactives}</div>
<div class="flex align-items-center mb-2">
<div class="bg-red-500 border-circle mr-2" style="width: 8px; height: 8px;"></div>
<span class="text-500 text-xs">Comptes suspendus</span>
</div>
</div>
</div>
</div>
</div>
<!-- Filtres et recherche avec Freya stricte -->
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4">
<h5 class="text-900 font-bold mb-4">
<i class="pi pi-search text-blue-500 mr-2"></i>
Recherche et Filtres
</h5>
<h:form id="formFiltres">
<div class="ui-fluid">
<div class="formgrid grid">
<div class="field col-12 md:col-4">
<p:outputLabel for="searchNom" value="Nom ou Email" />
<p:inputText id="searchNom" value="#{utilisateursBean.filtres.recherche}"
placeholder="Rechercher par nom ou email...">
<p:ajax event="keyup" update="dtUtilisateurs" />
</p:inputText>
</div>
<div class="field col-12 md:col-2">
<p:outputLabel for="filterRole" value="Rôle" />
<p:selectOneMenu id="filterRole" value="#{utilisateursBean.filtres.role}">
<f:selectItem itemLabel="Tous les rôles" itemValue="" />
<f:selectItem itemLabel="Super Admin" itemValue="SUPER_ADMIN" />
<f:selectItem itemLabel="Admin" itemValue="ADMIN" />
<f:selectItem itemLabel="Gestionnaire" itemValue="GESTIONNAIRE" />
<f:selectItem itemLabel="Utilisateur" itemValue="USER" />
<p:ajax update="dtUtilisateurs" />
</p:selectOneMenu>
</div>
<div class="field col-12 md:col-2">
<p:outputLabel for="filterStatut" value="Statut" />
<p:selectOneMenu id="filterStatut" value="#{utilisateursBean.filtres.statut}">
<f:selectItem itemLabel="Tous statuts" itemValue="" />
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
<f:selectItem itemLabel="Inactif" itemValue="INACTIF" />
<f:selectItem itemLabel="Suspendu" itemValue="SUSPENDU" />
<f:selectItem itemLabel="En attente" itemValue="ATTENTE" />
<p:ajax update="dtUtilisateurs" />
</p:selectOneMenu>
</div>
<div class="field col-12 md:col-2">
<p:outputLabel for="filterConnexion" value="Dernière connexion" />
<p:selectOneMenu id="filterConnexion" value="#{utilisateursBean.filtres.connexion}">
<f:selectItem itemLabel="Toutes" itemValue="" />
<f:selectItem itemLabel="Aujourd'hui" itemValue="AUJOURD_HUI" />
<f:selectItem itemLabel="Cette semaine" itemValue="SEMAINE" />
<f:selectItem itemLabel="Ce mois" itemValue="MOIS" />
<f:selectItem itemLabel="Plus de 30 jours" itemValue="PLUS_30_JOURS" />
<p:ajax update="dtUtilisateurs" />
</p:selectOneMenu>
</div>
<div class="field col-12 md:col-2">
<p:outputLabel for="filterOrganisation" value="Organisation" />
<p:selectOneMenu id="filterOrganisation" value="#{utilisateursBean.filtres.organisation}">
<f:selectItem itemLabel="Toutes" itemValue="" />
<f:selectItems value="#{utilisateursBean.organisationsDisponibles}"
var="org"
itemLabel="#{org.nom}"
itemValue="#{org.id}" />
<p:ajax update="dtUtilisateurs" />
</p:selectOneMenu>
</div>
</div>
<div class="flex gap-2 mt-3">
<p:commandButton value="Rechercher"
icon="pi pi-search"
styleClass="ui-button-primary ui-button-sm"
action="#{utilisateursBean.rechercher}"
update="dtUtilisateurs" />
<p:commandButton value="Réinitialiser"
icon="pi pi-refresh"
styleClass="ui-button-secondary ui-button-outlined ui-button-sm"
action="#{utilisateursBean.reinitialiserFiltres}"
update="@form dtUtilisateurs" />
</div>
</div>
</h:form>
</div>
</div>
<!-- Liste des utilisateurs avec Freya stricte -->
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
<div class="p-4">
<div class="flex align-items-center justify-content-between mb-4">
<h5 class="text-900 font-bold m-0">
<i class="pi pi-users text-blue-500 mr-2"></i>
Utilisateurs (#{utilisateursBean.utilisateursFiltres.size()})
</h5>
<div class="flex align-items-center gap-2">
<h:form id="formActionsGroupees">
<p:commandButton value="Actions groupées"
icon="pi pi-cog"
styleClass="ui-button-outlined ui-button-warning"
onclick="PF('dlgActionsGroupees').show();"
disabled="#{empty utilisateursBean.utilisateursSelectionnes}" />
</h:form>
</div>
</div>
<p:dataTable id="dtUtilisateurs"
value="#{utilisateursBean.utilisateursFiltres}"
var="utilisateur"
selection="#{utilisateursBean.utilisateursSelectionnes}"
rowKey="#{utilisateur.id}"
paginator="true"
rows="15"
paginatorPosition="both"
sortMode="single"
styleClass="p-datatable-sm"
emptyMessage="Aucun utilisateur trouvé">
<p:column selectionMode="multiple" width="40" />
<p:column headerText="Utilisateur" sortBy="#{utilisateur.nom}" width="300">
<div class="flex align-items-center">
<div class="surface-200 border-circle flex align-items-center justify-content-center mr-3"
style="width: 40px; height: 40px;">
<i class="pi pi-user text-600"></i>
</div>
<div>
<div class="text-900 font-medium">#{utilisateur.nomComplet}</div>
<div class="text-600 text-sm">#{utilisateur.email}</div>
</div>
</div>
</p:column>
<p:column headerText="Rôle" sortBy="#{utilisateur.role}" width="120">
<p:tag value="#{utilisateur.roleLibelle}" severity="#{utilisateur.roleSeverity}" />
</p:column>
<p:column headerText="Organisation" sortBy="#{utilisateur.organisation}" width="150">
<span class="text-900">#{utilisateur.organisationNom}</span>
</p:column>
<p:column headerText="Statut" sortBy="#{utilisateur.statut}" width="100">
<p:tag value="#{utilisateur.statutLibelle}" severity="#{utilisateur.statutSeverity}" />
</p:column>
<p:column headerText="Dernière connexion" sortBy="#{utilisateur.derniereConnexion}" width="140">
<div class="text-900 text-sm">#{utilisateur.derniereConnexionFormatee}</div>
<div class="text-600 text-xs">#{utilisateur.derniereConnexionRelative}</div>
</p:column>
<p:column headerText="Date création" sortBy="#{utilisateur.dateCreation}" width="120">
<span class="text-900 text-sm">#{utilisateur.dateCreationFormatee}</span>
</p:column>
<p:column headerText="Actions" width="150">
<h:form id="formActions#{utilisateur.id}">
<div class="flex gap-1">
<p:commandButton icon="pi pi-eye"
styleClass="ui-button-rounded ui-button-text ui-button-info"
title="Voir profil"
onclick="PF('dlgVoirUtilisateur').show();">
<f:setPropertyActionListener target="#{utilisateursBean.utilisateurSelectionne}" value="#{utilisateur}" />
</p:commandButton>
<p:commandButton icon="pi pi-pencil"
styleClass="ui-button-rounded ui-button-text ui-button-warning"
title="Modifier"
onclick="PF('dlgModifierUtilisateur').show();">
<f:setPropertyActionListener target="#{utilisateursBean.utilisateurSelectionne}" value="#{utilisateur}" />
</p:commandButton>
<p:commandButton icon="pi pi-key"
styleClass="ui-button-rounded ui-button-text ui-button-secondary"
title="Gérer permissions"
onclick="PF('dlgPermissions').show();">
<f:setPropertyActionListener target="#{utilisateursBean.utilisateurSelectionne}" value="#{utilisateur}" />
</p:commandButton>
<p:commandButton icon="pi pi-ban"
styleClass="ui-button-rounded ui-button-text ui-button-danger"
title="Désactiver"
onclick="return confirm('Êtes-vous sûr de vouloir désactiver cet utilisateur ?');"
action="#{utilisateursBean.desactiverUtilisateur(utilisateur)}"
rendered="#{utilisateur.statut != 'INACTIF'}" />
<p:commandButton icon="pi pi-check"
styleClass="ui-button-rounded ui-button-text ui-button-success"
title="Activer"
action="#{utilisateursBean.activerUtilisateur(utilisateur)}"
rendered="#{utilisateur.statut == 'INACTIF'}" />
</div>
</h:form>
</p:column>
</p:dataTable>
</div>
</div>
<!-- Dialog Nouvel Utilisateur avec Freya stricte -->
<p:dialog header="Nouvel Utilisateur" widgetVar="dlgNouvelUtilisateur" modal="true" width="600">
<h:form id="formNouvelUtilisateur">
<div class="ui-fluid">
<div class="formgrid grid">
<div class="field col-12 md:col-6">
<p:outputLabel for="newNom" value="Nom" />
<p:inputText id="newNom" value="#{utilisateursBean.nouvelUtilisateur.nom}"
required="true" placeholder="Nom de famille" />
</div>
<div class="field col-12 md:col-6">
<p:outputLabel for="newPrenom" value="Prénom" />
<p:inputText id="newPrenom" value="#{utilisateursBean.nouvelUtilisateur.prenom}"
required="true" placeholder="Prénom" />
</div>
<div class="field col-12 md:col-6">
<p:outputLabel for="newEmail" value="Email" />
<p:inputText id="newEmail" value="#{utilisateursBean.nouvelUtilisateur.email}"
required="true" placeholder="adresse@email.com" />
</div>
<div class="field col-12 md:col-6">
<p:outputLabel for="newTelephone" value="Téléphone" />
<p:inputText id="newTelephone" value="#{utilisateursBean.nouvelUtilisateur.telephone}"
placeholder="+225 XX XX XX XX" />
</div>
<div class="field col-12 md:col-6">
<p:outputLabel for="newRole" value="Rôle" />
<p:selectOneMenu id="newRole" value="#{utilisateursBean.nouvelUtilisateur.role}" required="true">
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
<f:selectItem itemLabel="👤 Utilisateur" itemValue="USER" />
<f:selectItem itemLabel="⚙️ Gestionnaire" itemValue="GESTIONNAIRE" />
<f:selectItem itemLabel="🛠️ Administrateur" itemValue="ADMIN" />
<f:selectItem itemLabel="🛡️ Super Admin" itemValue="SUPER_ADMIN" />
</p:selectOneMenu>
</div>
<div class="field col-12 md:col-6">
<p:outputLabel for="newOrganisation" value="Organisation" />
<p:selectOneMenu id="newOrganisation" value="#{utilisateursBean.nouvelUtilisateur.organisationId}">
<f:selectItem itemLabel="Aucune organisation" itemValue="" />
<f:selectItems value="#{utilisateursBean.organisationsDisponibles}"
var="org"
itemLabel="#{org.nom}"
itemValue="#{org.id}" />
</p:selectOneMenu>
</div>
<div class="field col-12">
<p:outputLabel for="newMotDePasse" value="Mot de passe temporaire" />
<p:password id="newMotDePasse" value="#{utilisateursBean.nouvelUtilisateur.motDePasse}"
required="true" placeholder="Mot de passe temporaire" />
</div>
<div class="field col-12">
<div class="flex align-items-center">
<p:selectBooleanCheckbox id="newEnvoyerEmail" value="#{utilisateursBean.nouvelUtilisateur.envoyerEmail}" />
<p:outputLabel for="newEnvoyerEmail" value="Envoyer les informations de connexion par email" styleClass="ml-2" />
</div>
</div>
</div>
</div>
<div class="flex gap-2 mt-3">
<p:commandButton value="Créer l'utilisateur" icon="pi pi-check"
styleClass="ui-button-success"
action="#{utilisateursBean.creerUtilisateur}"
update="@form dtUtilisateurs"
oncomplete="if(!args.validationFailed) PF('dlgNouvelUtilisateur').hide();" />
<p:commandButton value="Annuler" icon="pi pi-times"
styleClass="ui-button-secondary"
onclick="PF('dlgNouvelUtilisateur').hide();" type="button" />
</div>
</h:form>
</p:dialog>
</div>
</ui:define>
</ui:composition>

View File

@@ -209,13 +209,16 @@
</div>
<div class="field">
<p:outputLabel for="montantPaye" value="Montant à payer (FCFA)" />
<p:inputNumber id="montantPaye"
value="#{adhesionsBean.adhesionSelectionnee.montantPaye}"
<p:outputLabel for="montantPaiementPartiel" value="Montant à payer (FCFA)" />
<p:inputNumber id="montantPaiementPartiel"
value="#{adhesionsBean.montantPaiementPartiel}"
symbol=""
minValue="0"
maxValue="#{adhesionsBean.adhesionSelectionnee.fraisAdhesion}"
styleClass="w-full" />
maxValue="#{adhesionsBean.adhesionSelectionnee.montantRestant}"
styleClass="w-full"
required="true"
requiredMessage="Veuillez saisir le montant du paiement partiel" />
<p:message for="montantPaiementPartiel" />
</div>
<ui:include src="/templates/components/forms/form-field-select.xhtml">
@@ -248,7 +251,7 @@
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Enregistrer" />
<ui:param name="icon" value="pi pi-check" />
<ui:param name="action" value="#{adhesionsBean.enregistrerPaiement(adhesionsBean.adhesionSelectionnee.montantPaye, adhesionsBean.adhesionSelectionnee.methodePaiement, adhesionsBean.adhesionSelectionnee.referencePaiement)}" />
<ui:param name="action" value="#{adhesionsBean.enregistrerPaiementPartiel}" />
<ui:param name="update" value="@form :formPaiements" />
<ui:param name="oncomplete" value="PF('dlgPaiementPartiel').hide();" />
</ui:include>

View File

@@ -80,7 +80,7 @@
<p:column headerText="Date" sortBy="#{sauvegarde.date}">
<h:outputText value="#{sauvegarde.date}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm"/>
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime"/>
</h:outputText>
</p:column>

View File

@@ -56,7 +56,7 @@
<p:column headerText="Date approbation" sortBy="#{demande.dateDemande}">
<h:outputText value="#{demande.dateDemande}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>

View File

@@ -115,7 +115,7 @@
<p:column headerText="Date" sortBy="#{demande.dateDemande}">
<h:outputText value="#{demande.dateDemande}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>

View File

@@ -62,7 +62,7 @@
<p:column headerText="Date" sortBy="#{demande.dateDemande}">
<h:outputText value="#{demande.dateDemande}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>

View File

@@ -6,7 +6,7 @@
xmlns:p="http://primefaces.org/ui"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{demandesAideBean}"/>
<ui:param name="page" value="#{suggestionBean}"/>
<ui:define name="title">Suggestions et Feedback - UnionFlow</ui:define>
<ui:define name="content">
@@ -30,7 +30,8 @@
<p:commandButton value="Nouvelle Suggestion"
styleClass="p-button-primary"
icon="pi pi-plus"
onclick="PF('nouvelleSuggestionDialog').show()" />
action="#{suggestionBean.ouvrirDialogNouvelleSuggestion}"
update="@form" />
<p:commandButton value="Mes Suggestions"
styleClass="p-button-outlined"
icon="pi pi-user" />
@@ -41,28 +42,28 @@
<div class="grid">
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-blue-500 mb-2">247</div>
<div class="text-2xl font-bold text-blue-500 mb-2">#{suggestionBean.totalSuggestions}</div>
<div class="text-900 font-semibold mb-1">Suggestions</div>
<div class="text-600 text-sm">Soumises</div>
</div>
</div>
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-green-500 mb-2">43</div>
<div class="text-2xl font-bold text-green-500 mb-2">#{suggestionBean.suggestionsImplementees}</div>
<div class="text-900 font-semibold mb-1">Implémentées</div>
<div class="text-600 text-sm">Dans la v2.0</div>
</div>
</div>
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-purple-500 mb-2">1,523</div>
<div class="text-2xl font-bold text-purple-500 mb-2">#{suggestionBean.totalVotes}</div>
<div class="text-900 font-semibold mb-1">Votes</div>
<div class="text-600 text-sm">Ce mois-ci</div>
</div>
</div>
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-orange-500 mb-2">156</div>
<div class="text-2xl font-bold text-orange-500 mb-2">#{suggestionBean.contributeursActifs}</div>
<div class="text-900 font-semibold mb-1">Contributeurs</div>
<div class="text-600 text-sm">Actifs</div>
</div>
@@ -388,41 +389,48 @@
header="Soumettre une Nouvelle Suggestion"
modal="true"
width="800"
styleClass="surface-0">
styleClass="surface-0"
visible="#{suggestionBean.afficherDialogNouvelleSuggestion}">
<h:form id="nouvelleSuggestionForm">
<div class="ui-fluid">
<div class="formgrid grid">
<div class="field col-12 lg:col-6">
<label for="categorieSugg" class="block text-900 font-semibold mb-2">Catégorie *</label>
<p:selectOneMenu id="categorieSugg" styleClass="w-full">
<p:selectOneMenu id="categorieSugg"
value="#{suggestionBean.nouvelleSuggestion.categorie}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionnez une catégorie" itemValue="" />
<f:selectItem itemLabel="Interface Utilisateur" itemValue="ui" />
<f:selectItem itemLabel="Nouvelle Fonctionnalité" itemValue="feature" />
<f:selectItem itemLabel="Amélioration Performance" itemValue="performance" />
<f:selectItem itemLabel="Sécurité" itemValue="securite" />
<f:selectItem itemLabel="Intégration Externe" itemValue="integration" />
<f:selectItem itemLabel="Application Mobile" itemValue="mobile" />
<f:selectItem itemLabel="Rapports et Analytics" itemValue="reporting" />
<f:selectItem itemLabel="Interface Utilisateur" itemValue="UI" />
<f:selectItem itemLabel="Nouvelle Fonctionnalité" itemValue="FEATURE" />
<f:selectItem itemLabel="Amélioration Performance" itemValue="PERFORMANCE" />
<f:selectItem itemLabel="Sécurité" itemValue="SECURITE" />
<f:selectItem itemLabel="Intégration Externe" itemValue="INTEGRATION" />
<f:selectItem itemLabel="Application Mobile" itemValue="MOBILE" />
<f:selectItem itemLabel="Rapports et Analytics" itemValue="REPORTING" />
</p:selectOneMenu>
</div>
<div class="field col-12 lg:col-6">
<label for="prioriteSugg" class="block text-900 font-semibold mb-2">Priorité estimée</label>
<p:selectOneMenu id="prioriteSugg" styleClass="w-full">
<f:selectItem itemLabel="Basse" itemValue="basse" />
<f:selectItem itemLabel="Moyenne" itemValue="moyenne" />
<f:selectItem itemLabel="Haute" itemValue="haute" />
<f:selectItem itemLabel="Critique" itemValue="critique" />
<p:selectOneMenu id="prioriteSugg"
value="#{suggestionBean.nouvelleSuggestion.prioriteEstimee}"
styleClass="w-full">
<f:selectItem itemLabel="Basse" itemValue="BASSE" />
<f:selectItem itemLabel="Moyenne" itemValue="MOYENNE" />
<f:selectItem itemLabel="Haute" itemValue="HAUTE" />
<f:selectItem itemLabel="Critique" itemValue="CRITIQUE" />
</p:selectOneMenu>
</div>
<div class="field col-12">
<label for="titreSugg" class="block text-900 font-semibold mb-2">Titre de votre suggestion *</label>
<p:inputText id="titreSugg"
value="#{suggestionBean.nouvelleSuggestion.titre}"
placeholder="Résumez votre idée en une phrase claire"
styleClass="w-full" />
</div>
<div class="field col-12">
<label for="descriptionSugg" class="block text-900 font-semibold mb-2">Description détaillée *</label>
<p:inputTextarea id="descriptionSugg"
value="#{suggestionBean.nouvelleSuggestion.description}"
rows="6"
placeholder="Décrivez votre suggestion : problème rencontré, solution proposée, bénéfices attendus..."
styleClass="w-full" />
@@ -430,6 +438,7 @@
<div class="field col-12">
<label for="justificationSugg" class="block text-900 font-semibold mb-2">Justification métier</label>
<p:inputTextarea id="justificationSugg"
value="#{suggestionBean.nouvelleSuggestion.justification}"
rows="3"
placeholder="Expliquez pourquoi cette fonctionnalité serait utile et pour quels utilisateurs..."
styleClass="w-full" />
@@ -447,11 +456,14 @@
<div class="flex justify-content-end gap-2 mt-4">
<p:commandButton value="Annuler"
styleClass="p-button-outlined"
onclick="PF('nouvelleSuggestionDialog').hide()"
action="#{suggestionBean.fermerDialogNouvelleSuggestion}"
update="@form"
type="button" />
<p:commandButton value="Soumettre la Suggestion"
styleClass="p-button-primary"
icon="pi pi-send" />
icon="pi pi-send"
action="#{suggestionBean.creerSuggestion}"
update="@form" />
</div>
</div>
</h:form>

View File

@@ -6,7 +6,7 @@
xmlns:p="http://primefaces.org/ui"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{demandesAideBean}"/>
<ui:param name="page" value="#{ticketBean}"/>
<ui:define name="title">Mes Tickets Support - UnionFlow</ui:define>
<ui:define name="content">
@@ -30,7 +30,8 @@
<p:commandButton value="Nouveau Ticket"
styleClass="p-button-primary"
icon="pi pi-plus"
onclick="PF('nouveauTicketDialog').show()" />
action="#{ticketBean.ouvrirDialogNouveauTicket}"
update="@form" />
<p:commandButton value="FAQ"
styleClass="p-button-outlined"
icon="pi pi-question-circle" />
@@ -41,28 +42,28 @@
<div class="grid">
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-blue-500 mb-2">12</div>
<div class="text-2xl font-bold text-blue-500 mb-2">#{ticketBean.totalTickets}</div>
<div class="text-900 font-semibold mb-1">Tickets Créés</div>
<div class="text-600 text-sm">Au total</div>
</div>
</div>
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-orange-500 mb-2">3</div>
<div class="text-2xl font-bold text-orange-500 mb-2">#{ticketBean.ticketsEnAttente}</div>
<div class="text-900 font-semibold mb-1">En Attente</div>
<div class="text-600 text-sm">Réponse support</div>
</div>
</div>
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-green-500 mb-2">8</div>
<div class="text-2xl font-bold text-green-500 mb-2">#{ticketBean.ticketsResolus}</div>
<div class="text-900 font-semibold mb-1">Résolus</div>
<div class="text-600 text-sm">Avec succès</div>
</div>
</div>
<div class="col-12 lg:col-3">
<div class="surface-100 border-round p-4 text-center" style="min-height: 9rem">
<div class="text-2xl font-bold text-red-500 mb-2">1</div>
<div class="text-2xl font-bold text-red-500 mb-2">#{ticketBean.ticketsFermes}</div>
<div class="text-900 font-semibold mb-1">Fermé</div>
<div class="text-600 text-sm">Sans résolution</div>
</div>
@@ -130,191 +131,84 @@
Historique de vos Tickets
</h4>
<!-- Ticket 1 - En cours -->
<div class="surface-100 hover:surface-200 border-round p-4 mb-3 cursor-pointer transition-duration-200">
<ui:repeat value="#{ticketBean.tickets}" var="ticket">
<!-- Ticket dynamique -->
<div class="surface-100 hover:surface-200 border-round p-4 mb-3 cursor-pointer transition-duration-200 #{ticket.statut == 'FERME' ? 'opacity-70' : ''}">
<div class="flex align-items-center justify-content-between mb-3">
<div class="flex align-items-center gap-3">
<div class="w-3rem h-3rem border-circle bg-orange-100 flex align-items-center justify-content-center">
<i class="pi pi-clock text-orange-600 text-xl"></i>
<div class="w-3rem h-3rem border-circle #{ticket.statut == 'RESOLU' ? 'bg-green-100' : (ticket.statut == 'EN_ATTENTE' ? 'bg-blue-100' : (ticket.statut == 'FERME' ? 'bg-red-100' : 'bg-orange-100'))} flex align-items-center justify-content-center">
<i class="pi #{ticket.statut == 'RESOLU' ? 'pi-check' : (ticket.statut == 'EN_ATTENTE' ? 'pi-pause' : (ticket.statut == 'FERME' ? 'pi-times' : 'pi-clock'))} #{ticket.statut == 'RESOLU' ? 'text-green-600' : (ticket.statut == 'EN_ATTENTE' ? 'text-blue-600' : (ticket.statut == 'FERME' ? 'text-red-600' : 'text-orange-600'))} text-xl"></i>
</div>
<div>
<h6 class="text-900 font-semibold mb-1">#TK-2024-0157 - Problème d'export Excel</h6>
<h6 class="text-900 font-semibold mb-1">#{ticket.numeroTicket} - #{ticket.sujet}</h6>
<div class="flex align-items-center gap-2 mb-1">
<p:tag value="EN COURS" severity="warning" styleClass="text-xs" />
<p:tag value="HAUTE" severity="danger" styleClass="text-xs" />
<p:tag value="TECHNIQUE" severity="info" styleClass="text-xs" />
<p:tag value="#{ticket.statut}"
severity="#{ticket.statut == 'RESOLU' ? 'success' : (ticket.statut == 'FERME' ? 'danger' : (ticket.statut == 'EN_ATTENTE' ? 'info' : 'warning'))}"
styleClass="text-xs" />
<p:tag value="#{ticket.priorite}"
severity="#{ticket.priorite == 'URGENTE' or ticket.priorite == 'HAUTE' ? 'danger' : (ticket.priorite == 'NORMALE' ? 'success' : 'secondary')}"
styleClass="text-xs" />
<p:tag value="#{ticket.categorie}"
severity="info"
styleClass="text-xs" />
</div>
<p class="text-600 text-sm mb-0">Créé le 15 janvier 2024 • Dernière réponse il y a 2h</p>
<p class="text-600 text-sm mb-0">
Créé le #{ticket.dateCreation != null ? ticket.dateCreation : 'N/A'}
#{ticket.dateDerniereReponse != null ? ' • Dernière réponse ' + ticket.dateDerniereReponse : ''}
</p>
</div>
</div>
<div class="text-right">
<div class="text-600 text-sm mb-2">Agent: Marie Dubois</div>
#{ticket.agentNom != null ? '<div class="text-600 text-sm mb-2">Agent: ' + ticket.agentNom + '</div>' : ''}
<p:commandButton value="Voir Détails"
styleClass="p-button-outlined p-button-sm"
icon="pi pi-eye" />
icon="pi pi-eye"
action="#{ticketBean.voirDetails}"
update="@form">
<f:setPropertyActionListener target="#{ticketBean.ticketSelectionne}" value="#{ticket}" />
</p:commandButton>
</div>
</div>
<p class="text-700 line-height-3 mb-3">
Impossible d'exporter la liste des membres en format Excel. Le fichier généré est corrompu
et ne s'ouvre pas dans Excel. Cela concerne tous les exports depuis la version 2.1.
#{ticket.description != null ? ticket.description : 'Aucune description'}
</p>
#{ticket.resolution != null ? '<div class="surface-green-50 border-left-3 border-green-500 p-3 mb-3"><p class="text-green-700 text-sm mb-0"><i class="pi pi-check-circle mr-2"></i><strong>Résolution:</strong> ' + ticket.resolution + '</p></div>' : ''}
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-comments text-blue-500"></i>
<span class="text-600 text-sm">5 messages</span>
<i class="pi pi-paperclip text-600 ml-3"></i>
<span class="text-600 text-sm">2 fichiers</span>
</div>
<div class="flex align-items-center gap-2">
<i class="pi pi-clock text-600"></i>
<span class="text-600 text-sm">SLA: 4h restantes</span>
</div>
</div>
</div>
<!-- Ticket 2 - En attente -->
<div class="surface-100 hover:surface-200 border-round p-4 mb-3 cursor-pointer transition-duration-200">
<div class="flex align-items-center justify-content-between mb-3">
<div class="flex align-items-center gap-3">
<div class="w-3rem h-3rem border-circle bg-blue-100 flex align-items-center justify-content-center">
<i class="pi pi-pause text-blue-600 text-xl"></i>
</div>
<div>
<h6 class="text-900 font-semibold mb-1">#TK-2024-0143 - Demande de formation personnalisée</h6>
<div class="flex align-items-center gap-2 mb-1">
<p:tag value="EN ATTENTE" severity="info" styleClass="text-xs" />
<p:tag value="NORMALE" severity="success" styleClass="text-xs" />
<p:tag value="FONCTIONNALITÉ" severity="help" styleClass="text-xs" />
</div>
<p class="text-600 text-sm mb-0">Créé le 12 janvier 2024 • En attente de votre réponse</p>
</div>
</div>
<div class="text-right">
<div class="text-600 text-sm mb-2">Agent: Thomas Martin</div>
<p:commandButton value="Répondre"
styleClass="p-button-primary p-button-sm"
icon="pi pi-reply" />
</div>
</div>
<p class="text-700 line-height-3 mb-3">
Souhaitons organiser une formation sur mesure pour notre équipe administrative.
Besoin de devis pour 15 personnes sur 2 jours.
</p>
<div class="surface-blue-50 border-left-3 border-blue-500 p-3 mb-3">
<p class="text-blue-700 text-sm mb-0">
<i class="pi pi-info-circle mr-2"></i>
<strong>Action requise:</strong> Merci de préciser vos disponibilités pour les dates proposées.
</p>
</div>
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-comments text-blue-500"></i>
<span class="text-600 text-sm">3 messages</span>
</div>
<div class="flex align-items-center gap-2">
<i class="pi pi-exclamation-triangle text-orange-500"></i>
<span class="text-orange-600 text-sm">Réponse attendue depuis 3 jours</span>
</div>
</div>
</div>
<!-- Ticket 3 - Résolu -->
<div class="surface-100 hover:surface-200 border-round p-4 mb-3 cursor-pointer transition-duration-200">
<div class="flex align-items-center justify-content-between mb-3">
<div class="flex align-items-center gap-3">
<div class="w-3rem h-3rem border-circle bg-green-100 flex align-items-center justify-content-center">
<i class="pi pi-check text-green-600 text-xl"></i>
</div>
<div>
<h6 class="text-900 font-semibold mb-1">#TK-2024-0128 - Problème de connexion mobile</h6>
<div class="flex align-items-center gap-2 mb-1">
<p:tag value="RÉSOLU" severity="success" styleClass="text-xs" />
<p:tag value="BASSE" severity="secondary" styleClass="text-xs" />
<p:tag value="TECHNIQUE" severity="info" styleClass="text-xs" />
</div>
<p class="text-600 text-sm mb-0">Créé le 8 janvier 2024 • Résolu le 10 janvier 2024</p>
</div>
</div>
<div class="text-right">
<div class="text-600 text-sm mb-2">Agent: Sophie Leroy</div>
<div class="flex gap-2">
<p:commandButton value="Noter"
styleClass="p-button-outlined p-button-sm"
icon="pi pi-star" />
<p:commandButton value="Détails"
styleClass="p-button-outlined p-button-sm"
icon="pi pi-eye" />
</div>
</div>
</div>
<p class="text-700 line-height-3 mb-3">
Application ne se charge pas sur smartphone Android. Écran blanc après connexion.
</p>
<div class="surface-green-50 border-left-3 border-green-500 p-3 mb-3">
<p class="text-green-700 text-sm mb-0">
<i class="pi pi-check-circle mr-2"></i>
<strong>Résolution:</strong> Problème résolu en vidant le cache de l'application mobile.
</p>
</div>
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-comments text-blue-500"></i>
<span class="text-600 text-sm">6 messages</span>
<i class="pi pi-clock text-600 ml-3"></i>
<span class="text-600 text-sm">Résolu en 2 jours</span>
</div>
<div class="flex align-items-center gap-1">
<i class="pi pi-star-fill text-yellow-400"></i>
<span class="text-600 text-sm">Note: 5/5</span>
</div>
</div>
</div>
<!-- Ticket 4 - Fermé -->
<div class="surface-100 hover:surface-200 border-round p-4 cursor-pointer transition-duration-200 opacity-70">
<div class="flex align-items-center justify-content-between mb-3">
<div class="flex align-items-center gap-3">
<div class="w-3rem h-3rem border-circle bg-red-100 flex align-items-center justify-content-center">
<i class="pi pi-times text-red-600 text-xl"></i>
</div>
<div>
<h6 class="text-900 font-semibold mb-1">#TK-2024-0095 - Demande modification base</h6>
<div class="flex align-items-center gap-2 mb-1">
<p:tag value="FERMÉ" severity="danger" styleClass="text-xs" />
<p:tag value="HAUTE" severity="danger" styleClass="text-xs" />
<p:tag value="FONCTIONNALITÉ" severity="help" styleClass="text-xs" />
</div>
<p class="text-600 text-sm mb-0">Créé le 28 décembre 2023 • Fermé le 5 janvier 2024</p>
</div>
</div>
<div class="text-right">
<div class="text-600 text-sm mb-2">Agent: Marc Durand</div>
<p:commandButton value="Rouvrir"
styleClass="p-button-outlined p-button-sm"
icon="pi pi-replay" />
</div>
</div>
<p class="text-700 line-height-3 mb-3">
Demande de modification des champs de la base de données membres pour ajouter
des informations métier spécifiques.
</p>
<div class="surface-red-50 border-left-3 border-red-500 p-3 mb-3">
<p class="text-red-700 text-sm mb-0">
<i class="pi pi-times-circle mr-2"></i>
<strong>Fermé:</strong> Demande non compatible avec l'architecture actuelle.
</p>
</div>
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-comments text-blue-500"></i>
<span class="text-600 text-sm">8 messages</span>
</div>
<div class="flex align-items-center gap-2">
<i class="pi pi-ban text-red-500"></i>
<span class="text-red-600 text-sm">Non résolu</span>
<span class="text-600 text-sm">#{ticket.nbMessages != null ? ticket.nbMessages : 0} message#{ticket.nbMessages != null and ticket.nbMessages > 1 ? 's' : ''}</span>
#{ticket.nbFichiers != null and ticket.nbFichiers > 0 ? '<i class="pi pi-paperclip text-600 ml-3"></i><span class="text-600 text-sm">' + ticket.nbFichiers + ' fichier' + (ticket.nbFichiers > 1 ? 's' : '') + '</span>' : ''}
</div>
#{ticket.noteSatisfaction != null ? '<div class="flex align-items-center gap-1"><i class="pi pi-star-fill text-yellow-400"></i><span class="text-600 text-sm">Note: ' + ticket.noteSatisfaction + '/5</span></div>' : ''}
</div>
</div>
</ui:repeat>
<p:dataTable value="#{ticketBean.tickets}"
var="ticket"
emptyMessage="Aucun ticket trouvé"
rendered="#{empty ticketBean.tickets}">
<p:column headerText="Numéro">#{ticket.numeroTicket}</p:column>
<p:column headerText="Sujet">#{ticket.sujet}</p:column>
<p:column headerText="Statut">
<p:tag value="#{ticket.statut}"
severity="#{ticket.statut == 'RESOLU' ? 'success' : (ticket.statut == 'FERME' ? 'danger' : (ticket.statut == 'EN_ATTENTE' ? 'info' : 'warning'))}" />
</p:column>
<p:column headerText="Priorité">
<p:tag value="#{ticket.priorite}"
severity="#{ticket.priorite == 'URGENTE' or ticket.priorite == 'HAUTE' ? 'danger' : (ticket.priorite == 'NORMALE' ? 'success' : 'secondary')}" />
</p:column>
<p:column headerText="Date Création">#{ticket.dateCreation}</p:column>
<p:column headerText="Actions">
<p:commandButton icon="pi pi-eye"
styleClass="p-button-text p-button-sm"
title="Voir détails"
action="#{ticketBean.voirDetails}"
update="@form">
<f:setPropertyActionListener target="#{ticketBean.ticketSelectionne}" value="#{ticket}" />
</p:commandButton>
</p:column>
</p:dataTable>
</div>
</div>
</div>
@@ -371,7 +265,8 @@
header="Créer un Nouveau Ticket"
modal="true"
width="800"
styleClass="surface-0">
styleClass="surface-0"
visible="#{ticketBean.afficherDialogNouveauTicket}">
<h:form id="nouveauTicketForm">
<div class="ui-fluid">
<div class="formgrid grid">
@@ -421,11 +316,14 @@
<div class="flex justify-content-end gap-2 mt-4">
<p:commandButton value="Annuler"
styleClass="p-button-outlined"
onclick="PF('nouveauTicketDialog').hide()"
action="#{ticketBean.fermerDialogNouveauTicket}"
update="@form"
type="button" />
<p:commandButton value="Créer le Ticket"
styleClass="p-button-primary"
icon="pi pi-send" />
icon="pi pi-send"
action="#{ticketBean.creerTicket}"
update="@form" />
</div>
</div>
</h:form>

View File

@@ -132,7 +132,7 @@
<p:column headerText="Date" sortBy="#{demande.dateDemande}">
<h:outputText value="#{demande.dateDemande}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>

View File

@@ -118,7 +118,7 @@
<p:column headerText="Date" sortBy="#{cotisation.dateCreation}" style="width:120px">
<h:outputText value="#{cotisation.dateCreation}" rendered="#{cotisation.dateCreation != null}">
<f:convertDateTime pattern="dd/MM/yyyy" />
<f:convertDateTime pattern="dd/MM/yyyy" type="localDateTime" />
</h:outputText>
</p:column>

View File

@@ -6,7 +6,7 @@
xmlns:p="http://primefaces.org/ui"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{cotisationsGestionBean}"/>
<ui:param name="page" value="#{cotisationsBean}"/>
<ui:define name="title">Rapports Financiers - UnionFlow</ui:define>
<ui:define name="content">

View File

@@ -6,7 +6,7 @@
xmlns:p="http://primefaces.org/ui"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{cotisationsGestionBean}"/>
<ui:param name="page" value="#{cotisationsBean}"/>
<ui:define name="title">Relances de Cotisations - UnionFlow</ui:define>
<ui:define name="content">

View File

@@ -1,657 +1,275 @@
<!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:uf="http://xmlns.jcp.org/jsf/composite/components"
template="/templates/main-template.xhtml">
<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:uf="http://xmlns.jcp.org/jsf/composite/components"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{membreListeBean}"/>
<ui:define name="title">Liste des Membres - UnionFlow</ui:define>
<ui:define name="title">Gestion des Membres</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-users text-blue-500" />
<ui:param name="title" value="Liste des Membres" />
<ui:param name="description" value="Gestion et suivi des membres de l'association" />
<ui:define name="actions">
<h:form id="formActionsMembres">
<div class="flex gap-2">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Nouveau membre" />
<ui:param name="icon" value="pi pi-user-plus" />
<ui:param name="outcome" value="/pages/secure/membre/inscription" />
</ui:include>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Import/Export" />
<ui:param name="icon" value="pi pi-upload" />
<ui:param name="onclick" value="PF('dlgImportExport').show();" />
<ui:param name="outlined" value="true" />
</ui:include>
</div>
</h:form>
</ui:define>
</ui:include>
<h:form id="formMembres">
<p:messages id="messages" showDetail="true" closable="true" />
<!-- ================================================================
EN-TÊTE (DRY/WOU: card-header)
================================================================ -->
<ui:decorate template="/templates/components/cards/card-header.xhtml">
<ui:param name="title" value="Gestion des Membres" />
<ui:param name="subtitle" value="Liste, recherche et gestion des membres de l'organisation." />
<ui:param name="styleClass" value="mb-3" />
<ui:define name="actions">
<p:button value="Nouveau Membre" icon="pi pi-user-plus" outcome="membreInscriptionPage"
styleClass="ui-button-success mr-2" />
<p:commandButton value="Import / Export" icon="pi pi-file-excel"
onclick="PF('dlgImportExport').show();" type="button" styleClass="ui-button-info" />
</ui:define>
</ui:decorate>
<!-- Liste des membres -->
<div class="card">
<h:form id="formMembres">
<h5>Tous les Membres</h5>
<!-- Filtres et recherche (DRY/WOU: filter-bar avec composants réutilisables) -->
<ui:decorate template="/templates/components/cards/filter-bar.xhtml">
<ui:param name="title" value="Filtres" />
<ui:param name="styleClass" value="mb-3" />
<ui:define name="filters">
<!-- Recherche globale (DRY/WOU: form-field-search-text avec icône) -->
<div class="col-12 md:col-3">
<div class="field">
<p:outputLabel for="searchFilter" value="Rechercher" />
<span class="p-input-icon-left w-full">
<i class="pi pi-search"></i>
<p:inputText id="searchFilter"
placeholder="Nom, prénom, email..."
value="#{membreListeBean.searchFilter}"
styleClass="w-full">
<p:ajax event="keyup" update="dtMembres" delay="500"/>
</p:inputText>
</span>
</div>
</div>
<!-- Statut (DRY/WOU: form-field-select avec AJAX) -->
<div class="col-12 md:col-2">
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="statutFilter" />
<ui:param name="label" value="Statut" />
<ui:param name="value" value="#{membreListeBean.statutFilter}" />
<ui:define name="items">
<f:selectItem itemLabel="Tous les statuts" itemValue="" />
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
<f:selectItem itemLabel="Inactif" itemValue="INACTIF" />
<f:selectItem itemLabel="Suspendu" itemValue="SUSPENDU" />
<f:selectItem itemLabel="Radié" itemValue="RADIE" />
</ui:define>
<ui:define name="ajax">
<p:ajax event="change" update="dtMembres" />
</ui:define>
</ui:include>
</div>
<!-- Type (DRY/WOU: form-field-select avec AJAX) -->
<div class="col-12 md:col-2">
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="typeFilter" />
<ui:param name="label" value="Type" />
<ui:param name="value" value="#{membreListeBean.typeFilter}" />
<ui:define name="items">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
<f:selectItem itemLabel="Associé" itemValue="ASSOCIE" />
<f:selectItem itemLabel="Bienfaiteur" itemValue="BIENFAITEUR" />
<f:selectItem itemLabel="Honoraire" itemValue="HONORAIRE" />
</ui:define>
<ui:define name="ajax">
<p:ajax event="change" update="dtMembres" />
</ui:define>
</ui:include>
</div>
<!-- Cotisation (DRY/WOU: form-field-select avec AJAX) -->
<div class="col-12 md:col-2">
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="cotisationFilter" />
<ui:param name="label" value="Cotisation" />
<ui:param name="value" value="#{membreListeBean.cotisationFilter}" />
<ui:define name="items">
<f:selectItem itemLabel="Toutes cotisations" itemValue="" />
<f:selectItem itemLabel="À jour" itemValue="A_JOUR" />
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD" />
<f:selectItem itemLabel="Jamais payé" itemValue="JAMAIS_PAYE" />
</ui:define>
<ui:define name="ajax">
<p:ajax event="change" update="dtMembres" />
</ui:define>
</ui:include>
</div>
<!-- Entité/Organisation (DRY/WOU: form-field-select avec AJAX) -->
<div class="col-12 md:col-2">
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="entiteFilter" />
<ui:param name="label" value="Entité" />
<ui:param name="value" value="#{membreListeBean.entiteFilter}" />
<ui:define name="items">
<f:selectItem itemLabel="Toutes entités" itemValue="" />
<f:selectItems value="#{membreListeBean.entitesDisponibles}"
var="entite"
itemLabel="#{entite.nom}"
itemValue="#{entite.id}" />
</ui:define>
<ui:define name="ajax">
<p:ajax event="change" update="dtMembres" />
</ui:define>
</ui:include>
</div>
</ui:define>
<ui:define name="actions">
<!-- Filtres avancés (DRY/WOU: button-secondary) -->
<div class="col-12 md:col-auto">
<div class="field">
<label class="invisible">Filtres avancés</label>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Filtres avancés" />
<ui:param name="icon" value="pi pi-filter" />
<ui:param name="onclick" value="PF('dlgFiltresAvances').show();" />
<ui:param name="styleClass" value="w-full" />
</ui:include>
</div>
</div>
<!-- Actualiser (DRY/WOU: button-secondary avec icône seule) -->
<div class="col-12 md:col-auto">
<div class="field">
<label class="invisible">Actualiser</label>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="" />
<ui:param name="icon" value="pi pi-refresh" />
<ui:param name="action" value="#{membreListeBean.actualiser}" />
<ui:param name="update" value=":formMembres:dtMembres" />
<ui:param name="title" value="Actualiser" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full" />
</ui:include>
</div>
</div>
<!-- Réinitialiser (DRY/WOU: button-secondary) -->
<div class="col-12 md:col-auto">
<div class="field">
<label class="invisible">Réinitialiser</label>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Réinitialiser" />
<ui:param name="icon" value="pi pi-filter-slash" />
<ui:param name="action" value="#{membreListeBean.reinitialiserFiltres}" />
<ui:param name="update" value="dtMembres searchFilter statutFilter typeFilter cotisationFilter entiteFilter" />
<ui:param name="styleClass" value="w-full" />
</ui:include>
</div>
</div>
</ui:define>
</ui:decorate>
<!-- ================================================================
STATISTIQUES (DRY/WOU: stat-card)
================================================================ -->
<h:panelGroup id="panelStatistiques" layout="block" styleClass="grid mb-3">
<div class="col-12 md:col-3">
<ui:decorate template="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{membreListeBean.totalMembres}" />
<ui:param name="label" value="Total Membres" />
<ui:param name="icon" value="pi pi-users" />
<ui:param name="bgColor" value="bg-blue-100" />
</ui:decorate>
</div>
<div class="col-12 md:col-3">
<ui:decorate template="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{membreListeBean.membresActifs}" />
<ui:param name="label" value="Actifs" />
<ui:param name="icon" value="pi pi-check-circle" />
<ui:param name="bgColor" value="bg-green-100" />
</ui:decorate>
</div>
<div class="col-12 md:col-3">
<ui:decorate template="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{membreListeBean.membresInactifs}" />
<ui:param name="label" value="Inactifs / Suspendus" />
<ui:param name="icon" value="pi pi-ban" />
<ui:param name="bgColor" value="bg-orange-100" />
</ui:decorate>
</div>
<div class="col-12 md:col-3">
<ui:decorate template="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{membreListeBean.nouveauxMembres}" />
<ui:param name="label" value="Nouveaux (30j)" />
<ui:param name="icon" value="pi pi-user-plus" />
<ui:param name="bgColor" value="bg-purple-100" />
</ui:decorate>
</div>
</h:panelGroup>
<!-- DataTable -->
<p:dataTable id="dtMembres"
var="membre"
value="#{membreListeBean.membres}"
paginator="true"
rows="15"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="10,15,25,50"
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
selection="#{membreListeBean.selectedMembres}"
rowKey="#{membre.id}"
selectionMode="multiple"
styleClass="mt-3">
<p:column selectionMode="multiple" style="width:50px" />
<p:column headerText="N° Membre" sortBy="#{membre.numeroMembre}" style="width:120px">
<h:outputText value="#{membre.numeroMembre}" styleClass="font-mono font-bold" />
</p:column>
<p:column headerText="Membre" sortBy="#{membre.nom}">
<div class="flex align-items-center">
<div class="border-circle overflow-hidden mr-3"
style="width: 40px; height: 40px;">
<h:graphicImage value="#{membre.photoUrl}"
style="width: 100%; height: 100%; object-fit: cover;"
rendered="#{membre.photoUrl != null}" />
<div class="bg-primary text-white flex align-items-center justify-content-center w-full h-full"
rendered="#{membre.photoUrl == null}">
<span style="font-size: 0.9rem;">#{membre.initiales}</span>
</div>
</div>
<div>
<div class="font-medium text-900">#{membre.nomComplet}</div>
<div class="text-600 text-sm">
<span>#{membre.telephone}</span>
<span class="mx-2"></span>
<span>#{membre.email}</span>
</div>
</div>
</div>
</p:column>
<p:column headerText="Type" sortBy="#{membre.typeMembre}" style="width:120px">
<p:tag value="#{membre.typeMembre}"
severity="#{membre.typeSeverity != null ? membre.typeSeverity : 'info'}"
icon="pi #{membre.typeIcon != null ? membre.typeIcon : 'pi-user'}" />
</p:column>
<p:column headerText="Statut" sortBy="#{membre.statut}" style="width:100px">
<p:tag value="#{membre.statut}"
severity="#{membre.statutSeverity}"
icon="pi #{membre.statutIcon}" />
</p:column>
<p:column headerText="Organisation" sortBy="#{membre.associationNom}" style="width:150px">
<h:outputText value="#{membre.associationNom != null ? membre.associationNom : 'Non renseigné'}" />
</p:column>
<p:column headerText="Adhésion" sortBy="#{membre.dateAdhesion}" style="width:120px">
<div>
<div class="font-medium">#{membre.dateAdhesion != null ? membre.dateAdhesion : 'Non renseigné'}</div>
<small class="text-600">#{membre.anciennete}</small>
</div>
</p:column>
<p:column headerText="Cotisations" style="width:120px">
<div class="text-center">
<div class="font-bold #{membre.cotisationColor}">#{membre.cotisationStatut}</div>
<small class="text-600">#{membre.dernierPaiement}</small>
</div>
</p:column>
<p:column headerText="Participation" style="width:100px">
<div class="text-center">
<div class="font-bold text-blue-500">#{membre.tauxParticipation}%</div>
<small class="text-600">#{membre.evenementsAnnee} événements</small>
</div>
</p:column>
<p:column headerText="Actions" style="width:200px">
<div class="flex gap-1">
<!-- DRY/WOU: Composite Component action-button-view -->
<uf:action-button-view itemId="#{membre.id}"
detailPage="/pages/secure/membre/profil.xhtml"
iconOnly="true"/>
<!-- DRY/WOU: Composite Component action-button-edit-nav -->
<uf:action-button-edit-nav itemId="#{membre.id}"
editPage="/pages/secure/membre/modifier.xhtml"
iconOnly="true"/>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-dollar" />
<ui:param name="action" value="#{membreListeBean.gererCotisations(membre)}" />
<ui:param name="title" value="Cotisations" />
<ui:param name="severity" value="success" />
</ui:include>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-envelope" />
<ui:param name="action" value="#{membreListeBean.contacterMembre(membre)}" />
<ui:param name="update" value=":formMembres :formContact" />
<ui:param name="oncomplete" value="PF('dlgContact').show();" />
<ui:param name="title" value="Contacter" />
<ui:param name="severity" value="" />
</ui:include>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-ban" />
<ui:param name="action" value="#{membreListeBean.suspendreMembre(membre)}" />
<ui:param name="onclick" value="return confirm('Êtes-vous sûr de vouloir suspendre ce membre ?');" />
<ui:param name="title" value="Suspendre" />
<ui:param name="severity" value="danger" />
<ui:param name="rendered" value="#{membre.statut == 'ACTIF'}" />
</ui:include>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-check" />
<ui:param name="action" value="#{membreListeBean.reactiverMembre(membre)}" />
<ui:param name="title" value="Réactiver" />
<ui:param name="severity" value="success" />
<ui:param name="rendered" value="#{membre.statut == 'SUSPENDU'}" />
</ui:include>
</div>
</p:column>
</p:dataTable>
<!-- Actions groupées -->
<div class="mt-3 flex justify-content-between align-items-center">
<div>
<span class="text-600">#{membreListeBean.selectedMembres.size()} membre(s) sélectionné(s)</span>
<span class="text-400 ml-2" rendered="#{empty membreListeBean.selectedMembres}">
- Cochez des cases pour activer les actions
<!-- ================================================================
BARRE DE FILTRES (DRY/WOU: filter-bar)
================================================================ -->
<ui:decorate template="/templates/components/cards/filter-bar.xhtml">
<ui:param name="title" value="" />
<ui:define name="filters">
<!-- Recherche textuelle -->
<div class="col-12 md:col-4">
<span class="p-input-icon-left w-full">
<i class="pi pi-search" />
<p:inputText id="searchFilter" value="#{membreListeBean.searchFilter}"
placeholder="Rechercher un membre..." styleClass="w-full">
<p:ajax event="keyup" listener="#{membreListeBean.rechercher}" update="dtMembres"
delay="500" />
</p:inputText>
</span>
</div>
<div class="flex gap-2">
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Envoyer message" />
<ui:param name="icon" value="pi pi-envelope" />
<ui:param name="onclick" value="PF('dlgMessageGroupe').show();" />
<ui:param name="outlined" value="true" />
<ui:param name="disabled" value="#{empty membreListeBean.selectedMembres}" />
</ui:include>
<ui:include src="/templates/components/buttons/button-warning.xhtml">
<ui:param name="value" value="Rappel cotisations" />
<ui:param name="icon" value="pi pi-bell" />
<ui:param name="action" value="#{membreListeBean.rappelCotisationsGroupe}" />
<ui:param name="update" value=":formMembres" />
<ui:param name="outlined" value="true" />
<ui:param name="disabled" value="#{empty membreListeBean.selectedMembres}" />
</ui:include>
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Exporter sélection" />
<ui:param name="icon" value="pi pi-file-excel" />
<ui:param name="action" value="#{membreListeBean.exporterSelection}" />
<ui:param name="outlined" value="true" />
<ui:param name="disabled" value="#{empty membreListeBean.selectedMembres}" />
</ui:include>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Modifier en lot" />
<ui:param name="icon" value="pi pi-pencil" />
<ui:param name="onclick" value="PF('dlgModificationLot').show();" />
<ui:param name="outlined" value="true" />
<ui:param name="disabled" value="#{empty membreListeBean.selectedMembres}" />
</ui:include>
</div>
</div>
</h:form>
</div>
<!-- Dialog Filtres Avancés -->
<p:dialog header="Filtres Avancés" widgetVar="dlgFiltresAvances" modal="true" width="600">
<h:form id="formFiltresAvances">
<div class="ui-fluid">
<div class="grid">
<div class="col-12 md:col-6">
<div class="field">
<p:outputLabel for="ageMin" value="Âge minimum" />
<p:inputNumber id="ageMin" value="#{membreListeBean.ageMin}" symbol="" styleClass="w-full" />
</div>
<div class="field">
<p:outputLabel for="ageMax" value="Âge maximum" />
<p:inputNumber id="ageMax" value="#{membreListeBean.ageMax}" symbol="" styleClass="w-full" />
</div>
<div class="field">
<p:outputLabel for="genre" value="Genre" />
<p:selectOneMenu id="genre" value="#{membreListeBean.genreFilter}" styleClass="w-full">
<f:selectItem itemLabel="Tous" itemValue="" />
<f:selectItem itemLabel="Homme" itemValue="M" />
<f:selectItem itemLabel="Femme" itemValue="F" />
</p:selectOneMenu>
</div>
<div class="field">
<p:outputLabel for="ville" value="Ville" />
<p:autoComplete id="ville"
value="#{membreListeBean.villeFilter}"
completeMethod="#{membreListeBean.completerVilles}"
placeholder="Saisir une ville..."
styleClass="w-full" />
</div>
</div>
<div class="col-12 md:col-6">
<ui:include src="/templates/components/forms/form-field-calendar.xhtml">
<ui:param name="id" value="dateAdhesionDebut" />
<ui:param name="label" value="Adhésion après le" />
<ui:param name="value" value="#{membreListeBean.dateAdhesionDebut}" />
</ui:include>
<ui:include src="/templates/components/forms/form-field-calendar.xhtml">
<ui:param name="id" value="dateAdhesionFin" />
<ui:param name="label" value="Adhésion avant le" />
<ui:param name="value" value="#{membreListeBean.dateAdhesionFin}" />
</ui:include>
<div class="field">
<p:outputLabel for="profession" value="Profession" />
<p:autoComplete id="profession"
value="#{membreListeBean.professionFilter}"
completeMethod="#{membreListeBean.completerProfessions}"
placeholder="Saisir une profession..."
styleClass="w-full" />
</div>
<div class="field">
<p:selectBooleanCheckbox id="desEnfants" value="#{membreListeBean.desEnfants}" />
<p:outputLabel for="desEnfants" value=" A des enfants déclarés" />
</div>
</div>
<!-- Filtre par statut -->
<div class="col-12 md:col-2">
<p:selectOneMenu id="statutFilter" value="#{membreListeBean.statutFilter}" styleClass="w-full">
<f:selectItem itemLabel="Tous les statuts" itemValue="" />
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
<f:selectItem itemLabel="Inactif" itemValue="INACTIF" />
<f:selectItem itemLabel="Suspendu" itemValue="SUSPENDU" />
<f:selectItem itemLabel="En attente" itemValue="EN_ATTENTE" />
<p:ajax event="valueChange" listener="#{membreListeBean.rechercher}" update="dtMembres" />
</p:selectOneMenu>
</div>
</div>
<div class="flex gap-2 mt-3">
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Appliquer" />
<ui:param name="icon" value="pi pi-check" />
<ui:param name="action" value="#{membreListeBean.appliquerFiltresAvances}" />
<ui:param name="update" value=":formMembres:dtMembres" />
<ui:param name="onclick" value="PF('dlgFiltresAvances').hide();" />
</ui:include>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Réinitialiser" />
<ui:param name="icon" value="pi pi-refresh" />
<ui:param name="action" value="#{membreListeBean.reinitialiserFiltres}" />
<ui:param name="update" value=":formFiltresAvances :formMembres:dtMembres" />
<ui:param name="outlined" value="true" />
</ui:include>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler" />
<ui:param name="icon" value="pi pi-times" />
<ui:param name="onclick" value="PF('dlgFiltresAvances').hide();" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="ui-button-danger" />
</ui:include>
</div>
</h:form>
</p:dialog>
<!-- Dialog Message Groupé -->
<p:dialog header="Envoyer un Message Groupé" widgetVar="dlgMessageGroupe" modal="true" width="600">
<h:form id="formMessageGroupe">
<div class="ui-fluid">
<ui:include src="/templates/components/forms/form-field-text.xhtml">
<ui:param name="id" value="sujetMessage" />
<ui:param name="label" value="Sujet" />
<ui:param name="value" value="#{membreListeBean.sujetMessage}" />
<ui:param name="required" value="true" />
<ui:param name="placeholder" value="Objet du message" />
</ui:include>
<ui:include src="/templates/components/forms/form-field-textarea.xhtml">
<ui:param name="id" value="contenuMessage" />
<ui:param name="label" value="Message" />
<ui:param name="value" value="#{membreListeBean.contenuMessage}" />
<ui:param name="required" value="true" />
<ui:param name="rows" value="6" />
</ui:include>
<div class="field">
<p:outputLabel for="canauxMessage" value="Canaux de diffusion" />
<p:selectCheckboxMenu id="canauxMessage" value="#{membreListeBean.canauxMessage}"
multiple="true" styleClass="w-full">
<f:selectItem itemLabel="📧 Email" itemValue="EMAIL" />
<f:selectItem itemLabel="📱 SMS" itemValue="SMS" />
<f:selectItem itemLabel="💬 WhatsApp" itemValue="WHATSAPP" />
<f:selectItem itemLabel="🔔 Notification push" itemValue="PUSH" />
</p:selectCheckboxMenu>
<!-- Filtre par rôle (aligné avec le modèle métier) -->
<div class="col-12 md:col-2">
<p:selectOneMenu id="roleFilter" value="#{membreListeBean.roleFilter}" styleClass="w-full">
<f:selectItem itemLabel="Tous les rôles" itemValue="" />
<f:selectItem itemLabel="Responsable" itemValue="RESPONSABLE" />
<f:selectItem itemLabel="Bureau" itemValue="BUREAU" />
<p:ajax event="valueChange" listener="#{membreListeBean.rechercher}" update="dtMembres" />
</p:selectOneMenu>
</div>
<div class="surface-50 p-3 border-round">
<div class="font-medium mb-2">Destinataires :</div>
<div class="text-600">#{membreListeBean.selectedMembres.size()} membre(s) recevront ce message</div>
</div>
</div>
<div class="flex gap-2 mt-3">
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Envoyer" />
<ui:param name="icon" value="pi pi-send" />
<ui:param name="action" value="#{membreListeBean.envoyerMessageGroupe}" />
<ui:param name="update" value=":formMessageGroupe :formMembres" />
<ui:param name="onclick" value="if(!args.validationFailed) PF('dlgMessageGroupe').hide();" />
</ui:include>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler" />
<ui:param name="icon" value="pi pi-times" />
<ui:param name="onclick" value="PF('dlgMessageGroupe').hide();" />
<ui:param name="outlined" value="false" />
</ui:include>
</div>
</h:form>
</p:dialog>
<!-- Dialog Import/Export -->
<p:dialog header="Import/Export des Membres" widgetVar="dlgImportExport" modal="true" width="500">
<h:form id="formImportExport">
<p:tabView>
<p:tab title="Import">
<div class="ui-fluid">
<div class="field">
<p:outputLabel for="fichierImport" value="Fichier Excel" />
<p:fileUpload id="fichierImport" mode="simple" skinSimple="true"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel" />
</div>
<div class="field">
<p:selectBooleanCheckbox id="mettreAJourExistants" value="#{membreListeBean.mettreAJourExistants}" />
<p:outputLabel for="mettreAJourExistants" value=" Mettre à jour les membres existants" />
</div>
<div class="surface-50 p-3 border-round">
<div class="font-medium mb-2">Format attendu :</div>
<small class="text-600">
Colonnes : Nom, Prénom, Email, Téléphone, Date naissance, Adresse, Profession, Type membre
</small>
</div>
</div>
<div class="flex gap-2 mt-3">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Importer" />
<ui:param name="icon" value="pi pi-upload" />
<ui:param name="action" value="#{membreListeBean.importerMembres}" />
<ui:param name="update" value=":formImportExport :formMembres" />
</ui:include>
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Télécharger modèle" />
<ui:param name="icon" value="pi pi-download" />
<ui:param name="action" value="#{membreListeBean.telechargerModele}" />
<ui:param name="outlined" value="true" />
</ui:include>
</div>
</p:tab>
<p:tab title="Export">
<div class="ui-fluid">
<div class="field">
<p:outputLabel for="formatExport" value="Format" />
<p:selectOneMenu id="formatExport" value="#{membreListeBean.formatExport}">
<f:selectItem itemLabel="Excel (.xlsx)" itemValue="EXCEL" />
<f:selectItem itemLabel="CSV (.csv)" itemValue="CSV" />
<f:selectItem itemLabel="PDF (.pdf)" itemValue="PDF" />
</p:selectOneMenu>
</div>
<div class="field">
<p:outputLabel for="colonnesExport" value="Colonnes à exporter" />
<p:selectCheckboxMenu id="colonnesExport" value="#{membreListeBean.colonnesExport}"
multiple="true">
<f:selectItem itemLabel="Informations personnelles" itemValue="PERSO" />
<f:selectItem itemLabel="Coordonnées" itemValue="CONTACT" />
<f:selectItem itemLabel="Informations adhésion" itemValue="ADHESION" />
<f:selectItem itemLabel="Cotisations" itemValue="COTISATIONS" />
<f:selectItem itemLabel="Participation événements" itemValue="EVENEMENTS" />
</p:selectCheckboxMenu>
</div>
<div class="field">
<p:selectBooleanCheckbox id="exporterSelection" value="#{membreListeBean.exporterSelection}" />
<p:outputLabel for="exporterSelection" value=" Exporter seulement la sélection" />
</div>
</div>
<div class="flex gap-2 mt-3">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Exporter" />
<ui:param name="icon" value="pi pi-download" />
<ui:param name="action" value="#{membreListeBean.exporterMembres}" />
</ui:include>
</div>
</p:tab>
</p:tabView>
<div class="flex justify-content-end mt-3">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Fermer" />
<ui:param name="icon" value="pi pi-times" />
<ui:param name="onclick" value="PF('dlgImportExport').hide();" />
<ui:param name="outlined" value="false" />
</ui:include>
</div>
</h:form>
</p:dialog>
<!-- Dialog Contact Membre -->
<h:form id="formContact">
<p:dialog id="dlgContact"
header="Contacter #{membreListeBean.membreAContacter != null ? membreListeBean.membreAContacter.nomComplet : 'Membre'}"
widgetVar="dlgContact"
modal="true"
resizable="false"
style="width: 90vw; max-width: 600px;"
visible="#{membreListeBean.dialogContactVisible}">
<div class="ui-fluid" rendered="#{membreListeBean.membreAContacter != null}">
<div class="field mb-4">
<div class="surface-100 border-round p-3">
<div class="flex align-items-center">
<div class="w-3rem h-3rem border-circle bg-primary-100 flex align-items-center justify-content-center mr-3">
<i class="pi pi-user text-primary text-xl"></i>
</div>
<div>
<div class="font-semibold text-900">#{membreListeBean.membreAContacter.nomComplet}</div>
<div class="text-600 text-sm">#{membreListeBean.membreAContacter.email != null ? membreListeBean.membreAContacter.email : 'Email non renseigné'}</div>
<div class="text-600 text-sm">#{membreListeBean.membreAContacter.telephone != null ? membreListeBean.membreAContacter.telephone : 'Téléphone non renseigné'}</div>
</div>
</div>
</div>
<!-- Filtre par organisation -->
<div class="col-12 md:col-2">
<p:selectOneMenu id="entiteFilter" value="#{membreListeBean.entiteFilter}" styleClass="w-full"
filter="true" filterMatchMode="contains">
<f:selectItem itemLabel="Toutes les organisations" itemValue="" />
<f:selectItems value="#{membreListeBean.organisationsDisponibles}" var="org"
itemLabel="#{org.nom}" itemValue="#{org.id}" />
<p:ajax event="valueChange" listener="#{membreListeBean.rechercher}" update="dtMembres" />
</p:selectOneMenu>
</div>
<div class="field">
<ui:include src="/templates/components/forms/form-field-text.xhtml">
<ui:param name="id" value="sujetContact" />
<ui:param name="label" value="Sujet" />
<ui:param name="value" value="#{membreListeBean.sujetContact}" />
<ui:param name="placeholder" value="Sujet du message (optionnel)" />
</ui:include>
</div>
<div class="field">
<ui:include src="/templates/components/forms/form-field-textarea.xhtml">
<ui:param name="id" value="messageContact" />
<ui:param name="label" value="Message *" />
<ui:param name="value" value="#{membreListeBean.messageContact}" />
<ui:param name="required" value="true" />
<ui:param name="rows" value="6" />
<ui:param name="placeholder" value="Saisissez votre message..." />
</ui:include>
</div>
</div>
<f:facet name="footer">
<div class="flex justify-content-end gap-2">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler" />
<ui:param name="icon" value="pi pi-times" />
<ui:param name="action" value="#{membreListeBean.annulerContact}" />
<ui:param name="update" value=":formContact" />
<ui:param name="oncomplete" value="PF('dlgContact').hide();" />
</ui:include>
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Envoyer" />
<ui:param name="icon" value="pi pi-send" />
<ui:param name="action" value="#{membreListeBean.envoyerMessageContact}" />
<ui:param name="update" value=":formContact :formMembres" />
<ui:param name="oncomplete" value="if(!args.validationFailed) { PF('dlgContact').hide(); }" />
</ui:include>
</div>
</f:facet>
</p:dialog>
</ui:define>
<ui:define name="actions">
<p:commandButton icon="pi pi-filter" title="Filtres avancés"
onclick="PF('dlgFiltresAvances').show();" type="button" styleClass="ui-button-outlined mr-1" />
<p:commandButton icon="pi pi-refresh" title="Actualiser" action="#{membreListeBean.actualiser}"
update="dtMembres panelStatistiques messages" styleClass="ui-button-outlined" />
</ui:define>
</ui:decorate>
<!-- ================================================================
ACTIONS GROUPÉES (visible si sélection)
================================================================ -->
<h:panelGroup id="panelActionsGroupeesWrapper">
<h:panelGroup id="panelActionsGroupees" layout="block" styleClass="flex gap-2 mb-3"
rendered="#{not empty membreListeBean.selectedMembres}">
<p:commandButton value="Rappel Cotisations" icon="pi pi-bell"
action="#{membreListeBean.rappelCotisationsGroupe}" update="messages"
styleClass="ui-button-warning ui-button-sm" />
<p:commandButton value="Message Groupé" icon="pi pi-envelope"
onclick="PF('dlgMessageGroupe').show();" type="button"
styleClass="ui-button-info ui-button-sm" />
<p:commandButton value="Exporter Sélection" icon="pi pi-download" ajax="false"
action="#{membreListeBean.exporterSelection}" styleClass="ui-button-secondary ui-button-sm" />
<h:outputText value="#{membreListeBean.selectedMembres.size()} membre(s) sélectionné(s)"
styleClass="ml-2 text-500 align-self-center" />
</h:panelGroup>
</h:panelGroup>
<!-- ================================================================
DataTable LAZY (pagination/tri/filtrage côté serveur)
================================================================ -->
<p:dataTable id="dtMembres" var="membre" value="#{membreListeBean.lazyModel}" lazy="true" paginator="true"
rows="20" rowsPerPageTemplate="10,20,50"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
currentPageReportTemplate="{startRecord}-{endRecord} sur {totalRecords}"
selection="#{membreListeBean.selectedMembres}" rowKey="#{membre.id}" emptyMessage="Aucun membre trouvé."
stripedRows="true" showGridlines="false" size="small" styleClass="mb-4" tableStyle="min-width: 60rem">
<!-- AJAX sélection pour mise à jour des actions groupées -->
<p:ajax event="rowSelect" update=":formMembres:panelActionsGroupeesWrapper" />
<p:ajax event="rowUnselect" update=":formMembres:panelActionsGroupeesWrapper" />
<p:ajax event="toggleSelect" update=":formMembres:panelActionsGroupeesWrapper" />
<!-- Colonne sélection checkbox -->
<p:column selectionMode="multiple" headerText="" style="width: 3rem; text-align: center;" />
<!-- Colonne: Membre (Nom + Email) -->
<ui:decorate template="/templates/components/columns/column-name-with-subtitle.xhtml">
<ui:param name="headerText" value="Membre" />
<ui:param name="name" value="#{membre.nomComplet}" />
<ui:param name="subtitle" value="#{membre.email}" />
<ui:param name="sortBy" value="#{membre.nom}" />
<ui:param name="filterBy" value="#{membre.nom}" />
</ui:decorate>
<!-- Colonne: Rôle -->
<ui:decorate template="/templates/components/columns/column-tag.xhtml">
<ui:param name="headerText" value="Rôle" />
<ui:param name="value" value="#{membre.typeMembre}" />
<ui:param name="severity" value="#{membre.typeSeverity}" />
<ui:param name="icon" value="#{membre.typeIcon}" />
<ui:param name="width" value="8rem" />
</ui:decorate>
<!-- Colonne: Organisation -->
<ui:decorate template="/templates/components/columns/column-text-with-icon.xhtml">
<ui:param name="headerText" value="Organisation" />
<ui:param name="icon" value="pi pi-building" />
<ui:param name="text" value="#{not empty membre.associationNom ? membre.associationNom : '—'}" />
<ui:param name="sortBy" value="#{membre.associationNom}" />
<ui:param name="filterBy" value="#{membre.associationNom}" />
<ui:param name="styleClass" value="#{empty membre.associationNom ? 'text-500' : ''}" />
</ui:decorate>
<!-- Colonne: Téléphone -->
<ui:decorate template="/templates/components/columns/column-text-with-icon.xhtml">
<ui:param name="headerText" value="Téléphone" />
<ui:param name="icon" value="pi pi-phone" />
<ui:param name="text" value="#{not empty membre.telephone ? membre.telephone : '—'}" />
<ui:param name="styleClass" value="#{empty membre.telephone ? 'text-500' : ''}" />
</ui:decorate>
<!-- Colonne: Statut -->
<ui:decorate template="/templates/components/columns/column-tag.xhtml">
<ui:param name="headerText" value="Statut" />
<ui:param name="value" value="#{membre.statutLibelle}" />
<ui:param name="severity" value="#{membre.statutSeverity}" />
<ui:param name="icon" value="#{membre.statutIcon}" />
<ui:param name="sortBy" value="#{membre.statut}" />
<ui:param name="width" value="8rem" />
</ui:decorate>
<!-- Colonne: Date Adhésion -->
<p:column headerText="Adhésion" sortBy="#{membre.dateAdhesion}"
style="width: 8rem; text-align: center;">
<i class="pi pi-calendar mr-1 text-primary"></i>
<h:outputText value="#{membre.dateAdhesion}">
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate" />
</h:outputText>
</p:column>
<!-- Colonne: Actions (DRY/WOU: column-actions) -->
<ui:decorate template="/templates/components/columns/column-actions.xhtml">
<ui:param name="width" value="14rem" />
<ui:define name="actions">
<!-- Voir le profil -->
<p:button icon="pi pi-user" outcome="membreProfilPage" title="Profil"
styleClass="ui-button-info ui-button-sm ui-button-rounded mr-1">
<f:param name="id" value="#{membre.id}" />
</p:button>
<!-- Éditer -->
<p:button icon="pi pi-pencil" outcome="membreModifierPage" title="Modifier"
styleClass="ui-button-warning ui-button-sm ui-button-rounded mr-1">
<f:param name="id" value="#{membre.id}" />
</p:button>
<!-- Contacter -->
<p:commandButton icon="pi pi-envelope" title="Contacter"
action="#{membreListeBean.contacterMembre(membre)}" process="@this"
oncomplete="PF('dlgContact').show();" update=":dlgContact"
styleClass="ui-button-help ui-button-sm ui-button-rounded mr-1" />
<!-- Suspendre (visible si actif) -->
<p:commandButton icon="pi pi-ban" title="Suspendre"
action="#{membreListeBean.suspendreMembre(membre)}"
update="dtMembres messages panelStatistiques" rendered="#{membre.statut == 'ACTIF'}"
styleClass="ui-button-danger ui-button-sm ui-button-rounded mr-1">
<p:confirm header="Confirmation" message="Suspendre #{membre.nomComplet} ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<!-- Réactiver (visible si suspendu) -->
<p:commandButton icon="pi pi-replay" title="Réactiver"
action="#{membreListeBean.reactiverMembre(membre)}"
update="dtMembres messages panelStatistiques" rendered="#{membre.statut == 'SUSPENDU'}"
styleClass="ui-button-success ui-button-sm ui-button-rounded" />
</ui:define>
</ui:decorate>
</p:dataTable>
<!-- Confirmation dialog global -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade" responsive="true" width="450">
<p:commandButton value="Oui" type="button" styleClass="ui-button-danger ui-confirmdialog-yes"
icon="pi pi-check" />
<p:commandButton value="Non" type="button" styleClass="ui-button-secondary ui-confirmdialog-no"
icon="pi pi-times" />
</p:confirmDialog>
</h:form>
</ui:define>
<!-- ================================================================
DIALOGS EXTRAITS (DRY/WOU: fichiers include séparés)
================================================================ -->
<ui:include src="/ui/includes/membre-dialog-filtres-avances.xhtml" />
<ui:include src="/ui/includes/membre-dialog-message-groupe.xhtml" />
<ui:include src="/ui/includes/membre-dialog-import-export.xhtml" />
<ui:include src="/ui/includes/membre-dialog-contact.xhtml" />
</ui:define>
</ui:composition>

View File

@@ -1,122 +1,164 @@
<!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"
xmlns:uf="http://xmlns.jcp.org/jsf/composite/components">
<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">Organisations - UnionFlow</ui:define>
<ui:composition template="/templates/main-template.xhtml">
<ui:param name="page" value="#{organisationsBean}"/>
<ui:define name="title">Gestion des Organisations</ui:define>
<ui:define name="content">
<h:form id="formOrganisations">
<p:messages id="messages" showDetail="true" closable="true"/>
<!-- En-tête avec titre et action principale (DRY/WOU: card-header) -->
<ui:decorate template="/templates/components/cards/card-header.xhtml">
<ui:param name="title" value="Gestion des Organisations" />
<ui:param name="subtitle" value="Liste complète des organisations avec filtres et actions." />
<ui:param name="styleClass" value="mb-3" />
<ui:define name="actions">
<p:button value="Nouvelle Organisation"
icon="pi pi-plus"
outcome="/pages/secure/organisation/nouvelle"
styleClass="ui-button-success" />
</ui:define>
</ui:decorate>
<div class="ui-fluid">
<!-- Statistiques (DRY/WOU: stat-card) -->
<div class="grid mb-3">
<ui:include src="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{organisationsBean.totalOrganisations}" />
<ui:param name="label" value="Total" />
<ui:param name="icon" value="pi pi-building" />
<ui:param name="bgColor" value="blue" />
</ui:include>
<ui:include src="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{organisationsBean.organisationsActives}" />
<ui:param name="label" value="Actives" />
<ui:param name="icon" value="pi pi-check-circle" />
<ui:param name="bgColor" value="green" />
</ui:include>
<ui:include src="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{organisationsBean.organisationsInactives}" />
<ui:param name="label" value="Inactives" />
<ui:param name="icon" value="pi pi-times-circle" />
<ui:param name="bgColor" value="orange" />
</ui:include>
</div>
<!-- Filtres et recherche (DRY/WOU: filter-bar) -->
<ui:decorate template="/templates/components/cards/filter-bar.xhtml">
<ui:param name="title" value="Filtres" />
<ui:param name="styleClass" value="mb-3" />
<ui:define name="filters">
<div class="col-12 md:col-4">
<div class="field">
<p:outputLabel for="rechercheGlobale" value="Rechercher" />
<p:inputText id="rechercheGlobale"
value="#{organisationsBean.rechercheGlobale}"
placeholder="Nom, ville, description..."
styleClass="w-full">
<p:ajax event="keyup" update="dtOrganisations" listener="#{organisationsBean.appliquerFiltres}" delay="500"/>
</p:inputText>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- EN-TÊTE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="card mb-3">
<div class="formgrid grid align-items-center">
<div class="field col-12 lg:col-8 mb-0">
<h2 class="text-primary font-bold mb-1 mt-0">
<i class="pi pi-building text-blue-500 mr-2"></i>
Organisations
</h2>
<p class="text-600 m-0">
Administration des organisations ·
<span class="font-semibold text-900">#{organisationsBean.totalOrganisations}</span> au total ·
<span class="font-semibold text-green-600">#{organisationsBean.organisationsActives} active(s)</span>
</p>
</div>
<div class="col-12 md:col-2">
<div class="field">
<p:outputLabel for="filtreStatut" value="Statut" />
<p:selectOneMenu id="filtreStatut"
value="#{organisationsBean.filtreStatut}"
styleClass="w-full">
<f:selectItems value="#{organisationsBean.statutsSelectItems}" />
<p:ajax event="change" update="dtOrganisations" listener="#{organisationsBean.appliquerFiltres}"/>
</p:selectOneMenu>
</div>
</div>
<div class="col-12 md:col-2">
<div class="field">
<p:outputLabel for="filtreType" value="Type" />
<p:selectOneMenu id="filtreType"
value="#{organisationsBean.filtreType}"
styleClass="w-full">
<f:selectItems value="#{organisationsBean.typesSelectItems}" />
<p:ajax event="change" update="dtOrganisations" listener="#{organisationsBean.appliquerFiltres}"/>
</p:selectOneMenu>
</div>
</div>
</ui:define>
<ui:define name="actions">
<div class="col-12 md:col-2">
<div class="field">
<label class="invisible">Actions</label>
<p:commandButton value="Réinitialiser"
icon="pi pi-filter-slash"
action="#{organisationsBean.reinitialiserFiltres}"
update="dtOrganisations rechercheGlobale filtreStatut filtreType"
styleClass="ui-button-secondary w-full" />
</div>
</div>
<div class="col-12 md:col-2">
<div class="field">
<label class="invisible">Actions</label>
<div class="field col-12 lg:col-4 mb-0 flex justify-content-end gap-2">
<h:form id="formEntete">
<p:commandButton value="Rafraîchir"
icon="pi pi-refresh"
action="#{organisationsBean.recharger}"
update="@form"
styleClass="ui-button-secondary w-full" />
styleClass="ui-button-secondary ui-button-outlined"
actionListener="#{organisationsBean.recharger}"
update=":panelKPIs :formOrganisations" />
</h:form>
<p:button value="Nouvelle organisation"
icon="pi pi-plus"
styleClass="ui-button-success"
outcome="/pages/secure/organisation/nouvelle" />
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- KPIs -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<h:panelGroup id="panelKPIs" layout="block">
<div class="formgrid grid">
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Total Organisations" />
<ui:param name="value" value="#{organisationsBean.totalOrganisations}" />
<ui:param name="icon" value="pi-building" />
<ui:param name="iconColor" value="blue-600" />
<ui:param name="showGrowth" value="false" />
<ui:param name="showProgress" value="false" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Actives" />
<ui:param name="value" value="#{organisationsBean.organisationsActives}" />
<ui:param name="icon" value="pi-check-circle" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="progressValue" value="#{organisationsBean.tauxActivite}" />
<ui:param name="noDataLabel" value="Aucune organisation active" />
<ui:param name="showGrowth" value="false" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Inactives / Suspendues" />
<ui:param name="value" value="#{organisationsBean.organisationsInactives}" />
<ui:param name="icon" value="pi-pause-circle" />
<ui:param name="iconColor" value="orange-600" />
<ui:param name="showGrowth" value="false" />
<ui:param name="showProgress" value="false" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Taux d'activité" />
<ui:param name="value" value="#{organisationsBean.tauxActivite} %" />
<ui:param name="icon" value="pi-chart-pie" />
<ui:param name="iconColor" value="purple-600" />
<ui:param name="progressValue" value="#{organisationsBean.tauxActivite}" />
<ui:param name="showGrowth" value="false" />
</ui:include>
</div>
</h:panelGroup>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- FILTRES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="card mb-3">
<h5 class="mb-3 mt-0">
<i class="pi pi-filter text-blue-500 mr-2"></i>
Filtres et Recherche
</h5>
<h:form id="formFiltres">
<div class="formgrid grid align-items-end">
<div class="field col-12 md:col-6 lg:col-4">
<label for="searchNom" class="block text-900 font-medium mb-2">Nom / Description</label>
<span class="p-input-icon-left w-full">
<i class="pi pi-search"></i>
<p:inputText id="searchNom"
value="#{organisationsBean.rechercheGlobale}"
placeholder="Nom, ville, description..."
styleClass="w-full">
<p:ajax event="keyup" delay="400"
listener="#{organisationsBean.appliquerFiltres}"
update=":formOrganisations:dtOrganisations" />
</p:inputText>
</span>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<label for="filterStatut" class="block text-900 font-medium mb-2">Statut</label>
<p:selectOneMenu id="filterStatut"
value="#{organisationsBean.filtreStatut}"
styleClass="w-full">
<f:selectItems value="#{organisationsBean.statutsSelectItems}" />
<p:ajax listener="#{organisationsBean.appliquerFiltres}"
update=":formOrganisations:dtOrganisations" />
</p:selectOneMenu>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<label for="filterType" class="block text-900 font-medium mb-2">Type</label>
<p:selectOneMenu id="filterType"
value="#{organisationsBean.filtreType}"
styleClass="w-full">
<f:selectItems value="#{organisationsBean.typesSelectItems}" />
<p:ajax listener="#{organisationsBean.appliquerFiltres}"
update=":formOrganisations:dtOrganisations" />
</p:selectOneMenu>
</div>
<div class="field col-12 md:col-6 lg:col-2">
<p:commandButton value="Réinitialiser"
icon="pi pi-filter-slash"
styleClass="ui-button-secondary ui-button-outlined w-full"
actionListener="#{organisationsBean.reinitialiserFiltres}"
update="@form :formOrganisations:dtOrganisations" />
</div>
</div>
</ui:define>
</ui:decorate>
</h:form>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- TABLE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="card">
<h:form id="formOrganisations">
<p:messages id="messages" showDetail="true" closable="true" styleClass="mb-3" />
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">
<i class="pi pi-list text-primary mr-2"></i>
Liste des Organisations
</h5>
</div>
<!-- Table des organisations (DRY/WOU: card-simple) -->
<ui:decorate template="/templates/components/cards/card-simple.xhtml">
<ui:param name="title" value="Liste des Organisations" />
<ui:define name="content">
<!-- Note: p:dataTable avec var, sortBy, filterBy doit être directement dans la page -->
<p:dataTable id="dtOrganisations"
value="#{organisationsBean.organisationsFiltrees}"
var="org"
@@ -124,97 +166,130 @@
rows="20"
rowsPerPageTemplate="10,20,50,100"
paginatorPosition="bottom"
emptyMessage="Aucune organisation trouvée"
styleClass="table-responsive"
size="small">
<!-- Logo (DRY/WOU: organisation-logo) -->
<p:column headerText="" style="width: 80px;">
<ui:include src="/templates/components/layout/organisation-logo.xhtml">
<ui:param name="logo" value="#{org.logo}"/>
<ui:param name="size" value="40"/>
</ui:include>
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords} organisations"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
emptyMessage="Aucune organisation trouvée">
<!-- Organisation : nom + type -->
<p:column headerText="Organisation" sortBy="#{org.nom}">
<div class="flex align-items-center">
<div class="flex align-items-center justify-content-center bg-primary-100 border-circle mr-3"
style="width: 2.5rem; height: 2.5rem; min-width: 2.5rem;">
<i class="pi pi-building text-primary-600"></i>
</div>
<div>
<div class="text-900 font-semibold">#{org.nom}</div>
<div class="text-500 text-xs">
#{empty org.nomCourt ? '' : org.nomCourt}
</div>
</div>
</div>
</p:column>
<!-- Nom avec sous-titre -->
<p:column headerText="Nom" sortBy="#{org.nom}" filterBy="#{org.nom}">
<h:outputText value="#{org.nom}" styleClass="font-bold"/>
<br/>
<h:outputText value="#{org.typeLibelle}" styleClass="text-sm text-500"/>
</p:column>
<!-- Type -->
<p:column headerText="Type" sortBy="#{org.typeAssociation}" style="width: 150px;">
<p:tag value="#{org.typeLibelle}"
severity="#{org.typeAssociation == 'LIONS_CLUB' ? 'info' : 'primary'}"/>
<p:column headerText="Type" sortBy="#{org.typeAssociation}" style="width: 11rem;">
<p:tag value="#{org.typeLibelle}"
severity="info"
styleClass="text-xs" />
</p:column>
<!-- Localisation -->
<p:column headerText="Localisation" style="width: 200px;">
<i class="pi pi-map-marker mr-2 text-500"/>
<h:outputText value="#{org.ville}, #{org.region}"/>
<p:column headerText="Localisation" style="width: 13rem;">
<div class="flex align-items-center text-700 text-sm">
<i class="pi pi-map-marker text-400 mr-1"></i>
<span>
#{empty org.ville ? '' : org.ville}#{(not empty org.ville and not empty org.region) ? ', ' : ''}#{empty org.region ? '' : org.region}
</span>
</div>
<div class="text-400 text-xs mt-1" rendered="#{not empty org.pays}">
#{org.pays}
</div>
</p:column>
<!-- Membres -->
<p:column headerText="Membres" styleClass="text-center" style="width: 100px;">
<p:tag value="#{org.nombreMembres}" severity="info" icon="pi pi-users"/>
<p:column headerText="Membres" style="width: 7rem; text-align: center;">
<div class="text-center">
<span class="text-900 font-bold text-lg">
#{empty org.nombreMembres ? 0 : org.nombreMembres}
</span>
<div class="text-500 text-xs">membres</div>
</div>
</p:column>
<!-- Statut -->
<p:column headerText="Statut" sortBy="#{org.statut}" styleClass="text-center" style="width: 100px;">
<p:tag value="#{org.statut}"
severity="#{org.statut == organisationsBean.statutActive ? 'success' : 'warning'}"
icon="#{org.statut == organisationsBean.statutActive ? 'pi pi-check' : 'pi pi-times'}"/>
<p:column headerText="Statut" sortBy="#{org.statut}" style="width: 9rem; text-align: center;">
<p:tag value="#{org.statut}"
severity="#{org.statut == organisationsBean.statutActive ? 'success' :
(org.statut == organisationsBean.statutSuspendue ? 'danger' :
(org.statut == organisationsBean.statutDissoute ? 'danger' : 'warning'))}"
icon="pi #{org.statut == organisationsBean.statutActive ? 'pi-check' :
(org.statut == organisationsBean.statutSuspendue ? 'pi-ban' : 'pi-pause')}"
styleClass="text-xs w-full" />
</p:column>
<!-- Actions (DRY/WOU: Composite Components) -->
<p:column headerText="Actions" style="width:200px">
<!-- Actions -->
<p:column headerText="Actions" style="width: 10rem;" exportable="false">
<div class="flex gap-1">
<!-- DRY/WOU: Composite Component action-button-view -->
<uf:action-button-view itemId="#{org.id.toString()}"
detailPage="/pages/secure/organisation/detail.xhtml"
iconOnly="true"/>
<!-- DRY/WOU: button-icon pour Modifier -->
<p:button icon="pi pi-eye"
title="Voir le détail"
styleClass="ui-button-rounded ui-button-text ui-button-info ui-button-sm"
href="/pages/secure/organisation/detail.xhtml?id=#{org.id}" />
<p:commandButton icon="pi pi-pencil"
actionListener="#{organisationsBean.setOrganisationSelectionnee(org)}"
oncomplete="PF('dlgModifier').show();"
styleClass="ui-button-rounded ui-button-text ui-button-warning ui-button-sm"
title="Modifier"
update=":formModifier"
styleClass="ui-button-rounded ui-button-warning"
title="Modifier"/>
<!-- DRY/WOU: button-icon pour Activer/Désactiver -->
<p:commandButton icon="#{org.statut == organisationsBean.statutActive ? 'pi pi-ban' : 'pi pi-check'}"
actionListener="#{organisationsBean.basculerStatutOrganisation(org)}"
update=":formOrganisations:dtOrganisations :formOrganisations:messages"
styleClass="ui-button-rounded #{org.statut == organisationsBean.statutActive ? 'ui-button-secondary' : 'ui-button-success'}"
title="#{org.statut == organisationsBean.statutActive ? 'Désactiver' : 'Activer'}">
<p:confirm header="Confirmation"
message="Êtes-vous sûr de vouloir changer le statut de cette organisation ?"
icon="pi pi-exclamation-triangle"/>
oncomplete="PF('dlgModifier').show();">
<f:setPropertyActionListener target="#{organisationsBean.organisationSelectionnee}" value="#{org}" />
</p:commandButton>
<!-- DRY/WOU: button-icon pour Supprimer -->
<p:commandButton icon="pi pi-trash"
actionListener="#{organisationsBean.supprimerOrganisation(org)}"
update=":formOrganisations:dtOrganisations :formOrganisations:messages"
styleClass="ui-button-rounded ui-button-danger"
title="Supprimer">
<p:commandButton icon="#{org.statut == organisationsBean.statutActive ? 'pi pi-ban' : 'pi pi-check'}"
styleClass="ui-button-rounded ui-button-text ui-button-sm #{org.statut == organisationsBean.statutActive ? 'ui-button-secondary' : 'ui-button-success'}"
title="#{org.statut == organisationsBean.statutActive ? 'Désactiver' : 'Activer'}"
actionListener="#{organisationsBean.basculerStatutOrganisation(org)}"
update=":panelKPIs :formOrganisations:dtOrganisations :formOrganisations:messages">
<p:confirm header="Confirmation"
message="Êtes-vous sûr de vouloir supprimer cette organisation ? Cette action est irréversible."
icon="pi pi-exclamation-triangle"/>
message="Changer le statut de cette organisation ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<p:commandButton icon="pi pi-trash"
styleClass="ui-button-rounded ui-button-text ui-button-danger ui-button-sm"
title="Supprimer"
actionListener="#{organisationsBean.supprimerOrganisation(org)}"
update=":panelKPIs :formOrganisations:dtOrganisations :formOrganisations:messages">
<p:confirm header="Confirmation"
message="Supprimer définitivement cette organisation ? Cette action est irréversible."
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</div>
</p:column>
</p:dataTable>
</ui:define>
</ui:decorate>
<!-- Dialogue de confirmation global (DRY/WOU: confirm-dialog) -->
<ui:include src="/templates/components/dialogs/confirm-dialog.xhtml" />
</h:form>
<!-- Dialogue Modifier Organisation (DRY/WOU: form-dialog) -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade" responsive="true" width="380">
<p:commandButton value="Non"
type="button"
styleClass="ui-button-secondary ui-button-outlined"
icon="pi pi-times"
onclick="PF('confirmDialog').hide()" />
<p:commandButton value="Oui"
type="button"
styleClass="ui-button-danger"
icon="pi pi-check" />
</p:confirmDialog>
</h:form>
</div>
</div><!-- /ui-fluid -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- DIALOGUE MODIFICATION -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<ui:decorate template="/templates/components/dialogs/form-dialog.xhtml">
<ui:param name="dialogId" value="dlgModifier" />
<ui:param name="header" value="Modifier Organisation" />
<ui:param name="header" value="Modifier l'organisation" />
<ui:param name="widgetVar" value="dlgModifier" />
<ui:param name="formId" value="formModifier" />
<ui:param name="width" value="900" />
@@ -225,27 +300,24 @@
<ui:include src="/ui/includes/organisation-form.xhtml">
<ui:param name="model" value="#{organisationsBean.organisationSelectionnee}" />
<ui:param name="typesItems" value="#{organisationsBean.typesSelectItemsForForm}" />
<ui:param name="completionBean" value="#{organisationsBean}" />
</ui:include>
</ui:fragment>
</ui:define>
<ui:define name="footer">
<!-- Bouton Annuler : type="button" pour éviter la soumission -->
<p:commandButton value="Annuler"
icon="pi pi-times"
type="button"
onclick="PF('dlgModifier').hide();"
styleClass="ui-button-secondary" />
<!-- DRY/WOU: button-form-submit pour action backend -->
<ui:decorate template="/templates/components/buttons/button-form-submit.xhtml">
<ui:param name="value" value="Enregistrer" />
<ui:param name="icon" value="pi pi-check" />
<ui:param name="action" value="#{organisationsBean.modifierOrganisation}" />
<ui:param name="update" value=":formOrganisations:dtOrganisations :formOrganisations:messages" />
<ui:param name="oncomplete" value="if(!args.validationFailed) PF('dlgModifier').hide();" />
<ui:param name="severity" value="success" />
</ui:decorate>
styleClass="ui-button-secondary ui-button-outlined" />
<p:commandButton value="Enregistrer"
icon="pi pi-check"
actionListener="#{organisationsBean.modifierOrganisation}"
update=":panelKPIs :formOrganisations:dtOrganisations :formOrganisations:messages"
oncomplete="if(!args.validationFailed) PF('dlgModifier').hide();"
styleClass="ui-button-success" />
</ui:define>
</ui:decorate>
</ui:define>
</ui:composition>
</html>

View File

@@ -1,64 +1,113 @@
<!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">
<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:composition template="/templates/main-template.xhtml">
<ui:param name="page" value="#{organisationsBean}"/>
<ui:define name="title">Nouvelle Organisation</ui:define>
<ui:define name="content">
<!-- Initialiser le modèle et le catalogue des types à chaque affichage de la vue -->
<f:event type="preRenderView" listener="#{organisationsBean.preparerNouvelleOrganisation}" />
<h:form id="formNouvelleOrganisation">
<p:messages id="messages" showDetail="true" closable="true"/>
<div class="card mb-3">
<div class="flex justify-content-between align-items-center flex-column md:flex-row">
<div class="mb-2 md:mb-0">
<h3 class="m-0">Nouvelle Organisation</h3>
<span class="text-600">
Renseignez l'ensemble des informations de l'organisation.
</span>
<!-- En-tête avec titre et actions (DRY/WOU: card-header) -->
<ui:decorate template="/templates/components/cards/card-header.xhtml">
<ui:param name="title" value="Nouvelle Organisation" />
<ui:param name="subtitle" value="Créez une nouvelle organisation en renseignant les informations essentielles." />
<ui:param name="icon" value="pi pi-building" />
<ui:param name="styleClass" value="mb-3" />
<ui:define name="actions">
<!-- Bouton Enregistrer (MethodExpression direct) -->
<p:commandButton value="Enregistrer"
icon="pi pi-save"
action="#{organisationsBean.creerOrganisation}"
update="formNouvelleOrganisation messages"
styleClass="p-button-success p-button-lg"
validateClient="true">
<p:confirm header="Confirmation"
message="Voulez-vous créer cette organisation ?"
icon="pi pi-question-circle"/>
</p:commandButton>
<!-- DRY/WOU: button-secondary pour Annuler -->
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler"/>
<ui:param name="icon" value="pi pi-times"/>
<ui:param name="outcome" value="/pages/secure/organisation/liste"/>
</ui:include>
</ui:define>
<ui:define name="info">
<p class="text-500 text-sm m-0">
<i class="pi pi-asterisk text-red-500 mr-1" style="font-size: 0.5rem;"/>
Les champs marqués d'un astérisque sont obligatoires
</p>
</ui:define>
</ui:decorate>
<!-- Messages globaux -->
<p:messages id="messages" showDetail="true" closable="true" styleClass="mb-3"/>
<!-- Formulaire (DRY/WOU: card-simple) -->
<ui:decorate template="/templates/components/cards/card-simple.xhtml">
<ui:param name="styleClass" value="shadow-2" />
<ui:define name="content">
<!-- DRY/WOU: organisation-form.xhtml include réutilisable -->
<ui:include src="/ui/includes/organisation-form.xhtml">
<ui:param name="model" value="#{organisationsBean.nouvelleOrganisation}" />
<ui:param name="typesItems" value="#{organisationsBean.typesSelectItemsForForm}" />
<ui:param name="completionBean" value="#{organisationsBean}" />
</ui:include>
<!-- Actions du formulaire (bas de page) -->
<p:divider styleClass="mt-5"/>
<div class="flex justify-content-between align-items-center flex-column md:flex-row gap-3 mt-4">
<div class="text-600">
<i class="pi pi-shield mr-2"/>
Toutes les données sont sécurisées et conformes
</div>
<div class="flex gap-2">
<!-- Bouton Réinitialiser avec confirmation -->
<p:commandButton value="Réinitialiser"
icon="pi pi-refresh"
action="#{organisationsBean.preparerNouvelleOrganisation}"
update="formNouvelleOrganisation"
styleClass="p-button-outlined p-button-warning"
immediate="true">
<p:confirm header="Confirmation"
message="Réinitialiser le formulaire ? Toutes les données seront perdues."
icon="pi pi-exclamation-triangle"/>
</p:commandButton>
<!-- DRY/WOU: button-secondary pour Annuler -->
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler"/>
<ui:param name="icon" value="pi pi-times"/>
<ui:param name="outcome" value="/pages/secure/organisation/liste"/>
</ui:include>
<!-- Bouton Créer l'organisation (MethodExpression direct) -->
<p:commandButton value="Créer l'organisation"
icon="pi pi-check"
action="#{organisationsBean.creerOrganisation}"
update="formNouvelleOrganisation messages"
styleClass="p-button-success p-button-lg"
validateClient="true">
<p:confirm header="Confirmation de création"
message="Créer cette organisation avec les informations saisies ?"
icon="pi pi-question-circle"/>
</p:commandButton>
</div>
</div>
<div class="flex gap-2">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler"/>
<ui:param name="icon" value="pi pi-times"/>
<ui:param name="outcome" value="/pages/secure/organisation/liste"/>
</ui:include>
</div>
</div>
</div>
</ui:define>
</ui:decorate>
<div class="card">
<h5 class="mb-3">Informations de l'Organisation</h5>
<ui:include src="/ui/includes/organisation-form.xhtml">
<ui:param name="model" value="#{organisationsBean.nouvelleOrganisation}" />
<ui:param name="typesItems" value="#{organisationsBean.typesSelectItemsForForm}" />
</ui:include>
</div>
<!-- Dialog de confirmation global (DRY/WOU: confirm-dialog) -->
<ui:include src="/templates/components/dialogs/confirm-dialog.xhtml" />
<div class="mt-3 flex justify-content-end gap-2">
<!-- DRY/WOU: button-secondary pour navigation -->
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Annuler"/>
<ui:param name="icon" value="pi pi-times"/>
<ui:param name="outcome" value="/pages/secure/organisation/liste"/>
</ui:include>
<!-- Bouton Créer : p:commandButton direct car action avec méthode backend -->
<p:commandButton value="Créer"
icon="pi pi-check"
action="#{organisationsBean.creerOrganisation}"
update=":formNouvelleOrganisation:messages"
styleClass="ui-button-success" />
</div>
</h:form>
</ui:define>
</ui:composition>
</html>

View File

@@ -164,9 +164,11 @@
<div class="field">
<p:outputLabel for="devise" value="Devise" />
<p:selectOneMenu id="devise" value="#{configBean.config.devise}">
<f:selectItem itemLabel="FCFA (XOF)" itemValue="XOF" />
<f:selectItem itemLabel="Euro (EUR)" itemValue="EUR" />
<f:selectItem itemLabel="Dollar (USD)" itemValue="USD" />
<f:selectItem itemLabel="XOF — Franc CFA (UEMOA)" itemValue="XOF" />
<f:selectItem itemLabel="XAF — Franc CFA (CEMAC)" itemValue="XAF" />
<f:selectItem itemLabel="MAD — Dirham marocain" itemValue="MAD" />
<f:selectItem itemLabel="NGN — Naira nigérian" itemValue="NGN" />
<f:selectItem itemLabel="GHS — Cédi ghanéen" itemValue="GHS" />
</p:selectOneMenu>
</div>

View File

@@ -156,11 +156,12 @@
<p:selectOneMenu id="devise"
value="#{configurationBean.deviseDefaut}"
styleClass="w-full">
<f:selectItem itemLabel="FCFA (Franc CFA)" itemValue="XOF" />
<f:selectItem itemLabel="EUR (Euro)" itemValue="EUR" />
<f:selectItem itemLabel="USD (Dollar)" itemValue="USD" />
<f:selectItem itemLabel="GHS (Cédi Ghana)" itemValue="GHS" />
<f:selectItem itemLabel="NGN (Naira Nigeria)" itemValue="NGN" />
<f:selectItem itemLabel="XOF — Franc CFA (UEMOA)" itemValue="XOF" />
<f:selectItem itemLabel="XAF — Franc CFA (CEMAC)" itemValue="XAF" />
<f:selectItem itemLabel="MAD — Dirham marocain" itemValue="MAD" />
<f:selectItem itemLabel="GHS Cédi ghanéen" itemValue="GHS" />
<f:selectItem itemLabel="NGN Naira nigérian" itemValue="NGN" />
<f:selectItem itemLabel="ZAR — Rand sud-africain" itemValue="ZAR" />
</p:selectOneMenu>
</div>

View File

@@ -119,10 +119,7 @@
<p:selectOneMenu id="filterType"
value="#{entitesGestionBean.filtres.type}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItem itemLabel="Club Lions" itemValue="CLUB_LIONS" />
<f:selectItem itemLabel="LEO Club" itemValue="LEO_CLUB" />
<f:selectItem itemLabel="Branche" itemValue="BRANCHE" />
<f:selectItems value="#{entitesGestionBean.typesSelectItems}" />
<p:ajax update=":formTableEntites:dtEntites" />
</p:selectOneMenu>
</div>
@@ -145,11 +142,7 @@
<p:selectOneMenu id="filterRegion"
value="#{entitesGestionBean.filtres.region}"
styleClass="w-full">
<f:selectItem itemLabel="Toutes les régions" itemValue="" />
<f:selectItem itemLabel="Dakar" itemValue="DAKAR" />
<f:selectItem itemLabel="Thiès" itemValue="THIES" />
<f:selectItem itemLabel="Kaolack" itemValue="KAOLACK" />
<f:selectItem itemLabel="Saint-Louis" itemValue="SAINT_LOUIS" />
<f:selectItems value="#{entitesGestionBean.regionsDisponibles}" />
<p:ajax update=":formTableEntites:dtEntites" />
</p:selectOneMenu>
</div>
@@ -259,26 +252,23 @@
</p:column>
<p:column headerText="Actions" style="width:8rem" exportable="false">
<h:form id="formActions#{entite.id}">
<div class="flex gap-1">
<p:commandButton icon="pi pi-eye"
styleClass="ui-button-rounded ui-button-text ui-button-info ui-button-sm"
action="#{entitesGestionBean.voirEntite(entite)}"
title="Voir détails" />
<p:commandButton icon="pi pi-pencil"
styleClass="ui-button-rounded ui-button-text ui-button-warning ui-button-sm"
onclick="PF('dlgModifierEntite').show();"
title="Modifier">
<f:setPropertyActionListener target="#{entitesGestionBean.entiteSelectionne}" value="#{entite}" />
</p:commandButton>
<p:commandButton icon="pi pi-cog"
styleClass="ui-button-rounded ui-button-text ui-button-secondary ui-button-sm"
onclick="PF('dlgActionsEntite').show();"
title="Actions">
<f:setPropertyActionListener target="#{entitesGestionBean.entiteSelectionne}" value="#{entite}" />
</p:commandButton>
</div>
</h:form>
<div class="flex gap-1">
<p:button icon="pi pi-eye"
title="Voir détails"
styleClass="ui-button-rounded ui-button-text ui-button-info ui-button-sm"
href="/pages/secure/organisation/detail.xhtml?id=#{entite.id}" />
<p:button icon="pi pi-pencil"
styleClass="ui-button-rounded ui-button-text ui-button-warning ui-button-sm"
title="Modifier"
href="/pages/secure/organisation/detail.xhtml?id=#{entite.id}&amp;mode=edit" />
<p:commandButton icon="pi pi-cog"
styleClass="ui-button-rounded ui-button-text ui-button-secondary ui-button-sm"
title="Actions"
update=":formActionsEntite"
oncomplete="PF('dlgActionsEntite').show();">
<f:setPropertyActionListener target="#{entitesGestionBean.entiteSelectionne}" value="#{entite}" />
</p:commandButton>
</div>
</p:column>
</p:dataTable>
</h:form>
@@ -308,25 +298,19 @@
<label for="newType" class="block text-900 font-medium mb-2">Type d'entité *</label>
<p:selectOneMenu id="newType"
value="#{entitesGestionBean.nouvelleEntite.type}"
required="true">
<f:selectItem itemLabel="Sélectionner un type" itemValue="" />
<f:selectItem itemLabel="Club Lions" itemValue="CLUB_LIONS" />
<f:selectItem itemLabel="LEO Club" itemValue="LEO_CLUB" />
<f:selectItem itemLabel="Branche" itemValue="BRANCHE" />
required="true"
requiredMessage="Le type d'entité est requis.">
<f:selectItems value="#{entitesGestionBean.typesSelectItemsForForm}" />
</p:selectOneMenu>
<p:message for="newType" />
</div>
<div class="field col-12 md:col-6">
<label for="newRegion" class="block text-900 font-medium mb-2">Région *</label>
<p:selectOneMenu id="newRegion"
value="#{entitesGestionBean.nouvelleEntite.region}"
required="true">
<f:selectItem itemLabel="Sélectionner une région" itemValue="" />
<f:selectItem itemLabel="Dakar" itemValue="DAKAR" />
<f:selectItem itemLabel="Thiès" itemValue="THIES" />
<f:selectItem itemLabel="Kaolack" itemValue="KAOLACK" />
<f:selectItem itemLabel="Saint-Louis" itemValue="SAINT_LOUIS" />
</p:selectOneMenu>
<label for="newRegion" class="block text-900 font-medium mb-2">Région</label>
<p:inputText id="newRegion"
value="#{entitesGestionBean.nouvelleEntite.region}"
placeholder="Ex: Lagunes, Dakar, Abidjan..."
maxlength="100" />
</div>
<div class="field col-12 md:col-6">
@@ -394,38 +378,47 @@
<p:commandButton value="Gérer les membres"
icon="pi pi-users"
styleClass="ui-button-info ui-button-outlined ui-button-sm w-full"
action="#{entitesGestionBean.gererMembres}" />
action="#{entitesGestionBean.gererMembres}"
ajax="false" />
<p:commandButton value="Configuration"
icon="pi pi-cog"
<p:commandButton value="Voir / Configurer"
icon="pi pi-eye"
styleClass="ui-button-warning ui-button-outlined ui-button-sm w-full"
action="#{entitesGestionBean.configurerEntite}" />
action="#{entitesGestionBean.configurerEntite}"
ajax="false" />
<p:commandButton value="Rapports"
<p:commandButton value="Rapports &amp; Stats"
icon="pi pi-chart-bar"
styleClass="ui-button-secondary ui-button-outlined ui-button-sm w-full"
action="#{entitesGestionBean.voirRapports}" />
action="#{entitesGestionBean.voirRapports}"
ajax="false" />
<p:commandButton value="Suspendre"
icon="pi pi-ban"
styleClass="ui-button-danger ui-button-outlined ui-button-sm w-full"
action="#{entitesGestionBean.suspendreEntite}"
onclick="return confirm('Êtes-vous sûr de vouloir suspendre cette entité ?');"
update=":formTableEntites:dtEntites :formActionsEntite"
oncomplete="PF('dlgActionsEntite').hide();"
onclick="return confirm('Suspendre cette entité ?');"
rendered="#{entitesGestionBean.entiteSelectionne.statut == 'ACTIVE'}" />
<p:commandButton value="Réactiver"
icon="pi pi-check"
styleClass="ui-button-success ui-button-outlined ui-button-sm w-full"
action="#{entitesGestionBean.reactiverEntite}"
update=":formTableEntites:dtEntites :formActionsEntite"
oncomplete="PF('dlgActionsEntite').hide();"
rendered="#{entitesGestionBean.entiteSelectionne.statut == 'SUSPENDUE'}" />
<hr class="surface-border" />
<p:commandButton value="Supprimer"
<p:commandButton value="Supprimer définitivement"
icon="pi pi-trash"
styleClass="ui-button-danger ui-button-sm w-full"
onclick="return confirm('ATTENTION: Cette action est irréversible. Confirmer la suppression ?');"
action="#{entitesGestionBean.supprimerEntite}" />
action="#{entitesGestionBean.supprimerEntite}"
update=":formTableEntites:dtEntites"
oncomplete="PF('dlgActionsEntite').hide();"
onclick="return confirm('ATTENTION : action irréversible. Confirmer la suppression ?');" />
</div>
</h:form>
</p:dialog>

View File

@@ -162,6 +162,7 @@
<ui:include src="/ui/includes/organisation-form.xhtml">
<ui:param name="model" value="#{organisationsBean.nouvelleOrganisation}" />
<ui:param name="typesItems" value="#{organisationsBean.typesSelectItemsForForm}" />
<ui:param name="completionBean" value="#{organisationsBean}" />
</ui:include>
<f:facet name="footer">
@@ -191,6 +192,7 @@
<ui:include src="/ui/includes/organisation-form.xhtml">
<ui:param name="model" value="#{organisationsBean.organisationSelectionnee}" />
<ui:param name="typesItems" value="#{organisationsBean.typesSelectItemsForForm}" />
<ui:param name="completionBean" value="#{organisationsBean}" />
</ui:include>
<f:facet name="footer">

View File

@@ -2,7 +2,8 @@
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:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
Composant bouton icône seule réutilisable (WOU/DRY)
@@ -20,9 +21,9 @@
<ui:fragment rendered="#{empty rendered or rendered}">
<p:commandButton
icon="#{icon}"
action="#{action}"
update="#{update}"
onclick="#{onclick}"
type="button"
disabled="#{not empty disabled and disabled}"
styleClass="#{not empty rounded and rounded ? 'ui-button-rounded' : ''} #{not empty text and text ? 'ui-button-text' : ''} #{not empty severity ? 'ui-button-' += severity : ''} #{not empty styleClass ? styleClass : ''}"
title="#{title}" />

View File

@@ -15,21 +15,21 @@
-->
<div class="col-12 md:col-3">
<h:panelGroup layout="block"
styleClass="card #{bgColor == 'blue' ? 'bg-blue-100 border-left-3 border-blue-500' : (bgColor == 'green' ? 'bg-green-100 border-left-3 border-green-500' : (bgColor == 'orange' ? 'bg-orange-100 border-left-3 border-orange-500' : 'bg-blue-100 border-left-3 border-blue-500'))}">
<h:panelGroup layout="block"
styleClass="card bg-#{bgColor}-100 border-left-3 border-#{bgColor}-500">
<div class="flex justify-content-between">
<div>
<h:panelGroup layout="block"
styleClass="font-bold text-xl #{bgColor == 'blue' ? 'text-blue-900' : (bgColor == 'green' ? 'text-green-900' : (bgColor == 'orange' ? 'text-orange-900' : 'text-blue-900'))}">
styleClass="font-bold text-xl text-#{bgColor}-900">
<h:outputText value="#{value}" />
</h:panelGroup>
<h:panelGroup layout="block"
styleClass="#{bgColor == 'blue' ? 'text-blue-700' : (bgColor == 'green' ? 'text-green-700' : (bgColor == 'orange' ? 'text-orange-700' : 'text-blue-700'))}">
styleClass="text-#{bgColor}-700">
<h:outputText value="#{label}" />
</h:panelGroup>
</div>
<h:panelGroup layout="block"
styleClass="text-white border-round text-center #{bgColor == 'blue' ? 'bg-blue-500' : (bgColor == 'green' ? 'bg-green-500' : (bgColor == 'orange' ? 'bg-orange-500' : 'bg-blue-500'))}"
styleClass="text-white border-round text-center bg-#{bgColor}-500"
style="width: 2.5rem; height: 2.5rem; line-height: 2.5rem;">
<i class="#{icon} text-lg"></i>
</h:panelGroup>

View File

@@ -1,7 +1,5 @@
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:p="http://primefaces.org/ui">
<!--
Composant colonne Texte avec icône réutilisable - DRY/WOU
@@ -14,12 +12,13 @@
</ui:include>
-->
<p:column headerText="#{headerText}"
style="#{not empty width ? 'width: ' += width : ''}">
<p:column headerText="#{headerText}" sortBy="#{sortBy}" filterBy="#{filterBy}"
filterMatchMode="#{empty filterMatchMode ? 'contains' : filterMatchMode}"
style="#{not empty width ? 'width: ' += width : ''}">
<ui:fragment rendered="#{not empty icon}">
<i class="#{icon} mr-1"></i>
<i class="#{icon} mr-1 text-primary"></i>
</ui:fragment>
<h:outputText value="#{text}"/>
<h:outputText value="#{text}" styleClass="#{not empty styleClass ? styleClass : ''}" />
<ui:insert />
</p:column>
</ui:composition>
</ui:composition>

View File

@@ -11,22 +11,19 @@
<ui:param name="value" value="#{bean.property}"/>
<ui:param name="multiline" value="true" />
</ui:include>
Note : utilise l'opérateur EL `empty` pour gérer correctement null,
chaînes vides, 0 (entier valide) et false (booléen valide).
-->
<div class="mb-3">
<div class="text-600 text-sm mb-1">
<h:outputText value="#{label}" />
<h:panelGroup rendered="#{not empty value}">
<div class="mb-4">
<div style="font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--text-color-secondary);opacity:.7;margin-bottom:.35rem;">
<h:outputText value="#{label}" />
</div>
<div style="font-size:.925rem;font-weight:600;color:var(--text-color);#{multiline ? 'white-space:pre-line;line-height:1.6;' : ''}">
<h:outputText value="#{value}" escape="true" />
</div>
</div>
<div class="text-900">
<h:outputText value="#{value}"
rendered="#{value != null}"
escape="true"
style="#{multiline ? 'white-space: pre-line;' : ''}" />
<h:outputText value="Non renseigné"
rendered="#{value == null}"
styleClass="text-400" />
</div>
</div>
</h:panelGroup>
</ui:composition>

View File

@@ -1,57 +1,12 @@
<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">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<div class="layout-footer">
<div class="grid">
<div class="col-12 lg:col-4">
<div class="grid">
<div class="col-6">
<span class="footer-menutitle">NAVIGATION</span>
<ul>
<li><a href="/pages/secure/dashboard">Tableau de Bord</a></li>
<li><a href="/pages/secure/membre/liste">Membres</a></li>
<li><a href="/pages/secure/adhesion/liste">Adhésions</a></li>
<li><a href="/pages/secure/aide/documentation">Aide</a></li>
</ul>
</div>
<div class="col-6">
<span class="footer-menutitle"></span>
<ul>
<li><a href="/pages/secure/evenement/calendrier">Calendrier</a></li>
<li><a href="/pages/secure/cotisation/historique">Cotisations</a></li>
<li><a href="/pages/secure/rapport/membres">Rapports</a></li>
<li><a href="/pages/secure/admin/parametres">Paramètres</a></li>
</ul>
</div>
</div>
</div>
<div class="col-12 md:col-6 lg:col-3">
<span class="footer-menutitle">CONTACT</span>
<ul>
<li>+221 77 123 45 67</li>
<li>Plateau, Dakar,</li>
<li>Sénégal</li>
<li>contact@unionflow.sn</li>
</ul>
</div>
<div class="col-12 md:col-6 lg:col-5">
<span class="footer-menutitle">NEWSLETTER</span>
<span class="footer-subtitle">Rejoignez notre newsletter pour être informé des nouvelles fonctionnalités.</span>
<h:form>
<div class="newsletter-input">
<p:inputText placeholder="adresse email" />
<p:commandButton value="S'abonner" styleClass="ui-button-secondary" />
</div>
</h:form>
</div>
<div class="col-12">
<div class="footer-bottom">
<h4>UnionFlow</h4>
<h6>Copyright © Lions Dev Team</h6>
</div>
</div>
<div class="footer-bottom" style="justify-content:space-between;align-items:center;flex-wrap:wrap;gap:.5rem;">
<h4 style="margin:0;">UnionFlow</h4>
<h6 style="margin:0;opacity:.7;">Copyright &#169; Lions Dev Team</h6>
</div>
</div>
</ui:composition>
</ui:composition>

View File

@@ -21,7 +21,7 @@
<p:menuitem id="m_dashboard" value="Tableau de Bord" icon="pi pi-home" outcome="/pages/secure/dashboard" />
<!-- Super Administration -->
<p:submenu id="m_super_admin" label="Super Administration" icon="pi pi-shield">
<p:submenu id="m_super_admin" label="Super Administration" icon="pi pi-shield" rendered="#{menuBean.superAdminMenuVisible}">
<p:menuitem id="m_super_dashboard" value="Dashboard Super-Admin" icon="pi pi-chart-bar" outcome="/pages/super-admin/dashboard" />
<p:menuitem id="m_gestion_entites" value="Gestion des Entités" icon="pi pi-sitemap" outcome="/pages/super-admin/entites/gestion" />
<p:menuitem id="m_gestion_types_organisations" value="Types d'Organisation" icon="pi pi-tags" outcome="/pages/super-admin/types/organisations" />
@@ -29,140 +29,154 @@
</p:submenu>
<!-- Administration Générale -->
<p:submenu id="m_administration" label="Administration" icon="pi pi-cog">
<p:menuitem id="m_gestion_cotisations" value="Gestion Cotisations" icon="pi pi-dollar" outcome="/pages/admin/cotisations/gestion" />
<p:menuitem id="m_gestion_evenements" value="Gestion Événements" icon="pi pi-calendar" outcome="/pages/admin/evenements/gestion" />
<p:menuitem id="m_demandes_aide" value="Demandes d'Aide" icon="pi pi-heart" outcome="/pages/admin/demandes/gestion" />
<p:menuitem id="m_rapports_stats" value="Rapports et Statistiques" icon="pi pi-chart-bar" outcome="/pages/admin/rapports/statistiques" />
<p:submenu id="m_administration" label="Administration" icon="pi pi-cog" rendered="#{menuBean.administrationMenuVisible}">
<p:menuitem id="m_gestion_cotisations" value="Gestion Cotisations" icon="pi pi-dollar" outcome="/pages/admin/cotisations/gestion" rendered="#{menuBean.cotisationsAdminVisible}" />
<p:menuitem id="m_gestion_evenements" value="Gestion Événements" icon="pi pi-calendar" outcome="/pages/admin/evenements/gestion" rendered="#{menuBean.creationEvenementVisible}" />
<p:menuitem id="m_demandes_aide" value="Demandes d'Aide" icon="pi pi-heart" outcome="/pages/admin/demandes/gestion" rendered="#{menuBean.traitementAideVisible}" />
<p:menuitem id="m_rapports_stats" value="Rapports et Statistiques" icon="pi pi-chart-bar" outcome="/pages/admin/rapports/statistiques" rendered="#{menuBean.rapportsMenuVisible}" />
<p:menuitem id="m_gestion_documents" value="Gestion Documents" icon="pi pi-folder" outcome="/pages/admin/documents/gestion" />
<p:menuitem id="m_parametres" value="Paramètres Système" icon="pi pi-sliders-h" url="#" />
<p:menuitem id="m_utilisateurs" value="Gestion Utilisateurs" icon="pi pi-users" outcome="/pages/admin/utilisateurs/gestion" />
<p:menuitem id="m_roles" value="Rôles et Permissions" icon="pi pi-key" outcome="/pages/super-admin/roles/gestion" />
<p:menuitem id="m_audit" value="Journal d'Audit" icon="pi pi-file-o" outcome="/pages/admin/audit/journal" />
<p:menuitem id="m_parametres" value="Paramètres Système" icon="pi pi-sliders-h" url="#" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_roles" value="Rôles Applicatifs" icon="pi pi-key" outcome="/pages/super-admin/roles/gestion" rendered="#{menuBean.superAdmin}" />
<p:menuitem id="m_audit" value="Journal d'Audit Applicatif" icon="pi pi-file-o" outcome="/pages/admin/audit/journal" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:separator rendered="#{menuBean.keycloakUserManagerVisible}" />
<!-- Lions User Manager - Gestion Keycloak -->
<p:menuitem id="m_user_manager_list" value="Utilisateurs Keycloak" icon="pi pi-users-cog" outcome="/pages/user-manager/users/list" rendered="#{menuBean.keycloakUserManagerVisible}" />
<p:menuitem id="m_user_manager_create" value="Nouvel Utilisateur Keycloak" icon="pi pi-user-plus" outcome="/pages/user-manager/users/create" rendered="#{menuBean.keycloakUserManagerVisible}" />
<p:menuitem id="m_user_manager_roles" value="Rôles Keycloak" icon="pi pi-shield" outcome="/pages/user-manager/roles/list" rendered="#{menuBean.keycloakUserManagerVisible}" />
<p:menuitem id="m_user_manager_audit" value="Journal d'Audit Keycloak" icon="pi pi-history" outcome="/pages/user-manager/audit/logs" rendered="#{menuBean.keycloakUserManagerVisible}" />
</p:submenu>
<!-- Gestion des Membres -->
<p:submenu id="m_membres" label="Gestion des Membres" icon="pi pi-users">
<!-- Annuaire des Membres (MEMBRE_ACTIF et plus - Consultation) -->
<p:submenu id="m_annuaire" label="Annuaire des Membres" icon="pi pi-users" rendered="#{menuBean.annuaireMembresVisible}">
<p:menuitem id="m_liste_membres_lecture" value="Liste des Membres" icon="pi pi-list" outcome="/pages/secure/membre/liste" />
<p:menuitem id="m_recherche_membres" value="Rechercher un Membre" icon="pi pi-search" outcome="/pages/secure/membre/recherche" />
</p:submenu>
<!-- Gestion des Membres (SECRETAIRE, ADMIN - Administration) -->
<p:submenu id="m_gestion_membres" label="Gestion des Membres" icon="pi pi-users-cog" rendered="#{menuBean.gestionMembresMenuVisible}">
<p:menuitem id="m_inscription" value="Nouvelle Inscription" icon="pi pi-user-plus" outcome="/pages/secure/membre/inscription" />
<p:menuitem id="m_liste_membres" value="Liste des Membres" icon="pi pi-list" outcome="/pages/secure/membre/liste" />
<p:menuitem id="m_recherche_membres" value="Recherche Avancée" icon="pi pi-search" outcome="/pages/secure/membre/recherche" />
<p:menuitem id="m_profil_membre" value="Mon Profil" icon="pi pi-user" outcome="/pages/secure/membre/profil" />
<p:menuitem id="m_liste_membres" value="Liste Complète" icon="pi pi-list" outcome="/pages/secure/membre/liste" />
<p:menuitem id="m_validation_membres" value="Validation Inscriptions" icon="pi pi-check-circle" outcome="/pages/secure/membre/validation" />
<p:menuitem id="m_import_membres" value="Import en Masse" icon="pi pi-upload" outcome="/pages/secure/membre/import" />
<p:menuitem id="m_export_membres" value="Export Membres" icon="pi pi-download" outcome="/pages/secure/membre/export" />
<p:separator />
<!-- Lions User Manager - Gestion Keycloak -->
<p:menuitem id="m_user_manager_list" value="Utilisateurs Keycloak" icon="pi pi-users-cog" outcome="/pages/user-manager/users/list" />
<p:menuitem id="m_user_manager_create" value="Nouvel Utilisateur" icon="pi pi-user-plus" outcome="/pages/user-manager/users/create" />
<p:menuitem id="m_user_manager_roles" value="Gestion des Rôles" icon="pi pi-shield" outcome="/pages/user-manager/roles/list" />
<p:menuitem id="m_user_manager_audit" value="Journal d'Audit" icon="pi pi-history" outcome="/pages/user-manager/audit/logs" />
</p:submenu>
<!-- Gestion des Organisations -->
<p:submenu id="m_organisations" label="Organisations" icon="pi pi-building">
<p:submenu id="m_organisations" label="Organisations" icon="pi pi-building" rendered="#{menuBean.organisationsMenuVisible}">
<p:menuitem id="m_liste_organisations" value="Liste des Organisations" icon="pi pi-list" outcome="/pages/secure/organisation/liste" />
<p:menuitem id="m_nouvelle_organisation" value="Nouvelle Organisation" icon="pi pi-plus" outcome="/pages/secure/organisation/nouvelle" />
<p:menuitem id="m_statistiques_orga" value="Statistiques" icon="pi pi-chart-bar" url="#" />
<p:menuitem id="m_statistiques_orga" value="Statistiques" icon="pi pi-chart-bar" outcome="/pages/secure/organisation/statistiques" />
</p:submenu>
<!-- Gestion des Adhésions -->
<p:submenu id="m_adhesions" label="Gestion des Adhésions" icon="pi pi-bookmark">
<p:submenu id="m_adhesions" label="Gestion des Adhésions" icon="pi pi-bookmark" rendered="#{menuBean.adhesionsMenuVisible}">
<p:menuitem id="m_demande_adhesion" value="Nouvelle Demande" icon="pi pi-plus-circle" outcome="/pages/secure/adhesion/demande" />
<p:menuitem id="m_validation_adhesion" value="Validation des Demandes" icon="pi pi-check-circle" outcome="/pages/secure/adhesion/validation" />
<p:menuitem id="m_validation_adhesion" value="Validation des Demandes" icon="pi pi-check-circle" outcome="/pages/secure/adhesion/validation" rendered="#{menuBean.validationAdhesionVisible}" />
<p:menuitem id="m_liste_adhesions" value="Toutes les Adhésions" icon="pi pi-list" outcome="/pages/secure/adhesion/liste" />
<p:menuitem id="m_renouvellement" value="Renouvellements" icon="pi pi-refresh" outcome="/pages/secure/adhesion/renouvellement" />
<p:menuitem id="m_cartes_membres" value="Cartes de Membres" icon="pi pi-id-card" url="#" />
<p:menuitem id="m_historique_adhesions" value="Historique" icon="pi pi-history" url="#" />
<p:menuitem id="m_cartes_membres" value="Cartes de Membres" icon="pi pi-id-card" outcome="/pages/secure/adhesion/cartes-membres" rendered="#{menuBean.secretaire or menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_historique_adhesions" value="Historique" icon="pi pi-history" outcome="/pages/secure/adhesion/historique" />
</p:submenu>
<!-- Gestion Financière -->
<p:submenu id="m_finances" label="Gestion Financière" icon="pi pi-dollar">
<p:menuitem id="m_cotisations" value="Cotisations" icon="pi pi-credit-card" outcome="/pages/secure/cotisation/paiement" />
<p:menuitem id="m_historique_cotisations" value="Historique Paiements" icon="pi pi-history" outcome="/pages/secure/cotisation/historique" />
<p:menuitem id="m_relances" value="Relances et Rappels" icon="pi pi-bell" outcome="/pages/secure/cotisation/relances" />
<!-- Mes Finances (TOUS - Finances personnelles) -->
<p:submenu id="m_mes_finances" label="Mes Finances" icon="pi pi-wallet" rendered="#{menuBean.mesFinancesMenuVisible}">
<p:menuitem id="m_mes_cotisations" value="Mes Cotisations" icon="pi pi-credit-card" outcome="/pages/secure/membre/cotisations" rendered="#{menuBean.paiementCotisationVisible}" />
<p:menuitem id="m_payer_cotisations" value="Payer mes Cotisations" icon="pi pi-dollar" outcome="/pages/secure/cotisation/paiement" rendered="#{menuBean.paiementCotisationVisible}" />
<p:menuitem id="m_historique_finances" value="Historique" icon="pi pi-history" outcome="/pages/secure/cotisation/historique" />
</p:submenu>
<!-- Gestion Financière (TRESORIER, ADMIN - Administration finances) -->
<p:submenu id="m_gestion_finances" label="Gestion Financière" icon="pi pi-dollar" rendered="#{menuBean.gestionFinancesMenuVisible}">
<p:menuitem id="m_tresorerie" value="Trésorerie" icon="pi pi-wallet" outcome="/pages/secure/finance/tresorerie" />
<p:menuitem id="m_budgets" value="Gestion des Budgets" icon="pi pi-chart-pie" outcome="/pages/secure/finance/budgets" />
<p:menuitem id="m_comptabilite" value="Comptabilité" icon="pi pi-calculator" outcome="/pages/secure/comptabilite/gestion" />
<p:menuitem id="m_relances" value="Relances Cotisations" icon="pi pi-bell" outcome="/pages/secure/cotisation/relances" />
<p:menuitem id="m_rapports_cotisations" value="Rapports Cotisations" icon="pi pi-chart-bar" outcome="/pages/secure/cotisation/rapports" />
<p:menuitem id="m_budgets" value="Gestion des Budgets" icon="pi pi-chart-pie" url="#" />
<p:menuitem id="m_tresorerie" value="Trésorerie" icon="pi pi-wallet" url="#" />
<p:menuitem id="m_comptabilite" value="Comptabilité" icon="pi pi-calculator" url="#" />
<p:menuitem id="m_bilans" value="Bilans Financiers" icon="pi pi-chart-line" url="#" />
<p:menuitem id="m_bilans" value="Bilans Financiers" icon="pi pi-chart-line" outcome="/pages/secure/finance/bilans" />
</p:submenu>
<!-- Épargne et Crédit (RESPONSABLE_CREDIT, TRESORIER, ADMIN - Spécifique mutuelles) -->
<p:submenu id="m_epargne_credit" label="Épargne et Crédit" icon="pi pi-money-bill" rendered="#{menuBean.epargneCreditVisible}">
<p:menuitem id="m_demandes_credit" value="Demandes de Crédit" icon="pi pi-inbox" outcome="/pages/secure/credit/demandes" />
<p:menuitem id="m_evaluation_credit" value="Évaluation Solvabilité" icon="pi pi-search" outcome="/pages/secure/credit/evaluation" />
<p:menuitem id="m_suivi_credits" value="Suivi des Crédits" icon="pi pi-eye" outcome="/pages/secure/credit/suivi" />
<p:menuitem id="m_remboursements" value="Remboursements" icon="pi pi-replay" outcome="/pages/secure/credit/remboursements" />
<p:menuitem id="m_stats_credit" value="Statistiques Crédit" icon="pi pi-chart-bar" outcome="/pages/secure/credit/statistiques" />
</p:submenu>
<!-- Aide Sociale -->
<p:submenu id="m_aides" label="Aide Sociale" icon="pi pi-heart">
<p:menuitem id="m_demande_aide" value="Nouvelle Demande" icon="pi pi-plus" outcome="/pages/secure/aide/demande" />
<!-- Mes Demandes d'Aide (TOUS - Demandes personnelles) -->
<p:submenu id="m_mes_aides" label="Aide Sociale" icon="pi pi-heart" rendered="#{menuBean.mesAidesSocialesMenuVisible}">
<p:menuitem id="m_demande_aide" value="Faire une Demande" icon="pi pi-plus" outcome="/pages/secure/aide/demande" rendered="#{menuBean.demandeAideSocialeVisible}" />
<p:menuitem id="m_mes_demandes_aide" value="Mes Demandes" icon="pi pi-list" outcome="/pages/secure/aide/requests" rendered="#{menuBean.mesDemandesAideVisible}" />
<p:menuitem id="m_historique_aides" value="Historique" icon="pi pi-clock" outcome="/pages/secure/aide/historique" />
</p:submenu>
<!-- Gestion Aide Sociale (RESPONSABLE_SOCIAL, ADMIN - Administration aide sociale) -->
<p:submenu id="m_gestion_aides" label="Gestion Aide Sociale" icon="pi pi-heart-fill" rendered="#{menuBean.gestionAidesSocialesMenuVisible}">
<p:menuitem id="m_traitement_aide" value="Traitement des Demandes" icon="pi pi-cog" outcome="/pages/secure/aide/traitement" />
<p:menuitem id="m_evaluation_aide" value="Évaluation Sociale" icon="pi pi-search" url="#" />
<p:menuitem id="m_suivi_aide" value="Suivi des Bénéficiaires" icon="pi pi-eye" url="#" />
<p:menuitem id="m_historique_aides" value="Historique des Aides" icon="pi pi-clock" outcome="/pages/secure/aide/historique" />
<p:menuitem id="m_suivi_aide" value="Suivi des Bénéficiaires" icon="pi pi-eye" outcome="/pages/secure/aide/approved" />
<p:menuitem id="m_statistiques_aides" value="Statistiques Sociales" icon="pi pi-chart-line" outcome="/pages/secure/aide/statistiques" />
<p:menuitem id="m_fonds_solidarite" value="Fonds de Solidarité" icon="pi pi-heart-fill" url="#" />
</p:submenu>
<!-- Gestion des Événements -->
<p:submenu id="m_evenements" label="Gestion des Événements" icon="pi pi-calendar">
<p:menuitem id="m_creation_evenement" value="Nouvel Événement" icon="pi pi-plus" outcome="/pages/secure/evenement/creation" />
<!-- Événements (TOUS - Participation) -->
<p:submenu id="m_mes_evenements" label="Événements" icon="pi pi-calendar" rendered="#{menuBean.mesEvenementsMenuVisible}">
<p:menuitem id="m_calendrier" value="Calendrier" icon="pi pi-calendar-plus" outcome="/pages/secure/evenement/calendrier" />
<p:menuitem id="m_mes_inscriptions_events" value="Mes Inscriptions" icon="pi pi-list" outcome="/pages/secure/evenement/participants" rendered="#{menuBean.mesInscriptionsEvenementsVisible}" />
<p:menuitem id="m_mes_reservations" value="Mes Réservations" icon="pi pi-ticket" outcome="/pages/secure/evenement/reservations" />
</p:submenu>
<!-- Gestion Événements (RESPONSABLE_EVENEMENTS, SECRETAIRE, ADMIN - Organisation) -->
<p:submenu id="m_gestion_evenements" label="Gestion Événements" icon="pi pi-calendar-clock" rendered="#{menuBean.gestionEvenementsMenuVisible}">
<p:menuitem id="m_creation_evenement" value="Nouvel Événement" icon="pi pi-plus" outcome="/pages/secure/evenement/creation" />
<p:menuitem id="m_planification" value="Planification" icon="pi pi-clock" outcome="/pages/secure/evenement/planification" />
<p:menuitem id="m_participation" value="Gestion des Participations" icon="pi pi-users" outcome="/pages/secure/evenement/participation" />
<p:menuitem id="m_gestion_generale_evenements" value="Gestion Générale" icon="pi pi-cog" outcome="/pages/secure/evenement/gestion" />
<p:menuitem id="m_logistique" value="Logistique" icon="pi pi-truck" outcome="/pages/secure/evenement/logistique" />
<p:menuitem id="m_bilan_evenements" value="Bilan des Événements" icon="pi pi-chart-bar" outcome="/pages/secure/evenement/bilan" />
<p:menuitem id="m_reservations" value="Réservations" icon="pi pi-ticket" outcome="/pages/secure/evenement/reservations" />
<p:menuitem id="m_participation" value="Gestion Participations" icon="pi pi-users" outcome="/pages/secure/evenement/participation" />
<p:menuitem id="m_gestion_generale_evenements" value="Gestion Générale" icon="pi pi-cog" outcome="/pages/secure/evenement/gestion" />
<p:menuitem id="m_bilan_evenements" value="Bilans" icon="pi pi-chart-bar" outcome="/pages/secure/evenement/bilan" />
</p:submenu>
<!-- Communication -->
<p:submenu id="m_communication" label="Communication" icon="pi pi-envelope">
<p:menuitem id="m_messages" value="Centre de Messages" icon="pi pi-inbox" url="#" />
<p:menuitem id="m_notifications" value="Notifications" icon="pi pi-bell" url="#" />
<p:menuitem id="m_annonces" value="Annonces Officielles" icon="pi pi-megaphone" url="#" />
<p:menuitem id="m_newsletter" value="Newsletter" icon="pi pi-send" url="#" />
<p:menuitem id="m_sms" value="Envoi SMS" icon="pi pi-mobile" url="#" />
<p:menuitem id="m_emailing" value="Campagnes Email" icon="pi pi-at" url="#" />
<p:menuitem id="m_reseaux_sociaux" value="Réseaux Sociaux" icon="pi pi-share-alt" url="#" />
<!-- Communication (TOUS - Messages et notifications personnelles) -->
<p:submenu id="m_mes_communications" label="Communication" icon="pi pi-envelope" rendered="#{menuBean.mesCommunicationsMenuVisible}">
<p:menuitem id="m_mes_notifications" value="Mes Notifications" icon="pi pi-bell" outcome="/pages/secure/communication/notifications" />
</p:submenu>
<!-- Gestion Documentaire -->
<p:submenu id="m_documents" label="Gestion Documentaire" icon="pi pi-folder">
<p:submenu id="m_documents" label="Gestion Documentaire" icon="pi pi-folder" rendered="#{menuBean.documentsMenuVisible}">
<p:menuitem id="m_bibliotheque" value="Bibliothèque" icon="pi pi-book" url="#" />
<p:menuitem id="m_mes_documents" value="Mes Documents" icon="pi pi-file" url="#" />
<p:menuitem id="m_modeles" value="Modèles et Templates" icon="pi pi-file-o" url="#" />
<p:menuitem id="m_archivage" value="Archivage" icon="pi pi-archive" url="#" />
<p:menuitem id="m_mes_documents" value="Mes Documents" icon="pi pi-file" outcome="/pages/secure/documents/mes-documents" />
<p:menuitem id="m_modeles" value="Modèles et Templates" icon="pi pi-file-o" url="#" rendered="#{menuBean.secretaire or menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_archivage" value="Archivage" icon="pi pi-archive" url="#" rendered="#{menuBean.secretaire or menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_partage" value="Documents Partagés" icon="pi pi-share-alt" url="#" />
<p:menuitem id="m_signatures" value="Signatures Électroniques" icon="pi pi-verified" url="#" />
<p:menuitem id="m_workflow_doc" value="Workflow Documentaire" icon="pi pi-sitemap" url="#" />
<p:menuitem id="m_signatures" value="Signatures Électroniques" icon="pi pi-verified" url="#" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_workflow_doc" value="Workflow Documentaire" icon="pi pi-sitemap" url="#" rendered="#{menuBean.secretaire or menuBean.adminOrganisation or menuBean.superAdmin}" />
</p:submenu>
<!-- Formation et Développement -->
<p:submenu id="m_formation" label="Formation et Développement" icon="pi pi-graduation-cap">
<p:menuitem id="m_formations" value="Catalogue de Formations" icon="pi pi-book" url="#" />
<p:menuitem id="m_inscriptions_formation" value="Inscriptions" icon="pi pi-user-plus" url="#" />
<p:menuitem id="m_planning_formation" value="Planning des Formations" icon="pi pi-calendar" url="#" />
<p:menuitem id="m_certifications" value="Certifications" icon="pi pi-verified" url="#" />
<p:menuitem id="m_competences" value="Gestion des Compétences" icon="pi pi-star" url="#" />
<p:menuitem id="m_e_learning" value="E-Learning" icon="pi pi-desktop" url="#" />
<p:menuitem id="m_evaluations" value="Évaluations" icon="pi pi-check-square" url="#" />
<!-- Formations (TOUS - Inscriptions et suivi) -->
<p:submenu id="m_mes_formations" label="Formations" icon="pi pi-graduation-cap" rendered="#{menuBean.mesFormationsMenuVisible}">
<p:menuitem id="m_info_formations" value="Informations" icon="pi pi-info-circle" outcome="/pages/secure/dashboard" />
</p:submenu>
<!-- Rapports et Analyses -->
<p:submenu id="m_rapports" label="Rapports et Analyses" icon="pi pi-chart-bar">
<p:menuitem id="m_tableaux_bord" value="Tableaux de Bord" icon="pi pi-chart-line" url="#" />
<p:submenu id="m_rapports" label="Rapports et Analyses" icon="pi pi-chart-bar" rendered="#{menuBean.rapportsMenuVisible}">
<p:menuitem id="m_tableaux_bord" value="Tableaux de Bord" icon="pi pi-chart-line" outcome="/pages/secure/rapport/tableaux-bord" />
<p:menuitem id="m_rapport_membres" value="Rapport Membres" icon="pi pi-users" outcome="/pages/secure/rapport/membres" />
<p:menuitem id="m_rapport_finances" value="Rapport Financier" icon="pi pi-dollar" outcome="/pages/secure/rapport/finances" />
<p:menuitem id="m_rapport_finances" value="Rapport Financier" icon="pi pi-dollar" outcome="/pages/secure/rapport/finances" rendered="#{menuBean.rapportFinancierVisible}" />
<p:menuitem id="m_rapport_activites" value="Rapport d'Activités" icon="pi pi-chart-line" outcome="/pages/secure/rapport/activites" />
<p:menuitem id="m_indicateurs" value="Indicateurs de Performance" icon="pi pi-gauge" url="#" />
<p:menuitem id="m_analyses_predicitives" value="Analyses Prédictives" icon="pi pi-eye" url="#" />
<p:menuitem id="m_export" value="Exports Personnalisés" icon="pi pi-download" outcome="/pages/secure/rapport/export" />
<p:menuitem id="m_indicateurs" value="Indicateurs de Performance" icon="pi pi-gauge" url="#" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_analyses_predicitives" value="Analyses Prédictives" icon="pi pi-eye" url="#" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_export" value="Exports Personnalisés" icon="pi pi-download" outcome="/pages/secure/rapport/export" rendered="#{menuBean.exportsPersonnalisesVisible}" />
</p:submenu>
<!-- Outils et Utilitaires -->
<p:submenu id="m_outils" label="Outils et Utilitaires" icon="pi pi-wrench">
<p:submenu id="m_outils" label="Outils et Utilitaires" icon="pi pi-wrench" rendered="#{menuBean.outilsMenuVisible}">
<p:menuitem id="m_calculatrices" value="Calculatrices" icon="pi pi-calculator" url="#" />
<p:menuitem id="m_generateurs" value="Générateurs" icon="pi pi-cog" url="#" />
<p:menuitem id="m_imports" value="Imports de Données" icon="pi pi-upload" url="#" />
<p:menuitem id="m_exports_masse" value="Exports en Masse" icon="pi pi-download" url="#" />
<p:menuitem id="m_sauvegardes" value="Sauvegardes" icon="pi pi-save" url="#" />
<p:menuitem id="m_synchronisation" value="Synchronisation" icon="pi pi-sync" url="#" />
<p:menuitem id="m_maintenance" value="Maintenance" icon="pi pi-wrench" url="#" />
<p:menuitem id="m_api_externe" value="APIs Externes" icon="pi pi-cloud" url="#" />
<p:menuitem id="m_generateurs" value="Générateurs" icon="pi pi-cog" url="#" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_imports" value="Imports de Données" icon="pi pi-upload" url="#" rendered="#{menuBean.importExportMembreVisible}" />
<p:menuitem id="m_exports_masse" value="Exports en Masse" icon="pi pi-download" outcome="/pages/secure/outils/exports-masse" rendered="#{menuBean.exportsPersonnalisesVisible}" />
<p:menuitem id="m_sauvegardes" value="Sauvegardes" icon="pi pi-save" url="#" rendered="#{menuBean.maintenanceVisible}" />
<p:menuitem id="m_synchronisation" value="Synchronisation" icon="pi pi-sync" url="#" rendered="#{menuBean.adminOrganisation or menuBean.superAdmin}" />
<p:menuitem id="m_maintenance" value="Maintenance" icon="pi pi-wrench" url="#" rendered="#{menuBean.maintenanceVisible}" />
<p:menuitem id="m_api_externe" value="APIs Externes" icon="pi pi-cloud" url="#" rendered="#{menuBean.superAdmin}" />
</p:submenu>
<!-- Mon Espace Personnel -->

View File

@@ -4,104 +4,329 @@
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<div class="layout-topbar">
<!--
╔═══════════════════════════════════════════════════════════╗
║ UnionFlow - Elite Topbar (Freya Design System) ║
║ Real-time Session Monitor | Modern UI | Professional ║
╚═══════════════════════════════════════════════════════════╝
-->
<h:outputStylesheet library="css" name="topbar-elite.css" />
<div class="layout-topbar unionflow-elite">
<div class="layout-topbar-wrapper">
<!-- LEFT SECTION -->
<div class="layout-topbar-left">
<a href="#" class="menu-button">
<i class="pi pi-bars"/>
</a>
<h:link id="logolink" outcome="/pages/secure/dashboard" styleClass="layout-topbar-logo">
<p:graphicImage name="images/#{ guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}" library="freya-layout" />
<p:graphicImage name="images/#{guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}"
library="freya-layout"
alt="UnionFlow"
title="Retour au tableau de bord"/>
</h:link>
<span class="app-version">v1.0</span>
</div>
<!-- CENTER - Menu -->
<ui:include src="/templates/components/layout/menu.xhtml" />
<!-- RIGHT SECTION -->
<div class="layout-topbar-right">
<ul class="layout-topbar-actions">
<li class="topbar-item search-item ">
<a href="#">
<!-- Search -->
<li class="topbar-item search-item">
<a href="#" title="Rechercher">
<i class="topbar-icon pi pi-search"/>
</a>
<h:form>
<h:panelGroup styleClass="search-input-wrapper">
<p:inputText placeholder="Rechercher..." />
<i class="pi pi-search"/>
</h:panelGroup>
</h:form>
<ul>
<h:form onsubmit="return false;">
<h:panelGroup styleClass="search-input-wrapper">
<p:inputText placeholder="Rechercher..." />
<div class="search-dropdown">
<h:form styleClass="search-form-elite">
<div class="search-wrapper-elite">
<i class="pi pi-search"/>
</h:panelGroup>
<p:inputText placeholder="Rechercher membres, organisations..."
styleClass="search-input"/>
<p:commandButton icon="pi pi-arrow-right"
styleClass="p-button-rounded p-button-sm"/>
</div>
</h:form>
</div>
</li>
<!-- Notifications -->
<li class="topbar-item notifications-item">
<a href="#" title="Notifications">
<i class="topbar-icon pi pi-bell"/>
<span class="badge-count">3</span>
</a>
<ul class="notifications-dropdown">
<li class="notif-header">
<span class="font-semibold">Notifications</span>
<span class="count-label">3 nouvelles</span>
</li>
<li class="divider"/>
<li class="notif-item">
<i class="pi pi-info-circle text-blue-500"/>
<div>
<div class="notif-title">Nouvelle adhésion</div>
<div class="notif-time">Il y a 5 min</div>
</div>
</li>
<li class="notif-item">
<i class="pi pi-check-circle text-green-500"/>
<div>
<div class="notif-title">Cotisation validée</div>
<div class="notif-time">Il y a 1h</div>
</div>
</li>
<li class="divider"/>
<li class="notif-footer">
<a href="#" class="text-primary">Voir tout</a>
</li>
</ul>
</li>
<li class="topbar-item user-profile">
<a href="#" title="#{userSession.currentUser.nomComplet}">
<div class="flex align-items-center">
<div class="bg-primary text-white border-round flex align-items-center justify-content-center mr-2"
style="width: 32px; height: 32px; font-size: 12px; font-weight: bold;">
<!-- User Profile -->
<li class="topbar-item user-profile elite-user">
<a href="#" class="profile-trigger">
<div class="avatar-container">
<div class="avatar bg-gradient-primary">
#{userSession.currentUser.initiales}
</div>
<div class="text-sm">
<div class="text-900 font-medium">#{userSession.currentUser.nomComplet}</div>
<div class="text-600 text-xs">#{userSession.typeCompte}</div>
<span class="status-dot online"/>
</div>
<div class="user-info">
<div class="user-header">
<span class="user-name">#{userSession.currentUser.nomComplet}</span>
<span class="role-badge">#{userSession.typeCompte}</span>
</div>
<div class="session-timer">
<h:panelGroup id="sessionTimerDisplay">
<i class="#{sessionMonitor.timeIndicatorIcon} icon-sm"/>
<span class="#{sessionMonitor.timeIndicatorClass} timer-text">
#{sessionMonitor.formattedRemainingTime}
</span>
</h:panelGroup>
</div>
</div>
<i class="pi pi-angle-down arrow"/>
</a>
<ul>
<li>
<h:form>
<p:commandLink action="#{navigationBean.goToProfile}">
<i class="pi pi-user mr-2"></i>
<span>Mon Profil</span>
</p:commandLink>
</h:form>
</li>
<li>
<h:form>
<p:commandLink action="#{navigationBean.goToSettings}">
<i class="pi pi-cog mr-2"></i>
<span>Paramètres</span>
</p:commandLink>
</h:form>
</li>
<li>
<a href="/pages/secure/messages.xhtml">
<i class="pi pi-envelope mr-2"></i>
<span>Messages</span>
</a>
</li>
<li class="border-top-1 surface-border">
<div class="p-2 text-xs text-600">
<div>Entité: #{userSession.entite.nom}</div>
<div>Connecté depuis:
<span class="text-green-600">#{jwtTokenManager.timeUntilExpiration / 60} min</span>
<!-- User Dropdown -->
<ul class="user-dropdown elite-dropdown">
<!-- Header -->
<li class="dropdown-header">
<div class="header-content">
<div class="header-avatar">
<div class="avatar-lg bg-gradient-primary">
#{userSession.currentUser.initiales}
</div>
<span class="status-indicator online">
<i class="pi pi-circle-fill"/>
</span>
</div>
<div class="header-info">
<div class="name">#{userSession.currentUser.nomComplet}</div>
<div class="email">#{userSession.currentUser.email}</div>
<span class="role-tag">#{userSession.typeCompte}</span>
</div>
</div>
</li>
<li>
<!-- Session Card -->
<li class="session-card">
<div class="card-content">
<div class="info-row">
<span class="label">
<i class="pi pi-building"/>
Organisation
</span>
<span class="value">#{userSession.entite.nom}</span>
</div>
<div class="info-row">
<span class="label">
<i class="#{sessionMonitor.timeIndicatorIcon}"/>
Temps restant
</span>
<span class="value #{sessionMonitor.timeIndicatorClass}">
#{sessionMonitor.formattedRemainingTime}
</span>
</div>
<!-- Progress Bar -->
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill"
style="width: #{100 - sessionMonitor.sessionProgressPercent}%"/>
</div>
<div class="progress-label">
#{sessionMonitor.remainingMinutes} min
</div>
</div>
</div>
</li>
<li class="divider"/>
<!-- Actions -->
<li class="menu-section">
<div class="section-title">
<i class="pi pi-user"/>
Mon Compte
</div>
<div class="section-items">
<h:form>
<p:commandLink action="#{navigationBean.goToProfile}"
styleClass="menu-item">
<i class="pi pi-user-edit"/>
<span>Mon Profil</span>
<i class="pi pi-angle-right arrow-right"/>
</p:commandLink>
</h:form>
<h:form>
<p:commandLink action="#{navigationBean.goToSettings}"
styleClass="menu-item">
<i class="pi pi-cog"/>
<span>Paramètres</span>
<i class="pi pi-angle-right arrow-right"/>
</p:commandLink>
</h:form>
<a href="/pages/secure/messages.xhtml" class="menu-item">
<i class="pi pi-envelope"/>
<span>Messages</span>
<span class="item-badge">2</span>
<i class="pi pi-angle-right arrow-right"/>
</a>
</div>
</li>
<li class="divider"/>
<!-- Preferences -->
<li class="menu-section">
<div class="section-title">
<i class="pi pi-palette"/>
Préférences
</div>
<div class="section-items">
<a href="#" class="menu-item">
<i class="pi pi-sun"/>
<span>Thème</span>
<span class="value-badge">Clair</span>
</a>
<a href="#" class="menu-item">
<i class="pi pi-bell"/>
<span>Notifications</span>
<i class="pi pi-angle-right arrow-right"/>
</a>
</div>
</li>
<li class="divider"/>
<!-- Support -->
<li class="menu-section compact">
<div class="section-items">
<a href="#" class="menu-item">
<i class="pi pi-question-circle text-blue-500"/>
<span>Aide</span>
</a>
<a href="#" class="menu-item">
<i class="pi pi-info-circle text-cyan-500"/>
<span>À propos</span>
</a>
</div>
</li>
<li class="divider logout-divider"/>
<!-- Logout -->
<li class="logout-section">
<h:form>
<p:commandLink action="#{loginBean.logout}"
styleClass="text-red-600"
onclick="return confirm('Êtes-vous sûr de vouloir vous déconnecter ?');">
<i class="pi pi-sign-out mr-2"></i>
<p:commandLink styleClass="logout-btn"
onclick="PF('logoutDialog').show(); return false;">
<i class="pi pi-sign-out"/>
<span>Déconnexion</span>
<i class="pi pi-lock ml-auto"/>
</p:commandLink>
</h:form>
</li>
</ul>
</li>
</ul>
<a href="#" class="layout-rightpanel-button">
<i class="pi pi-arrow-left"></i>
<a href="#" class="layout-rightpanel-button" title="Configuration">
<i class="pi pi-arrow-left"/>
</a>
</div>
</div>
</div>
</ui:composition>
<!-- LOGOUT DIALOG -->
<p:dialog id="logoutDialog"
widgetVar="logoutDialog"
header="Confirmation de déconnexion"
modal="true"
closable="true"
styleClass="elite-dialog"
responsive="true"
width="450">
<div class="dialog-content">
<div class="icon-wrapper">
<i class="pi pi-sign-out icon-lg"/>
</div>
<h3 class="dialog-title">Êtes-vous sûr de vouloir vous déconnecter ?</h3>
<div class="info-box">
<div class="info-item">
<i class="pi pi-user"/>
<span>#{userSession.currentUser.nomComplet}</span>
</div>
<div class="info-item">
<i class="pi pi-clock"/>
<span>Session: #{sessionMonitor.formattedRemainingTime}</span>
</div>
</div>
<p class="warning-text">
<i class="pi pi-info-circle"/>
Vous devrez vous reconnecter pour accéder à l'application.
</p>
</div>
<f:facet name="footer">
<div class="dialog-footer">
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="p-button-outlined p-button-secondary"
onclick="PF('logoutDialog').hide(); return false;"/>
<h:form>
<p:commandButton value="Se déconnecter"
icon="pi pi-sign-out"
styleClass="p-button-danger"
action="#{loginBean.logout}"
onclick="PF('logoutDialog').hide();"/>
</h:form>
</div>
</f:facet>
</p:dialog>
<!-- SESSION TIMER AUTO-REFRESH -->
<h:form id="sessionTimerForm">
<p:poll interval="5"
listener="#{sessionMonitor.updateActivity}"
update=":sessionTimerDisplay"
global="false"
autoStart="true"/>
</h:form>
</ui:composition>

View File

@@ -5,7 +5,7 @@
xmlns:p="http://primefaces.org/ui">
<!--
Fragment réutilisable pour le formulaire d'Organisation
Fragment réutilisable pour le formulaire d'Organisation - Version Elite
Paramètres attendus via <ui:param>:
- model: l'objet cible (ex: #{organisationsBean.nouvelleOrganisation})
- typesItems: la liste des SelectItem pour les types
@@ -15,18 +15,26 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 1 : INFORMATIONS GÉNÉRALES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Informations générales" toggleable="true" collapsed="false" styleClass="mb-3">
<p:fieldset legend="📋 Informations générales" toggleable="true" collapsed="false" styleClass="mb-3">
<div class="formgrid grid">
<!-- Nom complet -->
<div class="field col-12 md:col-8">
<p:outputLabel for="nom" value="Nom complet *" styleClass="font-semibold" />
<p:outputLabel for="nom" value="Nom complet" styleClass="font-semibold">
<span class="text-red-500">*</span>
</p:outputLabel>
<p:inputText id="nom"
value="#{model.nom}"
required="true"
requiredMessage="Le nom de l'organisation est requis."
maxlength="200"
placeholder="Ex: Association pour le développement durable" />
placeholder="Ex: Association pour le développement durable">
<f:validateLength minimum="3" maximum="200" />
</p:inputText>
<p:message for="nom" />
<small class="text-500">
<i class="pi pi-info-circle mr-1"/>
Nom officiel complet de l'organisation (3-200 caractères)
</small>
</div>
<!-- Nom court / Sigle -->
@@ -35,53 +43,91 @@
<p:inputText id="nomCourt"
value="#{model.nomCourt}"
maxlength="100"
placeholder="Ex: ADD" />
placeholder="Ex: ADD">
<f:validateLength maximum="100" />
</p:inputText>
<small class="text-500">
<i class="pi pi-tag mr-1"/>
Acronyme ou nom abrégé
</small>
</div>
<!-- Type d'organisation -->
<div class="field col-12 md:col-6">
<p:outputLabel for="type" value="Type d'organisation *" styleClass="font-semibold" />
<p:selectOneMenu id="type"
value="#{model.typeAssociation}"
<p:outputLabel for="type" value="Type d'organisation" styleClass="font-semibold">
<span class="text-red-500">*</span>
</p:outputLabel>
<p:selectOneMenu id="type"
value="#{model.typeAssociation}"
required="true"
requiredMessage="Le type d'organisation est requis.">
<f:selectItem itemLabel="-- Sélectionnez un type --" itemValue="#{null}" noSelectionOption="true" />
<f:selectItems value="#{typesItems}" />
</p:selectOneMenu>
<p:message for="type" />
<p:tooltip for="type" value="Catégorie juridique ou fonctionnelle de l'organisation" position="top"/>
</div>
<!-- Statut de l'organisation -->
<div class="field col-12 md:col-3">
<p:outputLabel for="statut" value="Statut" styleClass="font-semibold">
<span class="text-red-500">*</span>
</p:outputLabel>
<p:selectOneMenu id="statut"
value="#{model.statut}"
required="true"
requiredMessage="Le statut de l'organisation est requis.">
<f:selectItem itemLabel="✅ Active" itemValue="ACTIVE" />
<f:selectItem itemLabel="🔨 En création" itemValue="EN_CREATION" />
<f:selectItem itemLabel="⏸️ Inactive" itemValue="INACTIVE" />
<f:selectItem itemLabel="⚠️ Suspendue" itemValue="SUSPENDUE" />
<f:selectItem itemLabel="🚫 Dissoute" itemValue="DISSOUTE" />
</p:selectOneMenu>
<p:message for="statut" />
<p:tooltip for="statut" value="État actuel de l'organisation - peut être modifié ultérieurement" position="top"/>
</div>
<!-- Date de fondation -->
<div class="field col-12 md:col-3">
<p:outputLabel for="dateFondation" value="Date de fondation" styleClass="font-semibold" />
<p:datePicker id="dateFondation"
value="#{model.dateFondation}"
pattern="dd/MM/yyyy"
<p:datePicker id="dateFondation"
value="#{model.dateFondation}"
pattern="dd/MM/yyyy"
showIcon="true"
yearNavigator="true"
yearRange="1900:2025"
maxdate="#{null}"
placeholder="jj/mm/aaaa" />
<p:tooltip for="dateFondation" value="Date officielle de création de l'organisation" position="top"/>
</div>
<!-- Numéro d'enregistrement -->
<div class="field col-12 md:col-3">
<p:outputLabel for="numEnreg" value="N° d'enregistrement" styleClass="font-semibold" />
<p:inputText id="numEnreg"
value="#{model.numeroRegistre}"
<div class="field col-12 md:col-6">
<p:outputLabel for="numEnreg" value="N° d'enregistrement / RCCM" styleClass="font-semibold" />
<p:inputText id="numEnreg"
value="#{model.numeroRegistre}"
maxlength="100"
placeholder="Ex: RNA-W123456789" />
placeholder="Ex: CI-ABJ-01-2024-B12-12345">
<f:validateLength maximum="100" />
</p:inputText>
<small class="text-500">
<i class="pi pi-shield mr-1"/>
Numéro d'immatriculation officiel (RCCM, RNA, etc.)
</small>
</div>
<!-- Description -->
<div class="field col-12">
<p:outputLabel for="description" value="Description" styleClass="font-semibold" />
<p:inputTextarea id="description"
value="#{model.description}"
rows="4"
<p:inputTextarea id="description"
value="#{model.description}"
rows="4"
maxlength="2000"
placeholder="Décrivez brièvement l'organisation..."
autoResize="false" />
<small class="text-muted">#{2000 - (empty model.description ? 0 : model.description.length())} caractères restants</small>
placeholder="Décrivez brièvement la mission, les objectifs et les activités principales de l'organisation..."
autoResize="false">
<f:validateLength maximum="2000" />
</p:inputTextarea>
<p:message for="description" />
<small class="text-500">Maximum 2000 caractères</small>
</div>
</div>
</p:fieldset>
@@ -89,76 +135,119 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 2 : COORDONNÉES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Coordonnées" toggleable="true" collapsed="false" styleClass="mb-3">
<p:fieldset legend="📞 Coordonnées" toggleable="true" collapsed="false" styleClass="mb-3">
<div class="formgrid grid">
<!-- Email principal -->
<div class="field col-12 md:col-6">
<p:outputLabel for="email" value="Email principal" styleClass="font-semibold" />
<p:inputText id="email"
value="#{model.email}"
<p:inputText id="email"
value="#{model.email}"
maxlength="200"
type="email"
placeholder="contact@exemple.com" />
placeholder="contact@organisation.com">
<f:validateLength maximum="200" />
</p:inputText>
<p:message for="email" />
<small class="text-500">
<i class="pi pi-envelope mr-1"/>
Email principal de contact
</small>
</div>
<!-- Email secondaire -->
<div class="field col-12 md:col-6">
<p:outputLabel for="email2" value="Email secondaire" styleClass="font-semibold" />
<p:inputText id="email2"
value="#{model.emailSecondaire}"
<p:inputText id="email2"
value="#{model.emailSecondaire}"
maxlength="200"
type="email"
placeholder="admin@exemple.com" />
placeholder="admin@organisation.com">
<f:validateLength maximum="200" />
</p:inputText>
<p:message for="email2" />
<small class="text-500">
<i class="pi pi-envelope mr-1"/>
Email alternatif (optionnel)
</small>
</div>
<!-- Téléphone principal -->
<div class="field col-12 md:col-6">
<p:outputLabel for="telephone" value="Téléphone principal" styleClass="font-semibold" />
<p:inputText id="telephone"
value="#{model.telephone}"
maxlength="20"
placeholder="+225 XX XX XX XX XX" />
<p:inputText id="telephone"
value="#{model.telephone}"
maxlength="30"
placeholder="+225 07 00 00 00 00">
<f:validateLength maximum="30" />
</p:inputText>
<p:message for="telephone" />
<small class="text-500">
<i class="pi pi-phone mr-1"/>
Numéro principal avec indicatif pays
</small>
</div>
<!-- Téléphone secondaire -->
<div class="field col-12 md:col-6">
<p:outputLabel for="telephone2" value="Téléphone secondaire" styleClass="font-semibold" />
<p:inputText id="telephone2"
value="#{model.telephoneSecondaire}"
maxlength="20"
placeholder="+225 XX XX XX XX XX" />
<p:inputText id="telephone2"
value="#{model.telephoneSecondaire}"
maxlength="30"
placeholder="+221 77 00 00 00">
<f:validateLength maximum="30" />
</p:inputText>
<p:message for="telephone2" />
<small class="text-500">
<i class="pi pi-phone mr-1"/>
Numéro alternatif (optionnel)
</small>
</div>
<!-- Site web -->
<div class="field col-12 md:col-6">
<p:outputLabel for="siteWeb" value="Site web" styleClass="font-semibold" />
<p:inputText id="siteWeb"
value="#{model.siteWeb}"
<p:inputText id="siteWeb"
value="#{model.siteWeb}"
maxlength="500"
placeholder="https://www.exemple.com" />
placeholder="https://www.organisation.com">
<f:validateLength maximum="500" />
</p:inputText>
<p:message for="siteWeb" />
<small class="text-500">
<i class="pi pi-globe mr-1"/>
URL du site officiel (http:// ou https://)
</small>
</div>
<!-- Logo URL -->
<div class="field col-12 md:col-6">
<p:outputLabel for="logo" value="Logo (URL)" styleClass="font-semibold" />
<p:inputText id="logo"
value="#{model.logo}"
<p:inputText id="logo"
value="#{model.logo}"
maxlength="500"
placeholder="https://www.exemple.com/logo.png" />
placeholder="https://www.organisation.com/logo.png">
<f:validateLength maximum="500" />
</p:inputText>
<p:message for="logo" />
<small class="text-500">
<i class="pi pi-image mr-1"/>
Lien vers le logo de l'organisation (PNG, JPG, SVG)
</small>
</div>
<!-- Réseaux sociaux -->
<div class="field col-12">
<p:outputLabel for="reseauxSociaux" value="Réseaux sociaux" styleClass="font-semibold" />
<p:inputTextarea id="reseauxSociaux"
value="#{model.reseauxSociaux}"
rows="3"
<p:inputTextarea id="reseauxSociaux"
value="#{model.reseauxSociaux}"
rows="3"
maxlength="1000"
placeholder='Ex: {"facebook":"@pageOfficielle", "twitter":"@compte", "linkedin":"entreprise/nom"}'
autoResize="false" />
<small class="text-muted">Format JSON recommandé</small>
placeholder='{"facebook":"@page_officielle", "twitter":"@compte", "linkedin":"entreprise/nom", "instagram":"@profil"}'
autoResize="false">
<f:validateLength maximum="1000" />
</p:inputTextarea>
<small class="text-500">
<i class="pi pi-share-alt mr-1"/>
Format JSON recommandé - Maximum 1000 caractères
</small>
</div>
</div>
</p:fieldset>
@@ -166,80 +255,174 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 3 : LOCALISATION -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Localisation" toggleable="true" collapsed="false" styleClass="mb-3">
<p:fieldset legend="📍 Localisation" toggleable="true" collapsed="false" styleClass="mb-3">
<div class="formgrid grid">
<!-- Adresse complète -->
<div class="field col-12">
<p:outputLabel for="adresse" value="Adresse complète" styleClass="font-semibold" />
<p:inputTextarea id="adresse"
value="#{model.adresse}"
rows="2"
<p:inputTextarea id="adresse"
value="#{model.adresse}"
rows="2"
maxlength="500"
placeholder="N° rue, quartier..."
autoResize="false" />
placeholder="N° de la parcelle, nom de la rue, quartier..."
autoResize="false">
<f:validateLength maximum="500" />
</p:inputTextarea>
<small class="text-500">Maximum 500 caractères</small>
</div>
<!-- Quartier / Commune -->
<div class="field col-12 md:col-5">
<p:outputLabel for="quartier" value="Quartier / Commune" styleClass="font-semibold" />
<p:inputText id="quartier"
value="#{model.quartier}"
maxlength="100"
placeholder="Ex: Cocody, Hay Riad, Plateau...">
<f:validateLength maximum="100" />
</p:inputText>
<small class="text-500">
<i class="pi pi-home mr-1"/>
Quartier, commune ou arrondissement
</small>
</div>
<!-- Ville -->
<div class="field col-12 md:col-4">
<p:outputLabel for="ville" value="Ville" styleClass="font-semibold" />
<p:inputText id="ville"
value="#{model.ville}"
maxlength="100"
placeholder="Ex: Abidjan" />
<p:autoComplete id="ville"
value="#{model.ville}"
completeMethod="#{completionBean.completerVilles}"
minQueryLength="1"
queryDelay="200"
dropdown="true"
forceSelection="false"
placeholder="Ex: Abidjan"
maxlength="100">
<f:validateLength maximum="100" />
</p:autoComplete>
<p:tooltip for="ville" value="Commencez à taper pour voir les suggestions de villes de Côte d'Ivoire" position="top"/>
<small class="text-500">
<i class="pi pi-building mr-1"/>
Ville du siège social
</small>
</div>
<!-- Code postal -->
<div class="field col-12 md:col-2">
<p:outputLabel for="codePostal" value="Code postal" styleClass="font-semibold" />
<p:inputText id="codePostal"
value="#{model.codePostal}"
maxlength="10"
placeholder="Ex: 01 BP 123" />
<p:inputText id="codePostal"
value="#{model.codePostal}"
maxlength="20"
placeholder="Ex: 01 BP 123, 75001…">
<f:validateLength maximum="20" />
</p:inputText>
<p:message for="codePostal" />
<small class="text-500">
<i class="pi pi-map-marker mr-1"/>
Format : 3-10 caractères (chiffres, lettres, tiret)
</small>
</div>
<!-- Région -->
<div class="field col-12 md:col-3">
<p:outputLabel for="region" value="Région" styleClass="font-semibold" />
<p:inputText id="region"
value="#{model.region}"
maxlength="100"
placeholder="Ex: Lagunes" />
<p:autoComplete id="region"
value="#{model.region}"
completeMethod="#{completionBean.completerRegions}"
minQueryLength="1"
queryDelay="200"
dropdown="true"
forceSelection="false"
placeholder="Ex: Lagunes"
maxlength="100">
<f:validateLength maximum="100" />
</p:autoComplete>
<p:tooltip for="region" value="Commencez à taper pour voir les suggestions de régions de Côte d'Ivoire" position="top"/>
<small class="text-500">
<i class="pi pi-map mr-1"/>
Région administrative
</small>
</div>
<!-- Pays -->
<div class="field col-12 md:col-3">
<p:outputLabel for="pays" value="Pays" styleClass="font-semibold" />
<p:inputText id="pays"
value="#{model.pays}"
maxlength="100"
placeholder="Ex: Côte d'Ivoire" />
<p:selectOneMenu id="pays"
value="#{model.pays}">
<f:selectItem itemLabel="— Sélectionner —" itemValue="" />
<!-- Afrique de l'Ouest (UEMOA / XOF) -->
<f:selectItem itemLabel="🇨🇮 Côte d'Ivoire" itemValue="Côte d'Ivoire" />
<f:selectItem itemLabel="🇸🇳 Sénégal" itemValue="Sénégal" />
<f:selectItem itemLabel="🇲🇱 Mali" itemValue="Mali" />
<f:selectItem itemLabel="🇧🇫 Burkina Faso" itemValue="Burkina Faso" />
<f:selectItem itemLabel="🇬🇳 Guinée" itemValue="Guinée" />
<f:selectItem itemLabel="🇳🇪 Niger" itemValue="Niger" />
<f:selectItem itemLabel="🇹🇬 Togo" itemValue="Togo" />
<f:selectItem itemLabel="🇧🇯 Bénin" itemValue="Bénin" />
<f:selectItem itemLabel="🇬🇼 Guinée-Bissau" itemValue="Guinée-Bissau" />
<!-- Afrique Centrale (CEMAC / XAF) -->
<f:selectItem itemLabel="🇨🇲 Cameroun" itemValue="Cameroun" />
<f:selectItem itemLabel="🇬🇦 Gabon" itemValue="Gabon" />
<f:selectItem itemLabel="🇨🇬 Congo" itemValue="Congo" />
<f:selectItem itemLabel="🇨🇩 RD Congo" itemValue="RD Congo" />
<f:selectItem itemLabel="🇹🇩 Tchad" itemValue="Tchad" />
<f:selectItem itemLabel="🇨🇫 Centrafrique" itemValue="Centrafrique" />
<!-- Afrique du Nord -->
<f:selectItem itemLabel="🇲🇦 Maroc" itemValue="Maroc" />
<f:selectItem itemLabel="🇩🇿 Algérie" itemValue="Algérie" />
<f:selectItem itemLabel="🇹🇳 Tunisie" itemValue="Tunisie" />
<!-- Autres Afrique -->
<f:selectItem itemLabel="🇳🇬 Nigeria" itemValue="Nigeria" />
<f:selectItem itemLabel="🇬🇭 Ghana" itemValue="Ghana" />
<f:selectItem itemLabel="🇰🇪 Kenya" itemValue="Kenya" />
<f:selectItem itemLabel="🇿🇦 Afrique du Sud" itemValue="Afrique du Sud" />
<f:selectItem itemLabel="🇷🇼 Rwanda" itemValue="Rwanda" />
<f:selectItem itemLabel="🇪🇹 Éthiopie" itemValue="Éthiopie" />
<!-- Diaspora / Europe -->
<f:selectItem itemLabel="🇫🇷 France" itemValue="France" />
<f:selectItem itemLabel="🇧🇪 Belgique" itemValue="Belgique" />
<f:selectItem itemLabel="🌍 Autre" itemValue="Autre" />
</p:selectOneMenu>
<p:tooltip for="pays" value="Pays où l'organisation est enregistrée" position="top"/>
</div>
<!-- Coordonnées GPS -->
<div class="field col-12">
<p:outputLabel value="Coordonnées GPS" styleClass="font-semibold" />
<p:divider styleClass="my-3">
<span class="text-600 font-semibold">
<i class="pi pi-map-marker mr-2"/>
Coordonnées GPS (optionnel)
</span>
</p:divider>
</div>
<!-- Latitude -->
<div class="field col-12 md:col-6">
<p:outputLabel for="latitude" value="Latitude" />
<p:inputNumber id="latitude"
value="#{model.latitude}"
<p:outputLabel for="latitude" value="Latitude" styleClass="font-semibold"/>
<p:inputNumber id="latitude"
value="#{model.latitude}"
decimalPlaces="6"
minValue="-90"
maxValue="90"
placeholder="Ex: 5.316667" />
<small class="text-muted">Valeur entre -90 et 90</small>
<small class="text-500">
<i class="pi pi-compass mr-1"/>
Valeur entre -90 et 90 (ex: Abidjan ≈ 5.316667)
</small>
</div>
<!-- Longitude -->
<div class="field col-12 md:col-6">
<p:outputLabel for="longitude" value="Longitude" />
<p:inputNumber id="longitude"
value="#{model.longitude}"
<p:outputLabel for="longitude" value="Longitude" styleClass="font-semibold"/>
<p:inputNumber id="longitude"
value="#{model.longitude}"
decimalPlaces="6"
minValue="-180"
maxValue="180"
placeholder="Ex: -4.033333" />
<small class="text-muted">Valeur entre -180 et 180</small>
<small class="text-500">
<i class="pi pi-compass mr-1"/>
Valeur entre -180 et 180 (ex: Abidjan ≈ -4.033333)
</small>
</div>
</div>
</p:fieldset>
@@ -247,63 +430,103 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 4 : STRUCTURE & HIÉRARCHIE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Structure &amp; Hiérarchie" toggleable="true" collapsed="true" styleClass="mb-3">
<p:fieldset legend="🏢 Structure &amp; Hiérarchie" toggleable="true" collapsed="true" styleClass="mb-3">
<div class="formgrid grid">
<!-- Statut juridique -->
<div class="field col-12">
<p:outputLabel value="Statut juridique" styleClass="font-semibold block mb-3" />
<p:outputLabel value="Statut juridique" styleClass="font-semibold block mb-3">
<i class="pi pi-building text-primary mr-2"/>
</p:outputLabel>
</div>
<div class="field col-12 md:col-4">
<div class="field-checkbox">
<p:selectBooleanCheckbox id="publique" value="#{model.organisationPublique}" />
<p:outputLabel for="publique" value="Organisation publique" styleClass="ml-2" />
<p:outputLabel for="publique" value="Organisation publique" styleClass="ml-2 font-semibold" />
</div>
<small class="text-muted block mt-1">Institution étatique ou gouvernementale</small>
<small class="text-500 block mt-1">
<i class="pi pi-shield mr-1"/>
Institution étatique ou gouvernementale
</small>
</div>
<div class="field col-12 md:col-4">
<div class="field-checkbox">
<p:selectBooleanCheckbox id="accepteMembres" value="#{model.accepteNouveauxMembres}" />
<p:outputLabel for="accepteMembres" value="Recrutement ouvert" styleClass="ml-2" />
<p:outputLabel for="accepteMembres" value="Recrutement ouvert" styleClass="ml-2 font-semibold" />
</div>
<small class="text-muted block mt-1">Accepte de nouveaux membres</small>
<small class="text-500 block mt-1">
<i class="pi pi-user-plus mr-1"/>
Accepte de nouveaux membres actuellement
</small>
</div>
<div class="field col-12 md:col-4">
<div class="field-checkbox">
<p:selectBooleanCheckbox id="cotisationObl" value="#{model.cotisationObligatoire}" />
<p:outputLabel for="cotisationObl" value="Cotisation obligatoire" styleClass="ml-2" />
<p:outputLabel for="cotisationObl" value="Cotisation obligatoire" styleClass="ml-2 font-semibold" />
</div>
<small class="text-muted block mt-1">Adhésion payante requise</small>
<small class="text-500 block mt-1">
<i class="pi pi-money-bill mr-1"/>
Adhésion payante requise pour les membres
</small>
</div>
<!-- Séparateur visuel -->
<div class="field col-12">
<p:divider />
<p:outputLabel value="Rattachement hiérarchique" styleClass="font-semibold block mb-3" />
<p:divider styleClass="my-3">
<span class="text-600 font-semibold">
<i class="pi pi-sitemap mr-2"/>
Rattachement hiérarchique
</span>
</p:divider>
</div>
<!-- Organisation parente -->
<div class="field col-12 md:col-9">
<p:outputLabel for="orgParente" value="Organisation parente" styleClass="font-semibold" />
<p:inputText id="orgParente"
value="#{model.organisationParenteId}"
maxlength="36"
placeholder="Sélectionner ou saisir l'identifiant de l'organisation mère" />
<small class="text-muted">Laisser vide si l'organisation est indépendante ou au sommet de la hiérarchie</small>
<p:autoComplete id="orgParente"
value="#{model.organisationParenteId}"
completeMethod="#{completionBean.rechercherOrganisations}"
var="org"
itemLabel="#{org.nom}"
itemValue="#{org.id}"
placeholder="Rechercher une organisation parente..."
minQueryLength="2"
queryDelay="300"
forceSelection="true"
dropdown="true"
scrollHeight="300"
emptyMessage="Aucune organisation trouvée">
<p:column headerText="Nom" style="width:60%">
<h:outputText value="#{org.nom}" styleClass="font-semibold"/>
</p:column>
<p:column headerText="Type" style="width:40%">
<h:outputText value="#{org.typeLibelle}" styleClass="text-500" />
</p:column>
</p:autoComplete>
<p:message for="orgParente" />
<p:tooltip for="orgParente" value="Recherchez et sélectionnez l'organisation mère si cette organisation est une sous-structure. Laissez vide si c'est une organisation racine." position="top"/>
<small class="text-500">
<i class="pi pi-sitemap mr-1"/>
Laisser vide si l'organisation est indépendante ou au sommet de la hiérarchie
</small>
</div>
<!-- Niveau hiérarchique -->
<div class="field col-12 md:col-3">
<p:outputLabel for="niveau" value="Niveau" styleClass="font-semibold" />
<p:inputNumber id="niveau"
value="#{model.niveauHierarchique}"
<p:outputLabel for="niveau" value="Niveau hiérarchique" styleClass="font-semibold" />
<p:inputNumber id="niveau"
value="#{model.niveauHierarchique}"
decimalPlaces="0"
minValue="0"
maxValue="10"
placeholder="0" />
<small class="text-muted">0 = Niveau national/racine<br/>1 = Niveau régional<br/>2+ = Niveaux locaux</small>
<small class="text-500">
0 = National/Racine<br/>
1 = Régional<br/>
2+ = Niveaux locaux
</small>
</div>
</div>
</p:fieldset>
@@ -311,26 +534,34 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 5 : MEMBRES & GOUVERNANCE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Membres &amp; Gouvernance" toggleable="true" collapsed="true" styleClass="mb-3">
<p:fieldset legend="👥 Membres &amp; Gouvernance" toggleable="true" collapsed="true" styleClass="mb-3">
<div class="formgrid grid">
<!-- Nombre de membres -->
<div class="field col-12 md:col-6">
<p:outputLabel for="nbMembres" value="Nombre de membres" styleClass="font-semibold" />
<p:inputNumber id="nbMembres"
value="#{model.nombreMembres}"
<p:inputNumber id="nbMembres"
value="#{model.nombreMembres}"
decimalPlaces="0"
minValue="0"
placeholder="0" />
<small class="text-500">
<i class="pi pi-users mr-1"/>
Nombre total de membres actifs de l'organisation
</small>
</div>
<!-- Nombre d'administrateurs -->
<div class="field col-12 md:col-6">
<p:outputLabel for="nbAdmins" value="Nombre d'administrateurs" styleClass="font-semibold" />
<p:inputNumber id="nbAdmins"
value="#{model.nombreAdministrateurs}"
<p:inputNumber id="nbAdmins"
value="#{model.nombreAdministrateurs}"
decimalPlaces="0"
minValue="0"
placeholder="0" />
<small class="text-500">
<i class="pi pi-user-edit mr-1"/>
Nombre de membres du bureau exécutif ou du conseil d'administration
</small>
</div>
</div>
</p:fieldset>
@@ -338,68 +569,114 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 6 : BUDGET & FINANCES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Budget &amp; Finances" toggleable="true" collapsed="true" styleClass="mb-3">
<p:fieldset legend="💰 Budget &amp; Finances" toggleable="true" collapsed="true" styleClass="mb-3">
<div class="formgrid grid">
<!-- Devise — placée EN PREMIER pour piloter les décimales des champs montants -->
<div class="field col-12 md:col-3">
<p:outputLabel for="devise" value="Devise" styleClass="font-semibold" />
<p:selectOneMenu id="devise"
value="#{model.devise}">
<!-- Devises africaines uniquement -->
<!-- Franc CFA : pas de centimes (0 décimale) -->
<f:selectItem itemLabel="XOF — Franc CFA (UEMOA)" itemValue="XOF" />
<f:selectItem itemLabel="XAF — Franc CFA (CEMAC)" itemValue="XAF" />
<!-- Afrique du Nord -->
<f:selectItem itemLabel="MAD — Dirham marocain" itemValue="MAD" />
<f:selectItem itemLabel="DZD — Dinar algérien" itemValue="DZD" />
<f:selectItem itemLabel="TND — Dinar tunisien" itemValue="TND" />
<!-- Autres devises africaines (2 décimales) -->
<f:selectItem itemLabel="NGN — Naira nigérian" itemValue="NGN" />
<f:selectItem itemLabel="GHS — Cedi ghanéen" itemValue="GHS" />
<f:selectItem itemLabel="KES — Shilling kényan" itemValue="KES" />
<f:selectItem itemLabel="ZAR — Rand sud-africain" itemValue="ZAR" />
<p:ajax event="change" update="panelBudget panelCotisation" />
</p:selectOneMenu>
<p:tooltip for="devise" value="Code ISO 4217 de la devise (3 lettres)" position="top"/>
<small class="text-500">
<i class="pi pi-info-circle mr-1"/>
XOF/XAF (Franc CFA) : entiers, sans centimes
</small>
</div>
<!-- Budget annuel -->
<div class="field col-12 md:col-5">
<p:outputLabel for="budget" value="Budget annuel" styleClass="font-semibold" />
<p:inputNumber id="budget"
value="#{model.budgetAnnuel}"
decimalPlaces="2"
minValue="0"
placeholder="0.00" />
</div>
<!-- Devise -->
<div class="field col-12 md:col-2">
<p:outputLabel for="devise" value="Devise" styleClass="font-semibold" />
<p:inputText id="devise"
value="#{model.devise}"
maxlength="3"
placeholder="XOF"
style="text-transform: uppercase;" />
<small class="text-muted">Code ISO (ex: XOF, EUR)</small>
<h:panelGroup id="panelBudget" layout="block">
<p:inputNumber id="budget"
value="#{model.budgetAnnuel}"
decimalPlaces="#{model.devise == 'XOF' or model.devise == 'XAF' ? 0 : 2}"
minValue="0"
placeholder="#{model.devise == 'XOF' or model.devise == 'XAF' ? '0' : '0,00'}"
decimalSeparator=","
thousandSeparator=" " />
</h:panelGroup>
<small class="text-500">
<i class="pi pi-chart-line mr-1"/>
Budget prévisionnel ou réalisé de l'année en cours
</small>
</div>
<!-- Montant cotisation -->
<div class="field col-12 md:col-5">
<p:outputLabel for="montantCotisation" value="Cotisation annuelle" styleClass="font-semibold" />
<p:inputNumber id="montantCotisation"
value="#{model.montantCotisationAnnuelle}"
decimalPlaces="2"
minValue="0"
placeholder="0.00" />
<div class="field col-12 md:col-4">
<p:outputLabel for="montantCotisation" value="Cotisation annuelle (membre)" styleClass="font-semibold" />
<h:panelGroup id="panelCotisation" layout="block">
<p:inputNumber id="montantCotisation"
value="#{model.montantCotisationAnnuelle}"
decimalPlaces="#{model.devise == 'XOF' or model.devise == 'XAF' ? 0 : 2}"
minValue="0"
placeholder="#{model.devise == 'XOF' or model.devise == 'XAF' ? '0' : '0,00'}"
decimalSeparator=","
thousandSeparator=" " />
</h:panelGroup>
<small class="text-500">
<i class="pi pi-wallet mr-1"/>
Montant annuel de la cotisation par membre (si applicable)
</small>
</div>
</div>
</p:fieldset>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 7 : MISSION & ACTIVITÉS -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Mission &amp; Activités" toggleable="true" collapsed="true" styleClass="mb-3">
<p:fieldset legend="🎯 Mission &amp; Activités" toggleable="true" collapsed="true" styleClass="mb-3">
<div class="formgrid grid">
<!-- Objectifs -->
<div class="field col-12">
<p:outputLabel for="objectifs" value="Objectifs stratégiques" styleClass="font-semibold" />
<p:inputTextarea id="objectifs"
value="#{model.objectifs}"
rows="4"
<p:inputTextarea id="objectifs"
value="#{model.objectifs}"
rows="4"
maxlength="2000"
placeholder="Décrivez les objectifs principaux de l'organisation..."
autoResize="false" />
<small class="text-muted">#{2000 - (empty model.objectifs ? 0 : model.objectifs.length())} caractères restants</small>
placeholder="Décrivez les objectifs principaux de l'organisation, sa vision et ses buts stratégiques..."
autoResize="false">
<f:validateLength maximum="2000" />
</p:inputTextarea>
<p:message for="objectifs" />
<small class="text-500">
<i class="pi pi-flag mr-1"/>
Maximum 2000 caractères
</small>
</div>
<!-- Activités principales -->
<div class="field col-12">
<p:outputLabel for="activites" value="Activités principales" styleClass="font-semibold" />
<p:inputTextarea id="activites"
value="#{model.activitesPrincipales}"
rows="4"
<p:inputTextarea id="activites"
value="#{model.activitesPrincipales}"
rows="4"
maxlength="2000"
placeholder="Décrivez les activités et programmes mis en œuvre..."
autoResize="false" />
<small class="text-muted">#{2000 - (empty model.activitesPrincipales ? 0 : model.activitesPrincipales.length())} caractères restants</small>
placeholder="Décrivez les activités, programmes et initiatives concrètes mis en œuvre par l'organisation..."
autoResize="false">
<f:validateLength maximum="2000" />
</p:inputTextarea>
<p:message for="activites" />
<small class="text-500">
<i class="pi pi-list mr-1"/>
Maximum 2000 caractères
</small>
</div>
</div>
</p:fieldset>
@@ -407,28 +684,40 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 8 : PARTENARIATS & CERTIFICATIONS -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Partenariats &amp; Certifications" toggleable="true" collapsed="true" styleClass="mb-3">
<p:fieldset legend="🤝 Partenariats &amp; Certifications" toggleable="true" collapsed="true" styleClass="mb-3">
<div class="formgrid grid">
<!-- Certifications / Labels -->
<div class="field col-12 md:col-6">
<p:outputLabel for="certifications" value="Certifications / Labels" styleClass="font-semibold" />
<p:inputTextarea id="certifications"
value="#{model.certifications}"
rows="3"
<p:inputTextarea id="certifications"
value="#{model.certifications}"
rows="3"
maxlength="500"
placeholder="Ex: ISO 9001, Label RSE..."
autoResize="false" />
placeholder="Ex: ISO 9001:2015, Label RSE, Certification Qualité..."
autoResize="false">
<f:validateLength maximum="500" />
</p:inputTextarea>
<small class="text-500">
<i class="pi pi-verified mr-1"/>
Maximum 500 caractères
</small>
</div>
<!-- Partenaires principaux -->
<div class="field col-12 md:col-6">
<p:outputLabel for="partenaires" value="Partenaires principaux" styleClass="font-semibold" />
<p:inputTextarea id="partenaires"
value="#{model.partenaires}"
rows="3"
<p:inputTextarea id="partenaires"
value="#{model.partenaires}"
rows="3"
maxlength="1000"
placeholder="Liste des partenaires stratégiques..."
autoResize="false" />
placeholder="Liste des partenaires stratégiques, bailleurs, organisations partenaires..."
autoResize="false">
<f:validateLength maximum="1000" />
</p:inputTextarea>
<small class="text-500">
<i class="pi pi-users mr-1"/>
Maximum 1000 caractères
</small>
</div>
</div>
</p:fieldset>
@@ -436,21 +725,26 @@
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- SECTION 9 : NOTES ADMINISTRATIVES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<p:fieldset legend="Notes administratives" toggleable="true" collapsed="true" styleClass="mb-3">
<p:fieldset legend="📝 Notes administratives" toggleable="true" collapsed="true" styleClass="mb-3">
<div class="formgrid grid">
<div class="field col-12">
<p:outputLabel for="notes" value="Notes internes" styleClass="font-semibold" />
<p:inputTextarea id="notes"
value="#{model.notes}"
rows="4"
<p:inputTextarea id="notes"
value="#{model.notes}"
rows="4"
maxlength="1000"
placeholder="Notes réservées à l'usage administratif interne..."
autoResize="false" />
<small class="text-muted">Ces notes ne sont visibles que par les administrateurs</small>
placeholder="Notes réservées à l'usage administratif interne, remarques, historique..."
autoResize="false">
<f:validateLength maximum="1000" />
</p:inputTextarea>
<small class="text-500">
<i class="pi pi-lock mr-1"/>
Ces notes ne sont visibles que par les administrateurs - Maximum 1000 caractères
</small>
</div>
</div>
</p:fieldset>
</div>
</ui:fragment>
</ui:fragment>

View File

@@ -7,21 +7,32 @@ quarkus.log.category."jakarta.faces".level=INFO
quarkus.log.category."org.apache.myfaces".level=INFO
quarkus.log.category."org.primefaces".level=INFO
# Configuration MyFaces pour développement
quarkus.myfaces.project-stage=Development
# Hot reload
quarkus.live-reload.instrumentation=true
# Configuration Keycloak pour développement
# Configuration Keycloak pour développement LOCAL
%dev.quarkus.oidc.enabled=true
%dev.quarkus.oidc.tls.verification=none
%dev.quarkus.oidc.authentication.redirect-path=/auth/callback
# %dev.quarkus.oidc.authentication.force-redirect-https=false # Not supported in this Quarkus version
%dev.quarkus.security.auth.enabled=true
# Secret Keycloak pour développement (UNIQUEMENT pour dev local)
# ⚠️ ATTENTION: Ne jamais commiter ce secret en production
# En production, utiliser la variable d'environnement KEYCLOAK_CLIENT_SECRET
%dev.quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET:7dnWMwlabtoyp08F6FIuDxzDPE5VdUF6}
# Configuration pour Keycloak local sur http://localhost:8180
%dev.quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow
%dev.quarkus.oidc.client-id=unionflow-client
# SÉCURITÉ: TLS verification désactivée UNIQUEMENT pour Keycloak local HTTP
# En production, toujours utiliser HTTPS avec tls.verification=required
%dev.quarkus.oidc.tls.verification=none
%dev.quarkus.oidc.authentication.redirect-path=/auth/callback
%dev.quarkus.oidc.authentication.restore-path-after-redirect=true
%dev.quarkus.oidc.authentication.scopes=openid,profile,email,roles
%dev.quarkus.oidc.token.issuer=any
# Secret Keycloak pour développement
# SÉCURITÉ: En dev local, on peut utiliser un secret par défaut pour faciliter le développement
# En production, utilisez TOUJOURS une variable d'environnement
%dev.quarkus.oidc.credentials.secret=P18Mw0uNVzSPeI4P0fymD18r2oxiejw4
# Configuration Backend pour dev local
%dev.unionflow.backend.url=http://localhost:8085
# Logging OIDC pour debug
%dev.quarkus.log.category."io.quarkus.oidc".level=DEBUG

View File

@@ -9,11 +9,41 @@ quarkus.http.so-reuse-port=true
quarkus.http.tcp-quick-ack=true
quarkus.http.tcp-cork=true
# Configuration Session HTTP - Production
quarkus.http.session-timeout=60m
quarkus.http.session-cookie-same-site=strict
quarkus.http.session-cookie-http-only=true
quarkus.http.session-cookie-secure=true
# ===================================================================================================
# Headers de Sécurité HTTP - PRODUCTION
# ===================================================================================================
# Prévention du MIME sniffing
quarkus.http.header."X-Content-Type-Options".value=nosniff
quarkus.http.header."X-Content-Type-Options".methods=GET,POST,PUT,DELETE,PATCH
# Protection contre le clickjacking
quarkus.http.header."X-Frame-Options".value=DENY
quarkus.http.header."X-Frame-Options".methods=GET,POST,PUT,DELETE,PATCH
# HSTS - Force HTTPS pour 1 an
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains; preload
quarkus.http.header."Strict-Transport-Security".methods=GET,POST,PUT,DELETE,PATCH
# Content Security Policy
# Permet 'unsafe-inline' pour PrimeFaces/JSF qui génèrent des scripts inline
quarkus.http.header."Content-Security-Policy".value=default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'
quarkus.http.header."Content-Security-Policy".methods=GET,POST,PUT,DELETE,PATCH
# Protection XSS (legacy, mais utile pour anciens navigateurs)
quarkus.http.header."X-XSS-Protection".value=1; mode=block
quarkus.http.header."X-XSS-Protection".methods=GET,POST,PUT,DELETE,PATCH
# Politique de référents
quarkus.http.header."Referrer-Policy".value=strict-origin-when-cross-origin
quarkus.http.header."Referrer-Policy".methods=GET,POST,PUT,DELETE,PATCH
# Permissions Policy (anciennement Feature-Policy)
quarkus.http.header."Permissions-Policy".value=geolocation=(), microphone=(), camera=()
quarkus.http.header."Permissions-Policy".methods=GET,POST,PUT,DELETE,PATCH
# Compression HTTP pour améliorer les performances
quarkus.http.enable-compression=true
quarkus.http.compression-level=6
# Configuration logging - Production
quarkus.log.console.enable=true
@@ -23,35 +53,6 @@ quarkus.log.category."dev.lions.unionflow".level=INFO
quarkus.log.category."org.primefaces".level=WARN
quarkus.log.category."org.apache.myfaces".level=WARN
# MyFaces Configuration - Production
quarkus.myfaces.project-stage=Production
quarkus.myfaces.state-saving-method=server
quarkus.myfaces.number-of-views-in-session=50
quarkus.myfaces.number-of-sequential-views-in-session=10
quarkus.myfaces.serialize-state-in-session=false
quarkus.myfaces.client-view-state-timeout=3600000
quarkus.myfaces.view-expired-exception-handler-redirect-page=/
quarkus.myfaces.check-id-production-mode=true
quarkus.myfaces.strict-xhtml-links=true
quarkus.myfaces.refresh-transient-build-on-pss=true
quarkus.myfaces.resource-max-time-expires=604800000
quarkus.myfaces.resource-buffer-size=2048
# PrimeFaces Configuration - Production
primefaces.THEME=none
primefaces.FONT_AWESOME=true
primefaces.CLIENT_SIDE_VALIDATION=true
primefaces.MOVE_SCRIPTS_TO_BOTTOM=true
primefaces.CSP=true
primefaces.UPLOADER=commons
primefaces.AUTO_UPDATE=false
primefaces.CACHE_PROVIDER=org.primefaces.cache.DefaultCacheProvider
primefaces.RESOURCE_HANDLER=org.primefaces.application.resource.PrimeResourceHandler
# OmniFaces Configuration - Production
omnifaces.CDN_RESOURCE_HANDLER_DISABLED=true
omnifaces.COMBINED_RESOURCE_HANDLER_DISABLED=false
# Configuration Backend UnionFlow - Production
unionflow.backend.url=${UNIONFLOW_BACKEND_URL:https://api.lions.dev/unionflow}
@@ -59,8 +60,9 @@ unionflow.backend.url=${UNIONFLOW_BACKEND_URL:https://api.lions.dev/unionflow}
quarkus.rest-client."unionflow-api".url=${unionflow.backend.url}
quarkus.rest-client."unionflow-api".scope=jakarta.inject.Singleton
quarkus.rest-client."unionflow-api".connect-timeout=5000
quarkus.rest-client."unionflow-api".read-timeout=30000
quarkus.rest-client."unionflow-api".providers=dev.lions.unionflow.client.service.RestClientExceptionMapper,dev.lions.unionflow.client.security.JwtClientRequestFilter
quarkus.rest-client."unionflow-api".read-timeout=15000
quarkus.rest-client."unionflow-api".providers=dev.lions.unionflow.client.service.RestClientExceptionMapper
# NOTE: JwtClientRequestFilter retiré - utilisation de AuthHeaderFactory via @RegisterClientHeaders
# Configuration Keycloak OIDC - Production
quarkus.oidc.enabled=true
@@ -68,35 +70,50 @@ quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.d
quarkus.oidc.client-id=unionflow-client
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
quarkus.oidc.application-type=web-app
# Callback path - must match Keycloak Valid Redirect URIs configuration
quarkus.oidc.authentication.redirect-path=/auth/callback
quarkus.oidc.authentication.force-redirect-https-scheme=true
quarkus.oidc.authentication.restore-path-after-redirect=true
# Default landing page after successful login
quarkus.oidc.authentication.scopes=openid,profile,email,roles
quarkus.oidc.token.issuer=https://security.lions.dev/realms/unionflow
quarkus.oidc.tls.verification=required
quarkus.oidc.authentication.cookie-same-site=lax
quarkus.oidc.authentication.java-script-auto-redirect=false
quarkus.oidc.discovery-enabled=true
quarkus.oidc.verify-access-token=true
# Activation de la sécurité
quarkus.security.auth.enabled=true
# IMPORTANT: L'ordre des permissions compte - les plus spécifiques doivent être EN PREMIER
# Chemins publics (non protégés par OIDC) - Production
quarkus.http.auth.permission.public.paths=/,/index.xhtml,/pages/public/*,/auth/*,/q/*,/q/oidc/*,/favicon.ico,/resources/*,/META-INF/resources/*,/images/*,/jakarta.faces.resource/*,/javax.faces.resource/*
quarkus.http.auth.permission.public.policy=permit
# Tous les autres chemins nécessitent une authentification
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.paths=/pages/secure/*
quarkus.http.auth.permission.authenticated.policy=authenticated
# Configuration Session - Production
# Configuration Session (custom properties, gérées côté applicatif)
unionflow.session.timeout=${SESSION_TIMEOUT:1800}
unionflow.session.remember-me.duration=${REMEMBER_ME_DURATION:604800}
# Configuration de sécurité - Production
# Configuration de sécurité (custom properties, gérées côté applicatif)
unionflow.security.enable-csrf=${ENABLE_CSRF:true}
unionflow.security.password.min-length=${PASSWORD_MIN_LENGTH:8}
unionflow.security.password.require-special-chars=${PASSWORD_REQUIRE_SPECIAL:true}
unionflow.security.max-login-attempts=${MAX_LOGIN_ATTEMPTS:5}
unionflow.security.lockout-duration=${LOCKOUT_DURATION:300}
# ===================================================================================================
# Configuration Quarkus Arc CDI - Intégration avec JSF/MyFaces - Production
# ===================================================================================================
# Active le mode découverte de beans pour permettre à Arc de gérer tous les beans CDI et JSF
quarkus.arc.unremovable-types=jakarta.faces.application.Application,jakarta.faces.context.FacesContext,jakarta.enterprise.context.Conversation
# Permet à Arc de détecter et gérer les beans avec annotations JSF custom scopes (@ViewScoped, etc.)
quarkus.arc.detect-unused-false-positives=true
# Force Arc à gérer les beans même s'ils ne sont pas référencés directement
quarkus.arc.remove-unused-beans=false
# Active le support complet CDI pour JSF Expression Language (EL)
# Cela permet à MyFaces d'utiliser Arc BeanManager pour résoudre #{bean.property}
quarkus.arc.auto-inject-fields=true

View File

@@ -10,48 +10,11 @@ quarkus.http.so-reuse-port=true
quarkus.http.tcp-quick-ack=true
quarkus.http.tcp-cork=true
# Configuration Session HTTP
quarkus.http.session-timeout=60m
quarkus.http.session-cookie-same-site=lax
quarkus.http.session-cookie-http-only=true
quarkus.http.session-cookie-secure=false
# Configuration logging
quarkus.log.console.enable=true
quarkus.log.console.level=INFO
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
# MyFaces Configuration
quarkus.myfaces.project-stage=Development
quarkus.myfaces.state-saving-method=server
quarkus.myfaces.number-of-views-in-session=50
quarkus.myfaces.number-of-sequential-views-in-session=10
quarkus.myfaces.serialize-state-in-session=false
quarkus.myfaces.client-view-state-timeout=3600000
quarkus.myfaces.view-expired-exception-handler-redirect-page=/
quarkus.myfaces.check-id-production-mode=false
quarkus.myfaces.strict-xhtml-links=false
quarkus.myfaces.refresh-transient-build-on-pss=true
quarkus.myfaces.resource-max-time-expires=604800000
quarkus.myfaces.resource-buffer-size=2048
# PrimeFaces Configuration
# IMPORTANT: Nous laissons PrimeFaces sans th<74>me par d<>faut et chargeons le th<74>me Freya via index.xhtml/main-template.xhtml
# pour <20>viter tout double-chargement (ex: Saga + Freya).
primefaces.THEME=none
primefaces.FONT_AWESOME=true
primefaces.CLIENT_SIDE_VALIDATION=true
primefaces.MOVE_SCRIPTS_TO_BOTTOM=true
primefaces.CSP=false
primefaces.UPLOADER=commons
primefaces.AUTO_UPDATE=false
primefaces.CACHE_PROVIDER=org.primefaces.cache.DefaultCacheProvider
primefaces.RESOURCE_HANDLER=org.primefaces.application.resource.PrimeResourceHandler
# OmniFaces Configuration
omnifaces.CDN_RESOURCE_HANDLER_DISABLED=true
omnifaces.COMBINED_RESOURCE_HANDLER_DISABLED=false
# Configuration Backend UnionFlow
unionflow.backend.url=${UNIONFLOW_BACKEND_URL:http://localhost:8085}
@@ -61,8 +24,8 @@ quarkus.rest-client."unionflow-api".scope=jakarta.inject.Singleton
quarkus.rest-client."unionflow-api".connect-timeout=5000
quarkus.rest-client."unionflow-api".read-timeout=30000
# Gestion des erreurs REST + propagation JWT vers le backend
quarkus.rest-client."unionflow-api".providers=dev.lions.unionflow.client.service.RestClientExceptionMapper,dev.lions.unionflow.client.security.JwtClientRequestFilter
# Gestion des erreurs REST (JwtClientRequestFilter est déprécié - remplacé par AuthHeaderFactory)
quarkus.rest-client."unionflow-api".providers=dev.lions.unionflow.client.service.RestClientExceptionMapper
# Configuration Keycloak OIDC
quarkus.oidc.enabled=true
@@ -74,28 +37,22 @@ quarkus.oidc.authentication.redirect-path=/auth/callback
quarkus.oidc.authentication.restore-path-after-redirect=true
quarkus.oidc.authentication.scopes=openid,profile,email,roles
quarkus.oidc.token.issuer=https://security.lions.dev/realms/unionflow
quarkus.oidc.tls.verification=none
# quarkus.oidc.authentication.force-redirect-https=false # Not supported in this Quarkus version
# SÉCURITÉ: TLS verification DOIT être 'required' par défaut
# Seulement 'none' en développement local (voir application-dev.properties)
quarkus.oidc.tls.verification=required
quarkus.oidc.authentication.cookie-same-site=lax
quarkus.oidc.authentication.java-script-auto-redirect=false
quarkus.oidc.discovery-enabled=true
# TEMPORAIRE: contourner un access token invalide (claim realm_access dupliqu<71>e c<>t<EFBFBD> KC)
# Pour un flux web-app, on peut s'appuyer sur l'ID Token et d<>sactiver la v<>rification de l'Access Token
# Les deux cl<63>s ci?dessous couvrent les variantes selon version de Quarkus; l'une sera ignor<6F>e si non support<72>e.
# Vérification du token activée
# ✅ CORRIGÉ: Le mapper Keycloak problématique a été supprimé (17/11/2025)
# Le scope "roles" gère maintenant correctement realm_access.roles (objet unique)
quarkus.oidc.verify-access-token=true
# Configuration du logout OIDC
quarkus.oidc.logout.path=/logout
quarkus.oidc.logout.post-logout-path=/index.xhtml
# Activation de la sécurité
quarkus.security.auth.enabled=true
# Chemins publics (non prot<6F>g<EFBFBD>s par OIDC) - Doit <20>tre d<>fini en premier
# Chemins publics (non protégés par OIDC) - Doit être défini en premier
# La page d'accueil (/) et index.xhtml sont publics pour permettre l'affichage initial
# IMPORTANT: Les ressources JSF/PrimeFaces sont servies via /jakarta.faces.resource/* (ou /javax.faces.resource/* selon la stack)
# Elles doivent <EFBFBD>tre publiques pour permettre le chargement des CSS/JS (th<EFBFBD>me Freya, primeicons, primeflex, etc.)
# On inclut <EFBFBD>galement /q/oidc/* pour laisser Quarkus OIDC exposer ses endpoints internes si n<EFBFBD>cessaire.
# Elles doivent être publiques pour permettre le chargement des CSS/JS (thème Freya, primeicons, primeflex, etc.)
# On inclut également /q/oidc/* pour laisser Quarkus OIDC exposer ses endpoints internes si nécessaire.
quarkus.http.auth.permission.public.paths=/,/index.xhtml,/pages/public/*,/auth/*,/q/*,/q/oidc/*,/favicon.ico,/resources/*,/META-INF/resources/*,/images/*,/jakarta.faces.resource/*,/javax.faces.resource/*
quarkus.http.auth.permission.public.policy=permit
@@ -105,11 +62,11 @@ quarkus.http.auth.permission.public.policy=permit
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated
# Configuration Session
# Configuration Session (custom properties, gérées côté applicatif)
unionflow.session.timeout=${SESSION_TIMEOUT:1800}
unionflow.session.remember-me.duration=${REMEMBER_ME_DURATION:604800}
# Configuration de sécurité
# Configuration de sécurité (custom properties, gérées côté applicatif)
unionflow.security.enable-csrf=${ENABLE_CSRF:true}
unionflow.security.password.min-length=${PASSWORD_MIN_LENGTH:8}
unionflow.security.password.require-special-chars=${PASSWORD_REQUIRE_SPECIAL:true}

Some files were not shown because too many files have changed in this diff Show More