fix(frontend): corrections workflow v3.0 — inscription événements, CreateMembreRequest, AJAX session expiry

Services:
- EvenementService: POST /inscriptions (sans membreId), DELETE /inscriptions, GET /recherche, GET /type/{type}
- MembreService: creer() accepte CreateMembreRequest au lieu de MembreResponse
- Nouveaux services: BackupService, EpargneService, FinanceApprovalService, LogsService, MessageService, OrganisationService, PaiementClientService

Beans:
- MembreInscriptionBean: construit CreateMembreRequest.builder() avec organisationId UUID
- EvenementsBean: inscrireParticipant(id) sans userId (backend infère depuis token)
- DashboardBean: checkAccessAndRedirect() SUPER_ADMIN en premier

Sécurité:
- AuthenticationFilter: gestion AJAX PrimeFaces (partial/ajax → XML partial-response redirect)
- PermissionChecker: vérification rôles côté bean
- k8s/: manifestes secrets SMTP et Wave (placeholders à remplir)

Pages XHTML: dashboards rôles, cotisations, membres, événements, organisations
This commit is contained in:
dahoud
2026-04-07 20:54:20 +00:00
parent 0dc050f422
commit ac0c5a67a1
96 changed files with 7264 additions and 2497 deletions

View File

@@ -259,10 +259,10 @@
value="#{cotisationsGestionBean.filtres.statut}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les statuts" itemValue="" />
<f:selectItem itemLabel="Payées" itemValue="PAYE" />
<f:selectItem itemLabel="Payées" itemValue="PAYEE" />
<f:selectItem itemLabel="En attente" itemValue="EN_ATTENTE" />
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD" />
<f:selectItem itemLabel="Annulées" itemValue="ANNULE" />
<f:selectItem itemLabel="Annulées" itemValue="ANNULEE" />
</p:selectOneMenu>
</div>
@@ -465,25 +465,29 @@
<p:column headerText="Actions" style="width:8rem" exportable="false">
<div class="flex gap-1">
<p:commandButton icon="pi pi-check"
<p:commandButton icon="pi pi-check"
styleClass="ui-button-rounded ui-button-text ui-button-success ui-button-sm"
title="Enregistrer paiement"
action="#{cotisationsGestionBean.enregistrerPaiement(cotisation)}"
rendered="#{cotisation.statut != 'PAYE'}" />
<p:commandButton icon="pi pi-file-pdf"
action="#{cotisationsGestionBean.ouvrirDialogPaiement(cotisation)}"
update="formPaiement"
process="@this"
rendered="#{cotisation.statut != 'PAYEE' and cotisation.statut != 'ANNULEE'}" />
<p:commandButton icon="pi pi-file-pdf"
styleClass="ui-button-rounded ui-button-text ui-button-info ui-button-sm"
title="Générer reçu"
action="#{cotisationsGestionBean.genererRecu(cotisation)}"
rendered="#{cotisation.statut == 'PAYE'}" />
rendered="#{cotisation.statut == 'PAYEE' or cotisation.statut == 'PARTIELLEMENT_PAYEE'}" />
<p:commandButton icon="pi pi-send"
styleClass="ui-button-rounded ui-button-text ui-button-warning ui-button-sm"
title="Envoyer rappel"
action="#{cotisationsGestionBean.envoyerRappel(cotisation)}"
rendered="#{cotisation.statut == 'EN_RETARD'}" />
<p:commandButton icon="pi pi-eye"
<p:commandButton icon="pi pi-eye"
styleClass="ui-button-rounded ui-button-text ui-button-secondary ui-button-sm"
title="Voir détails"
action="#{cotisationsGestionBean.voirDetails(cotisation)}" />
action="#{cotisationsGestionBean.voirDetails(cotisation)}"
update="formDetailCotisation"
process="@this" />
</div>
</p:column>
</p:dataTable>
@@ -721,5 +725,185 @@
</div>
</h:form>
</p:dialog>
<!-- Dialog Enregistrement Paiement -->
<ui:decorate template="/templates/components/dialogs/form-dialog.xhtml">
<ui:param name="dialogId" value="dlgPaiement" />
<ui:param name="header" value="Enregistrer un paiement" />
<ui:param name="widgetVar" value="dlgPaiement" />
<ui:param name="formId" value="formPaiement" />
<ui:param name="width" value="500" />
<ui:param name="dynamic" value="false" />
<ui:define name="content">
<ui:fragment rendered="#{cotisationsGestionBean.cotisationAPayer != null}">
<!-- Récapitulatif cotisation -->
<div class="col-12">
<div class="surface-100 border-round-lg p-3 mb-2">
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Membre" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationAPayer.nomMembre}" />
<ui:param name="valueClass" value="font-semibold" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Référence" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationAPayer.numeroReference}" />
<ui:param name="valueClass" value="text-500 text-sm" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Montant dû" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationAPayer.montantFormatte}" />
<ui:param name="valueClass" value="font-bold text-primary" />
</ui:include>
</div>
</div>
<!-- Montant payé -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-number.xhtml">
<ui:param name="id" value="montantPaiement" />
<ui:param name="label" value="Montant payé *" />
<ui:param name="value" value="#{cotisationsGestionBean.montantPaiement}" />
<ui:param name="minValue" value="0" />
</ui:include>
</div>
<!-- Mode de paiement -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="modePaiement" />
<ui:param name="label" value="Mode de paiement *" />
<ui:param name="value" value="#{cotisationsGestionBean.modePaiement}" />
<ui:param name="required" value="true" />
<ui:define name="items">
<f:selectItem itemLabel="Espèces" itemValue="ESPECES" />
<f:selectItem itemLabel="Virement bancaire" itemValue="VIREMENT" />
<f:selectItem itemLabel="Chèque" itemValue="CHEQUE" />
<f:selectItem itemLabel="Wave" itemValue="WAVE" />
<f:selectItem itemLabel="Orange Money" itemValue="ORANGE_MONEY" />
<f:selectItem itemLabel="Mobile Money" itemValue="MOBILE_MONEY" />
<f:selectItem itemLabel="Carte bancaire" itemValue="CARTE" />
<f:selectItem itemLabel="Manuel (autre)" itemValue="MANUEL" />
</ui:define>
</ui:include>
</div>
<!-- Date de paiement -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-calendar.xhtml">
<ui:param name="id" value="datePaiement" />
<ui:param name="label" value="Date de paiement *" />
<ui:param name="value" value="#{cotisationsGestionBean.datePaiement}" />
<ui:param name="required" value="true" />
<ui:param name="pattern" value="dd/MM/yyyy" />
</ui:include>
</div>
<!-- Référence transaction -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-text.xhtml">
<ui:param name="id" value="referencePaiement" />
<ui:param name="label" value="Référence / N° transaction" />
<ui:param name="value" value="#{cotisationsGestionBean.referencePaiement}" />
<ui:param name="placeholder" value="Ex: TXN-20260407-001" />
</ui:include>
</div>
</ui:fragment>
</ui:define>
<ui:define name="footer">
<p:commandButton value="Annuler"
icon="pi pi-times"
type="button"
onclick="PF('dlgPaiement').hide();"
styleClass="ui-button-secondary ui-button-outlined" />
<p:commandButton value="Confirmer le paiement"
icon="pi pi-check"
styleClass="ui-button-success"
action="#{cotisationsGestionBean.confirmerPaiement}"
update="formPaiement"
process="formPaiement"
oncomplete="if(!args.validationFailed) PF('dlgPaiement').hide();" />
</ui:define>
</ui:decorate>
<!-- Dialog Détail Cotisation -->
<ui:decorate template="/templates/components/dialogs/form-dialog.xhtml">
<ui:param name="dialogId" value="dlgDetailCotisation" />
<ui:param name="header" value="Détail de la cotisation" />
<ui:param name="widgetVar" value="dlgDetailCotisation" />
<ui:param name="formId" value="formDetailCotisation" />
<ui:param name="width" value="600" />
<ui:param name="dynamic" value="false" />
<ui:define name="content">
<ui:fragment rendered="#{cotisationsGestionBean.cotisationDetail != null}">
<!-- Badge statut + référence -->
<div class="col-12 mb-2">
<div class="flex align-items-center gap-2">
<p:tag value="#{cotisationsGestionBean.cotisationDetail.statutLibelle}"
severity="#{cotisationsGestionBean.cotisationDetail.statutSeverity}"
icon="pi #{cotisationsGestionBean.cotisationDetail.statutIcon}" />
<span class="text-500 text-sm">#{cotisationsGestionBean.cotisationDetail.numeroReference}</span>
</div>
</div>
<!-- Champs détails -->
<div class="col-12">
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Membre" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.nomMembre} (#{cotisationsGestionBean.cotisationDetail.numeroMembre})" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Organisation" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.nomOrganisation}" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Type" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.typeCotisationLibelle}" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Période" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.periode}" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Montant dû" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.montantFormatte}" />
<ui:param name="valueClass" value="font-bold" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Montant payé" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.montantPaye != null ? cotisationsGestionBean.cotisationDetail.montantPaye : 0} #{cotisationsGestionBean.cotisationDetail.codeDevise}" />
<ui:param name="valueClass" value="font-bold text-green-600" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Échéance" />
<ui:param name="value" value="#{cotisationsGestionBean.cotisationDetail.dateEcheanceFormattee}" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Date paiement" />
<ui:param name="value" value="#{not empty cotisationsGestionBean.cotisationDetail.datePaiementFormattee ? cotisationsGestionBean.cotisationDetail.datePaiementFormattee : '—'}" />
</ui:include>
</div>
<!-- Progression -->
<div class="col-12" rendered="#{cotisationsGestionBean.cotisationDetail.pourcentagePaiement gt 0}">
<div class="text-500 text-sm mb-1">Progression du paiement</div>
<p:progressBar value="#{cotisationsGestionBean.cotisationDetail.pourcentagePaiement}" displayOnly="true" />
<div class="text-xs text-500 mt-1">#{cotisationsGestionBean.cotisationDetail.pourcentagePaiement}%</div>
</div>
<!-- Alerte retard -->
<div class="col-12" rendered="#{cotisationsGestionBean.cotisationDetail.enRetard}">
<div class="surface-50 border-round p-2 flex align-items-center gap-2">
<i class="pi pi-exclamation-triangle text-red-500" />
<span class="text-red-500 text-sm font-medium">#{cotisationsGestionBean.cotisationDetail.retardTexte}</span>
</div>
</div>
<!-- Observations -->
<div class="col-12" rendered="#{not empty cotisationsGestionBean.cotisationDetail.observations}">
<div class="text-500 text-sm mb-1">Observations</div>
<div class="text-900 text-sm surface-50 border-round p-2">#{cotisationsGestionBean.cotisationDetail.observations}</div>
</div>
</ui:fragment>
</ui:define>
<ui:define name="footer">
<p:commandButton value="Fermer"
icon="pi pi-times"
type="button"
onclick="PF('dlgDetailCotisation').hide();"
styleClass="ui-button-secondary ui-button-outlined" />
</ui:define>
</ui:decorate>
</ui:define>
</ui:composition>

View File

@@ -33,20 +33,20 @@
</p>
<p class="text-600 m-0">
<i class="pi pi-calendar mr-1"></i>
Membre depuis #{membreDashboardBean.membre.dateAdhesion}
Membre depuis #{membreDashboardBean.membre.dateAdhesionFormatee}
</p>
</div>
</div>
<h:form id="formProfileActions">
<div class="flex gap-2">
<p:commandButton value="Mon profil"
icon="pi pi-user"
<p:commandButton value="Mon profil"
icon="pi pi-user"
styleClass="ui-button-outlined ui-button-info"
action="/pages/membre/profil?faces-redirect=true" />
<p:commandButton value="Mes cotisations"
icon="pi pi-dollar"
action="/pages/secure/personnel/profil.xhtml?faces-redirect=true" />
<p:commandButton value="Mes cotisations"
icon="pi pi-dollar"
styleClass="ui-button-outlined ui-button-success"
action="/pages/membre/cotisations?faces-redirect=true" />
action="/pages/secure/cotisation/historique.xhtml?faces-redirect=true" />
</div>
</h:form>
</div>
@@ -154,10 +154,10 @@
<div class="font-medium text-900 mb-2">Cotisations</div>
<div class="text-600 mb-3 text-sm">Consultez votre situation et payez en ligne</div>
<h:form id="formActionCotisations">
<p:commandButton value="Voir mes cotisations"
icon="pi pi-arrow-right"
<p:commandButton value="Voir mes cotisations"
icon="pi pi-arrow-right"
styleClass="ui-button-outlined ui-button-success w-full"
action="/pages/membre/cotisations?faces-redirect=true" />
action="/pages/secure/cotisation/historique.xhtml?faces-redirect=true" />
</h:form>
</div>
</div>
@@ -168,10 +168,10 @@
<div class="font-medium text-900 mb-2">Événements</div>
<div class="text-600 mb-3 text-sm">Découvrez et inscrivez-vous aux événements</div>
<h:form id="formActionEvenements">
<p:commandButton value="Voir les événements"
icon="pi pi-arrow-right"
<p:commandButton value="Voir les événements"
icon="pi pi-arrow-right"
styleClass="ui-button-outlined ui-button-info w-full"
action="/pages/membre/evenements?faces-redirect=true" />
action="/pages/secure/evenement/gestion.xhtml?faces-redirect=true" />
</h:form>
</div>
</div>
@@ -182,10 +182,10 @@
<div class="font-medium text-900 mb-2">Demandes</div>
<div class="text-600 mb-3 text-sm">Faites une demande d'aide ou de service</div>
<h:form id="formActionDemandes">
<p:commandButton value="Nouvelle demande"
icon="pi pi-arrow-right"
<p:commandButton value="Nouvelle demande"
icon="pi pi-arrow-right"
styleClass="ui-button-outlined ui-button-warning w-full"
action="/pages/membre/demandes?faces-redirect=true" />
action="/pages/secure/aide/demande.xhtml?faces-redirect=true" />
</h:form>
</div>
</div>
@@ -202,10 +202,10 @@
Mes Prochains Événements
</h5>
<h:form id="formVoirTousEvenements">
<p:commandButton value="Voir tous"
icon="pi pi-arrow-right"
<p:commandButton value="Voir tous"
icon="pi pi-arrow-right"
styleClass="ui-button-text ui-button-sm"
action="/pages/membre/evenements?faces-redirect=true" />
action="/pages/secure/evenement/gestion.xhtml?faces-redirect=true" />
</h:form>
</div>
@@ -252,10 +252,10 @@
<i class="pi pi-calendar text-6xl text-300 mb-3"></i>
<div class="text-600 mb-3">Aucun événement à venir</div>
<h:form id="formDecouvrirEvenements">
<p:commandButton value="Découvrir les événements"
icon="pi pi-search"
<p:commandButton value="Découvrir les événements"
icon="pi pi-search"
styleClass="ui-button-outlined ui-button-info"
action="/pages/membre/evenements?faces-redirect=true" />
action="/pages/secure/evenement/gestion.xhtml?faces-redirect=true" />
</h:form>
</div>
</div>
@@ -328,10 +328,10 @@
Activité Récente
</h5>
<h:form id="formHistoriqueComplet">
<p:commandButton value="Voir l'historique complet"
icon="pi pi-arrow-right"
<p:commandButton value="Voir l'historique complet"
icon="pi pi-arrow-right"
styleClass="ui-button-text ui-button-sm"
action="/pages/membre/historique?faces-redirect=true" />
action="/pages/secure/cotisation/historique.xhtml?faces-redirect=true" />
</h:form>
</div>
@@ -365,18 +365,18 @@
styleClass="ui-button-success"
action="#{membreDashboardBean.payerCotisations}"
rendered="#{membreDashboardBean.peutPayerCotisations}" />
<p:commandButton value="Faire une demande"
icon="pi pi-file-edit"
<p:commandButton value="Faire une demande"
icon="pi pi-file-edit"
styleClass="ui-button-outlined ui-button-warning"
action="/pages/membre/demandes/creation?faces-redirect=true" />
<p:commandButton value="Contacter l'administration"
icon="pi pi-envelope"
action="/pages/secure/aide/demande.xhtml?faces-redirect=true" />
<p:commandButton value="Contacter l'administration"
icon="pi pi-envelope"
styleClass="ui-button-outlined ui-button-info"
action="/pages/membre/contact?faces-redirect=true" />
<p:commandButton value="Mes documents"
icon="pi pi-folder"
action="/pages/secure/aide/demande.xhtml?faces-redirect=true" />
<p:commandButton value="Mes documents"
icon="pi pi-folder"
styleClass="ui-button-outlined ui-button-secondary"
action="/pages/membre/documents?faces-redirect=true" />
action="/pages/secure/documents/mes-documents.xhtml?faces-redirect=true" />
</div>
</h:form>
</div>

View File

@@ -111,7 +111,7 @@
<p:column headerText="Restant" style="width:120px">
<h:outputText value="#{adhesion.montantRestantFormatte}"
styleClass="#{adhesion.montantRestant.compareTo(java.math.BigDecimal.ZERO) > 0 ? 'text-orange-500 font-bold' : 'text-green-500'}" />
styleClass="#{adhesion.montantRestant.signum() gt 0 ? 'text-orange-500 font-bold' : 'text-green-500'}" />
</p:column>
<p:column headerText="Date Approbation" sortBy="#{adhesion.dateApprobation}" style="width:150px">

View File

