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

@@ -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>