@@ -6,7 +6,6 @@
xmlns:p="http://primefaces.org/ui"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{configurationBean}"/>
<ui:define name="title">Sauvegarde et Restauration - UnionFlow</ui:define>
<ui:define name="content">
@@ -21,46 +20,91 @@
<i class="pi pi-database text-green-500 mr-2"></i>
Sauvegarde et Restauration
</h3>
<p class="text-600 m-0 mt-2">
Gérez les sauvegardes et restaurez la base de données
</p>
<p class="text-600 m-0 mt-2">Gérez les sauvegardes et restaurez le système</p>
</div>
<div class="flex gap-2 mt-2 md:mt-0">
<p:commandButton value="Créer une sauvegarde"
icon="pi pi-save"
styleClass="ui-button-success"
action="#{configurationBean.creerSauvegarde}"
update="@form"/>
<p:commandButton value="Point de restauration"
icon="pi pi-bookmark"
styleClass="ui-button-outlined ui-button-secondary"
action="#{sauvegardeBean.creerPointDeRestauration}"
update="@form"
title="Créer un point de restauration avant toute action"/>
<p:commandButton value="Nouvelle sauvegarde"
icon="pi pi-save"
styleClass="ui-button-success"
onclick="PF('dlgNouvelleSauvegarde').show(); return false;"/>
<p:commandButton icon="pi pi-refresh"
styleClass="ui-button-text"
action="#{sauvegardeBean.actualiser}"
update="@form"
title="Actualiser"/>
</div>
</div>
</div>
<!-- Information système -->
<div class="card mb-3">
<h5 class="mb-3">État du Système</h5>
<div class="grid">
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3">
<div class="text-600 text-sm mb-1">Dernière sauvegarde</div>
<div class="font-bold text-lg">#{configurationBean.derniereSauvegarde}</div>
<!-- Configuration et état -->
<div class="grid mb-3">
<div class="col-12 md:col-8">
<div class="card h-full">
<h5 class="mb-3">État des sauvegardes</h5>
<div class="grid" rendered="#{sauvegardeBean.configuration != null}">
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm mb-1">Dernière sauvegarde</div>
<div class="font-bold">
<h:outputText value="#{sauvegardeBean.configuration.lastBackup}"
rendered="#{sauvegardeBean.configuration.lastBackup != null}">
<f:convertDateTime pattern="dd/MM HH:mm" type="localDateTime"/>
</h:outputText>
<h:outputText value="Jamais"
rendered="#{sauvegardeBean.configuration.lastBackup == null}"/>
</div>
</div>
</div>
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm mb-1">Prochaine</div>
<div class="font-bold">
<h:outputText value="#{sauvegardeBean.configuration.nextScheduledBackup}"
rendered="#{sauvegardeBean.configuration.nextScheduledBackup != null}">
<f:convertDateTime pattern="dd/MM HH:mm" type="localDateTime"/>
</h:outputText>
</div>
</div>
</div>
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm mb-1">Total sauvegardes</div>
<div class="font-bold text-lg">#{sauvegardeBean.configuration.totalBackups}</div>
</div>
</div>
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm mb-1">Espace utilisé</div>
<div class="font-bold">#{sauvegardeBean.configuration.totalSizeFormatted}</div>
</div>
</div>
</div>
</div>
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3">
<div class="text-600 text-sm mb-1">Fréquence</div>
<div class="font-bold text-lg">#{configurationBean.frequenceSauvegarde}</div>
</div>
</div>
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3">
<div class="text-600 text-sm mb-1">Rétention</div>
<div class="font-bold text-lg">#{configurationBean.retentionSauvegardes} jours</div>
</div>
</div>
<div class="col-12 md:col-3">
<div class="surface-100 border-round p-3">
<div class="text-600 text-sm mb-1">Temps d'activité</div>
<div class="font-bold text-lg text-green-500">#{configurationBean.tempsActivite}</div>
</div>
<div class="col-12 md:col-4">
<div class="card h-full">
<h5 class="mb-3">Configuration auto</h5>
<div rendered="#{sauvegardeBean.configuration != null}">
<div class="flex align-items-center justify-content-between mb-2">
<span>Sauvegarde automatique</span>
<p:tag value="#{sauvegardeBean.configuration.autoBackupEnabled ? 'ACTIF' : 'INACTIF'}"
severity="#{sauvegardeBean.configuration.autoBackupEnabled ? 'success' : 'danger'}"/>
</div>
<div class="text-600 text-sm">
Fréquence : #{sauvegardeBean.configuration.frequency}<br/>
Heure : #{sauvegardeBean.configuration.backupTime}<br/>
Rétention : #{sauvegardeBean.configuration.retentionDays} jours
</div>
<p:commandButton value="Modifier la config"
icon="pi pi-cog"
styleClass="ui-button-outlined mt-3 w-full"
onclick="PF('dlgConfig').show(); return false;"/>
</div>
</div>
</div>
@@ -68,55 +112,241 @@
<!-- Liste des sauvegardes -->
<div class="card">
<h5 class="mb-3">Sauvegardes Disponibles</h5>
<p:dataTable id="dtSauvegardes"
var="sauvegarde"
value="#{configurationBean.sauvegardes}"
paginator="true"
rows="10"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="5,10,25">
<p:column headerText="Date" sortBy="#{sauvegarde.date}">
<h:outputText value="#{sauvegarde.date}">
<h5 class="mb-3">Sauvegardes disponibles</h5>
<p:dataTable id="dtSauvegardes"
var="sauv"
value="#{sauvegardeBean.sauvegardes}"
paginator="true"
rows="10"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="5,10,25"
emptyMessage="Aucune sauvegarde disponible"
rowHover="true">
<p:column headerText="Nom" sortBy="#{sauv.name}">
<div>
<div class="font-semibold">#{sauv.name}</div>
<div class="text-500 text-sm">#{sauv.description}</div>
</div>
</p:column>
<p:column headerText="Type" sortBy="#{sauv.type}">
<p:tag value="#{sauv.type}"
severity="#{sauvegardeBean.getTypeSeverity(sauv.type)}"/>
</p:column>
<p:column headerText="Taille" sortBy="#{sauv.sizeBytes}">
#{sauv.sizeFormatted != null ? sauv.sizeFormatted : '-'}
</p:column>
<p:column headerText="Statut">
<p:tag value="#{sauv.status}"
severity="#{sauvegardeBean.getStatusSeverity(sauv.status)}"
icon="pi #{sauvegardeBean.getStatusIcon(sauv.status)}"/>
</p:column>
<p:column headerText="Créée le" sortBy="#{sauv.createdAt}">
<h:outputText value="#{sauv.createdAt}" rendered="#{sauv.createdAt != null}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime"/>
</h:outputText>
</p:column>
<p:column headerText="Taille" sortBy="#{sauvegarde.taille}">
<div class="font-medium">#{sauvegarde.taille}</div>
<p:column headerText="Par">#{sauv.createdBy}</p:column>
<p:column headerText="Contenu" style="width:120px">
<div class="flex gap-1 flex-wrap">
<p:badge value="DB" severity="info" rendered="#{sauv.includesDatabase}"/>
<p:badge value="Files" severity="warning" rendered="#{sauv.includesFiles}"/>
<p:badge value="Config" severity="success" rendered="#{sauv.includesConfiguration}"/>
</div>
</p:column>
<p:column headerText="Type" sortBy="#{sauvegarde.type}">
<p:tag value="#{sauvegarde.type}" severity="info"/>
</p:column>
<p:column headerText="Statut" sortBy="#{sauvegarde.statut}">
<p:tag value="#{sauvegarde.statut}"
severity="#{sauvegarde.statutSeverity}"
icon="pi #{sauvegarde.statutIcon}"/>
</p:column>
<p:column headerText="Actions" style="width:200px">
<p:column headerText="Actions" style="width:150px">
<div class="flex gap-1">
<p:commandButton icon="pi pi-download"
styleClass="ui-button-rounded ui-button-text ui-button-info"
action="#{configurationBean.telechargerSauvegarde(sauvegarde)}"
title="Télécharger"/>
<p:commandButton icon="pi pi-refresh"
styleClass="ui-button-rounded ui-button-text ui-button-success"
action="#{configurationBean.restaurerSauvegarde(sauvegarde)}"
title="Restaurer"
onclick="return confirm('Êtes-vous sûr de vouloir restaurer cette sauvegarde ?');"/>
<p:commandButton icon="pi pi-trash"
styleClass="ui-button-rounded ui-button-text ui-button-danger"
action="#{configurationBean.supprimerSauvegarde(sauvegarde)}"
title="Supprimer"/>
<p:commandButton icon="pi pi-refresh"
styleClass="ui-button-rounded ui-button-success ui-button-text"
title="Restaurer"
rendered="#{sauv.status == 'COMPLETED'}"
action="#{sauvegardeBean.selectionnerPourRestauration(sauv)}"
update="@none"
oncomplete="PF('dlgRestaurer').show()"/>
<p:commandButton icon="pi pi-trash"
styleClass="ui-button-rounded ui-button-danger ui-button-text"
title="Supprimer"
action="#{sauvegardeBean.supprimer(sauv)}"
update="dtSauvegardes messages"
onclick="return confirm('Supprimer définitivement cette sauvegarde ?');"/>
</div>
</p:column>
</p:dataTable>
</div>
</h:form>
<!-- Dialog Nouvelle Sauvegarde -->
<p:dialog header="Créer une sauvegarde"
widgetVar="dlgNouvelleSauvegarde"
modal="true"
width="450">
<h:form id="formNouvelleSauvegarde">
<div class="grid">
<div class="col-12">
<div class="field">
<p:outputLabel for="savNom" value="Nom *"/>
<p:inputText id="savNom"
value="#{sauvegardeBean.nouvelleSauvegarde.nom}"
styleClass="w-full" required="true"/>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="savDesc" value="Description"/>
<p:inputTextarea id="savDesc"
value="#{sauvegardeBean.nouvelleSauvegarde.description}"
rows="2" styleClass="w-full"/>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel value="Contenu à inclure"/>
<div class="flex flex-column gap-2 mt-2">
<div class="flex align-items-center gap-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.nouvelleSauvegarde.includeDatabase}" id="cbDb"/>
<p:outputLabel for="cbDb" value="Base de données"/>
</div>
<div class="flex align-items-center gap-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.nouvelleSauvegarde.includeFiles}" id="cbFiles"/>
<p:outputLabel for="cbFiles" value="Fichiers"/>
</div>
<div class="flex align-items-center gap-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.nouvelleSauvegarde.includeConfiguration}" id="cbConfig"/>
<p:outputLabel for="cbConfig" value="Configuration"/>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-content-end gap-2">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgNouvelleSauvegarde').hide(); return false;"/>
<p:commandButton value="Lancer la sauvegarde"
icon="pi pi-save"
styleClass="ui-button-success"
action="#{sauvegardeBean.creerSauvegarde}"
update="formSauvegarde messages"
oncomplete="if(!args.validationFailed) PF('dlgNouvelleSauvegarde').hide();"/>
</div>
</h:form>
</p:dialog>
<!-- Dialog Restaurer -->
<p:dialog header="Restaurer la sauvegarde"
widgetVar="dlgRestaurer"
modal="true"
width="450">
<h:form id="formRestaurer">
<div class="flex align-items-center mb-3 p-3 border-round bg-red-50">
<i class="pi pi-exclamation-triangle text-red-500 text-2xl mr-3"></i>
<div>
<div class="font-bold text-red-500">Action irréversible</div>
<div class="text-600 text-sm">La restauration va écraser les données actuelles.</div>
</div>
</div>
<div class="field">
<p:outputLabel value="Créer un point de restauration avant ?"/>
<div class="flex align-items-center gap-2 mt-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.optionsRestauration.creerPointAvant}" id="cbPoint"/>
<p:outputLabel for="cbPoint" value="Oui (recommandé)"/>
</div>
</div>
<div class="field mt-3">
<p:outputLabel value="Éléments à restaurer"/>
<div class="flex flex-column gap-2 mt-2">
<div class="flex align-items-center gap-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.optionsRestauration.restoreDatabase}" id="cbRestDb"/>
<p:outputLabel for="cbRestDb" value="Base de données"/>
</div>
<div class="flex align-items-center gap-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.optionsRestauration.restoreFiles}" id="cbRestFiles"/>
<p:outputLabel for="cbRestFiles" value="Fichiers"/>
</div>
<div class="flex align-items-center gap-2">
<p:selectBooleanCheckbox value="#{sauvegardeBean.optionsRestauration.restoreConfiguration}" id="cbRestConfig"/>
<p:outputLabel for="cbRestConfig" value="Configuration"/>
</div>
</div>
</div>
<div class="flex justify-content-end gap-2 mt-3">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgRestaurer').hide(); return false;"/>
<p:commandButton value="Confirmer la restauration"
icon="pi pi-refresh"
styleClass="ui-button-danger"
action="#{sauvegardeBean.restaurer}"
update="formSauvegarde messages"
oncomplete="if(!args.validationFailed) PF('dlgRestaurer').hide();"/>
</div>
</h:form>
</p:dialog>
<!-- Dialog Configuration -->
<p:dialog header="Configuration des sauvegardes automatiques"
widgetVar="dlgConfig"
modal="true"
width="500">
<h:form id="formConfig">
<div class="grid">
<div class="col-12">
<div class="flex align-items-center gap-2 mb-3">
<p:selectBooleanCheckbox value="#{sauvegardeBean.configSauvegarde.autoActivee}" id="cbAutoActivee"/>
<p:outputLabel for="cbAutoActivee" value="Activer les sauvegardes automatiques"/>
</div>
</div>
<div class="col-12 md:col-6">
<div class="field">
<p:outputLabel for="freq" value="Fréquence"/>
<p:selectOneMenu id="freq"
value="#{sauvegardeBean.configSauvegarde.frequence}"
styleClass="w-full">
<f:selectItem itemLabel="Horaire" itemValue="HOURLY"/>
<f:selectItem itemLabel="Quotidien" itemValue="DAILY"/>
<f:selectItem itemLabel="Hebdomadaire" itemValue="WEEKLY"/>
</p:selectOneMenu>
</div>
</div>
<div class="col-12 md:col-6">
<div class="field">
<p:outputLabel for="heure" value="Heure (HH:mm)"/>
<p:inputText id="heure"
value="#{sauvegardeBean.configSauvegarde.heure}"
styleClass="w-full"
placeholder="02:00"/>
</div>
</div>
<div class="col-12 md:col-6">
<div class="field">
<p:outputLabel for="retention" value="Rétention (jours)"/>
<p:inputNumber id="retention"
value="#{sauvegardeBean.configSauvegarde.retentionJours}"
styleClass="w-full"
minValue="1"/>
</div>
</div>
</div>
<div class="flex justify-content-end gap-2 mt-3">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgConfig').hide(); return false;"/>
<p:commandButton value="Enregistrer"
icon="pi pi-check"
styleClass="ui-button-success"
action="#{sauvegardeBean.sauvegarderConfiguration}"
update="formSauvegarde messages"
oncomplete="if(!args.validationFailed) PF('dlgConfig').hide();"/>
</div>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -0,0 +1,241 @@
<!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">Messagerie - UnionFlow</ui:define>
<ui:define name="content">
<h:form id="formMessaging">
<p:messages id="messages" showDetail="true" closable="true"/>
<!-- En-tête -->
<div class="card mb-3">
<div class="flex justify-content-between align-items-center flex-column md:flex-row">
<div>
<h3 class="m-0">
<i class="pi pi-comments text-primary mr-2"></i>
Messagerie
</h3>
<p class="text-600 m-0 mt-2">Conversations et messages de l'organisation</p>
</div>
<div class="flex gap-2 mt-2 md:mt-0">
<p:commandButton value="Nouvelle conversation"
icon="pi pi-plus"
styleClass="ui-button-success"
onclick="PF('dlgNouvelleConv').show(); return false;"/>
<p:commandButton icon="pi pi-refresh"
styleClass="ui-button-text"
action="#{messagingBean.actualiser}"
update="@form"
title="Actualiser"/>
</div>
</div>
</div>
<!-- Layout messagerie : liste conversations + panneau messages -->
<div class="grid">
<!-- Colonne gauche : liste des conversations -->
<div class="col-12 md:col-4">
<div class="card" style="height:600px; overflow-y:auto;">
<h5 class="mb-3">Conversations</h5>
<p:dataList id="listConversations"
var="conv"
value="#{messagingBean.conversations}"
emptyMessage="Aucune conversation">
<div class="flex align-items-center p-2 border-bottom-1 surface-border cursor-pointer hover:surface-100
#{messagingBean.conversationActive != null and messagingBean.conversationActive.id == conv.id ? 'surface-200' : ''}"
onclick="PF('dlgMessages').show();">
<p:commandLink update="panelMessages messages"
action="#{messagingBean.ouvrirConversation(conv)}"
styleClass="flex align-items-center w-full text-color no-underline">
<div class="flex-auto ml-2">
<div class="flex justify-content-between">
<span class="font-semibold">#{conv.name}</span>
<p:tag value="#{conv.type}"
severity="#{conv.type == 'BROADCAST' ? 'warning' : conv.type == 'ANNOUNCEMENT' ? 'danger' : 'info'}"
styleClass="text-xs"/>
</div>
<div class="text-600 text-sm mt-1 white-space-nowrap overflow-hidden text-overflow-ellipsis"
style="max-width:200px">
<h:outputText value="#{conv.lastMessage != null ? conv.lastMessage.content : 'Aucun message'}"
rendered="#{conv.lastMessage != null}"/>
</div>
<div class="flex justify-content-between align-items-center mt-1">
<span class="text-500 text-xs">
<h:outputText value="#{conv.updatedAt}">
<f:convertDateTime pattern="dd/MM HH:mm" type="localDateTime"/>
</h:outputText>
</span>
<p:badge value="#{conv.unreadCount}"
severity="danger"
rendered="#{conv.unreadCount > 0}"/>
</div>
</div>
</p:commandLink>
<!-- Actions rapides -->
<div class="ml-2">
<p:commandButton icon="#{conv.isPinned ? 'pi pi-thumbtack text-primary' : 'pi pi-thumbtack text-400'}"
styleClass="ui-button-rounded ui-button-text"
action="#{messagingBean.basculerEpinglage(conv)}"
update="listConversations"
title="#{conv.isPinned ? 'Désépingler' : 'Épingler'}"/>
</div>
</div>
</p:dataList>
</div>
</div>
<!-- Colonne droite : messages de la conversation active -->
<div class="col-12 md:col-8">
<div id="panelMessages" class="card" style="height:600px; display:flex; flex-direction:column;">
<h:panelGroup rendered="#{messagingBean.conversationActive == null}">
<div class="flex align-items-center justify-content-center h-full text-600">
<div class="text-center">
<i class="pi pi-comments text-4xl mb-3 text-300"></i>
<p>Sélectionnez une conversation</p>
</div>
</div>
</h:panelGroup>
<h:panelGroup rendered="#{messagingBean.conversationActive != null}" layout="block" style="display:flex; flex-direction:column; height:100%">
<!-- En-tête conversation -->
<div class="flex justify-content-between align-items-center mb-3 pb-3 border-bottom-1 surface-border">
<div>
<h5 class="m-0">#{messagingBean.conversationActive.name}</h5>
<span class="text-600 text-sm">#{messagingBean.conversationActive.participantIds.size()} participant(s)</span>
</div>
<div class="flex gap-2">
<p:commandButton icon="pi pi-volume-#{messagingBean.conversationActive.isMuted ? 'off text-primary' : 'up text-400'}"
styleClass="ui-button-rounded ui-button-text"
action="#{messagingBean.basculerSilence(messagingBean.conversationActive)}"
update="listConversations panelMessages"
title="#{messagingBean.conversationActive.isMuted ? 'Activer le son' : 'Mettre en silence'}"/>
<p:commandButton icon="pi pi-inbox text-400"
styleClass="ui-button-rounded ui-button-text"
action="#{messagingBean.archiverConversation(messagingBean.conversationActive)}"
update="listConversations panelMessages"
title="Archiver"
onclick="return confirm('Archiver cette conversation ?');"/>
</div>
</div>
<!-- Zone messages -->
<div class="flex-auto overflow-y-auto" style="min-height:0;">
<p:dataList var="msg"
value="#{messagingBean.messagesConversationActive}"
emptyMessage="Aucun message">
<div class="flex mb-3 #{msg.senderId == userSession.userId ? 'justify-content-end' : ''}">
<div class="#{msg.senderId == userSession.userId ? 'bg-primary text-white' : 'surface-100'}
border-round p-3" style="max-width:70%">
<div class="flex justify-content-between align-items-center mb-1">
<span class="font-semibold text-sm">#{msg.senderName}</span>
<span class="text-xs ml-2 #{msg.senderId == userSession.userId ? 'text-white-alpha-70' : 'text-500'}">
<h:outputText value="#{msg.createdAt}">
<f:convertDateTime pattern="HH:mm" type="localDateTime"/>
</h:outputText>
</span>
</div>
<p class="m-0">#{msg.content}</p>
<div class="flex justify-content-between align-items-center mt-1" rendered="#{msg.senderId == userSession.userId}">
<p:tag value="#{msg.status}"
severity="#{messagingBean.getStatusSeverity(msg.status)}"
styleClass="text-xs"/>
<p:commandButton icon="pi pi-trash"
styleClass="ui-button-rounded ui-button-text ui-button-sm"
action="#{messagingBean.supprimerMessage(msg)}"
update="panelMessages"
onclick="return confirm('Supprimer ce message ?');"/>
</div>
</div>
</div>
</p:dataList>
</div>
<!-- Zone saisie message -->
<div class="mt-3 pt-3 border-top-1 surface-border">
<div class="flex gap-2">
<p:inputTextarea value="#{messagingBean.contenuMessage}"
rows="2"
styleClass="flex-auto"
placeholder="Saisissez votre message..."
autoResize="false"/>
<p:commandButton icon="pi pi-send"
styleClass="ui-button-primary align-self-end"
action="#{messagingBean.envoyerMessage}"
update="panelMessages"
title="Envoyer"/>
</div>
</div>
</h:panelGroup>
</div>
</div>
</div>
</h:form>
<!-- Dialog nouvelle conversation -->
<p:dialog header="Nouvelle conversation"
widgetVar="dlgNouvelleConv"
modal="true"
width="500"
responsive="true">
<h:form id="formNouvelleConv">
<div class="grid">
<div class="col-12">
<div class="field">
<p:outputLabel for="convNom" value="Nom *"/>
<p:inputText id="convNom"
value="#{messagingBean.nouvelleConversation.nom}"
styleClass="w-full" required="true"/>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="convType" value="Type *"/>
<p:selectOneMenu id="convType"
value="#{messagingBean.nouvelleConversation.type}"
styleClass="w-full">
<f:selectItem itemLabel="Groupe" itemValue="GROUP"/>
<f:selectItem itemLabel="Diffusion (Broadcast)" itemValue="BROADCAST"/>
<f:selectItem itemLabel="Annonce" itemValue="ANNOUNCEMENT"/>
</p:selectOneMenu>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="convDesc" value="Description"/>
<p:inputTextarea id="convDesc"
value="#{messagingBean.nouvelleConversation.description}"
rows="2" styleClass="w-full"/>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="convParticipants" value="IDs participants (séparés par virgule)"/>
<p:inputTextarea id="convParticipants"
value="#{messagingBean.nouvelleConversation.participantsIds}"
rows="2" styleClass="w-full"
placeholder="uuid1, uuid2, ..."/>
</div>
</div>
</div>
<div class="flex justify-content-end gap-2">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgNouvelleConv').hide(); return false;"/>
<p:commandButton value="Créer"
icon="pi pi-plus"
styleClass="ui-button-success"
action="#{messagingBean.creerConversation}"
update="formMessaging:listConversations formMessaging:messages"
oncomplete="if(!args.validationFailed) PF('dlgNouvelleConv').hide();"/>
</div>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -220,7 +220,7 @@
</p:column>
<p:column headerText="Échéance" sortBy="#{cotisation.dateEcheance}" style="width:120px">
<h:outputText value="#{cotisation.dateEcheanceFormatee}" />
<h:outputText value="#{cotisation.dateEcheanceFormattee}" />
</p:column>
<p:column headerText="Actions" style="width:200px">
@@ -438,13 +438,13 @@
<div class="col-12 md:col-6">
<div class="field">
<label class="font-medium">Date d'échéance</label>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.dateEcheanceFormatee}</p>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.dateEcheanceFormattee}</p>
</div>
</div>
<div class="col-12 md:col-6">
<div class="field">
<label class="font-medium">Date de paiement</label>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.datePaiementFormatee}</p>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.datePaiementFormattee}</p>
</div>
</div>
</div>

View File

@@ -152,7 +152,7 @@
</p:column>
<p:column headerText="Date Paiement" sortBy="#{cotisation.datePaiement}" style="width:150px">
<h:outputText value="#{cotisation.datePaiementFormatee}" />
<h:outputText value="#{cotisation.datePaiementFormattee}" />
</p:column>
<p:column headerText="Méthode" style="width:120px">
@@ -205,7 +205,7 @@
<div class="col-12 md:col-6">
<div class="field">
<label class="font-medium">Date d'échéance</label>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.dateEcheanceFormatee}</p>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.dateEcheanceFormattee}</p>
</div>
</div>
<div class="col-12 md:col-4">
@@ -229,7 +229,7 @@
<div class="col-12 md:col-6">
<div class="field">
<label class="font-medium">Date de paiement</label>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.datePaiementFormatee}</p>
<p class="text-600">#{cotisationsBean.cotisationSelectionnee.datePaiementFormattee}</p>
</div>
</div>
<div class="col-12 md:col-6">

View File

@@ -7,14 +7,14 @@
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{cotisationsBean}"/>
<ui:define name="title">Paiement de Cotisations - UnionFlow</ui:define>
<ui:define name="title">#{cotisationsBean.vueAdmin ? 'Paiement de Cotisations' : 'Payer mes Cotisations'} - UnionFlow</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-credit-card text-green-500" />
<ui:param name="title" value="Paiement de Cotisations" />
<ui:param name="description" value="Enregistrement et suivi des paiements de cotisations" />
<ui:param name="title" value="#{cotisationsBean.vueAdmin ? 'Paiement de Cotisations' : 'Payer mes Cotisations'}" />
<ui:param name="description" value="#{cotisationsBean.vueAdmin ? 'Enregistrement et suivi des paiements de cotisations' : 'Consultez et réglez vos cotisations en attente'}" />
<ui:define name="actions">
<h:form id="formActionsPaiement">
<div class="flex gap-2">
@@ -33,7 +33,7 @@
</ui:include>
<!-- Statistiques de paiement - TRESORIER/ADMIN SEULEMENT -->
<div class="grid" rendered="#{menuBean.gestionFinancesMenuVisible}">
<h:panelGroup layout="block" styleClass="grid" rendered="#{cotisationsBean.vueAdmin}">
<ui:include src="/templates/components/cards/stat-card.xhtml">
<ui:param name="value" value="#{cotisationsBean.statistiques.totalCollecteFormatte}" />
<ui:param name="label" value="Total Collecté" />
@@ -61,10 +61,10 @@
<ui:param name="icon" value="pi pi-percentage" />
<ui:param name="bgColor" value="purple" />
</ui:include>
</div>
</h:panelGroup>
<!-- Répartition par méthode de paiement - TRESORIER/ADMIN SEULEMENT -->
<div class="card" rendered="#{menuBean.gestionFinancesMenuVisible}">
<p:outputPanel layout="block" styleClass="card" rendered="#{cotisationsBean.vueAdmin}">
<h5>
<i class="pi pi-chart-pie mr-2"></i>
Répartition par Méthode de Paiement
@@ -107,17 +107,16 @@
</div>
</div>
</div>
</div>
</p:outputPanel>
<!-- Cotisations en attente de paiement -->
<div class="card">
<h:form id="formPaiements">
<h5>Cotisations en Attente de Paiement</h5>
<h5>#{cotisationsBean.vueAdmin ? 'Cotisations en Attente de Paiement' : 'Mes Cotisations en Attente'}</h5>
<p:dataTable id="dtPaiements"
value="#{cotisationsBean.cotisationsFiltrees}"
value="#{cotisationsBean.cotisationsFiltrees}"
var="cotisation"
filteredValue="#{cotisationsBean.cotisationsFiltrees}"
rowKey="#{cotisation.id}"
paginator="true"
rows="20"
@@ -133,14 +132,14 @@
<f:selectItem itemLabel="En attente" itemValue="EN_ATTENTE" />
<f:selectItem itemLabel="Partiellement payée" itemValue="PARTIELLEMENT_PAYEE" />
<f:selectItem itemLabel="En retard" itemValue="EN_RETARD" />
<p:ajax update="dtPaiements" />
<p:ajax listener="#{cotisationsBean.rechercher}" update="dtPaiements" />
</p:selectOneMenu>
</div>
</f:facet>
<!-- Colonne Membre - TRESORIER/ADMIN SEULEMENT -->
<p:column headerText="Membre" sortBy="#{cotisation.nomMembre}"
rendered="#{menuBean.gestionFinancesMenuVisible}">
rendered="#{cotisationsBean.vueAdmin}">
<div>
<div class="font-medium">#{cotisation.nomMembre}</div>
<div class="text-600 text-sm">#{cotisation.numeroMembre}</div>
@@ -160,28 +159,33 @@
</p:column>
<p:column headerText="Restant" style="width:120px">
<h:outputText value="#{cotisation.montantRestantFormatte}"
styleClass="#{cotisation.montantRestant.compareTo(java.math.BigDecimal.ZERO) > 0 ? 'text-orange-500 font-bold' : 'text-green-500'}" />
<h:outputText value="#{cotisation.montantRestantFormatte}"
styleClass="#{cotisation.montantRestantPositif ? 'text-orange-500 font-bold' : 'text-green-500'}" />
</p:column>
<p:column headerText="Échéance" sortBy="#{cotisation.dateEcheance}" style="width:120px">
<h:outputText value="#{cotisation.dateEcheanceFormatee}" />
<h:outputText value="#{cotisation.dateEcheanceFormattee}" />
</p:column>
<p:column headerText="Actions" style="width:200px">
<div class="flex gap-1">
<p:commandButton value="Payer"
icon="pi pi-check"
<p:column headerText="Actions" style="width:230px">
<div class="flex gap-1 flex-wrap">
<!-- Paiement Wave QR (membre self-service) -->
<p:commandButton value="Wave QR"
icon="pi pi-qrcode"
styleClass="p-button-success p-button-sm"
action="#{cotisationsBean.initierPaiementWavePour(cotisation)}"
process="@this"
update="formWaveQrContent formWaveHidden formWaveQr"
oncomplete="uf_ouvrirWaveDialog();" />
<!-- Autres méthodes (espèces, virement…) — admin/trésorier -->
<p:commandButton value="Manuel"
icon="pi pi-credit-card"
styleClass="p-button-secondary p-button-sm"
rendered="#{cotisationsBean.vueAdmin}"
action="#{cotisationsBean.selectionnerCotisation(cotisation)}"
process="@this"
update=":formPaiement"
oncomplete="PF('dlgPaiement').show();" />
<p:commandButton value="Partiel"
icon="pi pi-minus"
styleClass="p-button-info p-button-sm"
action="#{cotisationsBean.selectionnerCotisation(cotisation)}"
update=":formPaiementPartiel"
oncomplete="PF('dlgPaiementPartiel').show();" />
</div>
</p:column>
</p:dataTable>
@@ -201,7 +205,7 @@
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="methodePaiement" />
<ui:param name="label" value="Méthode de paiement" />
<ui:param name="value" value="#{cotisationsBean.cotisationSelectionnee.methodePaiement}" />
<ui:param name="value" value="#{cotisationsBean.methodePaiementDialog}" />
<ui:param name="required" value="true" />
<ui:define name="items">
<f:selectItem itemLabel="Wave Money" itemValue="WAVE_MONEY" />
@@ -213,18 +217,18 @@
<f:selectItem itemLabel="Carte bancaire" itemValue="CARTE_BANCAIRE" />
</ui:define>
</ui:include>
<ui:include src="/templates/components/forms/form-field-text.xhtml">
<ui:param name="id" value="referencePaiement" />
<ui:param name="label" value="Référence de paiement" />
<ui:param name="value" value="#{cotisationsBean.cotisationSelectionnee.referencePaiement}" />
<ui:param name="value" value="#{cotisationsBean.referencePaiementDialog}" />
<ui:param name="placeholder" value="Ex: WAVE-123456789" />
</ui:include>
<ui:include src="/templates/components/forms/form-field-textarea.xhtml">
<ui:param name="id" value="observationsPaiement" />
<ui:param name="label" value="Observations" />
<ui:param name="value" value="#{cotisationsBean.cotisationSelectionnee.observations}" />
<ui:param name="value" value="#{cotisationsBean.observationsDialog}" />
<ui:param name="rows" value="3" />
</ui:include>
</div>
@@ -260,31 +264,34 @@
<div class="field">
<p:outputLabel for="montantPaye" value="Montant à payer (FCFA)" />
<p:inputNumber id="montantPaye"
value="#{cotisationsBean.cotisationSelectionnee.montantPaye}"
<p:inputNumber id="montantPaye"
value="#{cotisationsBean.montantPaiementPartiel}"
symbol=""
minValue="0"
maxValue="#{cotisationsBean.cotisationSelectionnee.montantDu}"
styleClass="w-full" />
</div>
<ui:include src="/templates/components/forms/form-field-select.xhtml">
<ui:param name="id" value="methodePaiementPartiel" />
<ui:param name="label" value="Méthode de paiement" />
<ui:param name="value" value="#{cotisationsBean.cotisationSelectionnee.methodePaiement}" />
<ui:param name="value" value="#{cotisationsBean.methodePaiementDialog}" />
<ui:param name="required" value="true" />
<ui:define name="items">
<f:selectItem itemLabel="Wave Money" itemValue="WAVE_MONEY" />
<f:selectItem itemLabel="Espèces" itemValue="ESPECES" />
<f:selectItem itemLabel="Virement bancaire" itemValue="VIREMENT" />
<f:selectItem itemLabel="Chèque" itemValue="CHEQUE" />
<f:selectItem itemLabel="Orange Money" itemValue="ORANGE_MONEY" />
<f:selectItem itemLabel="Free Money" itemValue="FREE_MONEY" />
<f:selectItem itemLabel="Carte bancaire" itemValue="CARTE_BANCAIRE" />
</ui:define>
</ui:include>
<ui:include src="/templates/components/forms/form-field-text.xhtml">
<ui:param name="id" value="referencePaiementPartiel" />
<ui:param name="label" value="Référence de paiement" />
<ui:param name="value" value="#{cotisationsBean.cotisationSelectionnee.referencePaiement}" />
<ui:param name="value" value="#{cotisationsBean.referencePaiementDialog}" />
</ui:include>
</div>
@@ -298,7 +305,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="#{cotisationsBean.enregistrerPaiementPartiel(cotisationsBean.cotisationSelectionnee.montantPaye, cotisationsBean.cotisationSelectionnee.methodePaiement, cotisationsBean.cotisationSelectionnee.referencePaiement)}" />
<ui:param name="action" value="#{cotisationsBean.enregistrerPaiementPartiel}" />
<ui:param name="update" value="@form :formPaiements" />
<ui:param name="oncomplete" value="PF('dlgPaiementPartiel').hide();" />
</ui:include>
@@ -306,5 +313,161 @@
</h:form>
</p:dialog>
<!-- ═══════════════════════════════════════════════════════════════════
Dialog Paiement Wave QR Code
Flow : initierWave() → session Wave créée → QR affiché → poll 3s
→ COMPLETEE détectée → dialog fermé + liste rechargée
════════════════════════════════════════════════════════════════════ -->
<h:form id="formWaveQr">
<!-- Polling toutes les 3 secondes quand le dialog est ouvert -->
<p:poll id="pollWave"
interval="3"
autoStart="false"
widgetVar="wvPoll"
listener="#{cotisationsBean.pollStatutWave}"
update="formWaveQr"
oncomplete="uf_waveCheckComplete();" />
</h:form>
<p:dialog id="dlgWaveQrDialog"
header="Payer avec Wave — QR Code"
widgetVar="dlgWaveQr"
modal="true"
width="460"
resizable="false"
closable="true"
onHide="cotisationsBean_stopPoll();">
<h:form id="formWaveQrContent">
<div class="flex flex-column align-items-center gap-3 p-3">
<!-- Infos cotisation -->
<div class="w-full surface-50 border-round p-3 text-center">
<div class="font-bold text-lg">#{cotisationsBean.cotisationSelectionnee.montantDuFormatte}</div>
<div class="text-600 text-sm">#{cotisationsBean.cotisationSelectionnee.numeroReference}
— #{cotisationsBean.cotisationSelectionnee.typeCotisationLibelle}</div>
</div>
<!-- Zone QR code (générée par qrcode.js) -->
<p:outputPanel id="waveQrPanel" layout="block"
styleClass="flex flex-column align-items-center gap-2 w-full">
<!-- Attente de génération (statut EN_COURS) -->
<p:outputPanel rendered="#{cotisationsBean.waveStatut == 'EN_COURS' or cotisationsBean.waveStatut == 'INITIEE'}"
layout="block" styleClass="text-center">
<div id="wave-qr-container" style="width:220px;height:220px;margin:auto;border:1px solid #e2e8f0;border-radius:8px;display:flex;align-items:center;justify-content:center;">
<i class="pi pi-spin pi-spinner text-4xl text-primary" id="wave-qr-spinner" style="display:block;"/>
</div>
<p class="text-sm text-600 mt-2">Scannez ce QR code avec l'application <strong>Wave</strong></p>
<p class="text-xs text-400">Le QR code expire dans 30 minutes</p>
</p:outputPanel>
<!-- Paiement confirmé -->
<p:outputPanel rendered="#{cotisationsBean.waveStatut == 'COMPLETEE'}"
layout="block" styleClass="text-center">
<div style="font-size:4rem;"></div>
<div class="font-bold text-green-600 text-xl mt-2">Paiement confirmé !</div>
<p class="text-600 text-sm">Votre cotisation a été enregistrée.</p>
</p:outputPanel>
<!-- Session expirée ou échouée -->
<p:outputPanel rendered="#{cotisationsBean.waveStatut == 'EXPIREE' or cotisationsBean.waveStatut == 'ECHOUEE'}"
layout="block" styleClass="text-center">
<div style="font-size:3rem;">⏱️</div>
<div class="font-bold text-orange-600 mt-2">Session expirée</div>
<p class="text-600 text-sm">Veuillez fermer et recommencer.</p>
</p:outputPanel>
</p:outputPanel>
<!-- Instructions -->
<p:outputPanel rendered="#{cotisationsBean.waveStatut == 'EN_COURS' or cotisationsBean.waveStatut == 'INITIEE'}"
layout="block" styleClass="w-full">
<ol class="text-sm text-600 pl-3 mt-0" style="line-height:1.8">
<li>Ouvrez l'application <strong>Wave</strong> sur votre téléphone</li>
<li>Appuyez sur <strong>Scanner</strong></li>
<li>Scannez le QR code ci-dessus</li>
<li>Confirmez le paiement de <strong>#{cotisationsBean.cotisationSelectionnee.montantDuFormatte}</strong></li>
<li>Cette page se mettra à jour automatiquement</li>
</ol>
</p:outputPanel>
</div>
<!-- Actions -->
<div class="flex justify-content-end gap-2 p-3 pt-0">
<p:commandButton value="Fermer"
icon="pi pi-times"
styleClass="p-button-secondary p-button-outlined"
action="#{cotisationsBean.annulerPaiementWave}"
update="formWaveQrContent"
oncomplete="PF('dlgWaveQr').hide();" />
</div>
</h:form>
</p:dialog>
<!-- Script : ouverture dialog Wave + génération QR via qrcode.js -->
<h:outputScript>
//<![CDATA[
function cotisationsBean_initierWave() {
// Déclencher l'action bean via AJAX pour initier la session Wave
PrimeFaces.ajax.Request.handle({
source: 'formPaiements',
process: '@none',
update: 'formWaveQr',
oncomplete: function(xhr, status, args) {
// L'action est appelée séparément via le commandButton
}
});
}
// Appelé par le commandButton "Wave QR" — initie la session puis ouvre le dialog
function uf_ouvrirWaveDialog() {
PF('dlgWaveQr').show();
setTimeout(function() { uf_genererQr(); }, 300);
PF('wvPoll').start();
}
// Génère le QR code à partir du waveLaunchUrl injecté depuis le bean
function uf_genererQr() {
var container = document.getElementById('wave-qr-container');
var spinner = document.getElementById('wave-qr-spinner');
var url = document.getElementById('wave-launch-url-hidden');
if (!container || !url || !url.value) return;
if (spinner) spinner.style.display = 'none';
container.innerHTML = '';
new QRCode(container, {
text: url.value,
width: 210,
height: 210,
colorDark: '#1a202c',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
}
// Appelé après chaque poll pour vérifier si terminé
function uf_waveCheckComplete() {
var statut = document.getElementById('wave-statut-hidden');
if (!statut) return;
if (statut.value === 'COMPLETEE') {
PF('wvPoll').stop();
setTimeout(function() { PF('dlgWaveQr').hide(); }, 2000);
} else if (statut.value === 'EXPIREE' || statut.value === 'ECHOUEE') {
PF('wvPoll').stop();
}
}
function cotisationsBean_stopPoll() {
try { PF('wvPoll').stop(); } catch(e) {}
}
//]]>
</h:outputScript>
<!-- Champs cachés pour transmettre les valeurs Java → JavaScript -->
<h:form id="formWaveHidden" style="display:none">
<h:inputHidden id="wave-launch-url-hidden" value="#{cotisationsBean.waveLaunchUrl}" />
<h:inputHidden id="wave-statut-hidden" value="#{cotisationsBean.waveStatut}" />
</h:form>
</ui:define>
</ui:composition>

View File

@@ -106,7 +106,7 @@
<ui:include src="/templates/components/buttons/button-warning.xhtml">
<ui:param name="value" value="Envoyer" />
<ui:param name="icon" value="pi pi-send" />
<ui:param name="action" value="#{cotisationsBean.envoyerRappel}" />
<ui:param name="action" value="#{cotisationsBean.envoyerRappelPourRappel(rappel)}" />
<ui:param name="update" value="@form" />
<ui:param name="styleClass" value="p-button-sm" />
</ui:include>
@@ -163,7 +163,7 @@
</p:column>
<p:column headerText="Échéance" sortBy="#{cotisation.dateEcheance}" style="width:120px">
<h:outputText value="#{cotisation.dateEcheanceFormatee}" />
<h:outputText value="#{cotisation.dateEcheanceFormattee}" />
</p:column>
<p:column headerText="Jours Retard" style="width:120px">

View File

@@ -89,184 +89,273 @@
<ui:param name="noDataLabel" value="Aucune demande" />
</ui:include>
<!-- Mon historique de cotisations -->
<!-- Historique de mes cotisations -->
<div class="col-12 lg:col-8">
<div class="card">
<h5>Historique de mes cotisations</h5>
<p:dataTable value="#{dashboardMembreBean.historiqueCotisations}" var="cotis"
rows="5" paginator="true" paginatorPosition="bottom"
emptyMessage="Aucune cotisation enregistrée">
<p:column headerText="Date" style="width:120px">
<h:outputText value="#{cotis.datePaiement}">
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>
<p:column headerText="Période" style="width:100px">
<h:outputText value="#{cotis.periode}">
<f:convertDateTime pattern="MMMM yyyy" type="localDate" locale="fr"/>
</h:outputText>
</p:column>
<p:column headerText="Montant">
<span class="text-green-600 font-medium">#{cotis.montant} FCFA</span>
</p:column>
<p:column headerText="Mode">
<p:tag value="#{cotis.modePaiement}" severity="info"/>
</p:column>
<p:column headerText="Statut" style="width:100px">
<p:tag value="#{cotis.statut}"
severity="#{cotis.statut == 'PAYE' ? 'success' : 'warning'}"/>
</p:column>
</p:dataTable>
<h:form id="formCotisationsMembre">
<p:dataTable id="dtMesCotisations"
value="#{dashboardMembreBean.mesCotisations}" var="cotis"
rows="5" paginator="true" paginatorPosition="bottom"
emptyMessage="Aucune cotisation enregistrée">
<p:column headerText="Référence" style="width:130px">
<span class="text-600 text-sm">#{cotis.numeroReference}</span>
</p:column>
<p:column headerText="Période" style="width:110px">
<span>#{cotis.periode}</span>
</p:column>
<p:column headerText="Montant dû">
<span class="font-medium">#{cotis.montantFormatte} FCFA</span>
</p:column>
<p:column headerText="Payé">
<span class="text-green-600 font-medium">
<h:outputText value="#{cotis.montantPaye}">
<f:convertNumber pattern="#,##0" groupingSeparator=" " />
</h:outputText>
<span> FCFA</span>
</span>
</p:column>
<p:column headerText="Statut" style="width:120px">
<p:tag value="#{cotis.statutLibelle}"
severity="#{cotis.statutSeverity}"/>
</p:column>
<p:column headerText="Actions" style="width:90px">
<p:commandButton value="Payer"
icon="pi pi-credit-card"
styleClass="ui-button-success ui-button-sm"
rendered="#{cotis.statut != 'PAYEE' and cotis.statut != 'ANNULEE'}"
action="#{dashboardMembreBean.ouvrirDialogPaiement(cotis)}"
update="formPaiementMembre"
process="@this" />
</p:column>
</p:dataTable>
</h:form>
</div>
<!-- Cotisations en attente -->
<div class="card mt-3" rendered="#{not empty dashboardMembreBean.mesCotisationsEnAttente}">
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">Cotisations en attente de paiement</h5>
<p:badge value="#{dashboardMembreBean.mesCotisationsEnAttente.size()}" severity="warning"/>
</div>
<h:form id="formCotisationsEnAttente">
<p:dataTable value="#{dashboardMembreBean.mesCotisationsEnAttente}" var="cotisAtt"
rows="5" paginator="true" paginatorPosition="bottom"
emptyMessage="Aucune cotisation en attente">
<p:column headerText="Référence" style="width:130px">
<span class="text-600 text-sm">#{cotisAtt.numeroReference}</span>
</p:column>
<p:column headerText="Période">
<span>#{cotisAtt.periode}</span>
</p:column>
<p:column headerText="Montant dû">
<span class="font-medium text-orange-600">#{cotisAtt.montantFormatte} FCFA</span>
</p:column>
<p:column headerText="Échéance" style="width:110px">
<h:outputText value="#{cotisAtt.dateEcheance}">
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>
<p:column headerText="Actions" style="width:90px">
<p:commandButton value="Payer"
icon="pi pi-credit-card"
styleClass="ui-button-warning ui-button-sm"
action="#{dashboardMembreBean.ouvrirDialogPaiement(cotisAtt)}"
update="formPaiementMembre"
process="@this" />
</p:column>
</p:dataTable>
</h:form>
</div>
</div>
<!-- Actions personnelles -->
<div class="col-12 lg:col-4">
<h:form>
<div class="card mb-4">
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">Actions rapides</h5>
<span class="text-500 text-sm">Ce que je peux faire</span>
</div>
<div class="grid">
<!-- Payer mes cotisations -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Payer" />
<ui:param name="icon" value="pi pi-money-bill" />
<ui:param name="action" value="#{dashboardMembreBean.payerCotisation}" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Cotisation mensuelle</small>
</div>
<!-- Voir et s'inscrire aux événements -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Événements" />
<ui:param name="icon" value="pi pi-calendar" />
<ui:param name="action" value="#{dashboardMembreBean.inscrireEvenement}" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Calendrier</small>
</div>
<!-- Demander une aide sociale -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-warning.xhtml">
<ui:param name="value" value="Aide sociale" />
<ui:param name="icon" value="pi pi-heart" />
<ui:param name="action" value="#{dashboardMembreBean.demanderAide}" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Nouvelle demande</small>
</div>
<!-- Mon profil personnel -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Profil" />
<ui:param name="icon" value="pi pi-user" />
<ui:param name="action" value="#{dashboardMembreBean.allerAMonProfil}" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Mes informations</small>
</div>
</div>
<div class="card mb-4">
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">Actions rapides</h5>
<span class="text-500 text-sm">Ce que je peux faire</span>
</div>
</h:form>
<!-- Mes notifications personnelles -->
<div class="card">
<h5>Mes notifications</h5>
<div class="flex flex-column gap-3">
<ui:repeat value="#{dashboardMembreBean.mesNotifications}" var="notif">
<div class="flex align-items-center p-3 border-round border-1 border-200">
<i class="#{notif.icon} text-blue-500 text-xl mr-3"></i>
<div class="flex-1">
<div class="text-900 font-medium">#{notif.titre}</div>
<small class="text-600">#{notif.message}</small>
</div>
<small class="text-500">
<h:outputText value="#{notif.date}">
<f:convertDateTime pattern="dd/MM" type="localDateTime"/>
</h:outputText>
</small>
</div>
</ui:repeat>
<p:outputPanel rendered="#{empty dashboardMembreBean.mesNotifications}">
<div class="text-center text-500 p-3">
<i class="pi pi-inbox text-4xl mb-3"></i>
<p>Aucune notification pour le moment</p>
</div>
</p:outputPanel>
<div class="grid">
<!-- Payer mes cotisations -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Payer" />
<ui:param name="icon" value="pi pi-money-bill" />
<ui:param name="outcome" value="/pages/secure/membre/paiement-mes-cotisations.xhtml" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Cotisation mensuelle</small>
</div>
<!-- Voir et s'inscrire aux événements -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Événements" />
<ui:param name="icon" value="pi pi-calendar" />
<ui:param name="outcome" value="/pages/secure/evenement/calendrier.xhtml" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Calendrier</small>
</div>
<!-- Demander une aide sociale -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-warning.xhtml">
<ui:param name="value" value="Aide sociale" />
<ui:param name="icon" value="pi pi-heart" />
<ui:param name="outcome" value="/pages/secure/aide/demande.xhtml" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Nouvelle demande</small>
</div>
<!-- Mon profil personnel -->
<div class="col-6">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Profil" />
<ui:param name="icon" value="pi pi-user" />
<ui:param name="outcome" value="/pages/secure/membre/profil.xhtml" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
<small class="text-500 block text-center">Mes informations</small>
</div>
</div>
</div>
</div>
<!-- Événements à venir (publics) -->
<div class="col-12">
<!-- Résumé statut cotisations -->
<div class="card">
<div class="flex align-items-center justify-content-between mb-4">
<h5>Événements à venir</h5>
<h:form>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Voir tout" />
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="action" value="#{dashboardMembreBean.allerAuxEvenements}" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="ui-button-sm" />
</ui:include>
</h:form>
</div>
<div class="grid">
<ui:repeat value="#{dashboardMembreBean.evenementsPublics}" var="evt">
<div class="col-12 md:col-6 lg:col-4">
<div class="card border-1 border-200 h-full">
<div class="flex align-items-center justify-content-between mb-3">
<p:tag value="#{evt.type}" severity="info"/>
<span class="text-500 text-sm">#{evt.daysUntilEvent}</span>
</div>
<h6 class="text-900 mb-2">#{evt.title}</h6>
<p class="text-600 mb-3 text-sm line-height-3">#{evt.description}</p>
<div class="flex align-items-center text-600 text-sm mb-2">
<i class="pi pi-calendar mr-2"></i>
<h:outputText value="#{evt.startDate}">
<f:convertDateTime pattern="dd MMMM yyyy à HH:mm" type="localDateTime" locale="fr"/>
</h:outputText>
</div>
<div class="flex align-items-center text-600 text-sm mb-3">
<i class="pi pi-map-marker mr-2"></i>
<span>#{evt.location}</span>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-sm text-600">#{evt.participationSummary}</span>
<h:form>
<ui:include src="/templates/components/buttons/button-primary.xhtml">
<ui:param name="value" value="M'inscrire" />
<ui:param name="icon" value="pi pi-check" />
<ui:param name="action" value="#{dashboardMembreBean.inscrireAEvenement(evt.id)}" />
<ui:param name="disabled" value="#{evt.isFull}" />
<ui:param name="styleClass" value="ui-button-sm" />
</ui:include>
</h:form>
</div>
</div>
<h5>Statut de mes cotisations</h5>
<div class="flex flex-column gap-3">
<div class="flex align-items-center justify-content-between p-3 border-round surface-50">
<div class="flex align-items-center">
<i class="pi pi-check-circle text-green-500 text-xl mr-3"></i>
<span class="text-900 font-medium">Total historique</span>
</div>
</ui:repeat>
</div>
<p:outputPanel rendered="#{empty dashboardMembreBean.evenementsPublics}">
<div class="text-center text-500 p-5">
<i class="pi pi-calendar-times text-6xl mb-3"></i>
<p>Aucun événement prévu pour le moment</p>
<p:badge value="#{dashboardMembreBean.mesCotisations.size()}" severity="success"/>
</div>
</p:outputPanel>
<div class="flex align-items-center justify-content-between p-3 border-round surface-50">
<div class="flex align-items-center">
<i class="pi pi-clock text-orange-500 text-xl mr-3"></i>
<span class="text-900 font-medium">En attente</span>
</div>
<p:badge value="#{dashboardMembreBean.mesCotisationsEnAttente.size()}" severity="warning"/>
</div>
<div class="flex align-items-center justify-content-between p-3 border-round surface-50">
<div class="flex align-items-center">
<i class="pi pi-chart-line text-blue-500 text-xl mr-3"></i>
<span class="text-900 font-medium">Taux de paiement</span>
</div>
<span class="text-900 font-bold">
<h:outputText value="#{dashboardMembreBean.tauxCotisationsPerso}" rendered="#{dashboardMembreBean.tauxCotisationsPerso != null}" />
<h:outputText value="%" rendered="#{dashboardMembreBean.tauxCotisationsPerso != null}" />
<h:outputText value="N/A" rendered="#{dashboardMembreBean.tauxCotisationsPerso == null}" />
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Dialog Paiement Cotisation -->
<ui:decorate template="/templates/components/dialogs/form-dialog.xhtml">
<ui:param name="dialogId" value="dlgPaiementMembre" />
<ui:param name="header" value="Enregistrer un paiement" />
<ui:param name="widgetVar" value="dlgPaiementMembre" />
<ui:param name="formId" value="formPaiementMembre" />
<ui:param name="width" value="550" />
<ui:param name="dynamic" value="false" />
<ui:define name="content">
<!-- Récapitulatif -->
<div class="col-12">
<p class="text-600 mb-3">Récapitulatif de la cotisation à régler :</p>
</div>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Référence" />
<ui:param name="value" value="#{dashboardMembreBean.cotisationAPayer.numeroReference}" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Période" />
<ui:param name="value" value="#{dashboardMembreBean.cotisationAPayer.periode}" />
</ui:include>
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
<ui:param name="label" value="Montant dû" />
<ui:param name="value" value="#{dashboardMembreBean.cotisationAPayer.montantFormatte} FCFA" />
<ui:param name="valueClass" value="font-bold text-orange-600" />
</ui:include>
<div class="col-12"><hr class="my-2"/></div>
<!-- Montant à payer -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-number.xhtml">
<ui:param name="id" value="montantPaiementMembre" />
<ui:param name="label" value="Montant à payer (FCFA) *" />
<ui:param name="value" value="#{dashboardMembreBean.montantPaiement}" />
<ui:param name="placeholder" value="Saisir le montant" />
<ui:param name="minValue" value="1" />
</ui:include>
</div>
<!-- Mode de paiement -->
<div class="col-12">
<div class="field">
<p:outputLabel for="modePaiementMembre" value="Mode de paiement *" />
<p:selectOneMenu id="modePaiementMembre"
value="#{dashboardMembreBean.modePaiement}"
required="true"
styleClass="w-full">
<f:selectItem itemLabel="Espèces" itemValue="ESPECES" />
<f:selectItem itemLabel="Virement bancaire" itemValue="VIREMENT" />
<f:selectItem itemLabel="Wave Money" itemValue="WAVE" />
<f:selectItem itemLabel="Orange Money" itemValue="ORANGE_MONEY" />
<f:selectItem itemLabel="MTN Mobile Money" itemValue="MTN_MONEY" />
<f:selectItem itemLabel="Moov Money" itemValue="MOOV_MONEY" />
<f:selectItem itemLabel="Carte bancaire" itemValue="CARTE" />
<f:selectItem itemLabel="Chèque" itemValue="CHEQUE" />
</p:selectOneMenu>
</div>
</div>
<!-- Date de paiement -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-calendar.xhtml">
<ui:param name="id" value="datePaiementMembre" />
<ui:param name="label" value="Date de paiement *" />
<ui:param name="value" value="#{dashboardMembreBean.datePaiement}" />
<ui:param name="pattern" value="dd/MM/yyyy" />
<ui:param name="showIcon" value="true" />
</ui:include>
</div>
<!-- Référence de transaction -->
<div class="col-12">
<ui:include src="/templates/components/forms/form-field-text.xhtml">
<ui:param name="id" value="referencePaiementMembre" />
<ui:param name="label" value="Référence de transaction" />
<ui:param name="value" value="#{dashboardMembreBean.referencePaiement}" />
<ui:param name="placeholder" value="Numéro de transaction, reçu..." />
</ui:include>
</div>
</ui:define>
<ui:define name="footer">
<p:commandButton value="Annuler"
icon="pi pi-times"
type="button"
onclick="PF('dlgPaiementMembre').hide();"
styleClass="ui-button-secondary" />
<p:commandButton value="Confirmer le paiement"
icon="pi pi-check"
action="#{dashboardMembreBean.confirmerPaiement}"
update="formCotisationsMembre formCotisationsEnAttente formPaiementMembre"
process="formPaiementMembre"
styleClass="ui-button-success ml-2" />
</ui:define>
</ui:decorate>
</ui:define>
</ui:composition>

View File

@@ -380,8 +380,10 @@
</p:column>
<p:column headerText="Acteur" style="width:150px">
<div class="flex align-items-center">
<p:graphicImage name="images/avatar/profile.jpg" library="demo"
styleClass="w-2rem h-2rem border-circle mr-2"/>
<div class="bg-blue-100 border-circle flex align-items-center justify-content-center mr-2"
style="width:2rem;height:2rem;flex-shrink:0;">
<i class="pi pi-user text-blue-600 text-sm"></i>
</div>
<div>
<div class="text-900 font-medium">#{activity.userNom}</div>
<small class="text-500">#{activity.userRole}</small>
@@ -413,7 +415,7 @@
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Nouveau membre" />
<ui:param name="icon" value="pi pi-user-plus" />
<ui:param name="action" value="#{dashboardBean.redirectToNewMember}" />
<ui:param name="outcome" value="membreInscriptionPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
@@ -425,7 +427,7 @@
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Collecter" />
<ui:param name="icon" value="pi pi-wallet" />
<ui:param name="action" value="#{dashboardBean.redirectToCotisation}" />
<ui:param name="outcome" value="cotisationPaiementPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
@@ -437,7 +439,7 @@
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Événement" />
<ui:param name="icon" value="pi pi-calendar-plus" />
<ui:param name="action" value="#{dashboardBean.redirectToEvenement}" />
<ui:param name="outcome" value="evenementCreationPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
@@ -475,7 +477,7 @@
</div>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="action" value="#{dashboardBean.redirectToAdhesionValidation}" />
<ui:param name="outcome" value="adhesionValidationPage" />
<ui:param name="severity" value="info" />
</ui:include>
</div>
@@ -491,7 +493,7 @@
</div>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="action" value="#{dashboardBean.redirectToRelances}" />
<ui:param name="outcome" value="cotisationRelancesPage" />
<ui:param name="severity" value="warning" />
</ui:include>
</div>
@@ -507,7 +509,7 @@
</div>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="action" value="#{dashboardBean.redirectToAidesTraitement}" />
<ui:param name="outcome" value="aideTraitementPage" />
<ui:param name="severity" value="success" />
</ui:include>
</div>
@@ -523,7 +525,7 @@
</div>
<ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="action" value="#{dashboardBean.redirectToEvenementPlanning}" />
<ui:param name="outcome" value="evenementGestionPage" />
<ui:param name="styleClass" value="ui-button-help" />
</ui:include>
</div>

View File

@@ -0,0 +1,373 @@
<!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">Épargne - UnionFlow</ui:define>
<ui:define name="content">
<h:form id="formEpargne">
<p:messages id="messages" showDetail="true" closable="true"/>
<!-- En-tête -->
<div class="card mb-3">
<div class="flex justify-content-between align-items-center flex-column md:flex-row">
<div>
<h3 class="m-0">
<i class="pi pi-wallet text-primary mr-2"></i>
Comptes d'Épargne
</h3>
<p class="text-600 m-0 mt-2">Gestion des comptes d'épargne (conforme LCB-FT)</p>
</div>
<div class="flex gap-2 mt-2 md:mt-0">
<p:commandButton value="Ouvrir un compte"
icon="pi pi-plus"
styleClass="ui-button-success"
onclick="PF('dlgOuvertureCompte').show(); return false;"/>
<p:commandButton icon="pi pi-refresh"
styleClass="ui-button-text"
action="#{epargneBean.actualiser}"
update="@form"
title="Actualiser"/>
</div>
</div>
</div>
<!-- Alerte LCB-FT -->
<div class="card mb-3 border-left-3 border-orange-500">
<div class="flex align-items-center">
<i class="pi pi-shield text-orange-500 text-2xl mr-3"></i>
<div>
<div class="font-semibold">Conformité LCB-FT</div>
<div class="text-600 text-sm">
Les dépôts ≥ 500 000 FCFA nécessitent une déclaration d'origine des fonds.
Toute transaction suspecte est automatiquement signalée.
</div>
</div>
</div>
</div>
<!-- Tabs : Mes comptes / Tous les comptes (admin) -->
<p:tabView id="tabsEpargne">
<!-- Tab 1 : Mes comptes -->
<p:tab title="Mes comptes (#{epargneBean.mesComptes.size()})">
<div class="grid">
<ui:repeat value="#{epargneBean.mesComptes}" var="compte">
<div class="col-12 md:col-6 lg:col-4">
<div class="card border-round shadow-2 cursor-pointer hover:shadow-4 transition-all transition-duration-200"
onclick="PF('dlgTransactions').show();">
<p:commandLink update="panelTransactions messages"
action="#{epargneBean.selectionnerCompte(compte)}"
styleClass="text-color no-underline">
<div class="flex justify-content-between align-items-start mb-3">
<div>
<div class="font-bold text-lg">#{compte.numeroCompte}</div>
<div class="text-600 text-sm">#{compte.typeCompte}</div>
</div>
<p:tag value="#{compte.statut}"
severity="#{epargneBean.getStatutSeverity(compte.statut)}"/>
</div>
<div class="border-top-1 surface-border pt-3">
<div class="text-500 text-sm mb-1">Solde disponible</div>
<div class="text-primary font-bold text-2xl">
<h:outputText value="#{compte.soldeActuel}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</div>
<h:panelGroup rendered="#{compte.soldeBloque != null and compte.soldeBloque.signum() gt 0}">
<div class="text-orange-500 text-sm mt-1">
<i class="pi pi-lock mr-1"></i>
Bloqué :
<h:outputText value="#{compte.soldeBloque}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</div>
</h:panelGroup>
</div>
<div class="text-500 text-xs mt-2">
Ouvert le
<h:outputText value="#{compte.dateOuverture}">
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</div>
</p:commandLink>
</div>
</div>
</ui:repeat>
<h:panelGroup rendered="#{epargneBean.mesComptes.empty}">
<div class="col-12">
<div class="card text-center text-600 p-5">
<i class="pi pi-wallet text-4xl text-300 mb-3"></i>
<p>Aucun compte d'épargne. Ouvrez votre premier compte !</p>
</div>
</div>
</h:panelGroup>
</div>
</p:tab>
<!-- Tab 2 : Tous les comptes (Admin/OrgAdmin) -->
<p:tab title="Comptes de l'organisation">
<p:commandButton value="Charger"
icon="pi pi-download"
action="#{epargneBean.chargerComptesOrganisation}"
update="dtTousComptes"
styleClass="mb-3"/>
<p:dataTable id="dtTousComptes"
var="compte"
value="#{epargneBean.comptesOrganisation}"
paginator="true"
rows="20"
emptyMessage="Cliquez sur Charger pour afficher les comptes"
rowHover="true">
<p:column headerText="Numéro compte" sortBy="#{compte.numeroCompte}">
<span class="font-mono font-semibold">#{compte.numeroCompte}</span>
</p:column>
<p:column headerText="Type" sortBy="#{compte.typeCompte}">
<p:tag value="#{compte.typeCompte}" severity="info"/>
</p:column>
<p:column headerText="Solde disponible" sortBy="#{compte.soldeActuel}">
<span class="font-bold">
<h:outputText value="#{compte.soldeActuel}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</span>
</p:column>
<p:column headerText="Solde bloqué">
<h:outputText value="#{compte.soldeBloque}" rendered="#{compte.soldeBloque != null}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</p:column>
<p:column headerText="Statut" sortBy="#{compte.statut}">
<p:tag value="#{compte.statut}"
severity="#{epargneBean.getStatutSeverity(compte.statut)}"/>
</p:column>
<p:column headerText="Ouverture">
<h:outputText value="#{compte.dateOuverture}" rendered="#{compte.dateOuverture != null}">
<f:convertDateTime pattern="dd/MM/yyyy" type="localDate"/>
</h:outputText>
</p:column>
<p:column headerText="Actions" style="width:180px">
<div class="flex gap-1">
<p:commandButton icon="pi pi-list"
styleClass="ui-button-rounded ui-button-text ui-button-info"
title="Voir transactions"
action="#{epargneBean.selectionnerCompte(compte)}"
update="panelTransactions"
oncomplete="PF('dlgTransactions').show()"/>
<p:commandButton icon="pi pi-ban"
styleClass="ui-button-rounded ui-button-text ui-button-warning"
title="Bloquer"
action="#{epargneBean.changerStatutCompte(compte, 'BLOQUE')}"
update="dtTousComptes messages"
rendered="#{compte.statut == 'ACTIF'}"
onclick="return confirm('Bloquer ce compte ?');"/>
<p:commandButton icon="pi pi-check"
styleClass="ui-button-rounded ui-button-text ui-button-success"
title="Activer"
action="#{epargneBean.changerStatutCompte(compte, 'ACTIF')}"
update="dtTousComptes messages"
rendered="#{compte.statut == 'BLOQUE'}"/>
</div>
</p:column>
</p:dataTable>
</p:tab>
</p:tabView>
</h:form>
<!-- Dialog Transactions -->
<p:dialog header="Transactions - #{epargneBean.compteSelectionne != null ? epargneBean.compteSelectionne.numeroCompte : ''}"
widgetVar="dlgTransactions"
modal="true"
width="800"
responsive="true">
<h:form id="formTransactions">
<div id="panelTransactions">
<h:panelGroup rendered="#{epargneBean.compteSelectionne != null}">
<!-- Résumé compte -->
<div class="grid mb-3">
<div class="col-12 md:col-4">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm">Solde disponible</div>
<div class="text-primary font-bold text-xl">
<h:outputText value="#{epargneBean.compteSelectionne.soldeActuel}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</div>
</div>
</div>
<div class="col-12 md:col-4">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm">Type</div>
<div class="font-bold">#{epargneBean.compteSelectionne.typeCompte}</div>
</div>
</div>
<div class="col-12 md:col-4">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-sm">Statut</div>
<p:tag value="#{epargneBean.compteSelectionne.statut}"
severity="#{epargneBean.getStatutSeverity(epargneBean.compteSelectionne.statut)}"/>
</div>
</div>
</div>
<!-- Nouvelle transaction -->
<p:fieldset legend="Nouvelle transaction" toggleable="true" collapsed="true" class="mb-3">
<div class="grid">
<div class="col-12 md:col-3">
<p:selectOneMenu value="#{epargneBean.nouvelleTransaction.type}"
styleClass="w-full">
<f:selectItem itemLabel="Dépôt" itemValue="DEPOT"/>
<f:selectItem itemLabel="Retrait" itemValue="RETRAIT"/>
<f:selectItem itemLabel="Transfert sortant" itemValue="TRANSFERT_SORTANT"/>
</p:selectOneMenu>
</div>
<div class="col-12 md:col-3">
<p:inputNumber value="#{epargneBean.nouvelleTransaction.montant}"
placeholder="Montant (FCFA)"
styleClass="w-full"
minValue="0.01"/>
</div>
<div class="col-12 md:col-3">
<p:inputText value="#{epargneBean.nouvelleTransaction.motif}"
placeholder="Motif"
styleClass="w-full"/>
</div>
<div class="col-12 md:col-3">
<p:inputText value="#{epargneBean.nouvelleTransaction.origineFonds}"
placeholder="Origine des fonds (LCB-FT)"
styleClass="w-full #{epargneBean.isRequiertOrigineFonds(epargneBean.nouvelleTransaction.montant) ? 'border-orange-500' : ''}"/>
</div>
<h:panelGroup rendered="#{epargneBean.isRequiertOrigineFonds(epargneBean.nouvelleTransaction.montant)}">
<div class="col-12">
<div class="flex align-items-center text-orange-500 text-sm">
<i class="pi pi-exclamation-triangle mr-2"></i>
Montant ≥ 500 000 FCFA : déclaration d'origine des fonds obligatoire (LCB-FT)
</div>
</div>
</h:panelGroup>
<div class="col-12">
<p:commandButton value="Effectuer la transaction"
icon="pi pi-send"
styleClass="ui-button-success"
action="#{epargneBean.executerTransaction}"
update="panelTransactions formEpargne:messages"/>
</div>
</div>
</p:fieldset>
<!-- Historique transactions -->
<p:dataTable var="tx"
value="#{epargneBean.transactions}"
paginator="true"
rows="#{epargneBean.tailleTransactions}"
emptyMessage="Aucune transaction">
<p:column headerText="Type" style="width:60px">
<i class="pi #{epargneBean.getTypeTransactionIcon(tx.type.toString())}"/>
</p:column>
<p:column headerText="Type" sortBy="#{tx.type}">
<p:tag value="#{tx.type}" severity="info"/>
</p:column>
<p:column headerText="Montant" sortBy="#{tx.montant}">
<span class="font-bold">
<h:outputText value="#{tx.montant}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</span>
</p:column>
<p:column headerText="Solde avant">
<h:outputText value="#{tx.soldeAvant}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</p:column>
<p:column headerText="Solde après">
<h:outputText value="#{tx.soldeApres}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</p:column>
<p:column headerText="Motif">#{tx.motif}</p:column>
<p:column headerText="Date" sortBy="#{tx.dateTransaction}">
<h:outputText value="#{tx.dateTransaction}" rendered="#{tx.dateTransaction != null}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime"/>
</h:outputText>
</p:column>
</p:dataTable>
<div class="flex justify-content-center mt-3" rendered="#{epargneBean.plusDeTransactions}">
<p:commandButton value="Charger plus"
icon="pi pi-chevron-down"
styleClass="ui-button-text"
action="#{epargneBean.chargerPlusDeTransactions}"
update="panelTransactions"/>
</div>
</h:panelGroup>
</div>
</h:form>
</p:dialog>
<!-- Dialog Ouverture Compte -->
<p:dialog header="Ouvrir un compte d'épargne"
widgetVar="dlgOuvertureCompte"
modal="true"
width="450">
<h:form id="formOuvertureCompte">
<div class="grid">
<div class="col-12">
<div class="field">
<p:outputLabel for="membreId" value="ID du membre *"/>
<p:inputText id="membreId"
value="#{epargneBean.ouvertureCompte.membreId}"
styleClass="w-full" required="true"
placeholder="UUID du membre"/>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="typeCompte" value="Type de compte *"/>
<p:selectOneMenu id="typeCompte"
value="#{epargneBean.ouvertureCompte.typeCompte}"
styleClass="w-full">
<f:selectItem itemLabel="Épargne libre" itemValue="EPARGNE_LIBRE"/>
<f:selectItem itemLabel="Épargne bloquée (garantie)" itemValue="EPARGNE_BLOQUEE"/>
<f:selectItem itemLabel="Dépôt à terme" itemValue="DEPOT_A_TERME"/>
<f:selectItem itemLabel="Épargne projet/tontine" itemValue="EPARGNE_PROJET"/>
<f:selectItem itemLabel="Compte courant" itemValue="COURANT"/>
</p:selectOneMenu>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="notesOuverture" value="Notes d'ouverture"/>
<p:inputTextarea id="notesOuverture"
value="#{epargneBean.ouvertureCompte.notes}"
rows="2" styleClass="w-full"/>
</div>
</div>
</div>
<div class="flex justify-content-end gap-2">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgOuvertureCompte').hide(); return false;"/>
<p:commandButton value="Ouvrir le compte"
icon="pi pi-plus"
styleClass="ui-button-success"
action="#{epargneBean.ouvrirCompte}"
update="formEpargne messages"
oncomplete="if(!args.validationFailed) PF('dlgOuvertureCompte').hide();"/>
</div>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -33,7 +33,7 @@
<div class="card">
<h:form id="formCreation">
<ui:include src="/templates/components/forms/form-section.xhtml">
<ui:define name="title">Informations Générales</ui:define>
<ui:param name="title" value="Informations Générales" />
<ui:define name="content">
<div class="grid">
<div class="col-12">
@@ -95,7 +95,7 @@
</ui:include>
<ui:include src="/templates/components/forms/form-section.xhtml">
<ui:define name="title">Dates et Horaires</ui:define>
<ui:param name="title" value="Dates et Horaires" />
<ui:define name="content">
<div class="grid">
<div class="col-12 md:col-6">
@@ -139,7 +139,7 @@
</ui:include>
<ui:include src="/templates/components/forms/form-section.xhtml">
<ui:define name="title">Localisation</ui:define>
<ui:param name="title" value="Localisation" />
<ui:define name="content">
<div class="grid">
<div class="col-12 md:col-6">
@@ -181,7 +181,7 @@
</ui:include>
<ui:include src="/templates/components/forms/form-section.xhtml">
<ui:define name="title">Organisation et Participants</ui:define>
<ui:param name="title" value="Organisation et Participants" />
<ui:define name="content">
<div class="grid">
<div class="col-12 md:col-6">
@@ -221,7 +221,7 @@
</ui:include>
<ui:include src="/templates/components/forms/form-section.xhtml">
<ui:define name="title">Budget</ui:define>
<ui:param name="title" value="Budget" />
<ui:define name="content">
<div class="grid">
<div class="col-12 md:col-6">

View File

@@ -0,0 +1,351 @@
<!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">Workflow Financier - UnionFlow</ui:define>
<ui:define name="content">
<h:form id="formApprobations">
<p:messages id="messages" showDetail="true" closable="true"/>
<!-- En-tête avec KPIs -->
<div class="card mb-3">
<div class="flex justify-content-between align-items-center flex-column md:flex-row">
<div>
<h3 class="m-0">
<i class="pi pi-check-square text-primary mr-2"></i>
Workflow Financier
</h3>
<p class="text-600 m-0 mt-2">Approbations de transactions et gestion des budgets</p>
</div>
<div class="flex gap-2 mt-2 md:mt-0">
<p:commandButton value="Nouveau budget"
icon="pi pi-plus"
styleClass="ui-button-outlined"
onclick="PF('dlgNouveauBudget').show(); return false;"/>
<p:commandButton icon="pi pi-refresh"
styleClass="ui-button-text"
action="#{approbationsFinanceBean.actualiser}"
update="@form"
title="Actualiser"/>
</div>
</div>
</div>
<!-- Statistiques rapides -->
<div class="grid mb-3" rendered="#{approbationsFinanceBean.statistiques != null}">
<div class="col-12 md:col-3">
<div class="card text-center">
<div class="text-primary font-bold text-3xl">
#{approbationsFinanceBean.nombreEnAttente}
</div>
<div class="text-600 text-sm mt-1">En attente d'approbation</div>
</div>
</div>
</div>
<!-- Tabs : Approbations / Budgets / Historique -->
<p:tabView id="tabsFinance">
<!-- Tab 1 : Approbations en attente -->
<p:tab title="Approbations en attente (#{approbationsFinanceBean.nombreEnAttente})">
<p:dataTable id="dtApprobations"
var="appro"
value="#{approbationsFinanceBean.approbationsEnAttente}"
paginator="true"
rows="10"
emptyMessage="Aucune approbation en attente"
rowHover="true">
<p:column headerText="ID Transaction" style="width:180px">
<span class="text-sm font-mono">#{appro.transactionId.toString().substring(0,8)}...</span>
</p:column>
<p:column headerText="Type" sortBy="#{appro.transactionType}">
<p:tag value="#{appro.transactionType}" severity="info"/>
</p:column>
<p:column headerText="Montant" sortBy="#{appro.amount}">
<span class="font-bold">
<h:outputText value="#{appro.amount}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</span>
</p:column>
<p:column headerText="Demandeur" sortBy="#{appro.requesterName}">
#{appro.requesterName}
</p:column>
<p:column headerText="Niveau requis" sortBy="#{appro.requiredLevel}">
<p:tag value="#{appro.requiredLevel}" severity="warning"/>
</p:column>
<p:column headerText="Date" sortBy="#{appro.createdAt}">
<h:outputText value="#{appro.createdAt}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime"/>
</h:outputText>
</p:column>
<p:column headerText="Statut">
<p:tag value="#{appro.status}"
severity="#{approbationsFinanceBean.getStatusSeverity(appro.status)}"/>
</p:column>
<p:column headerText="Actions" style="width:160px">
<div class="flex gap-1">
<p:commandButton icon="pi pi-check"
styleClass="ui-button-rounded ui-button-success ui-button-text"
title="Approuver"
action="#{approbationsFinanceBean.selectionnerApprobation(appro)}"
update="@none"
oncomplete="PF('dlgApprouver').show()"/>
<p:commandButton icon="pi pi-times"
styleClass="ui-button-rounded ui-button-danger ui-button-text"
title="Rejeter"
action="#{approbationsFinanceBean.selectionnerApprobation(appro)}"
update="@none"
oncomplete="PF('dlgRejeter').show()"/>
</div>
</p:column>
</p:dataTable>
</p:tab>
<!-- Tab 2 : Budgets -->
<p:tab title="Budgets (#{approbationsFinanceBean.budgets.size()})">
<p:dataTable id="dtBudgets"
var="budget"
value="#{approbationsFinanceBean.budgets}"
paginator="true"
rows="10"
emptyMessage="Aucun budget"
rowHover="true">
<p:column headerText="Nom" sortBy="#{budget.name}">
<span class="font-semibold">#{budget.name}</span>
</p:column>
<p:column headerText="Période">
#{budget.period} #{budget.year}
<h:outputText value=" / Mois #{budget.month}" rendered="#{budget.month != null}"/>
</p:column>
<p:column headerText="Prévu" sortBy="#{budget.totalPlanned}">
<h:outputText value="#{budget.totalPlanned}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</p:column>
<p:column headerText="Réalisé" sortBy="#{budget.totalRealized}">
<h:outputText value="#{budget.totalRealized}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</p:column>
<p:column headerText="Taux">
<p:progressBar value="#{budget.realizationRate}"
displayOnly="true"
style="height:10px"/>
<span class="text-sm">#{budget.realizationRate != null ? budget.realizationRate.intValue() : 0}%</span>
</p:column>
<p:column headerText="Statut">
<p:tag value="#{budget.status}"
severity="#{approbationsFinanceBean.getStatusSeverity(budget.status)}"/>
</p:column>
<p:column headerText="Actions" style="width:100px">
<p:commandButton icon="pi pi-trash"
styleClass="ui-button-rounded ui-button-danger ui-button-text"
action="#{approbationsFinanceBean.supprimerBudget(budget)}"
update="dtBudgets messages"
onclick="return confirm('Supprimer ce budget ?');"/>
</p:column>
</p:dataTable>
</p:tab>
<!-- Tab 3 : Historique -->
<p:tab title="Historique">
<!-- Filtres -->
<div class="grid mb-3">
<div class="col-12 md:col-3">
<p:selectOneMenu value="#{approbationsFinanceBean.filtreStatut}"
styleClass="w-full"
placeholder="Tous les statuts">
<f:selectItem itemLabel="Tous" itemValue=""/>
<f:selectItem itemLabel="Approuvé" itemValue="APPROVED"/>
<f:selectItem itemLabel="Validé" itemValue="VALIDATED"/>
<f:selectItem itemLabel="Rejeté" itemValue="REJECTED"/>
<f:selectItem itemLabel="Expiré" itemValue="EXPIRED"/>
</p:selectOneMenu>
</div>
<div class="col-12 md:col-3">
<p:calendar value="#{approbationsFinanceBean.dateDebut}"
pattern="yyyy-MM-dd"
placeholder="Date début"
styleClass="w-full"/>
</div>
<div class="col-12 md:col-3">
<p:calendar value="#{approbationsFinanceBean.dateFin}"
pattern="yyyy-MM-dd"
placeholder="Date fin"
styleClass="w-full"/>
</div>
<div class="col-12 md:col-3">
<p:commandButton value="Filtrer"
icon="pi pi-filter"
action="#{approbationsFinanceBean.appliquerFiltres}"
update="dtHistorique"
styleClass="w-full"/>
</div>
</div>
<p:dataTable id="dtHistorique"
var="h"
value="#{approbationsFinanceBean.historiqueApprobations}"
paginator="true"
rows="20"
emptyMessage="Chargez l'historique via les filtres">
<p:column headerText="Type">
<p:tag value="#{h.transactionType}" severity="info"/>
</p:column>
<p:column headerText="Montant">
<h:outputText value="#{h.amount}">
<f:convertNumber type="currency" currencySymbol="FCFA " groupingUsed="true"/>
</h:outputText>
</p:column>
<p:column headerText="Demandeur">#{h.requesterName}</p:column>
<p:column headerText="Statut">
<p:tag value="#{h.status}" severity="#{approbationsFinanceBean.getStatusSeverity(h.status)}"/>
</p:column>
<p:column headerText="Date">
<h:outputText value="#{h.createdAt}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime"/>
</h:outputText>
</p:column>
<p:column headerText="Complété le">
<h:outputText value="#{h.completedAt}" rendered="#{h.completedAt != null}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime"/>
</h:outputText>
</p:column>
</p:dataTable>
</p:tab>
</p:tabView>
</h:form>
<!-- Dialog Approuver -->
<p:dialog header="Approuver la transaction"
widgetVar="dlgApprouver"
modal="true"
width="400">
<h:form id="formApprouver">
<div class="field">
<p:outputLabel for="commentaireAppro" value="Commentaire (optionnel)"/>
<p:inputTextarea id="commentaireAppro"
value="#{approbationsFinanceBean.commentaireApprobation}"
rows="3" styleClass="w-full"/>
</div>
<div class="flex justify-content-end gap-2 mt-3">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgApprouver').hide(); return false;"/>
<p:commandButton value="Approuver"
icon="pi pi-check"
styleClass="ui-button-success"
action="#{approbationsFinanceBean.approuver}"
update="formApprobations messages"
oncomplete="if(!args.validationFailed) PF('dlgApprouver').hide();"/>
</div>
</h:form>
</p:dialog>
<!-- Dialog Rejeter -->
<p:dialog header="Rejeter la transaction"
widgetVar="dlgRejeter"
modal="true"
width="400">
<h:form id="formRejeter">
<div class="field">
<p:outputLabel for="raisonRejet" value="Raison du rejet *"/>
<p:inputTextarea id="raisonRejet"
value="#{approbationsFinanceBean.raisonRejet}"
rows="3" styleClass="w-full" required="true"/>
</div>
<div class="flex justify-content-end gap-2 mt-3">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgRejeter').hide(); return false;"/>
<p:commandButton value="Rejeter"
icon="pi pi-times"
styleClass="ui-button-danger"
action="#{approbationsFinanceBean.rejeter}"
update="formApprobations messages"
oncomplete="if(!args.validationFailed) PF('dlgRejeter').hide();"/>
</div>
</h:form>
</p:dialog>
<!-- Dialog Nouveau Budget -->
<p:dialog header="Nouveau Budget"
widgetVar="dlgNouveauBudget"
modal="true"
width="500">
<h:form id="formNouveauBudget">
<div class="grid">
<div class="col-12">
<div class="field">
<p:outputLabel for="budgetNom" value="Nom *"/>
<p:inputText id="budgetNom"
value="#{approbationsFinanceBean.nouveauBudget.nom}"
styleClass="w-full" required="true"/>
</div>
</div>
<div class="col-12 md:col-6">
<div class="field">
<p:outputLabel for="budgetPeriode" value="Période *"/>
<p:selectOneMenu id="budgetPeriode"
value="#{approbationsFinanceBean.nouveauBudget.periode}"
styleClass="w-full">
<f:selectItem itemLabel="Mensuel" itemValue="MONTHLY"/>
<f:selectItem itemLabel="Trimestriel" itemValue="QUARTERLY"/>
<f:selectItem itemLabel="Semestriel" itemValue="SEMIANNUAL"/>
<f:selectItem itemLabel="Annuel" itemValue="ANNUAL"/>
</p:selectOneMenu>
</div>
</div>
<div class="col-12 md:col-6">
<div class="field">
<p:outputLabel for="budgetAnnee" value="Année *"/>
<p:inputNumber id="budgetAnnee"
value="#{approbationsFinanceBean.nouveauBudget.annee}"
styleClass="w-full" required="true"/>
</div>
</div>
<div class="col-12">
<div class="field">
<p:outputLabel for="budgetDesc" value="Description"/>
<p:inputTextarea id="budgetDesc"
value="#{approbationsFinanceBean.nouveauBudget.description}"
rows="2" styleClass="w-full"/>
</div>
</div>
</div>
<div class="flex justify-content-end gap-2">
<p:commandButton value="Annuler"
styleClass="ui-button-secondary"
onclick="PF('dlgNouveauBudget').hide(); return false;"/>
<p:commandButton value="Créer"
icon="pi pi-plus"
styleClass="ui-button-success"
action="#{approbationsFinanceBean.creerBudget}"
update="formApprobations messages"
oncomplete="if(!args.validationFailed) PF('dlgNouveauBudget').hide();"/>
</div>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>

View File

@@ -28,43 +28,49 @@
</ui:decorate>
<!-- ================================================================
STATISTIQUES (DRY/WOU: stat-card)
KPI MEMBRES (DRY/WOU: kpi-card — standard actuel)
Visible uniquement pour SECRETAIRE, ADMIN (pas MEMBRE_ACTIF)
================================================================ -->
<h:panelGroup id="panelStatistiques" layout="block" styleClass="grid mb-3"
<h:panelGroup id="panelStatistiques" layout="block" styleClass="formgrid grid mb-3"
rendered="#{menuBean.gestionMembresMenuVisible}">
<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>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Total Membres" />
<ui:param name="value" value="#{membreListeBean.totalMembres}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
<ui:param name="growthValue" value="#{membreListeBean.nouveauxMembres}" />
<ui:param name="growthLabel" value="nouveaux ce mois" />
<ui:param name="growthType" value="number" />
<ui:param name="showProgress" value="false" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Membres Actifs" />
<ui:param name="value" value="#{membreListeBean.membresActifs}" />
<ui:param name="icon" value="pi-check-circle" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="progressValue" value="#{membreListeBean.tauxActivite}" />
<ui:param name="noDataLabel" value="#{membreListeBean.tauxActivite}% d'activité" />
<ui:param name="showGrowth" value="false" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Inactifs / Suspendus" />
<ui:param name="value" value="#{membreListeBean.membresInactifs}" />
<ui:param name="icon" value="pi-ban" />
<ui:param name="iconColor" value="orange-600" />
<ui:param name="showProgress" value="false" />
<ui:param name="showGrowth" value="false" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Nouveaux (30 jours)" />
<ui:param name="value" value="#{membreListeBean.nouveauxMembres}" />
<ui:param name="icon" value="pi-user-plus" />
<ui:param name="iconColor" value="purple-600" />
<ui:param name="showProgress" value="false" />
<ui:param name="showGrowth" value="false" />
</ui:include>
</h:panelGroup>
<!-- ================================================================
@@ -107,8 +113,9 @@
</p:selectOneMenu>
</div>
<!-- Filtre par organisation -->
<div class="col-12 md:col-2">
<!-- Filtre par organisation (SUPER_ADMIN uniquement) -->
<p:outputPanel layout="block" styleClass="col-12 md:col-2"
rendered="#{userSession.isSuperAdmin()}">
<p:selectOneMenu id="entiteFilter" value="#{membreListeBean.entiteFilter}" styleClass="w-full"
filter="true" filterMatchMode="contains">
<f:selectItem itemLabel="Toutes les organisations" itemValue="" />
@@ -116,14 +123,16 @@
itemLabel="#{org.nom}" itemValue="#{org.id}" />
<p:ajax event="valueChange" listener="#{membreListeBean.rechercher}" update="dtMembres" />
</p:selectOneMenu>
</div>
</p:outputPanel>
</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" />
<div class="col-12 md:col-2 flex align-items-center justify-content-end gap-1">
<p:commandButton icon="pi pi-filter" title="Filtres avancés"
onclick="PF('dlgFiltresAvances').show();" type="button" styleClass="ui-button-outlined" />
<p:commandButton icon="pi pi-refresh" title="Actualiser" action="#{membreListeBean.actualiser}"
update="dtMembres panelStatistiques messages" styleClass="ui-button-outlined" />
</div>
</ui:define>
</ui:decorate>
@@ -178,9 +187,8 @@
<!-- 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="value" value="#{not empty membre.roles ? membre.roles[0] : '—'}" />
<ui:param name="severity" value="info" />
<ui:param name="width" value="8rem" />
</ui:decorate>
@@ -188,10 +196,10 @@
<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:param name="text" value="#{not empty membre.organisationNom ? membre.organisationNom : '—'}" />
<ui:param name="sortBy" value="#{membre.organisationNom}" />
<ui:param name="filterBy" value="#{membre.organisationNom}" />
<ui:param name="styleClass" value="#{empty membre.organisationNom ? 'text-500' : ''}" />
</ui:decorate>
<!-- Colonne: Téléphone -->
@@ -205,10 +213,9 @@
<!-- 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="value" value="#{membre.statutCompteLibelle}" />
<ui:param name="severity" value="#{membre.statutCompteSeverity}" />
<ui:param name="sortBy" value="#{membre.statutCompte}" />
<ui:param name="width" value="8rem" />
</ui:decorate>
@@ -249,14 +256,14 @@
actionListener="#{membreListeBean.preparerSuspendre(membre)}"
update=":formMembres:dlgConfirmSuspendre"
oncomplete="PF('dlgConfirmSuspendre').show();"
rendered="#{menuBean.gestionMembresMenuVisible and membre.statut == 'ACTIF'}"
rendered="#{menuBean.gestionMembresMenuVisible and membre.statutCompte == 'ACTIF'}"
styleClass="ui-button-danger ui-button-sm ui-button-rounded mr-1" />
<!-- Réactiver - ADMIN SEULEMENT (visible si suspendu) -->
<p:commandButton icon="pi pi-replay" title="Réactiver"
action="#{membreListeBean.reactiverMembre(membre)}"
update="dtMembres messages panelStatistiques"
rendered="#{menuBean.gestionMembresMenuVisible and membre.statut == 'SUSPENDU'}"
rendered="#{menuBean.gestionMembresMenuVisible and membre.statutCompte == 'SUSPENDU'}"
styleClass="ui-button-success ui-button-sm ui-button-rounded" />
</ui:define>
</ui:decorate>

View File

@@ -609,7 +609,66 @@
<ui:param name="styleClass" value="w-full mb-2" />
<ui:param name="rendered" value="#{membreProfilBean.membre.statut == 'SUSPENDU'}" />
</ui:include>
<!-- ── Cycle de vie adhésion dans l'organisation active ── -->
<p:separator rendered="#{userSession.activeOrganisationId != null}" />
<!-- Inviter (aucun lien existant) -->
<p:commandButton value="Inviter dans l'organisation"
icon="pi pi-user-plus"
styleClass="p-button-outlined p-button-info w-full mb-2"
rendered="#{userSession.activeOrganisationId != null and (membreProfilBean.statutAdhesion == null or membreProfilBean.statutAdhesion == 'null')}"
action="#{membreProfilBean.inviterDansOrgActive}"
update="formActions"
oncomplete="PF('dlgActions').hide();" />
<!-- Activer adhésion (INVITE ou EN_ATTENTE_VALIDATION) -->
<p:commandButton value="Activer l'adhésion"
icon="pi pi-check-circle"
styleClass="p-button-outlined p-button-success w-full mb-2"
rendered="#{membreProfilBean.statutAdhesion == 'INVITE' or membreProfilBean.statutAdhesion == 'EN_ATTENTE_VALIDATION'}"
action="#{membreProfilBean.activerAdhesion}"
update="formActions"
oncomplete="PF('dlgActions').hide();" />
<!-- Suspendre adhésion (ACTIF) -->
<p:commandButton value="Suspendre l'adhésion"
icon="pi pi-pause-circle"
styleClass="p-button-outlined p-button-warning w-full mb-2"
rendered="#{membreProfilBean.statutAdhesion == 'ACTIF'}"
action="#{membreProfilBean.suspendrAdhesion}"
update="formActions"
oncomplete="PF('dlgActions').hide();" />
<!-- Réactiver adhésion (SUSPENDU) -->
<p:commandButton value="Réactiver l'adhésion"
icon="pi pi-play-circle"
styleClass="p-button-outlined p-button-success w-full mb-2"
rendered="#{membreProfilBean.statutAdhesion == 'SUSPENDU'}"
action="#{membreProfilBean.activerAdhesion}"
update="formActions"
oncomplete="PF('dlgActions').hide();" />
<!-- Radier (tout statut sauf RADIE/ARCHIVE) -->
<p:commandButton value="Radier de l'organisation"
icon="pi pi-ban"
styleClass="p-button-outlined p-button-danger w-full mb-2"
rendered="#{membreProfilBean.statutAdhesion != null and membreProfilBean.statutAdhesion != 'null' and membreProfilBean.statutAdhesion != 'RADIE' and membreProfilBean.statutAdhesion != 'ARCHIVE'}"
onclick="return confirm('Confirmer la radiation de ce membre de l\'organisation ?');"
action="#{membreProfilBean.radierAdhesion}"
update="formActions"
oncomplete="PF('dlgActions').hide();" />
<p:commandButton id="btnChargerAdhesion"
value="Vérifier statut adhésion"
icon="pi pi-refresh"
styleClass="p-button-outlined p-button-secondary w-full mb-2"
rendered="#{userSession.activeOrganisationId != null}"
action="#{membreProfilBean.chargerStatutAdhesion}"
update="formActions" />
<p:separator />
<ui:include src="/templates/components/buttons/button-warning.xhtml">
<ui:param name="value" value="Changer de type" />
<ui:param name="icon" value="pi pi-user-edit" />

View File

@@ -196,14 +196,9 @@
<!-- Localisation -->
<p:column headerText="Localisation" style="width: 13rem;">
<div class="flex align-items-center text-700 text-sm">
<div class="flex align-items-center text-500 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}
<span></span>
</div>
</p:column>
@@ -239,10 +234,9 @@
<p:commandButton icon="pi pi-pencil"
styleClass="ui-button-rounded ui-button-text ui-button-warning ui-button-sm"
title="Modifier"
actionListener="#{organisationsBean.preparerModification(org)}"
update=":formModifier"
oncomplete="PF('dlgModifier').show();">
<f:setPropertyActionListener target="#{organisationsBean.organisationSelectionnee}" value="#{org}" />
</p:commandButton>
oncomplete="PF('dlgModifier').show();" />
<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'}"

View File

@@ -113,7 +113,7 @@
<div class="flex gap-2 flex-wrap">
<ui:repeat value="#{userSession.roles}" var="role">
<p:tag value="#{role}"
severity="#{role == 'SUPER_ADMIN' ? 'danger' : (role == 'ADMIN_ENTITE' ? 'warning' : 'info')}" />
severity="#{role == 'SUPER_ADMIN' ? 'danger' : (role == 'ADMIN_ORGANISATION' ? 'warning' : 'info')}" />
</ui:repeat>
</div>
</div>

View File

@@ -9,6 +9,7 @@
<ui:define name="title">Tableaux de Bord - UnionFlow</ui:define>
<ui:define name="content">
<h:form id="formTableauxBord">
<div class="ui-fluid">
<!-- En-tête -->
<div class="card">
@@ -136,6 +137,7 @@
</div>
<p:growl id="growl" life="3000" />
</h:form>
</ui:define>
</ui:composition>

View File

@@ -9,462 +9,351 @@
<ui:define name="title">Dashboard Super-Administrateur - UnionFlow</ui:define>
<ui:define name="content">
<div class="ui-fluid">
<!-- En-tête principal avec disposition Freya stricte -->
<div class="card">
<div class="formgrid grid">
<div class="field col-12 lg:col-8">
<h2 class="text-primary font-bold mb-2">
<i class="pi pi-crown text-yellow-500 mr-2"></i>
Tableau de bord Super-Administrateur
</h2>
<p class="text-600 mt-0">
Vue globale de la plateforme UnionFlow •
<span class="font-semibold">#{superAdminBean.totalEntites} organisations</span>
<span class="font-semibold text-green-600">#{superAdminBean.totalMembres} membres actifs</span>
</p>
<div class="grid">
<!-- En-tête -->
<div class="col-12">
<div class="card">
<div class="flex flex-column lg:flex-row lg:align-items-center lg:justify-content-between">
<div>
<h2 class="text-900 font-medium text-4xl mb-2">
<i class="pi pi-crown text-yellow-500 mr-2"></i>
Tableau de bord Super-Administrateur
</h2>
<p class="text-600 m-0">
#{superAdminBean.nomComplet} •
<span class="font-semibold">#{superAdminBean.totalEntites} organisations</span>
<span class="font-semibold text-green-600">#{superAdminBean.totalMembres} membres actifs</span>
</p>
<p class="text-500 text-sm mt-1 mb-0">Dernière connexion : #{superAdminBean.derniereConnexion}</p>
</div>
<div class="mt-3 lg:mt-0">
<h:form id="formHeaderActions">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Nouvelle organisation" />
<ui:param name="icon" value="pi pi-plus" />
<ui:param name="outcome" value="entiteNouvellePage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="mr-2" />
</ui:include>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Rapport global" />
<ui:param name="icon" value="pi pi-chart-bar" />
<ui:param name="outcome" value="superAdminRapportsPage" />
<ui:param name="outlined" value="true" />
</ui:include>
</h:form>
</div>
</div>
<div class="field col-12 lg:col-4 text-right">
<div class="text-900 font-bold text-lg">#{superAdminBean.nomComplet}</div>
<div class="text-500 text-sm">Dernière connexion: #{superAdminBean.derniereConnexion}</div>
<h:form id="formActionsRapidesHeader" styleClass="mt-2">
<p:commandButton icon="pi pi-plus"
title="Nouvelle organisation"
styleClass="ui-button-success ui-button-sm mr-3"
action="#{superAdminBean.creerEntite}" />
<p:commandButton icon="pi pi-chart-bar"
title="Rapport global"
styleClass="ui-button-info ui-button-outlined ui-button-sm mr-3"
action="#{superAdminBean.genererRapport}" />
<p:commandButton icon="pi pi-cog"
title="Configuration"
styleClass="ui-button-secondary ui-button-outlined ui-button-sm"
action="#{superAdminBean.configurer}" />
</div>
</div>
<!-- KPIs -->
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Membres Actifs" />
<ui:param name="value" value="#{superAdminBean.totalMembres}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
<ui:param name="growthValue" value="#{superAdminBean.croissanceMembres}" />
<ui:param name="growthLabel" value="ce mois" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageMembres}" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Organisations" />
<ui:param name="value" value="#{superAdminBean.totalEntites}" />
<ui:param name="icon" value="pi-sitemap" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="growthValue" value="#{superAdminBean.nouvellesEntites}" />
<ui:param name="growthLabel" value="nouvelles" />
<ui:param name="growthType" value="number" />
<ui:param name="noDataLabel" value="Aucune nouvelle entité ce mois" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageOrganisations}" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Revenus (FCFA)" />
<ui:param name="value" value="#{superAdminBean.revenusGlobaux}" />
<ui:param name="icon" value="pi-dollar" />
<ui:param name="iconColor" value="purple-600" />
<ui:param name="growthValue" value="#{superAdminBean.croissanceRevenus}" />
<ui:param name="growthLabel" value="vs mois dernier" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageRevenus}" />
</ui:include>
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Activité du Jour" />
<ui:param name="value" value="#{superAdminBean.activiteJournaliere}" />
<ui:param name="icon" value="pi-chart-line" />
<ui:param name="iconColor" value="orange-600" />
<ui:param name="statusIcon" value="pi-check-circle" />
<ui:param name="statusLabel" value="En ligne" />
<ui:param name="statusValue" value="#{superAdminBean.utilisateursActifs} actifs" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageActivite}" />
</ui:include>
<!-- Actions rapides -->
<div class="col-12 lg:col-6">
<div class="card">
<h5>
<i class="pi pi-bolt text-orange-500 mr-2"></i>
Actions Rapides
</h5>
<h:form id="formActionsRapides">
<div class="grid">
<div class="col-12 md:col-6">
<ui:include src="/templates/components/buttons/button-success.xhtml">
<ui:param name="value" value="Nouvelle Entité" />
<ui:param name="icon" value="pi pi-plus" />
<ui:param name="outcome" value="entiteNouvellePage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
</div>
<div class="col-12 md:col-6">
<ui:include src="/templates/components/buttons/button-info.xhtml">
<ui:param name="value" value="Gestion Entités" />
<ui:param name="icon" value="pi pi-building" />
<ui:param name="outcome" value="entiteGestionPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
</div>
<div class="col-12 md:col-6">
<ui:include src="/templates/components/buttons/button-warning.xhtml">
<ui:param name="value" value="Rapport Global" />
<ui:param name="icon" value="pi pi-chart-bar" />
<ui:param name="outcome" value="superAdminRapportsPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
</div>
<div class="col-12 md:col-6">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Configuration" />
<ui:param name="icon" value="pi pi-cog" />
<ui:param name="outcome" value="superAdminConfigurationPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
</div>
<div class="col-12 md:col-6">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Audit Système" />
<ui:param name="icon" value="pi pi-shield" />
<ui:param name="outcome" value="/pages/admin/audit/journal" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
</div>
<div class="col-12 md:col-6">
<ui:include src="/templates/components/buttons/button-primary.xhtml">
<ui:param name="value" value="Backup" />
<ui:param name="icon" value="pi pi-save" />
<ui:param name="outcome" value="/pages/secure/admin/sauvegarde" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="w-full mb-2" />
</ui:include>
</div>
</div>
</h:form>
</div>
</div>
<!-- Alertes Système -->
<div class="col-12 lg:col-6">
<div class="card">
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">
<i class="pi pi-exclamation-triangle text-orange-500 mr-2"></i>
Alertes Système
</h5>
<p:tag value="#{superAdminBean.alertesCount} alertes" severity="warning" styleClass="text-xs" />
</div>
<ui:repeat value="#{superAdminBean.alertesRecentes}" var="alerte" varStatus="status">
<div class="flex align-items-center p-3 mb-2 border-round border-left-3 border-orange-400"
style="background: var(--surface-50);">
<i class="pi #{alerte.icone} text-orange-500 text-xl mr-3"></i>
<div class="flex-1">
<div class="font-medium text-900">#{alerte.titre}</div>
<small class="text-500">#{alerte.entite} • #{alerte.date}</small>
</div>
<h:form id="formAlerte#{status.index}">
<p:commandButton icon="pi pi-eye" title="Voir détails"
actionListener="#{superAdminBean.voirAlerte(alerte)}"
styleClass="ui-button-rounded ui-button-info ui-button-text"
process="@this" update="@none" />
</h:form>
</div>
</ui:repeat>
<div class="text-center p-3" rendered="#{empty superAdminBean.alertesRecentes}">
<i class="pi pi-check-circle text-green-500 text-2xl mb-2"></i>
<div class="text-600 text-sm">Aucune alerte active</div>
</div>
<div class="text-center mt-2" rendered="#{superAdminBean.alertesCount gt 0}">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Voir toutes les alertes" />
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="outcome" value="superAdminAlertesPage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="ui-button-sm" />
</ui:include>
</div>
</div>
</div>
<!-- Top 5 Entités -->
<div class="col-12 lg:col-6">
<div class="card">
<h5>
<i class="pi pi-trophy text-yellow-500 mr-2"></i>
Top 5 Entités
</h5>
<ui:repeat value="#{superAdminBean.topEntites}" var="entite" varStatus="status">
<div class="flex align-items-center p-3 mb-2 border-round"
style="background: var(--surface-50);">
<div class="flex align-items-center justify-content-center surface-200 border-circle mr-3"
style="width: 2rem; height: 2rem;">
<span class="text-primary font-bold">#{status.index + 1}</span>
</div>
<div class="flex-1">
<div class="text-900 font-medium">#{entite.nom}</div>
<small class="text-500">#{entite.typeEntite}</small>
</div>
<div class="text-right">
<div class="text-900 font-bold">#{entite.nombreMembres}</div>
<small class="text-500">membres</small>
</div>
</div>
</ui:repeat>
<div class="text-center p-3" rendered="#{empty superAdminBean.topEntites}">
<i class="pi pi-info-circle text-300 text-2xl mb-2"></i>
<div class="text-600 text-sm">Aucune entité enregistrée</div>
</div>
</div>
</div>
<!-- Répartition par Type -->
<div class="col-12 lg:col-6">
<div class="card">
<h5>
<i class="pi pi-chart-pie text-purple-500 mr-2"></i>
Répartition par Type
</h5>
<ui:repeat value="#{superAdminBean.repartitionTypes}" var="type">
<div class="flex align-items-center justify-content-between p-3 mb-2 border-round"
style="background: var(--surface-50);">
<div class="flex align-items-center">
<i class="pi pi-building text-primary mr-3"></i>
<div>
<div class="text-900 font-medium">#{type.nom}</div>
</div>
</div>
<div class="text-right">
<div class="text-900 font-bold">#{type.nombre}</div>
<p:tag value="#{type.pourcentage}%" severity="info" styleClass="text-xs" />
</div>
</div>
</ui:repeat>
<div class="text-center p-3" rendered="#{empty superAdminBean.repartitionTypes}">
<i class="pi pi-info-circle text-300 text-2xl mb-2"></i>
<div class="text-600 text-sm">Aucune donnée disponible</div>
</div>
</div>
</div>
<!-- Activité Récente -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">
<i class="pi pi-history text-teal-500 mr-2"></i>
Activité Récente
</h5>
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Voir toute l'activité" />
<ui:param name="icon" value="pi pi-arrow-right" />
<ui:param name="outcome" value="superAdminActivitePage" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="ui-button-sm" />
</ui:include>
</div>
<ui:repeat value="#{superAdminBean.activitesRecentes}" var="activite" varStatus="status">
<div class="flex align-items-start p-3 mb-2 border-round border-left-3 border-blue-300"
style="background: var(--surface-50);">
<i class="pi pi-history text-blue-500 text-xl mr-3 mt-1"></i>
<div class="flex-1">
<div class="font-medium text-900 mb-1">#{activite.description}</div>
<div class="text-600 text-sm">#{activite.entite}</div>
<div class="flex align-items-center mt-1 gap-3">
<small class="text-500">#{activite.date}</small>
<small class="text-500" rendered="#{not empty activite.utilisateur}">
Par #{activite.utilisateur}
</small>
</div>
</div>
</div>
</ui:repeat>
<div class="text-center p-4" rendered="#{empty superAdminBean.activitesRecentes}">
<i class="pi pi-info-circle text-3xl text-300 mb-2"></i>
<div class="text-600">Aucune activité récente</div>
</div>
</div>
</div>
<!-- Performance Financière -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between mb-3">
<h5 class="m-0">
<i class="pi pi-dollar text-green-500 mr-2"></i>
Performance Financière Globale
</h5>
<h:form id="formExportFinancier">
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Exporter" />
<ui:param name="icon" value="pi pi-download" />
<ui:param name="action" value="#{superAdminBean.exporterRapportFinancier}" />
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="ui-button-sm" />
</ui:include>
</h:form>
</div>
</div>
</div>
<!-- KPIs Principaux avec grille Freya stricte et alignement parfait -->
<div class="formgrid grid">
<!-- KPI 1: Membres Actifs -->
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Membres Actifs" />
<ui:param name="value" value="#{superAdminBean.totalMembres}" />
<ui:param name="icon" value="pi-users" />
<ui:param name="iconColor" value="blue-600" />
<ui:param name="growthValue" value="#{superAdminBean.croissanceMembres}" />
<ui:param name="growthLabel" value="ce mois" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageMembres}" />
</ui:include>
<!-- KPI 2: Organisations -->
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Organisations" />
<ui:param name="value" value="#{superAdminBean.totalEntites}" />
<ui:param name="icon" value="pi-sitemap" />
<ui:param name="iconColor" value="green-600" />
<ui:param name="growthValue" value="#{superAdminBean.nouvellesEntites}" />
<ui:param name="growthLabel" value="nouvelles" />
<ui:param name="growthType" value="number" />
<ui:param name="noDataLabel" value="Aucune nouvelle entité ce mois" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageOrganisations}" />
</ui:include>
<!-- KPI 3: Revenus Globaux -->
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Revenus (FCFA)" />
<ui:param name="value" value="#{superAdminBean.revenusGlobaux}" />
<ui:param name="icon" value="pi-dollar" />
<ui:param name="iconColor" value="purple-600" />
<ui:param name="growthValue" value="#{superAdminBean.croissanceRevenus}" />
<ui:param name="growthLabel" value="vs mois dernier" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageRevenus}" />
</ui:include>
<!-- KPI 4: Activité du Jour -->
<ui:include src="/templates/components/cards/kpi-card.xhtml">
<ui:param name="title" value="Activité du Jour" />
<ui:param name="value" value="#{superAdminBean.activiteJournaliere}" />
<ui:param name="icon" value="pi-chart-line" />
<ui:param name="iconColor" value="orange-600" />
<ui:param name="statusIcon" value="pi-check-circle" />
<ui:param name="statusLabel" value="En ligne" />
<ui:param name="statusValue" value="#{superAdminBean.utilisateursActifs} actifs" />
<ui:param name="progressValue" value="#{superAdminBean.pourcentageActivite}" />
</ui:include>
</div>
<!-- Section Analytics et Actions avec disposition Freya stricte -->
<div class="formgrid grid">
<!-- Actions Rapides -->
<div class="field col-12 lg:col-6">
<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-bolt text-orange-500 mr-2"></i>
Actions Rapides
</h5>
<h:form id="formActionsRapides">
<div class="formgrid grid">
<div class="field col-12 md:col-6">
<p:commandButton value="Nouvelle Entité"
icon="pi pi-plus"
styleClass="ui-button-success ui-button-outlined ui-button-sm w-full"
action="#{superAdminBean.creerEntite}" />
</div>
<div class="field col-12 md:col-6">
<p:commandButton value="Gestion Entités"
icon="pi pi-building"
styleClass="ui-button-info ui-button-outlined ui-button-sm w-full"
action="#{superAdminBean.gererEntites}" />
</div>
<div class="field col-12 md:col-6">
<p:commandButton value="Rapport Global"
icon="pi pi-chart-bar"
styleClass="ui-button-warning ui-button-outlined ui-button-sm w-full"
action="#{superAdminBean.genererRapport}" />
</div>
<div class="field col-12 md:col-6">
<p:commandButton value="Configuration"
icon="pi pi-cog"
styleClass="ui-button-secondary ui-button-outlined ui-button-sm w-full"
action="#{superAdminBean.configurer}" />
</div>
<div class="field col-12 md:col-6">
<p:commandButton value="Audit Système"
icon="pi pi-shield"
styleClass="ui-button-danger ui-button-outlined ui-button-sm w-full"
action="#{superAdminBean.auditSysteme}" />
</div>
<div class="field col-12 md:col-6">
<p:commandButton value="Backup"
icon="pi pi-save"
styleClass="ui-button-primary ui-button-outlined ui-button-sm w-full"
action="#{superAdminBean.backup}" />
</div>
</div>
</h:form>
<div class="grid">
<div class="col-12 md:col-3">
<div class="text-center p-3 border-round bg-green-50">
<div class="text-green-600 font-medium text-xl">#{superAdminBean.revenus.mensuel}</div>
<div class="text-500">Revenus ce mois</div>
</div>
</div>
</div>
</div>
<!-- Alertes Système -->
<div class="field col-12 lg:col-6">
<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-exclamation-triangle text-orange-500 mr-2"></i>
Alertes Système
</h5>
<p:tag value="#{superAdminBean.alertesCount} alertes"
severity="warning" styleClass="text-xs" />
<div class="col-12 md:col-3">
<div class="text-center p-3 border-round bg-blue-50">
<div class="text-blue-600 font-medium text-xl">#{superAdminBean.revenus.annuel}</div>
<div class="text-500">Revenus annuels</div>
</div>
<div class="surface-100 border-round-lg p-3 mb-3">
<div class="flex align-items-center">
<i class="pi pi-info-circle text-blue-500 mr-2"></i>
<span class="text-600 text-sm">#{superAdminBean.alertesCount} alertes nécessitent votre attention</span>
</div>
</div>
<div class="col-12 md:col-3">
<div class="text-center p-3 border-round bg-purple-50">
<div class="text-purple-600 font-medium text-xl">#{superAdminBean.revenus.croissance}%</div>
<div class="text-500">Croissance annuelle</div>
</div>
<ui:repeat value="#{superAdminBean.alertesRecentes}" var="alerte">
<div class="surface-100 border-round-lg p-3 mb-3 hover:surface-200 transition-colors transition-duration-150">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center">
<i class="pi #{alerte.icone} #{alerte.couleur} mr-3 text-lg"></i>
<div>
<div class="text-900 font-medium">#{alerte.titre}</div>
<div class="text-500 text-xs">#{alerte.entite} • #{alerte.date}</div>
</div>
</div>
<h:form id="formAlerte#{alerte.id}">
<p:commandButton icon="pi pi-eye"
styleClass="ui-button-rounded ui-button-text ui-button-info ui-button-sm"
action="#{superAdminBean.voirAlerte(alerte)}"
title="Voir détails" />
</h:form>
</div>
</div>
</ui:repeat>
<div class="text-center mt-3">
<h:form id="formVoirToutesAlertes">
<p:commandButton value="Voir toutes les alertes"
icon="pi pi-arrow-right"
styleClass="ui-button-link ui-button-sm"
action="#{superAdminBean.voirToutesAlertes}" />
</h:form>
</div>
<div class="col-12 md:col-3">
<div class="text-center p-3 border-round bg-orange-50">
<div class="text-orange-600 font-medium text-xl">#{superAdminBean.revenus.moyenne}</div>
<div class="text-500">Revenu moyen / entité</div>
</div>
</div>
</div>
</div>
</div>
<!-- Section Évolution et Top Entités avec Freya stricte -->
<div class="formgrid grid">
<!-- Évolution des Entités -->
<div class="field col-12 lg:col-8">
<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-chart-bar text-blue-500 mr-2"></i>
Évolution des Entités
</h5>
<p:selectOneMenu value="#{superAdminBean.periodeEvolution}" styleClass="w-8rem">
<f:selectItem itemLabel="6 mois" itemValue="6M" />
<f:selectItem itemLabel="12 mois" itemValue="12M" />
<f:selectItem itemLabel="24 mois" itemValue="24M" />
</p:selectOneMenu>
</div>
<div class="surface-100 border-round-lg p-4">
<div class="flex align-items-end justify-content-around" style="height: 150px;">
<ui:repeat value="#{superAdminBean.evolutionEntites}" var="mois">
<div class="flex flex-column align-items-center" style="width: 100%;">
<div class="text-500 text-xs mb-1">#{mois.valeur}</div>
<div class="bg-primary border-round"
style="width: 40px; height: #{mois.hauteur}px; min-height: 20px;"></div>
<div class="text-500 text-xs mt-2">#{mois.periode}</div>
</div>
</ui:repeat>
</div>
<div class="flex align-items-center justify-content-between mt-3 pt-3 border-top-1 surface-border">
<div class="flex align-items-center">
<i class="pi pi-arrow-up text-green-500 mr-2"></i>
<span class="text-green-600 font-semibold">+#{superAdminBean.croissanceEntites}%</span>
<span class="text-500 text-sm ml-2">ce mois</span>
</div>
<div class="text-600 text-sm">
Total: <span class="font-semibold">#{superAdminBean.totalEntites}</span> entités
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Top Entités -->
<div class="field col-12 lg:col-4">
<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-trophy text-yellow-500 mr-2"></i>
Top 5 Entités
</h5>
<ui:repeat value="#{superAdminBean.topEntites}" var="entite" varStatus="status">
<div class="surface-100 border-round-lg p-3 mb-3 hover:surface-200 transition-colors transition-duration-150">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center">
<div class="flex align-items-center justify-content-center surface-200 border-circle mr-3"
style="width: 2rem; height: 2rem;">
<span class="text-primary font-bold">#{status.index + 1}</span>
</div>
<div>
<div class="text-900 font-medium">#{entite.nom}</div>
<div class="text-500 text-xs">#{entite.typeEntite}</div>
</div>
</div>
<div class="text-right">
<div class="text-900 font-bold">#{entite.nombreMembres}</div>
<div class="text-500 text-xs">membres</div>
</div>
</div>
</div>
</ui:repeat>
</div>
</div>
</div>
</div>
<!-- Section Répartition et Activité avec Freya stricte -->
<div class="formgrid grid">
<!-- Répartition par Type -->
<div class="field col-12 lg:col-6">
<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-chart-pie text-purple-500 mr-2"></i>
Répartition par Type
</h5>
<ui:repeat value="#{superAdminBean.repartitionTypes}" var="type">
<div class="surface-100 border-round-lg p-3 mb-3 hover:surface-200 transition-colors transition-duration-150">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center">
<div class="flex align-items-center justify-content-center #{type.couleurBg} border-round-lg mr-3"
style="width: 2.5rem; height: 2.5rem;">
<i class="pi #{type.icone} #{type.couleurTexte}"></i>
</div>
<div>
<div class="text-900 font-medium">#{type.nom}</div>
<div class="text-500 text-xs">#{type.description}</div>
</div>
</div>
<div class="text-right">
<div class="text-900 font-bold text-lg">#{type.nombre}</div>
<p:tag value="#{type.pourcentage}%" severity="info" styleClass="text-xs" />
</div>
</div>
</div>
</ui:repeat>
<!-- Barre de progression globale -->
<div class="surface-100 border-round-lg p-3 mt-3">
<div class="flex align-items-center justify-content-between mb-2">
<span class="text-600 font-medium text-sm">Répartition globale</span>
<span class="text-900 font-semibold">#{superAdminBean.totalEntites} entités</span>
</div>
<div class="flex border-round overflow-hidden" style="height: 8px;">
<ui:repeat value="#{superAdminBean.repartitionTypes}" var="type">
<div class="#{type.couleurBg}"
style="width: #{type.pourcentage}%; height: 100%;"></div>
</ui:repeat>
</div>
</div>
</div>
</div>
</div>
<!-- Activité Récente (Timeline) -->
<div class="field col-12 lg:col-6">
<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-history text-teal-500 mr-2"></i>
Activité Récente
</h5>
<div style="max-height: 400px; overflow-y: auto;">
<ui:repeat value="#{superAdminBean.activitesRecentes}" var="activite" varStatus="status">
<div class="flex mb-3">
<!-- Timeline -->
<div class="flex flex-column align-items-center mr-3" style="min-width: 40px;">
<div class="flex align-items-center justify-content-center surface-200 border-circle"
style="width: 2rem; height: 2rem;">
<i class="pi #{activite.icone} text-primary text-sm"></i>
</div>
<div class="surface-300" style="width: 2px; flex: 1; margin-top: 0.5rem;"
rendered="#{!status.last}"></div>
</div>
<!-- Contenu -->
<div class="flex-1">
<div class="surface-100 border-round-lg p-3">
<div class="flex align-items-center justify-content-between mb-2">
<span class="text-900 font-medium text-sm">#{activite.description}</span>
<span class="text-500 text-xs">#{activite.date}</span>
</div>
<div class="text-600 text-xs mb-1">#{activite.entite}</div>
<div class="text-700 text-xs" rendered="#{activite.details != null}">
#{activite.details}
</div>
<div class="flex align-items-center mt-2" rendered="#{activite.utilisateur != null}">
<div class="surface-300 border-circle mr-2" style="width: 1.5rem; height: 1.5rem;"></div>
<span class="text-500 text-xs">Par #{activite.utilisateur}</span>
</div>
</div>
</div>
</div>
</ui:repeat>
</div>
<div class="text-center mt-3 pt-3 border-top-1 surface-border">
<h:form id="formVoirTouteActivite">
<p:commandButton value="Voir toute l'activité"
icon="pi pi-arrow-right"
styleClass="ui-button-link ui-button-sm"
action="#{superAdminBean.voirTouteActivite}" />
</h:form>
</div>
</div>
</div>
</div>
</div>
<!-- Performance Financière Globale 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-dollar text-green-500 mr-2"></i>
Performance Financière Globale
</h5>
<div class="formgrid grid">
<div class="field col-12 md:col-6 lg:col-3">
<div class="surface-100 border-round-lg p-4 text-center hover:surface-200 transition-colors transition-duration-150">
<div class="text-500 text-sm mb-2">Revenus Ce Mois</div>
<div class="text-900 font-bold text-2xl mb-2">#{superAdminBean.revenus.mensuel}</div>
<div class="flex align-items-center justify-content-center">
<i class="pi pi-arrow-up text-green-500 text-xs mr-2"></i>
<span class="text-green-600 text-sm">+#{superAdminBean.revenus.croissanceMensuelle}%</span>
</div>
</div>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<div class="surface-100 border-round-lg p-4 text-center hover:surface-200 transition-colors transition-duration-150">
<div class="text-500 text-sm mb-2">Revenus Annuels</div>
<div class="text-900 font-bold text-2xl mb-2">#{superAdminBean.revenus.annuel}</div>
<div class="text-600 text-xs">
Objectif: #{superAdminBean.revenus.objectifAnnuel}
</div>
</div>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<div class="surface-100 border-round-lg p-4 text-center hover:surface-200 transition-colors transition-duration-150">
<div class="text-500 text-sm mb-2">Croissance Annuelle</div>
<div class="text-900 font-bold text-2xl mb-2">#{superAdminBean.revenus.croissance}%</div>
<div class="flex align-items-center justify-content-center">
<i class="pi pi-trending-up text-orange-500 text-xs mr-2"></i>
<span class="text-orange-600 text-sm">Tendance positive</span>
</div>
</div>
</div>
<div class="field col-12 md:col-6 lg:col-3">
<div class="surface-100 border-round-lg p-4 text-center hover:surface-200 transition-colors transition-duration-150">
<div class="text-500 text-sm mb-2">Revenu Moyen/Entité</div>
<div class="text-900 font-bold text-2xl mb-2">#{superAdminBean.revenus.moyenne}</div>
<div class="text-600 text-xs">
Sur #{superAdminBean.totalEntites} entités
</div>
</div>
</div>
</div>
<!-- Graphique de revenus -->
<div class="surface-100 border-round-lg p-4 mt-3">
<div class="flex align-items-center justify-content-between mb-3">
<span class="text-600 font-medium">Évolution des revenus (6 derniers mois)</span>
<h:form id="formExportFinancier">
<p:commandButton icon="pi pi-download"
title="Exporter"
styleClass="ui-button-secondary ui-button-outlined ui-button-sm"
action="#{superAdminBean.exporterRapportFinancier}" />
</h:form>
</div>
<div class="flex align-items-end justify-content-around" style="height: 120px;">
<ui:repeat value="#{superAdminBean.revenus.evolution}" var="mois">
<div class="flex flex-column align-items-center" style="width: 100%;">
<div class="text-500 text-xs mb-1">#{mois.valeur}</div>
<div class="bg-green-500 border-round"
style="width: 30px; height: #{mois.hauteur}px; min-height: 20px;"></div>
<div class="text-500 text-xs mt-2">#{mois.nom}</div>
</div>
</ui:repeat>
</div>
<div class="text-500 text-xs text-right mt-2">
<i class="pi pi-calendar mr-1"></i>
Dernière mise à jour: #{superAdminBean.revenus.derniereMAJ}
</div>
</div>
</div>
</div>
</div>
</ui:define>
</ui:composition>
</ui:composition>

View File

@@ -791,4 +791,125 @@
.user-info { display: none; }
.elite-dropdown { min-width: 300px; }
.search-dropdown { min-width: 280px; }
.org-switcher-label { display: none; }
}
/* ═══════════════════════════════════════════════════════════ */
/* ORG SWITCHER */
/* ═══════════════════════════════════════════════════════════ */
.org-switcher-item {
position: relative;
}
.org-switcher-trigger {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.4rem 0.75rem;
border-radius: 8px;
background: rgba(var(--primary-color-rgb, 99,102,241), 0.08);
border: 1px solid rgba(var(--primary-color-rgb, 99,102,241), 0.2);
color: var(--text-color, #374151);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: background 0.2s, border-color 0.2s;
max-width: 220px;
}
.org-switcher-trigger:hover {
background: rgba(var(--primary-color-rgb, 99,102,241), 0.15);
border-color: var(--primary-color, #6366f1);
text-decoration: none;
}
.org-switcher-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 160px;
}
.org-switcher-dropdown {
display: none;
position: absolute;
top: calc(100% + 8px);
left: 0;
min-width: 280px;
background: var(--surface-card, #fff);
border: 1px solid var(--surface-border, #e5e7eb);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.12);
z-index: 1000;
padding: 0.5rem 0;
list-style: none;
margin: 0;
}
.org-switcher-item:hover .org-switcher-dropdown,
.org-switcher-item:focus-within .org-switcher-dropdown {
display: block;
}
.org-item {
padding: 0;
}
.org-item-link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.6rem 1rem;
cursor: pointer;
text-decoration: none;
color: var(--text-color, #374151);
transition: background 0.15s;
}
.org-item-link:hover {
background: var(--surface-hover, #f9fafb);
}
.org-item-active .org-item-link {
background: rgba(var(--primary-color-rgb, 99,102,241), 0.06);
}
.org-item-content {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.org-item-name {
font-weight: 600;
font-size: 0.875rem;
color: var(--text-color, #111827);
}
.org-item-meta {
display: flex;
gap: 0.4rem;
}
.org-type-badge {
font-size: 0.7rem;
background: var(--surface-ground, #f3f4f6);
color: var(--text-color-secondary, #6b7280);
padding: 0.1rem 0.4rem;
border-radius: 4px;
}
.org-role-badge {
font-size: 0.7rem;
background: #ecfdf5;
color: #065f46;
padding: 0.1rem 0.4rem;
border-radius: 4px;
}
.org-check-icon {
color: var(--primary-color, #6366f1);
font-size: 0.875rem;
}

View File

@@ -3,30 +3,49 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:o="http://omnifaces.org/ui">
<!--
Composant bouton icône seule réutilisable (WOU/DRY)
Usage: <ui:include src="/templates/components/buttons/button-icon.xhtml">
<ui:param name="icon" value="pi pi-icon-name" />
<ui:param name="action" value="#{bean.method}" />
<ui:param name="outcome" value="/page" />
<ui:param name="update" value="componentId" />
<ui:param name="onclick" value="javascript" />
<ui:param name="severity" value="info|success|warning|danger" />
<ui:param name="rounded" value="true" />
<ui:param name="text" value="true" />
</ui:include>
action="#{bean.method}" supporté via o:methodParam (void ou retour String)
-->
<o:methodParam name="actionMethod" value="#{action}" />
<ui:fragment rendered="#{empty rendered or rendered}">
<p:commandButton
<!-- Navigation GET (p:button) -->
<p:button rendered="#{not empty outcome}"
icon="#{icon}"
action="#{action}"
outcome="#{outcome}"
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}" />
<!-- Avec action AJAX (commandButton) -->
<p:commandButton rendered="#{empty outcome and not empty action}"
icon="#{icon}"
action="#{actionMethod}"
update="#{update}"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
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}" />
<!-- Sans action ni outcome (bouton non-submit) -->
<p:commandButton rendered="#{empty outcome and empty action}" type="button"
icon="#{icon}"
onclick="#{onclick}"
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}" />
</ui:fragment>
</ui:composition>

View File

@@ -2,37 +2,39 @@
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:o="http://omnifaces.org/ui">
<!--
Composant bouton info réutilisable (WOU/DRY)
action="#{bean.method}" supporté via o:methodParam (void ou retour String)
-->
<o:methodParam name="actionMethod" value="#{action}" />
<ui:fragment rendered="#{empty rendered or rendered}">
<ui:fragment rendered="#{empty outcome}">
<ui:fragment rendered="#{not empty update and update != 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
update="#{update}"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
type="button"
disabled="#{not empty disabled and disabled}"
rendered="#{empty rendered or rendered}"
styleClass="ui-button-info #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
title="#{title}" />
</ui:fragment>
<ui:fragment rendered="#{empty update or update == 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
ajax="false"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
type="button"
disabled="#{not empty disabled and disabled}"
rendered="#{empty rendered or rendered}"
styleClass="ui-button-info #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
@@ -40,7 +42,7 @@
</ui:fragment>
</ui:fragment>
<ui:fragment rendered="#{not empty outcome}">
<p:button
<p:button
value="#{value}"
icon="#{icon}"
outcome="#{outcome}"
@@ -49,4 +51,3 @@
</ui:fragment>
</ui:fragment>
</ui:composition>

View File

@@ -2,9 +2,10 @@
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:o="http://omnifaces.org/ui">
<!--
Composant bouton primaire réutilisable (WOU/DRY)
Usage: <ui:include src="/templates/components/buttons/button-primary.xhtml">
<ui:param name="value" value="Texte du bouton" />
@@ -16,15 +17,18 @@
<ui:param name="disabled" value="false" />
<ui:param name="styleClass" value="" />
</ui:include>
action="#{bean.method}" supporté via o:methodParam (void ou retour String)
-->
<o:methodParam name="actionMethod" value="#{action}" />
<ui:fragment rendered="#{empty rendered or rendered}">
<ui:fragment rendered="#{empty outcome}">
<ui:fragment rendered="#{not empty update and update != 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
update="#{update}"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
@@ -33,10 +37,10 @@
title="#{title}" />
</ui:fragment>
<ui:fragment rendered="#{empty update or update == 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
ajax="false"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
@@ -46,7 +50,7 @@
</ui:fragment>
</ui:fragment>
<ui:fragment rendered="#{not empty outcome}">
<p:button
<p:button
value="#{value}"
icon="#{icon}"
outcome="#{outcome}"
@@ -55,4 +59,3 @@
</ui:fragment>
</ui:fragment>
</ui:composition>

View File

@@ -2,9 +2,10 @@
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:o="http://omnifaces.org/ui">
<!--
Composant bouton secondaire réutilisable (WOU/DRY)
Usage: <ui:include src="/templates/components/buttons/button-secondary.xhtml">
<ui:param name="value" value="Texte du bouton" />
@@ -17,15 +18,18 @@
<ui:param name="outlined" value="true" />
<ui:param name="styleClass" value="" />
</ui:include>
action="#{bean.method}" supporté via o:methodParam (void ou retour String)
-->
<o:methodParam name="actionMethod" value="#{action}" />
<ui:fragment rendered="#{empty rendered or rendered}">
<ui:fragment rendered="#{empty outcome}">
<ui:fragment rendered="#{not empty update and update != 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
update="#{update}"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
@@ -35,10 +39,10 @@
title="#{title}" />
</ui:fragment>
<ui:fragment rendered="#{empty update or update == 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
ajax="false"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
@@ -49,7 +53,7 @@
</ui:fragment>
</ui:fragment>
<ui:fragment rendered="#{not empty outcome}">
<p:button
<p:button
value="#{value}"
icon="#{icon}"
outcome="#{outcome}"

View File

@@ -2,19 +2,23 @@
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:o="http://omnifaces.org/ui">
<!--
Composant bouton succès réutilisable (WOU/DRY)
action="#{bean.method}" supporté via o:methodParam (void ou retour String)
-->
<o:methodParam name="actionMethod" value="#{action}" />
<ui:fragment rendered="#{empty rendered or rendered}">
<ui:fragment rendered="#{empty outcome}">
<ui:fragment rendered="#{not empty update and update != 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
update="#{update}"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
@@ -24,10 +28,10 @@
title="#{title}" />
</ui:fragment>
<ui:fragment rendered="#{empty update or update == 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
ajax="false"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
@@ -38,7 +42,7 @@
</ui:fragment>
</ui:fragment>
<ui:fragment rendered="#{not empty outcome}">
<p:button
<p:button
value="#{value}"
icon="#{icon}"
outcome="#{outcome}"

View File

@@ -2,37 +2,39 @@
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:o="http://omnifaces.org/ui">
<!--
Composant bouton warning réutilisable (WOU/DRY)
action="#{bean.method}" supporté via o:methodParam (void ou retour String)
-->
<o:methodParam name="actionMethod" value="#{action}" />
<ui:fragment rendered="#{empty rendered or rendered}">
<ui:fragment rendered="#{empty outcome}">
<ui:fragment rendered="#{not empty update and update != 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
update="#{update}"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
type="button"
disabled="#{not empty disabled and disabled}"
rendered="#{empty rendered or rendered}"
styleClass="ui-button-warning #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
title="#{title}" />
</ui:fragment>
<ui:fragment rendered="#{empty update or update == 'none'}">
<p:commandButton
<p:commandButton
value="#{value}"
icon="#{icon}"
action="#{action}"
action="#{actionMethod}"
ajax="false"
onclick="#{onclick}"
oncomplete="#{oncomplete}"
type="button"
disabled="#{not empty disabled and disabled}"
rendered="#{empty rendered or rendered}"
styleClass="ui-button-warning #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
@@ -40,7 +42,7 @@
</ui:fragment>
</ui:fragment>
<ui:fragment rendered="#{not empty outcome}">
<p:button
<p:button
value="#{value}"
icon="#{icon}"
outcome="#{outcome}"
@@ -49,4 +51,3 @@
</ui:fragment>
</ui:fragment>
</ui:composition>

View File

@@ -112,23 +112,23 @@
<c:choose>
<!-- Croissance en nombre -->
<c:when test="#{growthType == 'number'}">
<div class="flex align-items-center mb-2" rendered="#{showGrowth and not empty growthValue and growthValue != 0}">
<div class="flex align-items-center mb-2" rendered="#{showGrowth and not empty growthValue and growthValue != '0'}">
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}</span>
<span class="text-500 text-xs">#{growthLabel}</span>
</div>
<div class="text-500 text-xs" rendered="#{not showGrowth or empty growthValue or growthValue == 0}">
<div class="text-500 text-xs" rendered="#{not showGrowth or empty growthValue or growthValue == '0'}">
#{noDataLabel}
</div>
</c:when>
<!-- Croissance en pourcentage (défaut) -->
<c:otherwise>
<div class="flex align-items-center mb-2" rendered="#{showGrowth and not empty growthValue and growthValue != 0}">
<div class="flex align-items-center mb-2" rendered="#{showGrowth and not empty growthValue and growthValue != '0'}">
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}%</span>
<span class="text-500 text-xs">#{growthLabel}</span>
</div>
<div class="text-500 text-xs" rendered="#{not showGrowth or empty growthValue or growthValue == 0}">
<div class="text-500 text-xs" rendered="#{not showGrowth or empty growthValue or growthValue == '0'}">
#{noDataLabel}
</div>
</c:otherwise>

View File

@@ -57,7 +57,7 @@
fitViewport="#{empty fitViewport ? true : fitViewport}"
showEffect="#{empty showEffect ? 'fade' : showEffect}"
hideEffect="#{empty hideEffect ? 'fade' : hideEffect}"
dynamic="true"
dynamic="#{empty dynamic ? true : dynamic}"
styleClass="#{styleClass}">
<div class="ui-fluid">

View File

@@ -19,15 +19,15 @@
<div class="field">
<p:outputLabel for="#{id}" value="#{label}" />
<p:inputNumber id="#{id}"
value="#{value}"
symbol=""
placeholder="#{placeholder}"
minValue="#{minValue}"
maxValue="#{maxValue}"
<p:inputNumber id="#{id}"
value="#{value}"
symbol="#{not empty symbol ? symbol : ''}"
placeholder="#{not empty placeholder ? placeholder : ''}"
minValue="#{not empty minValue ? minValue : '-999999999999'}"
maxValue="#{not empty maxValue ? maxValue : '999999999999'}"
styleClass="w-full">
<p:ajax event="change"
update="#{not empty update ? update : ':formResultats:dtResultats @(.search-summary)'}" />
<p:ajax event="change"
update="#{not empty update ? update : '@none'}" />
</p:inputNumber>
</div>
</ui:composition>

View File

@@ -1,31 +1,26 @@
<!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">
<!--
Composant section de formulaire réutilisable (WOU/DRY)
Usage: <ui:decorate template="/templates/components/forms/form-section.xhtml">
<ui:param name="title" value="Titre de la section" />
<ui:param name="fluid" value="true" />
<ui:define name="content">
Contenu de la section
</ui:define>
</ui:decorate>
-->
<!--
Composant section de formulaire réutilisable (WOU/DRY)
Usage: <ui:include src="/templates/components/forms/form-section.xhtml">
<ui:param name="title" value="Titre de la section" />
<ui:param name="fluid" value="true" />
<ui:define name="content">
Contenu de la section
</ui:define>
</ui:include>
-->
<ui:composition>
<div class="card #{fluid ? 'ui-fluid' : ''}">
<h:panelGroup rendered="#{not empty title}">
<h5>#{title}</h5>
</h:panelGroup>
<ui:insert name="content">
<!-- Contenu par défaut si aucun content n'est fourni -->
</ui:insert>
<ui:insert name="content" />
</div>
</ui:composition>
</html>

View File

@@ -40,10 +40,10 @@
<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:menuitem id="m_user_manager_list" value="Utilisateurs Keycloak" icon="pi pi-user-edit" url="#" rendered="#{menuBean.keycloakUserManagerVisible}" />
<p:menuitem id="m_user_manager_create" value="Nouvel Utilisateur Keycloak" icon="pi pi-user-plus" url="#" rendered="#{menuBean.keycloakUserManagerVisible}" />
<p:menuitem id="m_user_manager_roles" value="Rôles Keycloak" icon="pi pi-shield" url="#" rendered="#{menuBean.keycloakUserManagerVisible}" />
<p:menuitem id="m_user_manager_audit" value="Journal d'Audit Keycloak" icon="pi pi-history" url="#" rendered="#{menuBean.keycloakUserManagerVisible}" />
</p:submenu>
<!-- Annuaire des Membres (MEMBRE_ACTIF et plus - Consultation) -->
@@ -53,7 +53,7 @@
</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:submenu id="m_gestion_membres" label="Gestion des Membres" icon="pi pi-users" 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 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" />
@@ -89,20 +89,97 @@
<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_approbations_finance" value="Approbations" icon="pi pi-check-square" outcome="/pages/secure/finance/approbations" />
<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_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}">
<!-- Épargne (module EPARGNE — mutuelle, coopérative) -->
<p:submenu id="m_epargne" label="Épargne" icon="pi pi-wallet" rendered="#{menuBean.epargneMenuVisible}">
<p:menuitem id="m_epargne_comptes" value="Comptes Épargne" icon="pi pi-wallet" outcome="/pages/secure/epargne/comptes" />
<p:menuitem id="m_epargne_transactions" value="Transactions" icon="pi pi-arrows-v" url="#" />
<p:menuitem id="m_epargne_stats" value="Statistiques Épargne" icon="pi pi-chart-bar" url="#" />
</p:submenu>
<!-- Mon Épargne (module EPARGNE — membres) -->
<p:submenu id="m_mon_epargne" label="Mon Épargne" icon="pi pi-wallet" rendered="#{menuBean.epargneMemberVisible and not menuBean.epargneMenuVisible}">
<p:menuitem id="m_mon_epargne_compte" value="Mon Compte Épargne" icon="pi pi-wallet" outcome="/pages/secure/epargne/comptes" />
<p:menuitem id="m_mon_epargne_historique" value="Historique" icon="pi pi-history" url="#" />
</p:submenu>
<!-- Crédit (module CREDIT — mutuelle, coopérative) -->
<p:submenu id="m_credit_admin" label="Gestion Crédit" icon="pi pi-credit-card" rendered="#{menuBean.creditMenuVisible}">
<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>
<!-- Mon Crédit (module CREDIT — membres) -->
<p:submenu id="m_mon_credit" label="Mon Crédit" icon="pi pi-credit-card" rendered="#{menuBean.creditMemberVisible and not menuBean.creditMenuVisible}">
<p:menuitem id="m_demande_pret" value="Demander un Prêt" icon="pi pi-plus" outcome="/pages/secure/credit/demandes" />
<p:menuitem id="m_mes_credits" value="Mes Crédits en Cours" icon="pi pi-list" outcome="/pages/secure/credit/suivi" />
</p:submenu>
<!-- Tontine (module TONTINE) -->
<p:submenu id="m_tontine" label="Tontine" icon="pi pi-sync" rendered="#{menuBean.tontineMenuVisible}">
<p:menuitem id="m_tontine_cycles" value="Cycles de Tontine" icon="pi pi-calendar" url="#" />
<p:menuitem id="m_tontine_cotisations" value="Cotisations Tontine" icon="pi pi-dollar" url="#" />
<p:menuitem id="m_tontine_remises" value="Remises" icon="pi pi-gift" url="#" />
<p:menuitem id="m_tontine_stats" value="Statistiques" icon="pi pi-chart-bar" url="#" />
</p:submenu>
<!-- Ma Tontine (module TONTINE — membres) -->
<p:submenu id="m_ma_tontine" label="Ma Tontine" icon="pi pi-sync" rendered="#{menuBean.tontineMemberVisible and not menuBean.tontineMenuVisible}">
<p:menuitem id="m_ma_tontine_cycles" value="Mes Cycles" icon="pi pi-calendar" url="#" />
<p:menuitem id="m_ma_tontine_cotisations" value="Mes Cotisations" icon="pi pi-dollar" url="#" />
</p:submenu>
<!-- Agriculture (module AGRICULTURE — coopératives agricoles) -->
<p:submenu id="m_agricole" label="Agriculture" icon="pi pi-sun" rendered="#{menuBean.agricoleMenuVisible}">
<p:menuitem id="m_campagnes_agricoles" value="Campagnes Agricoles" icon="pi pi-calendar" url="#" />
<p:menuitem id="m_parcelles" value="Gestion des Parcelles" icon="pi pi-map" url="#" />
<p:menuitem id="m_recoltes" value="Récoltes et Stocks" icon="pi pi-database" url="#" />
<p:menuitem id="m_agricole_stats" value="Statistiques" icon="pi pi-chart-bar" url="#" />
</p:submenu>
<!-- Collecte de Fonds (module COLLECTE_FONDS) -->
<p:submenu id="m_collecte" label="Collecte de Fonds" icon="pi pi-heart" rendered="#{menuBean.collecteFondsMenuVisible}">
<p:menuitem id="m_campagnes_collecte" value="Campagnes" icon="pi pi-megaphone" url="#" />
<p:menuitem id="m_dons_recus" value="Dons Reçus" icon="pi pi-inbox" url="#" />
<p:menuitem id="m_collecte_stats" value="Statistiques" icon="pi pi-chart-bar" url="#" />
</p:submenu>
<!-- Projets ONG (module PROJETS_ONG) -->
<p:submenu id="m_projets_ong" label="Projets ONG" icon="pi pi-globe" rendered="#{menuBean.projetOngMenuVisible}">
<p:menuitem id="m_liste_projets" value="Projets en Cours" icon="pi pi-list" url="#" />
<p:menuitem id="m_nouveau_projet" value="Nouveau Projet" icon="pi pi-plus" url="#" />
<p:menuitem id="m_suivi_projets" value="Suivi des Projets" icon="pi pi-chart-line" url="#" />
<p:menuitem id="m_rapports_ong" value="Rapports ONG" icon="pi pi-file" url="#" />
</p:submenu>
<!-- Culte / Dons Religieux (module CULTE_DONS) -->
<p:submenu id="m_culte" label="Culte et Dons" icon="pi pi-star" rendered="#{menuBean.culteMenuVisible}">
<p:menuitem id="m_dons_religieux" value="Dons et Offrandes" icon="pi pi-heart" url="#" />
<p:menuitem id="m_services_culte" value="Services / Cultes" icon="pi pi-calendar" url="#" />
<p:menuitem id="m_dons_stats" value="Statistiques Dons" icon="pi pi-chart-bar" url="#" />
</p:submenu>
<!-- Vote (module VOTES) -->
<p:submenu id="m_votes" label="Votes et Élections" icon="pi pi-check-circle" rendered="#{menuBean.voteMenuVisible}">
<p:menuitem id="m_campagnes_vote" value="Campagnes de Vote" icon="pi pi-megaphone" url="#" />
<p:menuitem id="m_resultats_vote" value="Résultats" icon="pi pi-chart-bar" url="#" />
</p:submenu>
<!-- Registre / Agrément (module REGISTRE_AGREMENT) -->
<p:submenu id="m_registre" label="Registre &amp; Agrément" icon="pi pi-verified" rendered="#{menuBean.registreAgrementMenuVisible}">
<p:menuitem id="m_agrement" value="Dossier d'Agrément" icon="pi pi-file" url="#" />
<p:menuitem id="m_statuts" value="Statuts et Règlements" icon="pi pi-book" url="#" />
<p:menuitem id="m_certificats" value="Certificats" icon="pi pi-id-card" url="#" />
</p:submenu>
<!-- Mes Demandes d'Aide (TOUS - Demandes personnelles) -->
<p:submenu id="m_mes_aides" label="Aide Sociale" icon="pi pi-heart" rendered="#{menuBean.mesAidesSocialesMenuVisible}">
@@ -126,7 +203,7 @@
</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:submenu id="m_gestion_evenements" label="Gestion Événements" icon="pi pi-calendar-plus" 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_logistique" value="Logistique" icon="pi pi-truck" outcome="/pages/secure/evenement/logistique" />
@@ -137,6 +214,7 @@
<!-- 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_messagerie" value="Messagerie" icon="pi pi-comments" outcome="/pages/secure/communication/conversations" />
<p:menuitem id="m_mes_notifications" value="Mes Notifications" icon="pi pi-bell" outcome="/pages/secure/communication/notifications" />
</p:submenu>
@@ -152,7 +230,7 @@
</p:submenu>
<!-- Formations (TOUS - Inscriptions et suivi) -->
<p:submenu id="m_mes_formations" label="Formations" icon="pi pi-graduation-cap" rendered="#{menuBean.mesFormationsMenuVisible}">
<p:submenu id="m_mes_formations" label="Formations" icon="pi pi-book" rendered="#{menuBean.mesFormationsMenuVisible}">
<p:menuitem id="m_info_formations" value="Informations" icon="pi pi-info-circle" outcome="/pages/secure/dashboard" />
</p:submenu>
@@ -173,7 +251,8 @@
<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_sauvegardes" value="Sauvegardes" icon="pi pi-save" outcome="/pages/secure/admin/sauvegarde" rendered="#{menuBean.maintenanceVisible}" />
<p:menuitem id="m_logs_systeme" value="Logs Système" icon="pi pi-list" outcome="/pages/admin/logs/systeme" 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}" />

View File

@@ -1,6 +1,7 @@
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
@@ -36,6 +37,42 @@
<div class="layout-topbar-right">
<ul class="layout-topbar-actions">
<!-- Org Switcher (visible seulement si l'utilisateur a des organisations) -->
<li class="topbar-item org-switcher-item"
style="#{empty userSession.mesOrganisations ? 'display:none' : ''}">
<a href="#" class="org-switcher-trigger" title="Changer d'organisation">
<i class="topbar-icon pi pi-building"/>
<span class="org-switcher-label">#{userSession.activeOrganisationNom}</span>
<i class="pi pi-angle-down ml-1" style="font-size:0.75rem"/>
</a>
<ul class="org-switcher-dropdown">
<li class="notif-header">
<span class="font-semibold">Mes organisations</span>
</li>
<li class="divider"/>
<h:form id="orgSwitcherForm">
<ui:repeat value="#{userSession.mesOrganisations}" var="org">
<li class="org-item #{userSession.activeOrganisationId != null and org.organisationId == userSession.activeOrganisationId.toString() ? 'org-item-active' : ''}">
<p:commandLink styleClass="org-item-link"
action="#{userSession.changerOrganisationActive(org.organisationId)}"
update="orgSwitcherForm"
process="@this">
<div class="org-item-content">
<div class="org-item-name">#{org.libelleCourt}</div>
<div class="org-item-meta">
<span class="org-type-badge">#{org.typeOrganisation}</span>
<span class="org-role-badge">#{org.statutMembre}</span>
</div>
</div>
<i class="pi pi-check org-check-icon"
style="#{userSession.activeOrganisationId != null and org.organisationId == userSession.activeOrganisationId.toString() ? '' : 'visibility:hidden'}"/>
</p:commandLink>
</li>
</ui:repeat>
</h:form>
</ul>
</li>
<!-- Search -->
<li class="topbar-item search-item">
<a href="#" title="Rechercher">
@@ -144,7 +181,7 @@
<i class="pi pi-building"/>
Organisation
</span>
<span class="value">#{userSession.entite.nom}</span>
<span class="value">#{userSession.activeOrganisationNom}</span>
</div>
<div class="info-row">
<span class="label">

View File

@@ -18,6 +18,8 @@
<title><ui:insert name="title">UnionFlow</ui:insert></title>
<h:outputScript name="js/layout.js" library="freya-layout" />
<h:outputScript name="js/prism.js" library="freya-layout"/>
<!-- QRCode.js — génération de QR codes côté client (Wave Checkout) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" type="text/javascript"><!-- --></script>
<ui:insert name="head"/>
</h:head>

View File

@@ -133,7 +133,7 @@
<div class="grid formgrid">
<div class="field col-12 md:col-6">
<p:outputLabel for="association" value="Organisation *" />
<p:selectOneMenu id="association" value="#{model.associationId}" converter="uuidConverter" required="true" requiredMessage="Organisation: une donnée est requise.">
<p:selectOneMenu id="association" value="#{model.organisationId}" converter="uuidConverter" required="true" requiredMessage="Organisation: une donnée est requise.">
<f:selectItems value="#{organisationsItems}" />
</p:selectOneMenu>
<p:message for="association" />

View File

@@ -3,9 +3,6 @@
# Configuration logging pour développement
quarkus.log.category."dev.lions.unionflow".level=DEBUG
quarkus.log.category."jakarta.faces".level=INFO
quarkus.log.category."org.apache.myfaces".level=INFO
quarkus.log.category."org.primefaces".level=INFO
# Hot reload
quarkus.live-reload.instrumentation=true