Compare commits
3 Commits
1da41e9724
...
bba28595f6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bba28595f6 | ||
|
|
1e835ba2c3 | ||
|
|
af18b42767 |
@@ -122,6 +122,13 @@
|
||||
<artifactId>unionflow-server-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lions User Manager Client - Module réutilisable de gestion d'utilisateurs Keycloak -->
|
||||
<dependency>
|
||||
<groupId>dev.lions.user.manager</groupId>
|
||||
<artifactId>lions-user-manager-client-quarkus-primefaces-freya</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Scheduler pour le rafraîchissement des tokens -->
|
||||
<dependency>
|
||||
|
||||
@@ -82,20 +82,42 @@ public interface MembreService {
|
||||
|
||||
@GET
|
||||
@Path("/export")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@Produces({"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "application/pdf", "application/json"})
|
||||
byte[] exporterExcel(
|
||||
@QueryParam("format") @DefaultValue("EXCEL") String format,
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("statut") String statut
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@QueryParam("dateAdhesionFin") String dateAdhesionFin,
|
||||
@QueryParam("colonnes") List<String> colonnesExport,
|
||||
@QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
|
||||
@QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
|
||||
@QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
|
||||
@QueryParam("motDePasse") String motDePasse
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/export/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Long compterMembresPourExport(
|
||||
@QueryParam("associationId") UUID associationId,
|
||||
@QueryParam("statut") String statut,
|
||||
@QueryParam("type") String type,
|
||||
@QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@QueryParam("dateAdhesionFin") String dateAdhesionFin
|
||||
);
|
||||
|
||||
@POST
|
||||
@Path("/import")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
ResultatImportDTO importerDonnees(
|
||||
@FormParam("file") java.io.InputStream fileInputStream,
|
||||
@FormParam("associationId") UUID associationId
|
||||
);
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
ResultatImportDTO importerDonnees(MembreImportMultipartForm form);
|
||||
|
||||
@GET
|
||||
@Path("/import/modele")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
byte[] telechargerModeleImport();
|
||||
|
||||
@GET
|
||||
@Path("/autocomplete/villes")
|
||||
|
||||
@@ -114,7 +114,7 @@ public class AuditBean implements Serializable {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des logs: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Détails de l'erreur de chargement des logs d'audit", e);
|
||||
tousLesLogs = new ArrayList<>();
|
||||
ajouterMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Erreur lors du chargement des logs: " + e.getMessage());
|
||||
|
||||
@@ -5,8 +5,6 @@ import jakarta.inject.Named;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -42,51 +40,58 @@ public class DemandesBean implements Serializable {
|
||||
private String commentaireAssignation;
|
||||
|
||||
// Statistiques
|
||||
private int enAttente = 12;
|
||||
private int urgentes = 5;
|
||||
private int traitees = 143;
|
||||
private int delaiMoyenTraitement = 3;
|
||||
private int enAttente = 0;
|
||||
private int urgentes = 0;
|
||||
private int traitees = 0;
|
||||
private int delaiMoyenTraitement = 0;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
initializeDemandes();
|
||||
initializeGestionnaires();
|
||||
initializeNouvelleDemande();
|
||||
calculerStatistiques();
|
||||
selectedDemandes = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void initializeDemandes() {
|
||||
demandes = new ArrayList<>();
|
||||
|
||||
String[] objets = {
|
||||
"Demande d'aide financière urgente", "Certificat de membership",
|
||||
"Mutation vers club Dakar", "Réclamation cotisation",
|
||||
"Aide médicale famille", "Certificat de bonne conduite"
|
||||
};
|
||||
|
||||
String[] types = {"AIDE_FINANCIERE", "CERTIFICAT", "MUTATION", "RECLAMATION", "AIDE_FINANCIERE", "CERTIFICAT"};
|
||||
String[] statuts = {"EN_ATTENTE", "EN_COURS", "APPROUVEE", "EN_ATTENTE", "URGENTE", "EN_COURS"};
|
||||
String[] priorites = {"URGENTE", "NORMALE", "NORMALE", "BASSE", "URGENTE", "NORMALE"};
|
||||
String[] demandeurs = {"Marie Kouassi", "Paul Traoré", "Fatou Sanogo", "Jean Ouattara", "Aissata Koné", "Ibrahim Touré"};
|
||||
|
||||
for (int i = 0; i < objets.length; i++) {
|
||||
Demande demande = new Demande();
|
||||
demande.setId(UUID.fromString(String.format("00000000-0000-0000-0000-%012d", i + 1)));
|
||||
demande.setReference("DEM-2024-" + String.format("%04d", i + 1));
|
||||
demande.setObjet(objets[i]);
|
||||
demande.setType(types[i]);
|
||||
demande.setStatut(statuts[i]);
|
||||
demande.setPriorite(priorites[i]);
|
||||
demande.setNomDemandeur(demandeurs[i]);
|
||||
demande.setNumeroMembre("M" + String.format("%06d", 1000 + i));
|
||||
demande.setTelephoneDemandeur("+225 07 " + String.format("%02d", 10 + i) + " " + String.format("%02d", 20 + i) + " " + String.format("%02d", 30 + i));
|
||||
demande.setDateDepot(LocalDate.now().minusDays(i * 2));
|
||||
demande.setDateEcheance(LocalDate.now().plusDays(7 + i));
|
||||
demande.setHeureDepot(LocalDateTime.now().minusHours(i * 3).format(DateTimeFormatter.ofPattern("HH:mm")));
|
||||
if (i % 3 != 0) demande.setAssigneA("Gestionnaire " + (i % 3 + 1));
|
||||
demandes.add(demande);
|
||||
// TODO: Charger depuis le backend via DemandeAideService
|
||||
// Pour l'instant, liste vide - les données viendront du backend
|
||||
}
|
||||
|
||||
private void calculerStatistiques() {
|
||||
if (demandes == null || demandes.isEmpty()) {
|
||||
enAttente = 0;
|
||||
urgentes = 0;
|
||||
traitees = 0;
|
||||
delaiMoyenTraitement = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer depuis les données réelles
|
||||
enAttente = (int) demandes.stream()
|
||||
.filter(d -> "EN_ATTENTE".equals(d.getStatut()))
|
||||
.count();
|
||||
|
||||
urgentes = (int) demandes.stream()
|
||||
.filter(d -> "URGENTE".equals(d.getPriorite()))
|
||||
.count();
|
||||
|
||||
traitees = (int) demandes.stream()
|
||||
.filter(d -> "APPROUVEE".equals(d.getStatut()) || "REJETEE".equals(d.getStatut()))
|
||||
.count();
|
||||
|
||||
// Calculer le délai moyen de traitement
|
||||
long totalJours = demandes.stream()
|
||||
.filter(d -> d.getDateDepot() != null && "APPROUVEE".equals(d.getStatut()))
|
||||
.mapToLong(d -> ChronoUnit.DAYS.between(d.getDateDepot(), LocalDate.now()))
|
||||
.sum();
|
||||
long countTraitees = demandes.stream()
|
||||
.filter(d -> d.getDateDepot() != null && "APPROUVEE".equals(d.getStatut()))
|
||||
.count();
|
||||
delaiMoyenTraitement = countTraitees > 0 ? (int) (totalJours / countTraitees) : 0;
|
||||
|
||||
// Initialiser les sous-listes
|
||||
demandesUrgentes = demandes.stream()
|
||||
.filter(d -> "URGENTE".equals(d.getPriorite()) || "EN_ATTENTE".equals(d.getStatut()))
|
||||
@@ -94,6 +99,11 @@ public class DemandesBean implements Serializable {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
dernieresDemandes = demandes.stream()
|
||||
.sorted((d1, d2) -> {
|
||||
if (d1.getDateDepot() == null) return 1;
|
||||
if (d2.getDateDepot() == null) return -1;
|
||||
return d2.getDateDepot().compareTo(d1.getDateDepot());
|
||||
})
|
||||
.limit(4)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
@@ -181,6 +191,8 @@ public class DemandesBean implements Serializable {
|
||||
|
||||
public void actualiser() {
|
||||
LOGGER.info("Actualisation des données");
|
||||
initializeDemandes();
|
||||
calculerStatistiques();
|
||||
}
|
||||
|
||||
public void filtrerUrgentes() {
|
||||
|
||||
@@ -147,7 +147,7 @@ public class EvenementsBean implements Serializable {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des événements: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Détails de l'erreur de chargement des événements", e);
|
||||
tousLesEvenements = new ArrayList<>();
|
||||
ajouterMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Erreur lors du chargement des événements: " + e.getMessage());
|
||||
@@ -522,7 +522,7 @@ public class EvenementsBean implements Serializable {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la création de l'événement: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Détails de l'erreur de création d'événement", e);
|
||||
ajouterMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Erreur lors de la création de l'événement: " + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -385,6 +385,51 @@ public class SuperAdminBean implements Serializable {
|
||||
public int getUtilisateursActifs() { return utilisateursActifs; }
|
||||
public void setUtilisateursActifs(int utilisateursActifs) { this.utilisateursActifs = utilisateursActifs; }
|
||||
|
||||
/**
|
||||
* Calcule les pourcentages pour les progress bars (jauges) basés sur des objectifs réalistes
|
||||
*/
|
||||
private void calculerPourcentagesJauges() {
|
||||
// Objectif : 1000 membres (100%)
|
||||
int objectifMembres = 1000;
|
||||
pourcentageMembres = totalMembres > 0 ? Math.min(100, (totalMembres * 100) / objectifMembres) : 0;
|
||||
|
||||
// Objectif : 50 organisations (100%)
|
||||
int objectifOrganisations = 50;
|
||||
pourcentageOrganisations = totalEntites > 0 ? Math.min(100, (totalEntites * 100) / objectifOrganisations) : 0;
|
||||
|
||||
// Objectif : 10 000 000 FCFA de revenus (100%)
|
||||
// Pour l'instant, si revenus = 0, on met 0%
|
||||
try {
|
||||
String revenusStr = revenusGlobaux.replaceAll("[^0-9]", "");
|
||||
if (!revenusStr.isEmpty()) {
|
||||
long revenusLong = Long.parseLong(revenusStr);
|
||||
long objectifRevenus = 10_000_000L; // 10 millions FCFA
|
||||
pourcentageRevenus = revenusLong > 0 ? Math.min(100, (int) ((revenusLong * 100) / objectifRevenus)) : 0;
|
||||
} else {
|
||||
pourcentageRevenus = 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
pourcentageRevenus = 0;
|
||||
}
|
||||
|
||||
// Objectif : 100 activités journalières (100%)
|
||||
int objectifActivite = 100;
|
||||
pourcentageActivite = activiteJournaliere > 0 ? Math.min(100, (activiteJournaliere * 100) / objectifActivite) : 0;
|
||||
}
|
||||
|
||||
// Getters pour les pourcentages des jauges
|
||||
public int getPourcentageMembres() { return pourcentageMembres; }
|
||||
public void setPourcentageMembres(int pourcentageMembres) { this.pourcentageMembres = pourcentageMembres; }
|
||||
|
||||
public int getPourcentageOrganisations() { return pourcentageOrganisations; }
|
||||
public void setPourcentageOrganisations(int pourcentageOrganisations) { this.pourcentageOrganisations = pourcentageOrganisations; }
|
||||
|
||||
public int getPourcentageRevenus() { return pourcentageRevenus; }
|
||||
public void setPourcentageRevenus(int pourcentageRevenus) { this.pourcentageRevenus = pourcentageRevenus; }
|
||||
|
||||
public int getPourcentageActivite() { return pourcentageActivite; }
|
||||
public void setPourcentageActivite(int pourcentageActivite) { this.pourcentageActivite = pourcentageActivite; }
|
||||
|
||||
// Classes internes
|
||||
public static class Alerte {
|
||||
private UUID id;
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
<ui:param name="onclick" value="PF('exportDialog').show(); return false;" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{auditBean.actualiser}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formFiltres :formTableau:tableauAudit" />
|
||||
<ui:param name="title" value="Actualiser" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="text" value="false" />
|
||||
<ui:param name="styleClass" value="ui-button-outlined ui-button-secondary" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="ui-button-sm" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
@@ -39,32 +39,44 @@
|
||||
|
||||
<!-- Statistiques -->
|
||||
<div class="grid">
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Événements Totaux" />
|
||||
<ui:param name="value" value="#{auditBean.totalEvenements}" />
|
||||
<ui:param name="label" value="Événements Totaux" />
|
||||
<ui:param name="icon" value="pi pi-history" />
|
||||
<ui:param name="bgColor" value="blue" />
|
||||
<ui:param name="icon" value="pi-history" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Connexions Réussies" />
|
||||
<ui:param name="value" value="#{auditBean.connexionsReussies}" />
|
||||
<ui:param name="label" value="Connexions Réussies" />
|
||||
<ui:param name="icon" value="pi pi-check-circle" />
|
||||
<ui:param name="bgColor" value="green" />
|
||||
<ui:param name="icon" value="pi-check-circle" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Tentatives Échouées" />
|
||||
<ui:param name="value" value="#{auditBean.tentativesEchouees}" />
|
||||
<ui:param name="label" value="Tentatives Échouées" />
|
||||
<ui:param name="icon" value="pi pi-times-circle" />
|
||||
<ui:param name="bgColor" value="orange" />
|
||||
<ui:param name="icon" value="pi-times-circle" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Alertes Sécurité" />
|
||||
<ui:param name="value" value="#{auditBean.alertesSecurite}" />
|
||||
<ui:param name="label" value="Alertes Sécurité" />
|
||||
<ui:param name="icon" value="pi pi-exclamation-triangle" />
|
||||
<ui:param name="bgColor" value="red" />
|
||||
<ui:param name="icon" value="pi-exclamation-triangle" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
@@ -181,7 +193,7 @@
|
||||
<ui:param name="value" value="Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{auditBean.reinitialiserFiltres}" />
|
||||
<ui:param name="update" value="@form :formTableau:tableauAudit" />
|
||||
<ui:param name="update" value=":formFiltres :formTableau:tableauAudit" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
@@ -258,12 +270,15 @@
|
||||
|
||||
<p:column headerText="Actions" style="width: 6%" styleClass="text-center">
|
||||
<div class="flex justify-content-center gap-1">
|
||||
<p:commandButton icon="pi pi-eye"
|
||||
title="Voir les détails"
|
||||
styleClass="p-button-sm p-button-rounded p-button-info"
|
||||
action="#{auditBean.selectionnerLog(log)}"
|
||||
update=":formDetails:dlgDetails"
|
||||
oncomplete="PF('dlgDetails').show();" />
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="" />
|
||||
<ui:param name="icon" value="pi pi-eye" />
|
||||
<ui:param name="action" value="#{auditBean.selectionnerLog(log)}" />
|
||||
<ui:param name="update" value=":formDetails:dlgDetails" />
|
||||
<ui:param name="oncomplete" value="PF('dlgDetails').show();" />
|
||||
<ui:param name="title" value="Voir les détails" />
|
||||
<ui:param name="styleClass" value="p-button-sm p-button-rounded" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
@@ -380,10 +395,12 @@
|
||||
</div>
|
||||
|
||||
<f:facet name="footer">
|
||||
<p:commandButton value="Fermer"
|
||||
icon="pi pi-times"
|
||||
onclick="PF('dlgDetails').hide();"
|
||||
styleClass="p-button-outlined" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Fermer" />
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="onclick" value="PF('dlgDetails').hide();" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
</h:form>
|
||||
@@ -423,14 +440,17 @@
|
||||
|
||||
<f:facet name="footer">
|
||||
<div class="flex justify-content-end gap-2">
|
||||
<p:commandButton value="Annuler"
|
||||
styleClass="p-button-outlined"
|
||||
onclick="PF('exportDialog').hide();"
|
||||
type="button" />
|
||||
<p:commandButton value="Exporter"
|
||||
icon="pi pi-download"
|
||||
styleClass="p-button-success"
|
||||
action="#{auditBean.exporter}" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Annuler" />
|
||||
<ui:param name="onclick" value="PF('exportDialog').hide();" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="Exporter" />
|
||||
<ui:param name="icon" value="pi pi-download" />
|
||||
<ui:param name="action" value="#{auditBean.exporter}" />
|
||||
<ui:param name="update" value="none" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
|
||||
@@ -47,144 +47,83 @@
|
||||
<!-- KPIs Financiers avec grille Freya stricte -->
|
||||
<div class="formgrid grid">
|
||||
<!-- KPI 1: Montant Collecté -->
|
||||
<div class="field col-12 md:col-6 lg:col-3 xl:col-2">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Collecté ce mois</span>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-check text-green-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{cotisationsGestionBean.montantCollecte}</div>
|
||||
<p:progressBar value="#{cotisationsGestionBean.progressionMensuelle}"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem;" />
|
||||
<div class="text-500 text-xs mt-2">
|
||||
<i class="pi pi-arrow-up text-green-500 text-xs"></i>
|
||||
#{cotisationsGestionBean.progressionMensuelle}% de l'objectif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Collecté ce mois" />
|
||||
<ui:param name="value" value="#{cotisationsGestionBean.montantCollecte}" />
|
||||
<ui:param name="icon" value="pi-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="growthValue" value="#{cotisationsGestionBean.progressionMensuelle}" />
|
||||
<ui:param name="growthLabel" value="de l'objectif" />
|
||||
<ui:param name="progressValue" value="#{cotisationsGestionBean.progressionMensuelle}" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3 xl:col-2" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 2: Membres à jour -->
|
||||
<div class="field col-12 md:col-6 lg:col-3 xl:col-2">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Membres à jour</span>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-users text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{cotisationsGestionBean.membresAJour}</div>
|
||||
<p:progressBar value="#{cotisationsGestionBean.pourcentageMembresAJour}"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem;" />
|
||||
<div class="text-500 text-xs mt-2">
|
||||
<i class="pi pi-circle-fill text-blue-500 text-xs"></i>
|
||||
#{cotisationsGestionBean.pourcentageMembresAJour}% conformes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Membres à jour" />
|
||||
<ui:param name="value" value="#{cotisationsGestionBean.membresAJour}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="statusIcon" value="pi-circle-fill" />
|
||||
<ui:param name="statusLabel" value="Conformes" />
|
||||
<ui:param name="statusValue" value="#{cotisationsGestionBean.pourcentageMembresAJour}%" />
|
||||
<ui:param name="progressValue" value="#{cotisationsGestionBean.pourcentageMembresAJour}" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3 xl:col-2" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 3: En Attente -->
|
||||
<div class="field col-12 md:col-6 lg:col-3 xl:col-2">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">En attente</span>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-clock text-orange-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{cotisationsGestionBean.montantEnAttente}</div>
|
||||
<div class="text-orange-600 font-semibold text-sm">
|
||||
#{cotisationsGestionBean.nombreCotisationsEnAttente} cotisations
|
||||
</div>
|
||||
<div class="text-500 text-xs mt-2">
|
||||
<i class="pi pi-exclamation-triangle text-orange-500 text-xs"></i>
|
||||
À traiter rapidement
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="En attente" />
|
||||
<ui:param name="value" value="#{cotisationsGestionBean.montantEnAttente}" />
|
||||
<ui:param name="icon" value="pi-clock" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="statusIcon" value="pi-exclamation-triangle" />
|
||||
<ui:param name="statusLabel" value="Cotisations" />
|
||||
<ui:param name="statusValue" value="#{cotisationsGestionBean.nombreCotisationsEnAttente}" />
|
||||
<ui:param name="noDataLabel" value="À traiter rapidement" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3 xl:col-2" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 4: Impayés -->
|
||||
<div class="field col-12 md:col-6 lg:col-3 xl:col-2">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Impayés</span>
|
||||
<div class="flex align-items-center justify-content-center bg-red-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-exclamation-circle text-red-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{cotisationsGestionBean.montantImpayes}</div>
|
||||
<div class="text-red-600 font-semibold text-sm">
|
||||
#{cotisationsGestionBean.joursRetardMoyen}j de retard moy.
|
||||
</div>
|
||||
<div class="text-500 text-xs mt-2">
|
||||
<i class="pi pi-arrow-down text-red-500 text-xs"></i>
|
||||
Action requise
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Impayés" />
|
||||
<ui:param name="value" value="#{cotisationsGestionBean.montantImpayes}" />
|
||||
<ui:param name="icon" value="pi-exclamation-circle" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
<ui:param name="statusIcon" value="pi-arrow-down" />
|
||||
<ui:param name="statusLabel" value="Retard moyen" />
|
||||
<ui:param name="statusValue" value="#{cotisationsGestionBean.joursRetardMoyen}j" />
|
||||
<ui:param name="noDataLabel" value="Action requise" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3 xl:col-2" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 5: Revenus 2024 -->
|
||||
<div class="field col-12 md:col-6 lg:col-3 xl:col-2">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Revenus 2024</span>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-chart-line text-purple-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{cotisationsGestionBean.revenus2024}</div>
|
||||
<div class="text-green-600 font-semibold text-sm">
|
||||
<i class="pi pi-arrow-up text-green-500"></i>
|
||||
#{cotisationsGestionBean.croissanceAnnuelle}
|
||||
</div>
|
||||
<div class="text-500 text-xs mt-2">
|
||||
Croissance annuelle
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Revenus 2024" />
|
||||
<ui:param name="value" value="#{cotisationsGestionBean.revenus2024}" />
|
||||
<ui:param name="icon" value="pi-chart-line" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="growthValue" value="#{cotisationsGestionBean.croissanceAnnuelle}" />
|
||||
<ui:param name="growthLabel" value="Croissance annuelle" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3 xl:col-2" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 6: Wave Money -->
|
||||
<div class="field col-12 md:col-6 lg:col-3 xl:col-2">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Prélèvements Auto</span>
|
||||
<div class="flex align-items-center justify-content-center bg-teal-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-mobile text-teal-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{cotisationsGestionBean.prelevementsActifs}</div>
|
||||
<div class="text-teal-600 font-semibold text-sm">
|
||||
#{cotisationsGestionBean.montantPrelevementsPrevu} FCFA/mois
|
||||
</div>
|
||||
<div class="text-500 text-xs mt-2">
|
||||
<i class="pi pi-sync text-teal-500 text-xs"></i>
|
||||
Automatique
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Prélèvements Auto" />
|
||||
<ui:param name="value" value="#{cotisationsGestionBean.prelevementsActifs}" />
|
||||
<ui:param name="icon" value="pi-mobile" />
|
||||
<ui:param name="iconColor" value="teal-600" />
|
||||
<ui:param name="statusIcon" value="pi-sync" />
|
||||
<ui:param name="statusLabel" value="Montant/mois" />
|
||||
<ui:param name="statusValue" value="#{cotisationsGestionBean.montantPrelevementsPrevu} FCFA" />
|
||||
<ui:param name="noDataLabel" value="Automatique" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3 xl:col-2" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Section Analytics avec disposition Freya -->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,85 +47,51 @@
|
||||
<!-- KPIs avec grille Freya stricte -->
|
||||
<div class="formgrid grid">
|
||||
<!-- KPI 1: Total Événements -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Total Événements</span>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-calendar text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{evenementsBean.statistiques.totalEvenements}</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-1"></i>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">+#{evenementsBean.statistiques.evenementsCeMois}</span>
|
||||
<span class="text-500 text-xs">ce mois</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Événements" />
|
||||
<ui:param name="value" value="#{evenementsBean.statistiques.totalEvenements}" />
|
||||
<ui:param name="icon" value="pi-calendar" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="growthValue" value="#{evenementsBean.statistiques.evenementsCeMois}" />
|
||||
<ui:param name="growthLabel" value="ce mois" />
|
||||
<ui:param name="growthType" value="number" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 2: Événements Actifs -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Événements Actifs</span>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-check text-green-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{evenementsBean.statistiques.evenementsActifs}</div>
|
||||
<p:progressBar value="#{evenementsBean.statistiques.tauxParticipationMoyen}"
|
||||
showValue="false"
|
||||
styleClass="surface-200 mt-2"
|
||||
style="height: 0.5rem;" />
|
||||
<div class="text-500 text-xs mt-2">#{evenementsBean.statistiques.tauxParticipationMoyen}% de participation</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Événements Actifs" />
|
||||
<ui:param name="value" value="#{evenementsBean.statistiques.evenementsActifs}" />
|
||||
<ui:param name="icon" value="pi-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="progressValue" value="#{evenementsBean.statistiques.tauxParticipationMoyen}" />
|
||||
<ui:param name="noDataLabel" value="#{evenementsBean.statistiques.tauxParticipationMoyen}% de participation" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 3: Participants Inscrits -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Participants</span>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-users text-orange-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{evenementsBean.statistiques.participantsTotal}</div>
|
||||
<div class="text-orange-600 font-semibold text-sm">
|
||||
Moyenne: #{evenementsBean.statistiques.moyenneParticipants}/événement
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Participants" />
|
||||
<ui:param name="value" value="#{evenementsBean.statistiques.participantsTotal}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="statusIcon" value="pi-info-circle" />
|
||||
<ui:param name="statusLabel" value="Moyenne" />
|
||||
<ui:param name="statusValue" value="#{evenementsBean.statistiques.moyenneParticipants}/événement" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 4: Budget Total -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Budget Total</span>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-dollar text-purple-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{evenementsBean.statistiques.budgetTotal}</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-trending-up text-purple-500 text-sm mr-1"></i>
|
||||
<span class="text-purple-600 text-sm">Suivi budgétaire optimal</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Budget Total" />
|
||||
<ui:param name="value" value="#{evenementsBean.statistiques.budgetTotal}" />
|
||||
<ui:param name="icon" value="pi-dollar" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="statusIcon" value="pi-trending-up" />
|
||||
<ui:param name="statusLabel" value="Suivi budgétaire" />
|
||||
<ui:param name="statusValue" value="optimal" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -41,66 +41,42 @@
|
||||
</div>
|
||||
|
||||
<!-- Soldes et Statistiques -->
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-purple-100 border-left-3 border-purple-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-purple-900 font-bold text-2xl">#{caisseBean.soldePrincipal}</div>
|
||||
<div class="text-purple-700">Solde Principal</div>
|
||||
</div>
|
||||
<div class="bg-purple-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-wallet text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formgrid grid">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Solde Principal" />
|
||||
<ui:param name="value" value="#{caisseBean.soldePrincipal}" />
|
||||
<ui:param name="icon" value="pi-wallet" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-green-100 border-left-3 border-green-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-green-900 font-bold text-2xl">#{caisseBean.totalEntrees}</div>
|
||||
<div class="text-green-700">Entrées (30j)</div>
|
||||
</div>
|
||||
<div class="bg-green-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-arrow-down text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Entrées (30j)" />
|
||||
<ui:param name="value" value="#{caisseBean.totalEntrees}" />
|
||||
<ui:param name="icon" value="pi-arrow-down" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-red-100 border-left-3 border-red-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-red-900 font-bold text-2xl">#{caisseBean.totalSorties}</div>
|
||||
<div class="text-red-700">Sorties (30j)</div>
|
||||
</div>
|
||||
<div class="bg-red-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-arrow-up text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Sorties (30j)" />
|
||||
<ui:param name="value" value="#{caisseBean.totalSorties}" />
|
||||
<ui:param name="icon" value="pi-arrow-up" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-blue-100 border-left-3 border-blue-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-blue-900 font-bold text-2xl">#{caisseBean.soldeWaveMoney}</div>
|
||||
<div class="text-blue-700">Wave Money</div>
|
||||
</div>
|
||||
<div class="bg-blue-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-mobile text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Wave Money" />
|
||||
<ui:param name="value" value="#{caisseBean.soldeWaveMoney}" />
|
||||
<ui:param name="icon" value="pi-mobile" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Graphiques -->
|
||||
|
||||
@@ -77,53 +77,46 @@
|
||||
|
||||
<!-- KPIs Financiers -->
|
||||
<h:panelGroup id="kpiPanel">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card text-center">
|
||||
<div class="text-900 text-xl mb-3 font-medium">Revenus Totaux</div>
|
||||
<div class="text-primary text-5xl font-bold mb-3">#{rapportBean.revenusTotaux}</div>
|
||||
<div class="flex align-items-center justify-content-center">
|
||||
<i class="pi pi-arrow-up text-green-500 mr-2"></i>
|
||||
<span class="text-green-500 font-medium">+#{rapportBean.croissanceRevenus}%</span>
|
||||
<span class="text-600 ml-2">vs période précédente</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formgrid grid">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Revenus Totaux" />
|
||||
<ui:param name="value" value="#{rapportBean.revenusTotaux}" />
|
||||
<ui:param name="icon" value="pi-dollar" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="growthValue" value="#{rapportBean.croissanceRevenus}" />
|
||||
<ui:param name="growthLabel" value="vs période précédente" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card text-center">
|
||||
<div class="text-900 text-xl mb-3 font-medium">Dépenses Totales</div>
|
||||
<div class="text-orange-500 text-5xl font-bold mb-3">#{rapportBean.depensesTotales}</div>
|
||||
<div class="flex align-items-center justify-content-center">
|
||||
<i class="pi pi-arrow-down text-red-500 mr-2"></i>
|
||||
<span class="text-red-500 font-medium">+#{rapportBean.croissanceDepenses}%</span>
|
||||
<span class="text-600 ml-2">vs période précédente</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Dépenses Totales" />
|
||||
<ui:param name="value" value="#{rapportBean.depensesTotales}" />
|
||||
<ui:param name="icon" value="pi-shopping-cart" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="growthValue" value="#{rapportBean.croissanceDepenses}" />
|
||||
<ui:param name="growthLabel" value="vs période précédente" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card text-center">
|
||||
<div class="text-900 text-xl mb-3 font-medium">Bénéfice Net</div>
|
||||
<div class="text-green-500 text-5xl font-bold mb-3">#{rapportBean.beneficeNet}</div>
|
||||
<div class="flex align-items-center justify-content-center">
|
||||
<p:progressBar value="#{rapportBean.margePercentage}"
|
||||
labelTemplate="Marge: {value}%"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Bénéfice Net" />
|
||||
<ui:param name="value" value="#{rapportBean.beneficeNet}" />
|
||||
<ui:param name="icon" value="pi-chart-line" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="progressValue" value="#{rapportBean.margePercentage}" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card text-center">
|
||||
<div class="text-900 text-xl mb-3 font-medium">Trésorerie</div>
|
||||
<div class="text-purple-500 text-5xl font-bold mb-3">#{rapportBean.tresorerie}</div>
|
||||
<div class="flex align-items-center justify-content-center">
|
||||
<i class="pi pi-info-circle text-blue-500 mr-2"></i>
|
||||
<span class="text-600">#{rapportBean.joursAutonomie} jours d'autonomie</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Trésorerie" />
|
||||
<ui:param name="value" value="#{rapportBean.tresorerie}" />
|
||||
<ui:param name="icon" value="pi-wallet" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="statusIcon" value="pi-info-circle" />
|
||||
<ui:param name="statusLabel" value="Jours d'autonomie" />
|
||||
<ui:param name="statusValue" value="#{rapportBean.joursAutonomie}" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
@@ -309,31 +302,42 @@
|
||||
<!-- Ratios et Indicateurs -->
|
||||
<div class="card">
|
||||
<h5>Indicateurs Clés de Performance</h5>
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="text-center p-3 surface-50 border-round">
|
||||
<div class="text-3xl font-bold text-primary mb-2">#{rapportBean.tauxRecouvrement}%</div>
|
||||
<div class="text-600">Taux de Recouvrement</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="text-center p-3 surface-50 border-round">
|
||||
<div class="text-3xl font-bold text-green-500 mb-2">#{rapportBean.ratioCouverture}</div>
|
||||
<div class="text-600">Ratio de Couverture</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="text-center p-3 surface-50 border-round">
|
||||
<div class="text-3xl font-bold text-orange-500 mb-2">#{rapportBean.coutMoyenMembre}</div>
|
||||
<div class="text-600">Coût Moyen/Membre</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="text-center p-3 surface-50 border-round">
|
||||
<div class="text-3xl font-bold text-purple-500 mb-2">#{rapportBean.revenuMoyenMembre}</div>
|
||||
<div class="text-600">Revenu Moyen/Membre</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formgrid grid">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Taux de Recouvrement" />
|
||||
<ui:param name="value" value="#{rapportBean.tauxRecouvrement}%" />
|
||||
<ui:param name="icon" value="pi-percentage" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Ratio de Couverture" />
|
||||
<ui:param name="value" value="#{rapportBean.ratioCouverture}" />
|
||||
<ui:param name="icon" value="pi-shield" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Coût Moyen/Membre" />
|
||||
<ui:param name="value" value="#{rapportBean.coutMoyenMembre}" />
|
||||
<ui:param name="icon" value="pi-user-minus" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Revenu Moyen/Membre" />
|
||||
<ui:param name="value" value="#{rapportBean.revenuMoyenMembre}" />
|
||||
<ui:param name="icon" value="pi-user-plus" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
@@ -377,26 +377,18 @@
|
||||
<i class="pi pi-chart-bar text-blue-500 mr-2"></i>
|
||||
Indicateurs Clés de Performance
|
||||
</h5>
|
||||
<div class="grid">
|
||||
<div class="formgrid grid">
|
||||
<ui:repeat value="#{rapportsBean.kpis}" var="kpi" varStatus="status">
|
||||
<div class="col-12">
|
||||
<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 mb-2">
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi #{kpi.icon} text-#{kpi.couleur} mr-2"></i>
|
||||
<span class="text-900 font-medium">#{kpi.libelle}</span>
|
||||
</div>
|
||||
<div class="text-#{kpi.couleur} font-bold text-xl">#{kpi.valeur}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<p:progressBar value="#{kpi.progression}" style="width: 60%; height: 4px;" />
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi #{kpi.tendance == 'UP' ? 'pi-arrow-up text-green-500' : (kpi.tendance == 'DOWN' ? 'pi-arrow-down text-red-500' : 'pi-minus text-600')} mr-1 text-sm"></i>
|
||||
<span class="text-600 text-sm">#{kpi.variation}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="#{kpi.libelle}" />
|
||||
<ui:param name="value" value="#{kpi.valeur}" />
|
||||
<ui:param name="icon" value="#{kpi.icon}" />
|
||||
<ui:param name="iconColor" value="#{kpi.couleur}" />
|
||||
<ui:param name="growthValue" value="#{kpi.variation}" />
|
||||
<ui:param name="growthLabel" value="variation" />
|
||||
<ui:param name="progressValue" value="#{kpi.progression}" />
|
||||
<ui:param name="colSize" value="col-12" />
|
||||
</ui:include>
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,100 +130,50 @@
|
||||
</div>
|
||||
|
||||
<!-- 1. MEMBRES : L'humain d'abord - le plus important -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card bg-gradient-blue border-left-4 border-blue-500">
|
||||
<div class="flex justify-content-between align-items-start mb-3">
|
||||
<div>
|
||||
<span class="text-blue-600 font-semibold text-sm uppercase">Communauté</span>
|
||||
<div class="text-900 font-bold text-3xl mt-2">#{dashboardBean.activeMembers}</div>
|
||||
<span class="text-700 text-sm">Membres actifs</span>
|
||||
</div>
|
||||
<div class="bg-blue-500 text-white border-round-xl flex align-items-center justify-content-center"
|
||||
style="width:3.5rem;height:3.5rem">
|
||||
<i class="pi pi-users text-2xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-up text-green-500 mr-1"></i>
|
||||
<span class="text-green-500 font-bold">+#{dashboardBean.membresEvolutionPourcent}%</span>
|
||||
<span class="text-600 text-sm ml-2">ce mois</span>
|
||||
</div>
|
||||
<p:progressBar value="#{dashboardBean.tauxActivite}" style="height: 4px; margin-top: 8px;"
|
||||
styleClass="bg-blue-200" />
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Membres Actifs" />
|
||||
<ui:param name="value" value="#{dashboardBean.activeMembers}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="growthValue" value="#{dashboardBean.membresEvolutionPourcent}" />
|
||||
<ui:param name="growthLabel" value="ce mois" />
|
||||
<ui:param name="progressValue" value="#{dashboardBean.tauxActivite}" />
|
||||
</ui:include>
|
||||
|
||||
<!-- 2. FINANCES : Santé financière - crucial pour la survie -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card bg-gradient-green border-left-4 border-green-500">
|
||||
<div class="flex justify-content-between align-items-start mb-3">
|
||||
<div>
|
||||
<span class="text-green-600 font-semibold text-sm uppercase">Trésorerie</span>
|
||||
<div class="text-900 font-bold text-2xl mt-2">#{dashboardBean.totalCotisations}</div>
|
||||
<span class="text-700 text-sm">FCFA collectés</span>
|
||||
</div>
|
||||
<div class="bg-green-500 text-white border-round-xl flex align-items-center justify-content-center"
|
||||
style="width:3.5rem;height:3.5rem">
|
||||
<i class="pi pi-dollar text-2xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-up text-green-500 mr-1"></i>
|
||||
<span class="text-green-500 font-bold">+#{dashboardBean.cotisationsEvolutionPourcent}%</span>
|
||||
<span class="text-600 text-sm ml-2">vs mois dernier</span>
|
||||
</div>
|
||||
<p:progressBar value="#{dashboardBean.tauxObjectifCotisations}" style="height: 4px; margin-top: 8px;"
|
||||
styleClass="bg-green-200" />
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="FCFA Collectés" />
|
||||
<ui:param name="value" value="#{dashboardBean.totalCotisations}" />
|
||||
<ui:param name="icon" value="pi-dollar" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="growthValue" value="#{dashboardBean.cotisationsEvolutionPourcent}" />
|
||||
<ui:param name="growthLabel" value="vs mois dernier" />
|
||||
<ui:param name="progressValue" value="#{dashboardBean.tauxObjectifCotisations}" />
|
||||
</ui:include>
|
||||
|
||||
<!-- 3. SOLIDARITÉ : Impact social - raison d'être -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card bg-gradient-purple border-left-4 border-purple-500">
|
||||
<div class="flex justify-content-between align-items-start mb-3">
|
||||
<div>
|
||||
<span class="text-purple-600 font-semibold text-sm uppercase">Solidarité</span>
|
||||
<div class="text-900 font-bold text-2xl mt-2">#{dashboardBean.aidesDistribuees}</div>
|
||||
<span class="text-700 text-sm">FCFA distribués</span>
|
||||
</div>
|
||||
<div class="bg-purple-500 text-white border-round-xl flex align-items-center justify-content-center"
|
||||
style="width:3.5rem;height:3.5rem">
|
||||
<i class="pi pi-heart text-2xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center">
|
||||
<div class="bg-orange-500 border-round mr-1" style="width: 8px; height: 8px;"></div>
|
||||
<span class="text-orange-500 font-bold">#{dashboardBean.pendingAides}</span>
|
||||
<span class="text-600 text-sm ml-2">demandes en attente</span>
|
||||
</div>
|
||||
<p:progressBar value="#{dashboardBean.tauxAidesTraitees}" style="height: 4px; margin-top: 8px;"
|
||||
styleClass="bg-purple-200" />
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="FCFA Distribués" />
|
||||
<ui:param name="value" value="#{dashboardBean.aidesDistribuees}" />
|
||||
<ui:param name="icon" value="pi-heart" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="statusIcon" value="pi-circle-fill" />
|
||||
<ui:param name="statusLabel" value="Demandes en attente" />
|
||||
<ui:param name="statusValue" value="#{dashboardBean.pendingAides}" />
|
||||
<ui:param name="progressValue" value="#{dashboardBean.tauxAidesTraitees}" />
|
||||
</ui:include>
|
||||
|
||||
<!-- 4. ENGAGEMENT : Vitalité de l'organisation -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card bg-gradient-orange border-left-4 border-orange-500">
|
||||
<div class="flex justify-content-between align-items-start mb-3">
|
||||
<div>
|
||||
<span class="text-orange-600 font-semibold text-sm uppercase">Engagement</span>
|
||||
<div class="text-900 font-bold text-3xl mt-2">#{dashboardBean.tauxParticipation}%</div>
|
||||
<span class="text-700 text-sm">Taux de participation</span>
|
||||
</div>
|
||||
<div class="bg-orange-500 text-white border-round-xl flex align-items-center justify-content-center"
|
||||
style="width:3.5rem;height:3.5rem">
|
||||
<i class="pi pi-chart-line text-2xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-calendar text-orange-500 mr-1"></i>
|
||||
<span class="text-orange-500 font-bold">#{dashboardBean.upcomingEvents}</span>
|
||||
<span class="text-600 text-sm ml-2">événements prévus</span>
|
||||
</div>
|
||||
<p:progressBar value="#{dashboardBean.tauxEngagement}" style="height: 4px; margin-top: 8px;"
|
||||
styleClass="bg-orange-200" />
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Taux de Participation" />
|
||||
<ui:param name="value" value="#{dashboardBean.tauxParticipation}%" />
|
||||
<ui:param name="icon" value="pi-chart-line" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="statusIcon" value="pi-calendar" />
|
||||
<ui:param name="statusLabel" value="Événements prévus" />
|
||||
<ui:param name="statusValue" value="#{dashboardBean.upcomingEvents}" />
|
||||
<ui:param name="progressValue" value="#{dashboardBean.tauxEngagement}" />
|
||||
</ui:include>
|
||||
|
||||
<!-- Tendances financières et analyses -->
|
||||
<div class="col-12 lg:col-8">
|
||||
|
||||
@@ -43,62 +43,45 @@
|
||||
|
||||
<!-- Résumé cotisations -->
|
||||
<div class="grid mb-3">
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-green-100 border-left-3 border-green-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-green-900 font-bold text-2xl">#{membreCotisationBean.cotisationsPayees}</div>
|
||||
<div class="text-green-700">Payées</div>
|
||||
</div>
|
||||
<div class="bg-green-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-check text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-orange-100 border-left-3 border-orange-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-orange-900 font-bold text-2xl">#{membreCotisationBean.cotisationsEnAttente}</div>
|
||||
<div class="text-orange-700">En Attente</div>
|
||||
</div>
|
||||
<div class="bg-orange-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-clock text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-red-100 border-left-3 border-red-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-red-900 font-bold text-2xl">#{membreCotisationBean.montantDu}</div>
|
||||
<div class="text-red-700">Montant Dû</div>
|
||||
</div>
|
||||
<div class="bg-red-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-exclamation-triangle text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-blue-100 border-left-3 border-blue-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-blue-900 font-bold text-2xl">#{membreCotisationBean.totalVerse}</div>
|
||||
<div class="text-blue-700">Total Versé</div>
|
||||
</div>
|
||||
<div class="bg-blue-500 text-white border-round text-center"
|
||||
style="width: 3rem; height: 3rem; line-height: 3rem;">
|
||||
<i class="pi pi-dollar text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Payées" />
|
||||
<ui:param name="value" value="#{membreCotisationBean.cotisationsPayees}" />
|
||||
<ui:param name="icon" value="pi-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="En Attente" />
|
||||
<ui:param name="value" value="#{membreCotisationBean.cotisationsEnAttente}" />
|
||||
<ui:param name="icon" value="pi-clock" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Montant Dû" />
|
||||
<ui:param name="value" value="#{membreCotisationBean.montantDu}" />
|
||||
<ui:param name="icon" value="pi-exclamation-triangle" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Versé" />
|
||||
<ui:param name="value" value="#{membreCotisationBean.totalVerse}" />
|
||||
<ui:param name="icon" value="pi-dollar" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Liste des cotisations -->
|
||||
@@ -127,11 +110,14 @@
|
||||
</div>
|
||||
</p:toolbarGroup>
|
||||
<p:toolbarGroup align="right">
|
||||
<p:commandButton icon="pi pi-refresh"
|
||||
styleClass="ui-button-outlined ui-button-secondary"
|
||||
action="#{membreCotisationBean.actualiser}"
|
||||
update="@form"
|
||||
title="Actualiser"/>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreCotisationBean.actualiser}" />
|
||||
<ui:param name="update" value=":formCotisations:dtCotisations" />
|
||||
<ui:param name="title" value="Actualiser" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</p:toolbarGroup>
|
||||
</p:toolbar>
|
||||
|
||||
|
||||
@@ -378,16 +378,15 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<p:commandButton
|
||||
value="🎯 Inscrire le membre"
|
||||
icon="pi pi-user-plus"
|
||||
action="#{membreInscriptionBean.inscrire}"
|
||||
update="@form"
|
||||
process="@form"
|
||||
onclick="PF('statusDialog').show();"
|
||||
oncomplete="PF('statusDialog').hide();"
|
||||
styleClass="ui-button-success"
|
||||
title="Soumettre l'inscription" />
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="🎯 Inscrire le membre" />
|
||||
<ui:param name="icon" value="pi pi-user-plus" />
|
||||
<ui:param name="action" value="#{membreInscriptionBean.inscrire}" />
|
||||
<ui:param name="update" value="messages" />
|
||||
<ui:param name="onclick" value="PF('statusDialog').show();" />
|
||||
<ui:param name="oncomplete" value="PF('statusDialog').hide();" />
|
||||
<ui:param name="title" value="Soumettre l'inscription" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="💾 Enregistrer brouillon" />
|
||||
|
||||
@@ -155,11 +155,15 @@
|
||||
<div class="col-12 md:col-auto">
|
||||
<div class="field">
|
||||
<label class="invisible">Actualiser</label>
|
||||
<p:commandButton icon="pi pi-refresh"
|
||||
action="#{membreListeBean.actualiser}"
|
||||
update="@form"
|
||||
title="Actualiser"
|
||||
styleClass="ui-button-outlined ui-button-secondary w-full" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreListeBean.actualiser}" />
|
||||
<ui:param name="update" value=":formMembres:dtMembres" />
|
||||
<ui:param name="title" value="Actualiser" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="w-full" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -278,7 +282,7 @@
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:param name="icon" value="pi pi-envelope" />
|
||||
<ui:param name="action" value="#{membreListeBean.contacterMembre(membre)}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formMembres :formContact" />
|
||||
<ui:param name="oncomplete" value="PF('dlgContact').show();" />
|
||||
<ui:param name="title" value="Contacter" />
|
||||
<ui:param name="severity" value="" />
|
||||
@@ -322,7 +326,7 @@
|
||||
<ui:param name="value" value="Rappel cotisations" />
|
||||
<ui:param name="icon" value="pi pi-bell" />
|
||||
<ui:param name="action" value="#{membreListeBean.rappelCotisationsGroupe}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formMembres" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="disabled" value="#{empty membreListeBean.selectedMembres}" />
|
||||
</ui:include>
|
||||
@@ -422,7 +426,7 @@
|
||||
<ui:param name="value" value="Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreListeBean.reinitialiserFiltres}" />
|
||||
<ui:param name="update" value="@form :formMembres:dtMembres" />
|
||||
<ui:param name="update" value=":formFiltresAvances :formMembres:dtMembres" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
@@ -478,7 +482,7 @@
|
||||
<ui:param name="value" value="Envoyer" />
|
||||
<ui:param name="icon" value="pi pi-send" />
|
||||
<ui:param name="action" value="#{membreListeBean.envoyerMessageGroupe}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formMessageGroupe :formMembres" />
|
||||
<ui:param name="onclick" value="if(!args.validationFailed) PF('dlgMessageGroupe').hide();" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
@@ -521,7 +525,7 @@
|
||||
<ui:param name="value" value="Importer" />
|
||||
<ui:param name="icon" value="pi pi-upload" />
|
||||
<ui:param name="action" value="#{membreListeBean.importerMembres}" />
|
||||
<ui:param name="update" value="@form :formMembres" />
|
||||
<ui:param name="update" value=":formImportExport :formMembres" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="Télécharger modèle" />
|
||||
@@ -634,14 +638,14 @@
|
||||
<ui:param name="value" value="Annuler" />
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="action" value="#{membreListeBean.annulerContact}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formContact" />
|
||||
<ui:param name="oncomplete" value="PF('dlgContact').hide();" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="Envoyer" />
|
||||
<ui:param name="icon" value="pi pi-send" />
|
||||
<ui:param name="action" value="#{membreListeBean.envoyerMessageContact}" />
|
||||
<ui:param name="update" value="@form :formMembres" />
|
||||
<ui:param name="update" value=":formContact :formMembres" />
|
||||
<ui:param name="oncomplete" value="if(!args.validationFailed) { PF('dlgContact').hide(); }" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
@@ -17,23 +17,15 @@
|
||||
<div class="flex align-items-start">
|
||||
<!-- Photo de profil -->
|
||||
<div class="mr-4">
|
||||
<div class="border-circle overflow-hidden" style="width: 120px; height: 120px;">
|
||||
<h:graphicImage value="#{membreProfilBean.membre.photoUrl}"
|
||||
style="width: 100%; height: 100%; object-fit: cover;"
|
||||
rendered="#{membreProfilBean.membre.photoUrl != null}" />
|
||||
<div class="bg-primary text-white flex align-items-center justify-content-center w-full h-full"
|
||||
rendered="#{membreProfilBean.membre.photoUrl == null}">
|
||||
<span style="font-size: 2.5rem;">#{membreProfilBean.membre.initiales}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h:form id="formPhoto" enctype="multipart/form-data">
|
||||
<p:fileUpload mode="simple" skinSimple="true"
|
||||
label="Changer photo" chooseLabel="Modifier"
|
||||
accept="image/*" maxFileSize="2000000"
|
||||
listener="#{membreProfilBean.changerPhoto}"
|
||||
update="@form"
|
||||
styleClass="mt-2 w-full" />
|
||||
</h:form>
|
||||
<ui:include src="/templates/components/profile-photo.xhtml">
|
||||
<ui:param name="photoId" value="photoProfil" />
|
||||
<ui:param name="photoUrl" value="#{membreProfilBean.membre.photoUrl}" />
|
||||
<ui:param name="initiales" value="#{membreProfilBean.membre.initiales}" />
|
||||
<ui:param name="formId" value="formPhoto" />
|
||||
<ui:param name="listener" value="#{membreProfilBean.changerPhoto}" />
|
||||
<ui:param name="update" value=":photoProfil" />
|
||||
<ui:param name="size" value="120" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Informations principales -->
|
||||
@@ -47,10 +39,11 @@
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="mb-2">
|
||||
<span class="font-medium text-900">Numéro membre:</span>
|
||||
<span class="ml-2 font-bold text-primary">#{membreProfilBean.membre.numeroMembre}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Numéro membre" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.numeroMembre}" />
|
||||
<ui:param name="valueClass" value="font-bold text-primary" />
|
||||
</ui:include>
|
||||
<div class="mb-2">
|
||||
<span class="font-medium text-900">Type:</span>
|
||||
<p:tag value="#{membreProfilBean.membre.typeMembre}"
|
||||
@@ -58,25 +51,26 @@
|
||||
icon="pi #{membreProfilBean.membre.typeIcon}"
|
||||
styleClass="ml-2" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-medium text-900">Entité:</span>
|
||||
<span class="ml-2">#{membreProfilBean.membre.entite}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Entité" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.entite}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="mb-2">
|
||||
<span class="font-medium text-900">Adhésion:</span>
|
||||
<span class="ml-2">#{membreProfilBean.membre.dateAdhesion}</span>
|
||||
<small class="text-600 ml-1">(#{membreProfilBean.membre.anciennete})</small>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Adhésion" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.dateAdhesion}" />
|
||||
<ui:param name="suffix" value="(#{membreProfilBean.membre.anciennete})" />
|
||||
</ui:include>
|
||||
<div class="mb-2">
|
||||
<span class="font-medium text-900">Cotisations:</span>
|
||||
<span class="ml-2 #{membreProfilBean.membre.cotisationColor}">#{membreProfilBean.membre.cotisationStatut}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-medium text-900">Participation:</span>
|
||||
<span class="ml-2 font-bold text-blue-500">#{membreProfilBean.membre.tauxParticipation}%</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Participation" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.tauxParticipation}%" />
|
||||
<ui:param name="valueClass" value="font-bold text-blue-500" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,65 +112,45 @@
|
||||
|
||||
<!-- Statistiques et KPIs -->
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-blue-100 border-left-3 border-blue-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-blue-900 font-bold text-xl">#{membreProfilBean.statistiques.evenementsParticipes}</div>
|
||||
<div class="text-blue-700">Événements</div>
|
||||
</div>
|
||||
<div class="bg-blue-500 text-white border-round text-center"
|
||||
style="width: 2.5rem; height: 2.5rem; line-height: 2.5rem;">
|
||||
<i class="pi pi-calendar text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Événements" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.evenementsParticipes}" />
|
||||
<ui:param name="icon" value="pi-calendar" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-green-100 border-left-3 border-green-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-green-900 font-bold text-xl">#{membreProfilBean.statistiques.cotisationsPayees}</div>
|
||||
<div class="text-green-700">Cotisations</div>
|
||||
</div>
|
||||
<div class="bg-green-500 text-white border-round text-center"
|
||||
style="width: 2.5rem; height: 2.5rem; line-height: 2.5rem;">
|
||||
<i class="pi pi-dollar text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Cotisations" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.cotisationsPayees}" />
|
||||
<ui:param name="icon" value="pi-dollar" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-orange-100 border-left-3 border-orange-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-orange-900 font-bold text-xl">#{membreProfilBean.statistiques.aidesRecues}</div>
|
||||
<div class="text-orange-700">Aides reçues</div>
|
||||
</div>
|
||||
<div class="bg-orange-500 text-white border-round text-center"
|
||||
style="width: 2.5rem; height: 2.5rem; line-height: 2.5rem;">
|
||||
<i class="pi pi-heart text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Aides reçues" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.aidesRecues}" />
|
||||
<ui:param name="icon" value="pi-heart" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="card bg-purple-100 border-left-3 border-purple-500">
|
||||
<div class="flex justify-content-between">
|
||||
<div>
|
||||
<div class="text-purple-900 font-bold text-xl">#{membreProfilBean.statistiques.scoreEngagement}</div>
|
||||
<div class="text-purple-700">Score engagement</div>
|
||||
</div>
|
||||
<div class="bg-purple-500 text-white border-round text-center"
|
||||
style="width: 2.5rem; height: 2.5rem; line-height: 2.5rem;">
|
||||
<i class="pi pi-star text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Score engagement" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.scoreEngagement}" />
|
||||
<ui:param name="icon" value="pi-star" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-3" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Contenu principal avec onglets -->
|
||||
@@ -188,52 +162,52 @@
|
||||
<div class="col-12 md:col-6">
|
||||
<h6 class="mb-3">Informations de base</h6>
|
||||
<div class="surface-50 p-3 border-round">
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Nom complet:</span>
|
||||
<span>#{membreProfilBean.membre.nomComplet}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Date de naissance:</span>
|
||||
<span>#{membreProfilBean.membre.dateNaissance}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Genre:</span>
|
||||
<span>#{membreProfilBean.membre.genre}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Situation familiale:</span>
|
||||
<span>#{membreProfilBean.membre.situationFamiliale}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center">
|
||||
<span class="font-medium">Profession:</span>
|
||||
<span>#{membreProfilBean.membre.profession}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Nom complet" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.nomComplet}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Date de naissance" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.dateNaissance}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Genre" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.genre}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Situation familiale" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.situationFamiliale}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Profession" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.profession}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<h6 class="mb-3">Coordonnées</h6>
|
||||
<div class="surface-50 p-3 border-round">
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Email:</span>
|
||||
<span>#{membreProfilBean.membre.email}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Téléphone:</span>
|
||||
<span>#{membreProfilBean.membre.telephone}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Adresse:</span>
|
||||
<span>#{membreProfilBean.membre.adresse}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Ville:</span>
|
||||
<span>#{membreProfilBean.membre.ville}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center">
|
||||
<span class="font-medium">Pays:</span>
|
||||
<span>#{membreProfilBean.membre.pays}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Email" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.email}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Téléphone" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.telephone}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Adresse" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.adresse}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Ville" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.ville}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Pays" />
|
||||
<ui:param name="value" value="#{membreProfilBean.membre.pays}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -272,20 +246,20 @@
|
||||
<p:tag value="#{membreProfilBean.cotisations.statutActuel}"
|
||||
severity="#{membreProfilBean.cotisations.statutSeverity}" />
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Dernier paiement:</span>
|
||||
<span>#{membreProfilBean.cotisations.dernierPaiement}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Prochaine échéance:</span>
|
||||
<span class="#{membreProfilBean.cotisations.prochaineEcheanceClass}">
|
||||
#{membreProfilBean.cotisations.prochaineEcheance}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center">
|
||||
<span class="font-medium">Total payé cette année:</span>
|
||||
<span class="font-bold text-green-500">#{membreProfilBean.cotisations.totalAnnee}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Dernier paiement" />
|
||||
<ui:param name="value" value="#{membreProfilBean.cotisations.dernierPaiement}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Prochaine échéance" />
|
||||
<ui:param name="value" value="#{membreProfilBean.cotisations.prochaineEcheance}" />
|
||||
<ui:param name="valueClass" value="#{membreProfilBean.cotisations.prochaineEcheanceClass}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Total payé cette année" />
|
||||
<ui:param name="value" value="#{membreProfilBean.cotisations.totalAnnee}" />
|
||||
<ui:param name="valueClass" value="font-bold text-green-500" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<h:form id="formCotisationsActions">
|
||||
@@ -363,29 +337,31 @@
|
||||
<div class="col-12 md:col-4">
|
||||
<h6 class="mb-3">Statistiques participation</h6>
|
||||
<div class="surface-50 p-3 border-round">
|
||||
<div class="flex justify-content-between align-items-center mb-3">
|
||||
<span class="font-medium">Taux de participation:</span>
|
||||
<span class="font-bold text-blue-500">#{membreProfilBean.statistiques.tauxParticipation}%</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Taux de participation" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.tauxParticipation}%" />
|
||||
<ui:param name="valueClass" value="font-bold text-blue-500" />
|
||||
</ui:include>
|
||||
<p:progressBar value="#{membreProfilBean.statistiques.tauxParticipation}"
|
||||
labelTemplate="" styleClass="mb-3" />
|
||||
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Cette année:</span>
|
||||
<span>#{membreProfilBean.statistiques.evenementsAnnee}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">Total:</span>
|
||||
<span>#{membreProfilBean.statistiques.evenementsTotal}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium">En tant qu'organisateur:</span>
|
||||
<span>#{membreProfilBean.statistiques.evenementsOrganises}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between align-items-center">
|
||||
<span class="font-medium">Absences:</span>
|
||||
<span class="text-red-500">#{membreProfilBean.statistiques.absences}</span>
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Cette année" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.evenementsAnnee}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Total" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.evenementsTotal}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="En tant qu'organisateur" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.evenementsOrganises}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Absences" />
|
||||
<ui:param name="value" value="#{membreProfilBean.statistiques.absences}" />
|
||||
<ui:param name="valueClass" value="text-red-500" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -554,7 +530,7 @@
|
||||
<ui:param name="value" value="Enregistrer" />
|
||||
<ui:param name="icon" value="pi pi-check" />
|
||||
<ui:param name="action" value="#{membreProfilBean.sauvegarderModifications}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formModifierProfil :photoProfil" />
|
||||
<ui:param name="onclick" value="if(!args.validationFailed) PF('dlgModifierProfil').hide();" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
@@ -570,20 +546,24 @@
|
||||
<p:dialog header="Contacter #{membreProfilBean.membre.prenom}" widgetVar="dlgContacter" modal="true" width="500">
|
||||
<h:form id="formContacter">
|
||||
<div class="ui-fluid">
|
||||
<div class="field">
|
||||
<p:outputLabel for="sujetContact" value="Sujet" />
|
||||
<p:inputText id="sujetContact" value="#{membreProfilBean.contact.sujet}" required="true" />
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/form-field-text.xhtml">
|
||||
<ui:param name="id" value="sujetContact" />
|
||||
<ui:param name="label" value="Sujet" />
|
||||
<ui:param name="value" value="#{membreProfilBean.contact.sujet}" />
|
||||
<ui:param name="required" value="true" />
|
||||
</ui:include>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="messageContact" value="Message" />
|
||||
<p:inputTextarea id="messageContact" value="#{membreProfilBean.contact.message}"
|
||||
rows="5" required="true" />
|
||||
</div>
|
||||
<ui:include src="/templates/components/forms/form-field-textarea.xhtml">
|
||||
<ui:param name="id" value="messageContact" />
|
||||
<ui:param name="label" value="Message" />
|
||||
<ui:param name="value" value="#{membreProfilBean.contact.message}" />
|
||||
<ui:param name="required" value="true" />
|
||||
<ui:param name="rows" value="5" />
|
||||
</ui:include>
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="canalContact" value="Canal de communication" />
|
||||
<p:selectCheckboxMenu id="canalContact" value="#{membreProfilBean.contact.canaux}" multiple="true">
|
||||
<p:selectCheckboxMenu id="canalContact" value="#{membreProfilBean.contact.canaux}" multiple="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="📧 Email" itemValue="EMAIL" />
|
||||
<f:selectItem itemLabel="📱 SMS" itemValue="SMS" />
|
||||
<f:selectItem itemLabel="💬 WhatsApp" itemValue="WHATSAPP" />
|
||||
@@ -592,14 +572,18 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-3">
|
||||
<p:commandButton value="Envoyer" icon="pi pi-send"
|
||||
styleClass="ui-button-success"
|
||||
action="#{membreProfilBean.envoyerMessage}"
|
||||
update="@form"
|
||||
oncomplete="if(!args.validationFailed) PF('dlgContacter').hide();" />
|
||||
<p:commandButton value="Annuler" icon="pi pi-times"
|
||||
styleClass="ui-button-secondary"
|
||||
onclick="PF('dlgContacter').hide();" type="button" />
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="Envoyer" />
|
||||
<ui:param name="icon" value="pi pi-send" />
|
||||
<ui:param name="action" value="#{membreProfilBean.envoyerMessage}" />
|
||||
<ui:param name="update" value=":formContacter" />
|
||||
<ui:param name="oncomplete" value="if(!args.validationFailed) PF('dlgContacter').hide();" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Annuler" />
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="onclick" value="PF('dlgContacter').hide();" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
@@ -609,46 +593,65 @@
|
||||
<h:form id="formActions">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<p:commandButton value="Suspendre membre"
|
||||
icon="pi pi-ban"
|
||||
styleClass="ui-button-danger w-full mb-2"
|
||||
action="#{membreProfilBean.suspendre}"
|
||||
onclick="return confirm('Êtes-vous sûr de vouloir suspendre ce membre ?');"
|
||||
rendered="#{membreProfilBean.membre.statut == 'ACTIF'}" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Suspendre membre" />
|
||||
<ui:param name="icon" value="pi pi-ban" />
|
||||
<ui:param name="action" value="#{membreProfilBean.suspendre}" />
|
||||
<ui:param name="onclick" value="return confirm('Êtes-vous sûr de vouloir suspendre ce membre ?');" />
|
||||
<ui:param name="styleClass" value="ui-button-danger w-full mb-2" />
|
||||
<ui:param name="rendered" value="#{membreProfilBean.membre.statut == 'ACTIF'}" />
|
||||
</ui:include>
|
||||
|
||||
<p:commandButton value="Réactiver membre"
|
||||
icon="pi pi-check"
|
||||
styleClass="ui-button-success w-full mb-2"
|
||||
action="#{membreProfilBean.reactiver}"
|
||||
rendered="#{membreProfilBean.membre.statut == 'SUSPENDU'}" />
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="Réactiver membre" />
|
||||
<ui:param name="icon" value="pi pi-check" />
|
||||
<ui:param name="action" value="#{membreProfilBean.reactiver}" />
|
||||
<ui:param name="styleClass" value="w-full mb-2" />
|
||||
<ui:param name="rendered" value="#{membreProfilBean.membre.statut == 'SUSPENDU'}" />
|
||||
</ui:include>
|
||||
|
||||
<p:commandButton value="Changer de type"
|
||||
icon="pi pi-user-edit"
|
||||
styleClass="ui-button-outlined ui-button-warning w-full mb-2"
|
||||
onclick="PF('dlgChangerType').show();" />
|
||||
<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" />
|
||||
<ui:param name="onclick" value="PF('dlgChangerType').show();" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="w-full mb-2" />
|
||||
</ui:include>
|
||||
|
||||
<p:commandButton value="Transférer vers entité"
|
||||
icon="pi pi-arrow-right"
|
||||
styleClass="ui-button-outlined ui-button-info w-full mb-2"
|
||||
onclick="PF('dlgTransferer').show();" />
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="Transférer vers entité" />
|
||||
<ui:param name="icon" value="pi pi-arrow-right" />
|
||||
<ui:param name="onclick" value="PF('dlgTransferer').show();" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="w-full mb-2" />
|
||||
</ui:include>
|
||||
|
||||
<p:commandButton value="Exporter données"
|
||||
icon="pi pi-download"
|
||||
styleClass="ui-button-outlined ui-button-secondary w-full mb-2"
|
||||
action="#{membreProfilBean.exporterDonnees}" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Exporter données" />
|
||||
<ui:param name="icon" value="pi pi-download" />
|
||||
<ui:param name="action" value="#{membreProfilBean.exporterDonnees}" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="w-full mb-2" />
|
||||
<ui:param name="update" value="none" />
|
||||
</ui:include>
|
||||
|
||||
<p:commandButton value="Supprimer membre"
|
||||
icon="pi pi-trash"
|
||||
styleClass="ui-button-outlined ui-button-danger w-full"
|
||||
onclick="return confirm('ATTENTION: Cette action est irréversible. Confirmer la suppression ?');"
|
||||
action="#{membreProfilBean.supprimer}" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Supprimer membre" />
|
||||
<ui:param name="icon" value="pi pi-trash" />
|
||||
<ui:param name="action" value="#{membreProfilBean.supprimer}" />
|
||||
<ui:param name="onclick" value="return confirm('ATTENTION: Cette action est irréversible. Confirmer la suppression ?');" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="ui-button-danger w-full" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-content-end mt-3">
|
||||
<p:commandButton value="Fermer" icon="pi pi-times"
|
||||
styleClass="ui-button-secondary"
|
||||
onclick="PF('dlgActions').hide();" type="button" />
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Fermer" />
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="onclick" value="PF('dlgActions').hide();" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
|
||||
@@ -36,32 +36,44 @@
|
||||
|
||||
<!-- Statistiques de recherche -->
|
||||
<div class="grid">
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Membres" />
|
||||
<ui:param name="value" value="#{membreRechercheBean.statistiques.totalMembres}" />
|
||||
<ui:param name="label" value="Total Membres" />
|
||||
<ui:param name="icon" value="pi pi-users" />
|
||||
<ui:param name="bgColor" value="blue" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Résultats trouvés" />
|
||||
<ui:param name="value" value="#{membreRechercheBean.statistiques.resultatsActuels}" />
|
||||
<ui:param name="label" value="Résultats trouvés" />
|
||||
<ui:param name="icon" value="pi pi-check" />
|
||||
<ui:param name="bgColor" value="green" />
|
||||
<ui:param name="icon" value="pi-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Filtres actifs" />
|
||||
<ui:param name="value" value="#{membreRechercheBean.statistiques.filtresActifs}" />
|
||||
<ui:param name="label" value="Filtres actifs" />
|
||||
<ui:param name="icon" value="pi pi-filter" />
|
||||
<ui:param name="bgColor" value="orange" />
|
||||
<ui:param name="icon" value="pi-filter" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Temps de recherche" />
|
||||
<ui:param name="value" value="#{membreRechercheBean.statistiques.tempsRecherche}ms" />
|
||||
<ui:param name="label" value="Temps de recherche" />
|
||||
<ui:param name="icon" value="pi pi-clock" />
|
||||
<ui:param name="bgColor" value="purple" />
|
||||
<ui:param name="icon" value="pi-clock" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
@@ -325,7 +337,7 @@
|
||||
<ui:param name="value" value="Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreRechercheBean.reinitialiserFiltres}" />
|
||||
<ui:param name="update" value="@form :formResultats:dtResultats @(.search-summary)" />
|
||||
<ui:param name="update" value=":formRechercheAvancee :formResultats:dtResultats @(.search-summary)" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
@@ -389,14 +401,14 @@
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span>Liste des membres</span>
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreRechercheBean.actualiserResultats}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formResultats:dtResultats" />
|
||||
<ui:param name="title" value="Actualiser" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="text" value="false" />
|
||||
<ui:param name="styleClass" value="ui-button-outlined ui-button-secondary" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="ui-button-sm" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:param name="icon" value="pi pi-cog" />
|
||||
@@ -572,7 +584,7 @@
|
||||
<ui:param name="value" value="Sauvegarder" />
|
||||
<ui:param name="icon" value="pi pi-check" />
|
||||
<ui:param name="action" value="#{membreRechercheBean.sauvegarderRecherche}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formSauvegarderRecherche" />
|
||||
<ui:param name="onclick" value="if(!args.validationFailed) PF('dlgSauvegarderRecherche').hide();" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
@@ -622,7 +634,7 @@
|
||||
<ui:include src="/templates/components/buttons/button-icon.xhtml">
|
||||
<ui:param name="icon" value="pi pi-trash" />
|
||||
<ui:param name="action" value="#{membreRechercheBean.supprimerRecherche(recherche)}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formRecherchesSauvegardees" />
|
||||
<ui:param name="onclick" value="return confirm('Supprimer cette recherche ?');" />
|
||||
<ui:param name="title" value="Supprimer" />
|
||||
<ui:param name="severity" value="danger" />
|
||||
@@ -685,7 +697,7 @@
|
||||
<ui:param name="value" value="Envoyer" />
|
||||
<ui:param name="icon" value="pi pi-send" />
|
||||
<ui:param name="action" value="#{membreRechercheBean.envoyerMessageGroupe}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
<ui:param name="update" value=":formMessageGroupe :formResultats" />
|
||||
<ui:param name="onclick" value="if(!args.validationFailed) PF('dlgMessageGroupe').hide();" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
|
||||
@@ -57,23 +57,18 @@
|
||||
<!-- KPIs Activités -->
|
||||
<div class="card">
|
||||
<h5>Indicateurs d'Activité</h5>
|
||||
<div class="grid">
|
||||
<div class="formgrid grid">
|
||||
<ui:repeat value="#{rapportsBean.kpis}" var="kpi">
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="surface-100 border-round-lg p-4">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="#{kpi.icon} text-2xl text-#{kpi.couleur}"></i>
|
||||
<span class="font-semibold">#{kpi.libelle}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-3xl font-bold text-#{kpi.couleur} mb-2">#{kpi.valeur}</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="#{kpi.variation > 0 ? 'pi pi-arrow-up text-green-500' : kpi.variation < 0 ? 'pi pi-arrow-down text-red-500' : 'pi pi-minus text-gray-500'}"></i>
|
||||
<span class="text-sm">#{kpi.variation}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="#{kpi.libelle}" />
|
||||
<ui:param name="value" value="#{kpi.valeur}" />
|
||||
<ui:param name="icon" value="#{kpi.icon}" />
|
||||
<ui:param name="iconColor" value="#{kpi.couleur}" />
|
||||
<ui:param name="growthValue" value="#{kpi.variation}" />
|
||||
<ui:param name="growthLabel" value="variation" />
|
||||
<ui:param name="colSize" value="col-12 md:col-4" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -150,23 +150,18 @@
|
||||
<!-- KPIs Financiers -->
|
||||
<div class="card">
|
||||
<h5>Indicateurs Clés de Performance</h5>
|
||||
<div class="grid">
|
||||
<div class="formgrid grid">
|
||||
<ui:repeat value="#{rapportsBean.kpis}" var="kpi">
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="surface-100 border-round-lg p-4">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="#{kpi.icon} text-2xl text-#{kpi.couleur}"></i>
|
||||
<span class="font-semibold">#{kpi.libelle}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-3xl font-bold text-#{kpi.couleur} mb-2">#{kpi.valeur}</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="#{kpi.variation > 0 ? 'pi pi-arrow-up text-green-500' : kpi.variation < 0 ? 'pi pi-arrow-down text-red-500' : 'pi pi-minus text-gray-500'}"></i>
|
||||
<span class="text-sm">#{kpi.variation}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="#{kpi.libelle}" />
|
||||
<ui:param name="value" value="#{kpi.valeur}" />
|
||||
<ui:param name="icon" value="#{kpi.icon}" />
|
||||
<ui:param name="iconColor" value="#{kpi.couleur}" />
|
||||
<ui:param name="growthValue" value="#{kpi.variation}" />
|
||||
<ui:param name="growthLabel" value="variation" />
|
||||
<ui:param name="colSize" value="col-12 md:col-4" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,19 +38,23 @@
|
||||
</ui:include>
|
||||
|
||||
<!-- Statistiques membres -->
|
||||
<div class="grid">
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<div class="formgrid grid">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Membres" />
|
||||
<ui:param name="value" value="#{rapportsBean.indicateurs.totalMembres}" />
|
||||
<ui:param name="label" value="Total Membres" />
|
||||
<ui:param name="icon" value="pi pi-users" />
|
||||
<ui:param name="bgColor" value="blue" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/stat-card.xhtml">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Croissance" />
|
||||
<ui:param name="value" value="#{rapportsBean.indicateurs.croissanceMembres}%" />
|
||||
<ui:param name="label" value="Croissance" />
|
||||
<ui:param name="icon" value="pi pi-arrow-up" />
|
||||
<ui:param name="bgColor" value="green" />
|
||||
<ui:param name="icon" value="pi-arrow-up" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -47,88 +47,51 @@
|
||||
<!-- KPIs Système avec grille Freya stricte -->
|
||||
<div class="formgrid grid">
|
||||
<!-- KPI 1: Statut Système -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Statut Système</span>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-check-circle text-green-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">Opérationnel</div>
|
||||
<div class="flex align-items-center">
|
||||
<div class="bg-green-500 border-circle mr-2" style="width: 8px; height: 8px;"></div>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">Uptime</span>
|
||||
<span class="text-500 text-xs">#{configurationBean.tempsActivite}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Statut Système" />
|
||||
<ui:param name="value" value="Opérationnel" />
|
||||
<ui:param name="icon" value="pi-check-circle" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="statusIcon" value="pi-circle-fill" />
|
||||
<ui:param name="statusLabel" value="Uptime" />
|
||||
<ui:param name="statusValue" value="#{configurationBean.tempsActivite}" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 2: Utilisateurs Connectés -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Utilisateurs Actifs</span>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-users text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{configurationBean.utilisateursConnectes}</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-clock text-blue-500 text-sm mr-1"></i>
|
||||
<span class="text-blue-600 font-semibold text-sm mr-2">Sessions</span>
|
||||
<span class="text-500 text-xs">#{configurationBean.sessionsActives} actives</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Utilisateurs Actifs" />
|
||||
<ui:param name="value" value="#{configurationBean.utilisateursConnectes}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="statusIcon" value="pi-clock" />
|
||||
<ui:param name="statusLabel" value="Sessions" />
|
||||
<ui:param name="statusValue" value="#{configurationBean.sessionsActives} actives" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 3: Performance Système -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Performance</span>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-chart-line text-orange-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">CPU #{configurationBean.cpuUtilisation}%</div>
|
||||
<p:progressBar value="#{configurationBean.cpuUtilisation}"
|
||||
showValue="false"
|
||||
styleClass="surface-200 mt-2"
|
||||
style="height: 0.5rem;" />
|
||||
<div class="text-500 text-xs mt-2">Mémoire: #{configurationBean.memoireUtilisee}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Performance" />
|
||||
<ui:param name="value" value="CPU #{configurationBean.cpuUtilisation}%" />
|
||||
<ui:param name="icon" value="pi-chart-line" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="progressValue" value="#{configurationBean.cpuUtilisation}" />
|
||||
<ui:param name="noDataLabel" value="Mémoire: #{configurationBean.memoireUtilisee}%" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 4: Dernière Sauvegarde -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Dernière Sauvegarde</span>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-save text-purple-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-lg mb-2">#{configurationBean.derniereSauvegarde}</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-database text-purple-500 text-sm mr-1"></i>
|
||||
<span class="text-purple-600 font-semibold text-sm mr-2">Auto</span>
|
||||
<span class="text-500 text-xs">#{configurationBean.frequenceSauvegarde}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Dernière Sauvegarde" />
|
||||
<ui:param name="value" value="#{configurationBean.derniereSauvegarde}" />
|
||||
<ui:param name="icon" value="pi-save" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="statusIcon" value="pi-database" />
|
||||
<ui:param name="statusLabel" value="Auto" />
|
||||
<ui:param name="statusValue" value="#{configurationBean.frequenceSauvegarde}" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Générale avec structure Freya -->
|
||||
|
||||
@@ -48,116 +48,51 @@
|
||||
<!-- KPIs Principaux avec grille Freya stricte et alignement parfait -->
|
||||
<div class="formgrid grid">
|
||||
<!-- KPI 1: Membres Actifs -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Membres Actifs</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-users text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-3">#{superAdminBean.totalMembres}</div>
|
||||
<div class="flex align-items-center mb-2" rendered="#{superAdminBean.croissanceMembres != null and superAdminBean.croissanceMembres != '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">+#{superAdminBean.croissanceMembres}%</span>
|
||||
<span class="text-500 text-xs">ce mois</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{superAdminBean.croissanceMembres == null or superAdminBean.croissanceMembres == '0'}">
|
||||
Données non disponibles
|
||||
</div>
|
||||
<p:progressBar value="87"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Organisations</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-sitemap text-green-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-3">#{superAdminBean.totalEntites}</div>
|
||||
<div class="flex align-items-center mb-2" rendered="#{superAdminBean.nouvellesEntites > 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">+#{superAdminBean.nouvellesEntites}</span>
|
||||
<span class="text-500 text-xs">nouvelles</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{superAdminBean.nouvellesEntites == 0}">
|
||||
Aucune nouvelle entité ce mois
|
||||
</div>
|
||||
<p:progressBar value="92"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Revenus (FCFA)</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-dollar text-purple-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-3">#{superAdminBean.revenusGlobaux}</div>
|
||||
<div class="flex align-items-center mb-2" rendered="#{superAdminBean.croissanceRevenus != null and superAdminBean.croissanceRevenus != '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">+#{superAdminBean.croissanceRevenus}%</span>
|
||||
<span class="text-500 text-xs">vs mois dernier</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{superAdminBean.croissanceRevenus == null or superAdminBean.croissanceRevenus == '0'}">
|
||||
Données non disponibles
|
||||
</div>
|
||||
<p:progressBar value="78"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Activité du Jour</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-chart-line text-orange-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-3">#{superAdminBean.activiteJournaliere}</div>
|
||||
<div class="flex align-items-center mb-2" rendered="#{superAdminBean.utilisateursActifs > 0}">
|
||||
<div class="bg-green-500 border-circle mr-2" style="width: 8px; height: 8px;"></div>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">En ligne</span>
|
||||
<span class="text-500 text-xs">#{superAdminBean.utilisateursActifs} actifs</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{superAdminBean.utilisateursActifs == 0}">
|
||||
Aucun utilisateur actif
|
||||
</div>
|
||||
<p:progressBar value="65"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 -->
|
||||
|
||||
@@ -46,85 +46,50 @@
|
||||
<!-- KPIs avec grille Freya stricte -->
|
||||
<div class="formgrid grid">
|
||||
<!-- KPI 1: Total Entités -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Total Entités</span>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-building text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{entitesGestionBean.statistiques.totalEntites}</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-1"></i>
|
||||
<span class="text-green-600 font-semibold text-sm">+8</span>
|
||||
<span class="text-500 text-xs ml-2">ce mois</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Entités" />
|
||||
<ui:param name="value" value="#{entitesGestionBean.statistiques.totalEntites}" />
|
||||
<ui:param name="icon" value="pi-building" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="growthValue" value="8" />
|
||||
<ui:param name="growthLabel" value="ce mois" />
|
||||
<ui:param name="growthType" value="number" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 2: Entités Actives -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Entités Actives</span>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-check text-green-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{entitesGestionBean.statistiques.entitesActives}</div>
|
||||
<p:progressBar value="92"
|
||||
showValue="false"
|
||||
styleClass="surface-200 mt-2"
|
||||
style="height: 0.5rem;" />
|
||||
<div class="text-500 text-xs mt-2">92% d'activité</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Entités Actives" />
|
||||
<ui:param name="value" value="#{entitesGestionBean.statistiques.entitesActives}" />
|
||||
<ui:param name="icon" value="pi-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="progressValue" value="92" />
|
||||
<ui:param name="noDataLabel" value="92% d'activité" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 3: Total Membres -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Total Membres</span>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-users text-orange-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{entitesGestionBean.statistiques.totalMembres}</div>
|
||||
<div class="text-orange-600 font-semibold text-sm">
|
||||
Moyenne: #{entitesGestionBean.statistiques.moyenneMembresParEntite}/entité
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Membres" />
|
||||
<ui:param name="value" value="#{entitesGestionBean.statistiques.totalMembres}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="statusIcon" value="pi-info-circle" />
|
||||
<ui:param name="statusLabel" value="Moyenne" />
|
||||
<ui:param name="statusValue" value="#{entitesGestionBean.statistiques.moyenneMembresParEntite}/entité" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 4: Revenus Totaux -->
|
||||
<div class="field col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-50 border-round-lg">
|
||||
<div class="p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">Revenus Totaux</span>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi pi-dollar text-purple-600 text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-bold text-xl mb-2">#{entitesGestionBean.statistiques.revenus}</div>
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-1"></i>
|
||||
<span class="text-green-600 text-sm">+15% vs année dernière</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Revenus Totaux" />
|
||||
<ui:param name="value" value="#{entitesGestionBean.statistiques.revenus}" />
|
||||
<ui:param name="icon" value="pi-dollar" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="growthValue" value="15" />
|
||||
<ui:param name="growthLabel" value="vs année dernière" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Section Filtres avec structure Freya -->
|
||||
|
||||
@@ -10,15 +10,34 @@
|
||||
|
||||
<ui:fragment rendered="#{empty rendered or rendered}">
|
||||
<ui:fragment rendered="#{empty outcome}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
type="button"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-info #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
<ui:fragment rendered="#{not empty update and update != 'none'}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
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
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
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 : ''}"
|
||||
title="#{title}" />
|
||||
</ui:fragment>
|
||||
</ui:fragment>
|
||||
<ui:fragment rendered="#{not empty outcome}">
|
||||
<p:button
|
||||
|
||||
@@ -20,22 +20,37 @@
|
||||
|
||||
<ui:fragment rendered="#{empty rendered or rendered}">
|
||||
<ui:fragment rendered="#{empty outcome}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
type="button"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-primary #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
<ui:fragment rendered="#{not empty update and update != 'none'}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
oncomplete="#{oncomplete}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-primary #{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
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
ajax="false"
|
||||
onclick="#{onclick}"
|
||||
oncomplete="#{oncomplete}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-primary #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
</ui:fragment>
|
||||
</ui:fragment>
|
||||
<ui:fragment rendered="#{not empty outcome}">
|
||||
<p:button
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
outcome="#{outcome}"
|
||||
styleClass="ui-button-primary #{styleClass}"
|
||||
styleClass="ui-button-primary #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
</ui:fragment>
|
||||
</ui:fragment>
|
||||
|
||||
@@ -21,15 +21,32 @@
|
||||
|
||||
<ui:fragment rendered="#{empty rendered or rendered}">
|
||||
<ui:fragment rendered="#{empty outcome}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-secondary #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
<ui:fragment rendered="#{not empty update and update != 'none'}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
oncomplete="#{oncomplete}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
rendered="#{empty rendered or rendered}"
|
||||
styleClass="ui-button-secondary #{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
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
ajax="false"
|
||||
onclick="#{onclick}"
|
||||
oncomplete="#{oncomplete}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
rendered="#{empty rendered or rendered}"
|
||||
styleClass="ui-button-secondary #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
</ui:fragment>
|
||||
</ui:fragment>
|
||||
<ui:fragment rendered="#{not empty outcome}">
|
||||
<p:button
|
||||
|
||||
@@ -10,14 +10,32 @@
|
||||
|
||||
<ui:fragment rendered="#{empty rendered or rendered}">
|
||||
<ui:fragment rendered="#{empty outcome}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-success #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
<ui:fragment rendered="#{not empty update and update != 'none'}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
oncomplete="#{oncomplete}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
rendered="#{empty rendered or rendered}"
|
||||
styleClass="ui-button-success #{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
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
ajax="false"
|
||||
onclick="#{onclick}"
|
||||
oncomplete="#{oncomplete}"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
rendered="#{empty rendered or rendered}"
|
||||
styleClass="ui-button-success #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
</ui:fragment>
|
||||
</ui:fragment>
|
||||
<ui:fragment rendered="#{not empty outcome}">
|
||||
<p:button
|
||||
|
||||
@@ -10,15 +10,34 @@
|
||||
|
||||
<ui:fragment rendered="#{empty rendered or rendered}">
|
||||
<ui:fragment rendered="#{empty outcome}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
update="#{update}"
|
||||
onclick="#{onclick}"
|
||||
type="button"
|
||||
disabled="#{not empty disabled and disabled}"
|
||||
styleClass="ui-button-warning #{not empty outlined and outlined ? 'ui-button-outlined' : ''} #{not empty styleClass ? styleClass : ''}"
|
||||
title="#{title}" />
|
||||
<ui:fragment rendered="#{not empty update and update != 'none'}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
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
|
||||
value="#{value}"
|
||||
icon="#{icon}"
|
||||
action="#{action}"
|
||||
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 : ''}"
|
||||
title="#{title}" />
|
||||
</ui:fragment>
|
||||
</ui:fragment>
|
||||
<ui:fragment rendered="#{not empty outcome}">
|
||||
<p:button
|
||||
|
||||
@@ -47,8 +47,14 @@
|
||||
<p:menuitem id="m_liste_membres" value="Liste des Membres" icon="pi pi-list" outcome="/pages/secure/membre/liste" />
|
||||
<p:menuitem id="m_recherche_membres" value="Recherche Avancée" icon="pi pi-search" outcome="/pages/secure/membre/recherche" />
|
||||
<p:menuitem id="m_profil_membre" value="Mon Profil" icon="pi pi-user" outcome="/pages/secure/membre/profil" />
|
||||
<p:menuitem id="m_import_membres" value="Import en Masse" icon="pi pi-upload" url="#" />
|
||||
<p:menuitem id="m_export_membres" value="Export Membres" icon="pi pi-download" url="#" />
|
||||
<p:menuitem id="m_import_membres" value="Import en Masse" icon="pi pi-upload" outcome="/pages/secure/membre/import" />
|
||||
<p:menuitem id="m_export_membres" value="Export Membres" icon="pi pi-download" outcome="/pages/secure/membre/export" />
|
||||
<p:separator />
|
||||
<!-- Lions User Manager - Gestion Keycloak -->
|
||||
<p:menuitem id="m_user_manager_list" value="Utilisateurs Keycloak" icon="pi pi-users-cog" outcome="/pages/user-manager/users/list" />
|
||||
<p:menuitem id="m_user_manager_create" value="Nouvel Utilisateur" icon="pi pi-user-plus" outcome="/pages/user-manager/users/create" />
|
||||
<p:menuitem id="m_user_manager_roles" value="Gestion des Rôles" icon="pi pi-shield" outcome="/pages/user-manager/roles/list" />
|
||||
<p:menuitem id="m_user_manager_audit" value="Journal d'Audit" icon="pi pi-history" outcome="/pages/user-manager/audit/logs" />
|
||||
</p:submenu>
|
||||
|
||||
<!-- Gestion des Organisations -->
|
||||
|
||||
@@ -1,14 +1,101 @@
|
||||
# Configuration UnionFlow Client - Profil Production
|
||||
# Ce fichier est chargé automatiquement quand le profil 'prod' est actif
|
||||
# Configuration UnionFlow Client - PRODUCTION
|
||||
# Ce fichier est utilisé avec le profil Quarkus "prod"
|
||||
|
||||
# Configuration logging pour production
|
||||
quarkus.log.console.level=WARN
|
||||
# Configuration HTTP
|
||||
quarkus.http.port=8086
|
||||
quarkus.http.host=0.0.0.0
|
||||
quarkus.http.root-path=/
|
||||
quarkus.http.so-reuse-port=true
|
||||
quarkus.http.tcp-quick-ack=true
|
||||
quarkus.http.tcp-cork=true
|
||||
|
||||
# Configuration MyFaces pour production
|
||||
# Configuration Session HTTP - Production
|
||||
quarkus.http.session-timeout=60m
|
||||
quarkus.http.session-cookie-same-site=strict
|
||||
quarkus.http.session-cookie-http-only=true
|
||||
quarkus.http.session-cookie-secure=true
|
||||
|
||||
# Configuration logging - Production
|
||||
quarkus.log.console.enable=true
|
||||
quarkus.log.console.level=INFO
|
||||
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
|
||||
quarkus.log.category."dev.lions.unionflow".level=INFO
|
||||
quarkus.log.category."org.primefaces".level=WARN
|
||||
quarkus.log.category."org.apache.myfaces".level=WARN
|
||||
|
||||
# MyFaces Configuration - Production
|
||||
quarkus.myfaces.project-stage=Production
|
||||
quarkus.myfaces.serialize-state-in-session=true
|
||||
quarkus.myfaces.state-saving-method=server
|
||||
quarkus.myfaces.number-of-views-in-session=50
|
||||
quarkus.myfaces.number-of-sequential-views-in-session=10
|
||||
quarkus.myfaces.serialize-state-in-session=false
|
||||
quarkus.myfaces.client-view-state-timeout=3600000
|
||||
quarkus.myfaces.view-expired-exception-handler-redirect-page=/
|
||||
quarkus.myfaces.check-id-production-mode=true
|
||||
quarkus.myfaces.strict-xhtml-links=true
|
||||
quarkus.myfaces.refresh-transient-build-on-pss=true
|
||||
quarkus.myfaces.resource-max-time-expires=604800000
|
||||
quarkus.myfaces.resource-buffer-size=2048
|
||||
|
||||
# Configuration Keycloak pour production
|
||||
%prod.quarkus.oidc.tls.verification=required
|
||||
%prod.quarkus.oidc.authentication.redirect-path=/auth/callback
|
||||
# PrimeFaces Configuration - Production
|
||||
primefaces.THEME=none
|
||||
primefaces.FONT_AWESOME=true
|
||||
primefaces.CLIENT_SIDE_VALIDATION=true
|
||||
primefaces.MOVE_SCRIPTS_TO_BOTTOM=true
|
||||
primefaces.CSP=true
|
||||
primefaces.UPLOADER=commons
|
||||
primefaces.AUTO_UPDATE=false
|
||||
primefaces.CACHE_PROVIDER=org.primefaces.cache.DefaultCacheProvider
|
||||
primefaces.RESOURCE_HANDLER=org.primefaces.application.resource.PrimeResourceHandler
|
||||
|
||||
# OmniFaces Configuration - Production
|
||||
omnifaces.CDN_RESOURCE_HANDLER_DISABLED=true
|
||||
omnifaces.COMBINED_RESOURCE_HANDLER_DISABLED=false
|
||||
|
||||
# Configuration Backend UnionFlow - Production
|
||||
unionflow.backend.url=${UNIONFLOW_BACKEND_URL:https://api.lions.dev/unionflow}
|
||||
|
||||
# Configuration REST Client - Production
|
||||
quarkus.rest-client."unionflow-api".url=${unionflow.backend.url}
|
||||
quarkus.rest-client."unionflow-api".scope=jakarta.inject.Singleton
|
||||
quarkus.rest-client."unionflow-api".connect-timeout=5000
|
||||
quarkus.rest-client."unionflow-api".read-timeout=30000
|
||||
quarkus.rest-client."unionflow-api".providers=dev.lions.unionflow.client.service.RestClientExceptionMapper,dev.lions.unionflow.client.security.JwtClientRequestFilter
|
||||
|
||||
# Configuration Keycloak OIDC - Production
|
||||
quarkus.oidc.enabled=true
|
||||
quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow}
|
||||
quarkus.oidc.client-id=unionflow-client
|
||||
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
|
||||
quarkus.oidc.application-type=web-app
|
||||
quarkus.oidc.authentication.redirect-path=/auth/callback
|
||||
quarkus.oidc.authentication.restore-path-after-redirect=true
|
||||
quarkus.oidc.authentication.scopes=openid,profile,email,roles
|
||||
quarkus.oidc.token.issuer=https://security.lions.dev/realms/unionflow
|
||||
quarkus.oidc.tls.verification=required
|
||||
quarkus.oidc.authentication.cookie-same-site=strict
|
||||
quarkus.oidc.authentication.java-script-auto-redirect=false
|
||||
quarkus.oidc.discovery-enabled=true
|
||||
quarkus.oidc.verify-access-token=true
|
||||
|
||||
# Activation de la sécurité
|
||||
quarkus.security.auth.enabled=true
|
||||
|
||||
# Chemins publics (non protégés par OIDC) - Production
|
||||
quarkus.http.auth.permission.public.paths=/,/index.xhtml,/pages/public/*,/auth/*,/q/*,/q/oidc/*,/favicon.ico,/resources/*,/META-INF/resources/*,/images/*,/jakarta.faces.resource/*,/javax.faces.resource/*
|
||||
quarkus.http.auth.permission.public.policy=permit
|
||||
|
||||
# Tous les autres chemins nécessitent une authentification
|
||||
quarkus.http.auth.permission.authenticated.paths=/*
|
||||
quarkus.http.auth.permission.authenticated.policy=authenticated
|
||||
|
||||
# Configuration Session - Production
|
||||
unionflow.session.timeout=${SESSION_TIMEOUT:1800}
|
||||
unionflow.session.remember-me.duration=${REMEMBER_ME_DURATION:604800}
|
||||
|
||||
# Configuration de sécurité - Production
|
||||
unionflow.security.enable-csrf=${ENABLE_CSRF:true}
|
||||
unionflow.security.password.min-length=${PASSWORD_MIN_LENGTH:8}
|
||||
unionflow.security.password.require-special-chars=${PASSWORD_REQUIRE_SPECIAL:true}
|
||||
unionflow.security.max-login-attempts=${MAX_LOGIN_ATTEMPTS:5}
|
||||
unionflow.security.lockout-duration=${LOCKOUT_DURATION:300}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.lions.unionflow.server.api.dto.membre;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -56,10 +57,12 @@ public class MembreSearchResultDTO {
|
||||
|
||||
/** Indique si c'est la première page */
|
||||
@Schema(description = "Indique si c'est la première page")
|
||||
@JsonProperty("isFirst")
|
||||
private boolean isFirst;
|
||||
|
||||
/** Indique si c'est la dernière page */
|
||||
@Schema(description = "Indique si c'est la dernière page")
|
||||
@JsonProperty("isLast")
|
||||
private boolean isLast;
|
||||
|
||||
/** Critères de recherche utilisés */
|
||||
@@ -181,19 +184,42 @@ public class MembreSearchResultDTO {
|
||||
* @return Résultat vide
|
||||
*/
|
||||
public static MembreSearchResultDTO empty(MembreSearchCriteria criteria) {
|
||||
return empty(criteria, 20, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method pour créer un résultat vide avec pageSize spécifique
|
||||
*
|
||||
* @param criteria Critères de recherche
|
||||
* @param pageSize Taille de la page
|
||||
* @param currentPage Page actuelle
|
||||
* @return Résultat vide
|
||||
*/
|
||||
public static MembreSearchResultDTO empty(MembreSearchCriteria criteria, int pageSize, int currentPage) {
|
||||
MembreSearchResultDTO result = new MembreSearchResultDTO();
|
||||
result.setMembres(List.of());
|
||||
result.setTotalElements(0L);
|
||||
result.setTotalPages(0);
|
||||
result.setCurrentPage(0);
|
||||
result.setPageSize(20);
|
||||
result.setCurrentPage(currentPage);
|
||||
result.setPageSize(pageSize);
|
||||
result.setNumberOfElements(0);
|
||||
result.setHasNext(false);
|
||||
result.setHasPrevious(false);
|
||||
result.setFirst(true);
|
||||
result.setLast(true);
|
||||
result.isFirst = true; // Assignation directe pour éviter les problèmes avec les setters Lombok
|
||||
result.isLast = true; // Assignation directe pour éviter les problèmes avec les setters Lombok
|
||||
result.setCriteria(criteria);
|
||||
result.setExecutionTimeMs(0L);
|
||||
// Initialiser statistics avec des valeurs vides
|
||||
result.setStatistics(SearchStatistics.builder()
|
||||
.membresActifs(0)
|
||||
.membresInactifs(0)
|
||||
.ageMoyen(0.0)
|
||||
.ageMin(0)
|
||||
.ageMax(0)
|
||||
.nombreOrganisations(0)
|
||||
.nombreRegions(0)
|
||||
.ancienneteMoyenne(0.0)
|
||||
.build());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,35 @@
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI pour Excel -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml-lite</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons CSV pour CSV -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
@@ -187,6 +216,20 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Maven Surefire pour les tests -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<!-- Exclure les migrations Flyway du classpath des tests -->
|
||||
<quarkus.flyway.enabled>false</quarkus.flyway.enabled>
|
||||
<quarkus.flyway.migrate-at-start>false</quarkus.flyway.migrate-at-start>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Jacoco pour la couverture de code -->
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
|
||||
@@ -78,6 +78,10 @@ public abstract class BaseRepository<T extends BaseEntity> {
|
||||
*/
|
||||
@Transactional
|
||||
public void delete(T entity) {
|
||||
// Si l'entité n'est pas dans le contexte de persistance, la merger d'abord
|
||||
if (!entityManager.contains(entity)) {
|
||||
entity = entityManager.merge(entity);
|
||||
}
|
||||
entityManager.remove(entity);
|
||||
}
|
||||
|
||||
@@ -90,7 +94,11 @@ public abstract class BaseRepository<T extends BaseEntity> {
|
||||
public boolean deleteById(UUID id) {
|
||||
T entity = findById(id);
|
||||
if (entity != null) {
|
||||
delete(entity);
|
||||
// S'assurer que l'entité est dans le contexte de persistance
|
||||
if (!entityManager.contains(entity)) {
|
||||
entity = entityManager.merge(entity);
|
||||
}
|
||||
entityManager.remove(entity);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -15,6 +15,9 @@ import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -357,7 +360,7 @@ public class MembreResource {
|
||||
public Response searchMembresAdvanced(
|
||||
@RequestBody(
|
||||
description = "Critères de recherche avancée",
|
||||
required = true,
|
||||
required = false,
|
||||
content =
|
||||
@Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
@@ -378,7 +381,6 @@ public class MembreResource {
|
||||
"includeInactifs": false
|
||||
}
|
||||
""")))
|
||||
@Valid
|
||||
MembreSearchCriteria criteria,
|
||||
@Parameter(description = "Numéro de page (0-based)", example = "0")
|
||||
@QueryParam("page")
|
||||
@@ -399,18 +401,19 @@ public class MembreResource {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
LOG.infof(
|
||||
"Recherche avancée de membres - critères: %s, page: %d, size: %d",
|
||||
criteria.getDescription(), page, size);
|
||||
|
||||
try {
|
||||
// Validation des critères
|
||||
if (criteria == null) {
|
||||
LOG.warn("Recherche avancée de membres - critères null rejetés");
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Les critères de recherche sont requis"))
|
||||
.build();
|
||||
}
|
||||
|
||||
LOG.infof(
|
||||
"Recherche avancée de membres - critères: %s, page: %d, size: %d",
|
||||
criteria.getDescription(), page, size);
|
||||
|
||||
// Nettoyage et validation des critères
|
||||
criteria.sanitize();
|
||||
|
||||
@@ -449,6 +452,11 @@ public class MembreResource {
|
||||
|
||||
return Response.ok(result).build();
|
||||
|
||||
} catch (jakarta.validation.ConstraintViolationException e) {
|
||||
LOG.warnf("Erreur de validation Jakarta dans la recherche avancée: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("message", "Critères de recherche invalides", "details", e.getMessage()))
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warnf("Erreur de validation dans la recherche avancée: %s", e.getMessage());
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
@@ -485,4 +493,151 @@ public class MembreResource {
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/import")
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Importer des membres depuis un fichier Excel ou CSV")
|
||||
@APIResponse(responseCode = "200", description = "Import terminé")
|
||||
public Response importerMembres(
|
||||
@Parameter(description = "Contenu du fichier à importer") @FormParam("file") byte[] fileContent,
|
||||
@Parameter(description = "Nom du fichier") @FormParam("fileName") String fileName,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @FormParam("organisationId") UUID organisationId,
|
||||
@Parameter(description = "Type de membre par défaut") @FormParam("typeMembreDefaut") String typeMembreDefaut,
|
||||
@Parameter(description = "Mettre à jour les membres existants") @FormParam("mettreAJourExistants") boolean mettreAJourExistants,
|
||||
@Parameter(description = "Ignorer les erreurs") @FormParam("ignorerErreurs") boolean ignorerErreurs) {
|
||||
|
||||
try {
|
||||
if (fileContent == null || fileContent.length == 0) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "Aucun fichier fourni"))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
fileName = "import.xlsx";
|
||||
}
|
||||
|
||||
if (typeMembreDefaut == null || typeMembreDefaut.isEmpty()) {
|
||||
typeMembreDefaut = "ACTIF";
|
||||
}
|
||||
|
||||
InputStream fileInputStream = new java.io.ByteArrayInputStream(fileContent);
|
||||
dev.lions.unionflow.server.service.MembreImportExportService.ResultatImport resultat = membreService.importerMembres(
|
||||
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("totalLignes", resultat.totalLignes);
|
||||
response.put("lignesTraitees", resultat.lignesTraitees);
|
||||
response.put("lignesErreur", resultat.lignesErreur);
|
||||
response.put("erreurs", resultat.erreurs);
|
||||
response.put("membresImportes", resultat.membresImportes);
|
||||
|
||||
return Response.ok(response).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'import");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'import: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@Operation(summary = "Exporter des membres en Excel, CSV ou PDF")
|
||||
@APIResponse(responseCode = "200", description = "Fichier exporté")
|
||||
public Response exporterMembres(
|
||||
@Parameter(description = "Format d'export") @QueryParam("format") @DefaultValue("EXCEL") String format,
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
|
||||
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Type de membre") @QueryParam("type") String type,
|
||||
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin,
|
||||
@Parameter(description = "Colonnes à exporter") @QueryParam("colonnes") List<String> colonnesExportList,
|
||||
@Parameter(description = "Inclure les en-têtes") @QueryParam("inclureHeaders") @DefaultValue("true") boolean inclureHeaders,
|
||||
@Parameter(description = "Formater les dates") @QueryParam("formaterDates") @DefaultValue("true") boolean formaterDates,
|
||||
@Parameter(description = "Inclure un onglet statistiques (Excel uniquement)") @QueryParam("inclureStatistiques") @DefaultValue("false") boolean inclureStatistiques,
|
||||
@Parameter(description = "Mot de passe pour chiffrer le fichier (optionnel)") @QueryParam("motDePasse") String motDePasse) {
|
||||
|
||||
try {
|
||||
// Récupérer les membres selon les filtres
|
||||
List<MembreDTO> membres = membreService.listerMembresPourExport(
|
||||
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
|
||||
|
||||
byte[] exportData;
|
||||
String contentType;
|
||||
String extension;
|
||||
|
||||
List<String> colonnesExport = colonnesExportList != null ? colonnesExportList : new ArrayList<>();
|
||||
|
||||
if ("CSV".equalsIgnoreCase(format)) {
|
||||
exportData = membreService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
|
||||
contentType = "text/csv";
|
||||
extension = "csv";
|
||||
} else {
|
||||
// Pour Excel, inclure les statistiques uniquement si demandé et si format Excel
|
||||
boolean stats = inclureStatistiques && "EXCEL".equalsIgnoreCase(format);
|
||||
exportData = membreService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, stats, motDePasse);
|
||||
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
extension = "xlsx";
|
||||
}
|
||||
|
||||
return Response.ok(exportData)
|
||||
.type(contentType)
|
||||
.header("Content-Disposition", "attachment; filename=\"membres_export_" +
|
||||
java.time.LocalDate.now() + "." + extension + "\"")
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de l'export: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/import/modele")
|
||||
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
@Operation(summary = "Télécharger le modèle Excel pour l'import")
|
||||
@APIResponse(responseCode = "200", description = "Modèle Excel généré")
|
||||
public Response telechargerModeleImport() {
|
||||
try {
|
||||
byte[] modele = membreService.genererModeleImport();
|
||||
return Response.ok(modele)
|
||||
.header("Content-Disposition", "attachment; filename=\"modele_import_membres.xlsx\"")
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la génération du modèle");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors de la génération du modèle: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Compter les membres selon les filtres pour l'export")
|
||||
@APIResponse(responseCode = "200", description = "Nombre de membres correspondant aux critères")
|
||||
public Response compterMembresPourExport(
|
||||
@Parameter(description = "ID de l'organisation (optionnel)") @QueryParam("associationId") UUID associationId,
|
||||
@Parameter(description = "Statut des membres") @QueryParam("statut") String statut,
|
||||
@Parameter(description = "Type de membre") @QueryParam("type") String type,
|
||||
@Parameter(description = "Date adhésion début") @QueryParam("dateAdhesionDebut") String dateAdhesionDebut,
|
||||
@Parameter(description = "Date adhésion fin") @QueryParam("dateAdhesionFin") String dateAdhesionFin) {
|
||||
|
||||
try {
|
||||
List<MembreDTO> membres = membreService.listerMembresPourExport(
|
||||
associationId, statut, type, dateAdhesionDebut, dateAdhesionFin);
|
||||
|
||||
return Response.ok(Map.of("count", membres.size())).build();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du comptage des membres");
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("error", "Erreur lors du comptage: " + e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,842 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Service pour l'import et l'export de membres depuis/vers Excel et CSV
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class MembreImportExportService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MembreImportExportService.class);
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy");
|
||||
|
||||
@Inject
|
||||
MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
@Inject
|
||||
MembreService membreService;
|
||||
|
||||
/**
|
||||
* Importe des membres depuis un fichier Excel ou CSV
|
||||
*/
|
||||
@Transactional
|
||||
public ResultatImport importerMembres(
|
||||
InputStream fileInputStream,
|
||||
String fileName,
|
||||
UUID organisationId,
|
||||
String typeMembreDefaut,
|
||||
boolean mettreAJourExistants,
|
||||
boolean ignorerErreurs) {
|
||||
|
||||
LOG.infof("Import de membres depuis le fichier: %s", fileName);
|
||||
|
||||
ResultatImport resultat = new ResultatImport();
|
||||
resultat.erreurs = new ArrayList<>();
|
||||
resultat.membresImportes = new ArrayList<>();
|
||||
|
||||
try {
|
||||
if (fileName.toLowerCase().endsWith(".csv")) {
|
||||
return importerDepuisCSV(fileInputStream, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
|
||||
} else if (fileName.toLowerCase().endsWith(".xlsx") || fileName.toLowerCase().endsWith(".xls")) {
|
||||
return importerDepuisExcel(fileInputStream, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Format de fichier non supporté. Formats acceptés: .xlsx, .xls, .csv");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'import");
|
||||
resultat.erreurs.add("Erreur générale: " + e.getMessage());
|
||||
return resultat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe depuis un fichier Excel
|
||||
*/
|
||||
private ResultatImport importerDepuisExcel(
|
||||
InputStream fileInputStream,
|
||||
UUID organisationId,
|
||||
String typeMembreDefaut,
|
||||
boolean mettreAJourExistants,
|
||||
boolean ignorerErreurs) throws IOException {
|
||||
|
||||
ResultatImport resultat = new ResultatImport();
|
||||
resultat.erreurs = new ArrayList<>();
|
||||
resultat.membresImportes = new ArrayList<>();
|
||||
int ligneNum = 0;
|
||||
|
||||
try (Workbook workbook = new XSSFWorkbook(fileInputStream)) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
Row headerRow = sheet.getRow(0);
|
||||
|
||||
if (headerRow == null) {
|
||||
throw new IllegalArgumentException("Le fichier Excel est vide ou n'a pas d'en-têtes");
|
||||
}
|
||||
|
||||
// Mapper les colonnes
|
||||
Map<String, Integer> colonnes = mapperColonnes(headerRow);
|
||||
|
||||
// Vérifier les colonnes obligatoires
|
||||
if (!colonnes.containsKey("nom") || !colonnes.containsKey("prenom") ||
|
||||
!colonnes.containsKey("email") || !colonnes.containsKey("telephone")) {
|
||||
throw new IllegalArgumentException("Colonnes obligatoires manquantes: nom, prenom, email, telephone");
|
||||
}
|
||||
|
||||
// Lire les données
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
ligneNum = i + 1;
|
||||
Row row = sheet.getRow(i);
|
||||
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
Membre membre = lireLigneExcel(row, colonnes, organisationId, typeMembreDefaut);
|
||||
|
||||
// Vérifier si le membre existe déjà
|
||||
Optional<Membre> membreExistant = membreRepository.findByEmail(membre.getEmail());
|
||||
|
||||
if (membreExistant.isPresent()) {
|
||||
if (mettreAJourExistants) {
|
||||
Membre existant = membreExistant.get();
|
||||
existant.setNom(membre.getNom());
|
||||
existant.setPrenom(membre.getPrenom());
|
||||
existant.setTelephone(membre.getTelephone());
|
||||
existant.setDateNaissance(membre.getDateNaissance());
|
||||
if (membre.getOrganisation() != null) {
|
||||
existant.setOrganisation(membre.getOrganisation());
|
||||
}
|
||||
membreRepository.persist(existant);
|
||||
resultat.membresImportes.add(membreService.convertToDTO(existant));
|
||||
resultat.lignesTraitees++;
|
||||
} else {
|
||||
resultat.erreurs.add(String.format("Ligne %d: Membre avec email %s existe déjà", ligneNum, membre.getEmail()));
|
||||
if (!ignorerErreurs) {
|
||||
throw new IllegalArgumentException("Membre existant trouvé et mise à jour désactivée");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
membre = membreService.creerMembre(membre);
|
||||
resultat.membresImportes.add(membreService.convertToDTO(membre));
|
||||
resultat.lignesTraitees++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String erreur = String.format("Ligne %d: %s", ligneNum, e.getMessage());
|
||||
resultat.erreurs.add(erreur);
|
||||
resultat.lignesErreur++;
|
||||
|
||||
if (!ignorerErreurs) {
|
||||
throw new RuntimeException(erreur, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultat.totalLignes = sheet.getLastRowNum();
|
||||
}
|
||||
|
||||
LOG.infof("Import terminé: %d lignes traitées, %d erreurs", resultat.lignesTraitees, resultat.lignesErreur);
|
||||
return resultat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe depuis un fichier CSV
|
||||
*/
|
||||
private ResultatImport importerDepuisCSV(
|
||||
InputStream fileInputStream,
|
||||
UUID organisationId,
|
||||
String typeMembreDefaut,
|
||||
boolean mettreAJourExistants,
|
||||
boolean ignorerErreurs) throws IOException {
|
||||
|
||||
ResultatImport resultat = new ResultatImport();
|
||||
resultat.erreurs = new ArrayList<>();
|
||||
resultat.membresImportes = new ArrayList<>();
|
||||
|
||||
try (InputStreamReader reader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8)) {
|
||||
Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build().parse(reader);
|
||||
|
||||
int ligneNum = 0;
|
||||
for (CSVRecord record : records) {
|
||||
ligneNum++;
|
||||
|
||||
try {
|
||||
Membre membre = lireLigneCSV(record, organisationId, typeMembreDefaut);
|
||||
|
||||
// Vérifier si le membre existe déjà
|
||||
Optional<Membre> membreExistant = membreRepository.findByEmail(membre.getEmail());
|
||||
|
||||
if (membreExistant.isPresent()) {
|
||||
if (mettreAJourExistants) {
|
||||
Membre existant = membreExistant.get();
|
||||
existant.setNom(membre.getNom());
|
||||
existant.setPrenom(membre.getPrenom());
|
||||
existant.setTelephone(membre.getTelephone());
|
||||
existant.setDateNaissance(membre.getDateNaissance());
|
||||
if (membre.getOrganisation() != null) {
|
||||
existant.setOrganisation(membre.getOrganisation());
|
||||
}
|
||||
membreRepository.persist(existant);
|
||||
resultat.membresImportes.add(membreService.convertToDTO(existant));
|
||||
resultat.lignesTraitees++;
|
||||
} else {
|
||||
resultat.erreurs.add(String.format("Ligne %d: Membre avec email %s existe déjà", ligneNum, membre.getEmail()));
|
||||
if (!ignorerErreurs) {
|
||||
throw new IllegalArgumentException("Membre existant trouvé et mise à jour désactivée");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
membre = membreService.creerMembre(membre);
|
||||
resultat.membresImportes.add(membreService.convertToDTO(membre));
|
||||
resultat.lignesTraitees++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String erreur = String.format("Ligne %d: %s", ligneNum, e.getMessage());
|
||||
resultat.erreurs.add(erreur);
|
||||
resultat.lignesErreur++;
|
||||
|
||||
if (!ignorerErreurs) {
|
||||
throw new RuntimeException(erreur, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultat.totalLignes = ligneNum;
|
||||
}
|
||||
|
||||
LOG.infof("Import CSV terminé: %d lignes traitées, %d erreurs", resultat.lignesTraitees, resultat.lignesErreur);
|
||||
return resultat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit une ligne Excel et crée un membre
|
||||
*/
|
||||
private Membre lireLigneExcel(Row row, Map<String, Integer> colonnes, UUID organisationId, String typeMembreDefaut) {
|
||||
Membre membre = new Membre();
|
||||
|
||||
// Colonnes obligatoires
|
||||
String nom = getCellValueAsString(row, colonnes.get("nom"));
|
||||
String prenom = getCellValueAsString(row, colonnes.get("prenom"));
|
||||
String email = getCellValueAsString(row, colonnes.get("email"));
|
||||
String telephone = getCellValueAsString(row, colonnes.get("telephone"));
|
||||
|
||||
if (nom == null || nom.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le nom est obligatoire");
|
||||
}
|
||||
if (prenom == null || prenom.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le prénom est obligatoire");
|
||||
}
|
||||
if (email == null || email.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("L'email est obligatoire");
|
||||
}
|
||||
if (telephone == null || telephone.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le téléphone est obligatoire");
|
||||
}
|
||||
|
||||
membre.setNom(nom.trim());
|
||||
membre.setPrenom(prenom.trim());
|
||||
membre.setEmail(email.trim().toLowerCase());
|
||||
membre.setTelephone(telephone.trim());
|
||||
|
||||
// Colonnes optionnelles
|
||||
if (colonnes.containsKey("date_naissance")) {
|
||||
LocalDate dateNaissance = getCellValueAsDate(row, colonnes.get("date_naissance"));
|
||||
if (dateNaissance != null) {
|
||||
membre.setDateNaissance(dateNaissance);
|
||||
}
|
||||
}
|
||||
if (membre.getDateNaissance() == null) {
|
||||
membre.setDateNaissance(LocalDate.now().minusYears(18));
|
||||
}
|
||||
|
||||
if (colonnes.containsKey("date_adhesion")) {
|
||||
LocalDate dateAdhesion = getCellValueAsDate(row, colonnes.get("date_adhesion"));
|
||||
if (dateAdhesion != null) {
|
||||
membre.setDateAdhesion(dateAdhesion);
|
||||
}
|
||||
}
|
||||
if (membre.getDateAdhesion() == null) {
|
||||
membre.setDateAdhesion(LocalDate.now());
|
||||
}
|
||||
|
||||
// Organisation
|
||||
if (organisationId != null) {
|
||||
Optional<Organisation> org = organisationRepository.findByIdOptional(organisationId);
|
||||
if (org.isPresent()) {
|
||||
membre.setOrganisation(org.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Statut par défaut
|
||||
membre.setActif(typeMembreDefaut == null || typeMembreDefaut.isEmpty() || "ACTIF".equals(typeMembreDefaut));
|
||||
|
||||
return membre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit une ligne CSV et crée un membre
|
||||
*/
|
||||
private Membre lireLigneCSV(CSVRecord record, UUID organisationId, String typeMembreDefaut) {
|
||||
Membre membre = new Membre();
|
||||
|
||||
// Colonnes obligatoires
|
||||
String nom = record.get("nom");
|
||||
String prenom = record.get("prenom");
|
||||
String email = record.get("email");
|
||||
String telephone = record.get("telephone");
|
||||
|
||||
if (nom == null || nom.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le nom est obligatoire");
|
||||
}
|
||||
if (prenom == null || prenom.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le prénom est obligatoire");
|
||||
}
|
||||
if (email == null || email.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("L'email est obligatoire");
|
||||
}
|
||||
if (telephone == null || telephone.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Le téléphone est obligatoire");
|
||||
}
|
||||
|
||||
membre.setNom(nom.trim());
|
||||
membre.setPrenom(prenom.trim());
|
||||
membre.setEmail(email.trim().toLowerCase());
|
||||
membre.setTelephone(telephone.trim());
|
||||
|
||||
// Colonnes optionnelles
|
||||
try {
|
||||
String dateNaissanceStr = record.get("date_naissance");
|
||||
if (dateNaissanceStr != null && !dateNaissanceStr.trim().isEmpty()) {
|
||||
membre.setDateNaissance(parseDate(dateNaissanceStr));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignorer si la date est invalide
|
||||
}
|
||||
if (membre.getDateNaissance() == null) {
|
||||
membre.setDateNaissance(LocalDate.now().minusYears(18));
|
||||
}
|
||||
|
||||
try {
|
||||
String dateAdhesionStr = record.get("date_adhesion");
|
||||
if (dateAdhesionStr != null && !dateAdhesionStr.trim().isEmpty()) {
|
||||
membre.setDateAdhesion(parseDate(dateAdhesionStr));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignorer si la date est invalide
|
||||
}
|
||||
if (membre.getDateAdhesion() == null) {
|
||||
membre.setDateAdhesion(LocalDate.now());
|
||||
}
|
||||
|
||||
// Organisation
|
||||
if (organisationId != null) {
|
||||
Optional<Organisation> org = organisationRepository.findByIdOptional(organisationId);
|
||||
if (org.isPresent()) {
|
||||
membre.setOrganisation(org.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Statut par défaut
|
||||
membre.setActif(typeMembreDefaut == null || typeMembreDefaut.isEmpty() || "ACTIF".equals(typeMembreDefaut));
|
||||
|
||||
return membre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mappe les colonnes Excel
|
||||
*/
|
||||
private Map<String, Integer> mapperColonnes(Row headerRow) {
|
||||
Map<String, Integer> colonnes = new HashMap<>();
|
||||
for (Cell cell : headerRow) {
|
||||
String headerName = getCellValueAsString(headerRow, cell.getColumnIndex()).toLowerCase()
|
||||
.replace(" ", "_")
|
||||
.replace("é", "e")
|
||||
.replace("è", "e")
|
||||
.replace("ê", "e");
|
||||
colonnes.put(headerName, cell.getColumnIndex());
|
||||
}
|
||||
return colonnes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient la valeur d'une cellule comme String
|
||||
*/
|
||||
private String getCellValueAsString(Row row, Integer columnIndex) {
|
||||
if (columnIndex == null || row == null) {
|
||||
return null;
|
||||
}
|
||||
Cell cell = row.getCell(columnIndex);
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
return String.valueOf((long) cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient la valeur d'une cellule comme Date
|
||||
*/
|
||||
private LocalDate getCellValueAsDate(Row row, Integer columnIndex) {
|
||||
if (columnIndex == null || row == null) {
|
||||
return null;
|
||||
}
|
||||
Cell cell = row.getCell(columnIndex);
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toInstant()
|
||||
.atZone(java.time.ZoneId.systemDefault())
|
||||
.toLocalDate();
|
||||
} else if (cell.getCellType() == CellType.STRING) {
|
||||
return parseDate(cell.getStringCellValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Erreur lors de la lecture de la date: %s", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse une date depuis une String
|
||||
*/
|
||||
private LocalDate parseDate(String dateStr) {
|
||||
if (dateStr == null || dateStr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
dateStr = dateStr.trim();
|
||||
|
||||
// Essayer différents formats
|
||||
String[] formats = {
|
||||
"dd/MM/yyyy",
|
||||
"yyyy-MM-dd",
|
||||
"dd-MM-yyyy",
|
||||
"dd.MM.yyyy"
|
||||
};
|
||||
|
||||
for (String format : formats) {
|
||||
try {
|
||||
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(format));
|
||||
} catch (DateTimeParseException e) {
|
||||
// Continuer avec le format suivant
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Format de date non reconnu: " + dateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte des membres vers Excel
|
||||
*/
|
||||
public byte[] exporterVersExcel(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) throws IOException {
|
||||
try (Workbook workbook = new XSSFWorkbook()) {
|
||||
Sheet sheet = workbook.createSheet("Membres");
|
||||
|
||||
int rowNum = 0;
|
||||
|
||||
// En-têtes
|
||||
if (inclureHeaders) {
|
||||
Row headerRow = sheet.createRow(rowNum++);
|
||||
int colNum = 0;
|
||||
|
||||
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
|
||||
headerRow.createCell(colNum++).setCellValue("Nom");
|
||||
headerRow.createCell(colNum++).setCellValue("Prénom");
|
||||
headerRow.createCell(colNum++).setCellValue("Date de naissance");
|
||||
}
|
||||
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
|
||||
headerRow.createCell(colNum++).setCellValue("Email");
|
||||
headerRow.createCell(colNum++).setCellValue("Téléphone");
|
||||
}
|
||||
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
|
||||
headerRow.createCell(colNum++).setCellValue("Date adhésion");
|
||||
headerRow.createCell(colNum++).setCellValue("Statut");
|
||||
}
|
||||
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
|
||||
headerRow.createCell(colNum++).setCellValue("Organisation");
|
||||
}
|
||||
}
|
||||
|
||||
// Données
|
||||
for (MembreDTO membre : membres) {
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
int colNum = 0;
|
||||
|
||||
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
|
||||
row.createCell(colNum++).setCellValue(membre.getNom() != null ? membre.getNom() : "");
|
||||
row.createCell(colNum++).setCellValue(membre.getPrenom() != null ? membre.getPrenom() : "");
|
||||
if (membre.getDateNaissance() != null) {
|
||||
Cell dateCell = row.createCell(colNum++);
|
||||
if (formaterDates) {
|
||||
dateCell.setCellValue(membre.getDateNaissance().format(DATE_FORMATTER));
|
||||
} else {
|
||||
dateCell.setCellValue(membre.getDateNaissance().toString());
|
||||
}
|
||||
} else {
|
||||
row.createCell(colNum++).setCellValue("");
|
||||
}
|
||||
}
|
||||
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
|
||||
row.createCell(colNum++).setCellValue(membre.getEmail() != null ? membre.getEmail() : "");
|
||||
row.createCell(colNum++).setCellValue(membre.getTelephone() != null ? membre.getTelephone() : "");
|
||||
}
|
||||
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
|
||||
if (membre.getDateAdhesion() != null) {
|
||||
Cell dateCell = row.createCell(colNum++);
|
||||
if (formaterDates) {
|
||||
dateCell.setCellValue(membre.getDateAdhesion().format(DATE_FORMATTER));
|
||||
} else {
|
||||
dateCell.setCellValue(membre.getDateAdhesion().toString());
|
||||
}
|
||||
} else {
|
||||
row.createCell(colNum++).setCellValue("");
|
||||
}
|
||||
row.createCell(colNum++).setCellValue(membre.getStatut() != null ? membre.getStatut().toString() : "");
|
||||
}
|
||||
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
|
||||
row.createCell(colNum++).setCellValue(membre.getAssociationNom() != null ? membre.getAssociationNom() : "");
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-size columns
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sheet.autoSizeColumn(i);
|
||||
}
|
||||
|
||||
// Ajouter un onglet statistiques si demandé
|
||||
if (inclureStatistiques && !membres.isEmpty()) {
|
||||
Sheet statsSheet = workbook.createSheet("Statistiques");
|
||||
creerOngletStatistiques(statsSheet, membres);
|
||||
}
|
||||
|
||||
// Écrire dans un ByteArrayOutputStream
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
workbook.write(outputStream);
|
||||
byte[] excelData = outputStream.toByteArray();
|
||||
|
||||
// Chiffrer le fichier si un mot de passe est fourni
|
||||
if (motDePasse != null && !motDePasse.trim().isEmpty()) {
|
||||
return chiffrerExcel(excelData, motDePasse);
|
||||
}
|
||||
|
||||
return excelData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un onglet statistiques dans le classeur Excel
|
||||
*/
|
||||
private void creerOngletStatistiques(Sheet sheet, List<MembreDTO> membres) {
|
||||
int rowNum = 0;
|
||||
|
||||
// Titre
|
||||
Row titleRow = sheet.createRow(rowNum++);
|
||||
Cell titleCell = titleRow.createCell(0);
|
||||
titleCell.setCellValue("Statistiques des Membres");
|
||||
CellStyle titleStyle = sheet.getWorkbook().createCellStyle();
|
||||
Font titleFont = sheet.getWorkbook().createFont();
|
||||
titleFont.setBold(true);
|
||||
titleFont.setFontHeightInPoints((short) 14);
|
||||
titleStyle.setFont(titleFont);
|
||||
titleCell.setCellStyle(titleStyle);
|
||||
|
||||
rowNum++; // Ligne vide
|
||||
|
||||
// Statistiques générales
|
||||
Row headerRow = sheet.createRow(rowNum++);
|
||||
headerRow.createCell(0).setCellValue("Indicateur");
|
||||
headerRow.createCell(1).setCellValue("Valeur");
|
||||
|
||||
// Style pour les en-têtes
|
||||
CellStyle headerStyle = sheet.getWorkbook().createCellStyle();
|
||||
Font headerFont = sheet.getWorkbook().createFont();
|
||||
headerFont.setBold(true);
|
||||
headerStyle.setFont(headerFont);
|
||||
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
headerRow.getCell(0).setCellStyle(headerStyle);
|
||||
headerRow.getCell(1).setCellStyle(headerStyle);
|
||||
|
||||
// Calcul des statistiques
|
||||
long totalMembres = membres.size();
|
||||
long membresActifs = membres.stream().filter(m -> "ACTIF".equals(m.getStatut())).count();
|
||||
long membresInactifs = membres.stream().filter(m -> "INACTIF".equals(m.getStatut())).count();
|
||||
long membresSuspendus = membres.stream().filter(m -> "SUSPENDU".equals(m.getStatut())).count();
|
||||
|
||||
// Organisations distinctes
|
||||
long organisationsDistinctes = membres.stream()
|
||||
.filter(m -> m.getAssociationNom() != null)
|
||||
.map(MembreDTO::getAssociationNom)
|
||||
.distinct()
|
||||
.count();
|
||||
|
||||
// Statistiques par type (si disponible dans le DTO)
|
||||
// Note: Le type de membre peut ne pas être disponible dans MembreDTO
|
||||
// Pour l'instant, on utilise le statut comme indicateur
|
||||
long typeActif = membresActifs;
|
||||
long typeAssocie = 0;
|
||||
long typeBienfaiteur = 0;
|
||||
long typeHonoraire = 0;
|
||||
|
||||
// Ajout des statistiques
|
||||
int currentRow = rowNum;
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Total Membres");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(totalMembres);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Actifs");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresActifs);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Inactifs");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresInactifs);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Membres Suspendus");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(membresSuspendus);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Organisations Distinctes");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(organisationsDistinctes);
|
||||
|
||||
currentRow++; // Ligne vide
|
||||
|
||||
// Section par type
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Répartition par Type");
|
||||
CellStyle sectionStyle = sheet.getWorkbook().createCellStyle();
|
||||
Font sectionFont = sheet.getWorkbook().createFont();
|
||||
sectionFont.setBold(true);
|
||||
sectionStyle.setFont(sectionFont);
|
||||
sheet.getRow(currentRow - 1).getCell(0).setCellStyle(sectionStyle);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Actif");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeActif);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Associé");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeAssocie);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Bienfaiteur");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeBienfaiteur);
|
||||
|
||||
sheet.createRow(currentRow++).createCell(0).setCellValue("Type Honoraire");
|
||||
sheet.getRow(currentRow - 1).createCell(1).setCellValue(typeHonoraire);
|
||||
|
||||
// Auto-size columns
|
||||
sheet.autoSizeColumn(0);
|
||||
sheet.autoSizeColumn(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Protège un fichier Excel avec un mot de passe
|
||||
* Utilise Apache POI pour protéger les feuilles et la structure du workbook
|
||||
* Note: Ceci protège contre la modification, pas un chiffrement complet du fichier
|
||||
*/
|
||||
private byte[] chiffrerExcel(byte[] excelData, String motDePasse) throws IOException {
|
||||
try {
|
||||
// Pour XLSX, on protège les feuilles et la structure du workbook
|
||||
// Note: POI 5.2.5 ne supporte pas le chiffrement complet XLSX (nécessite des bibliothèques externes)
|
||||
// On utilise la protection par mot de passe qui empêche la modification sans le mot de passe
|
||||
|
||||
try (java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(excelData);
|
||||
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
|
||||
// Protéger toutes les feuilles avec un mot de passe (empêche la modification des cellules)
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
|
||||
Sheet sheet = workbook.getSheetAt(i);
|
||||
sheet.protectSheet(motDePasse);
|
||||
}
|
||||
|
||||
// Protéger la structure du workbook (empêche l'ajout/suppression de feuilles)
|
||||
org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbookProtection protection =
|
||||
workbook.getCTWorkbook().getWorkbookProtection();
|
||||
if (protection == null) {
|
||||
protection = workbook.getCTWorkbook().addNewWorkbookProtection();
|
||||
}
|
||||
protection.setLockStructure(true);
|
||||
// Le mot de passe doit être haché selon le format Excel
|
||||
// Pour simplifier, on utilise le hash MD5 du mot de passe
|
||||
try {
|
||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
||||
byte[] passwordHash = md.digest(motDePasse.getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
|
||||
protection.setWorkbookPassword(passwordHash);
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
LOG.warnf("Impossible de hasher le mot de passe, protection partielle uniquement");
|
||||
}
|
||||
|
||||
workbook.write(outputStream);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la protection du fichier Excel");
|
||||
// En cas d'erreur, retourner le fichier non protégé avec un avertissement
|
||||
LOG.warnf("Le fichier sera exporté sans protection en raison d'une erreur");
|
||||
return excelData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte des membres vers CSV
|
||||
*/
|
||||
public byte[] exporterVersCSV(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates) throws IOException {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
CSVPrinter printer = new CSVPrinter(
|
||||
new java.io.OutputStreamWriter(outputStream, StandardCharsets.UTF_8),
|
||||
CSVFormat.DEFAULT)) {
|
||||
|
||||
// En-têtes
|
||||
if (inclureHeaders) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
|
||||
headers.add("Nom");
|
||||
headers.add("Prénom");
|
||||
headers.add("Date de naissance");
|
||||
}
|
||||
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
|
||||
headers.add("Email");
|
||||
headers.add("Téléphone");
|
||||
}
|
||||
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
|
||||
headers.add("Date adhésion");
|
||||
headers.add("Statut");
|
||||
}
|
||||
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
|
||||
headers.add("Organisation");
|
||||
}
|
||||
printer.printRecord(headers);
|
||||
}
|
||||
|
||||
// Données
|
||||
for (MembreDTO membre : membres) {
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (colonnesExport.contains("PERSO") || colonnesExport.isEmpty()) {
|
||||
values.add(membre.getNom() != null ? membre.getNom() : "");
|
||||
values.add(membre.getPrenom() != null ? membre.getPrenom() : "");
|
||||
if (membre.getDateNaissance() != null) {
|
||||
values.add(formaterDates ? membre.getDateNaissance().format(DATE_FORMATTER) : membre.getDateNaissance().toString());
|
||||
} else {
|
||||
values.add("");
|
||||
}
|
||||
}
|
||||
if (colonnesExport.contains("CONTACT") || colonnesExport.isEmpty()) {
|
||||
values.add(membre.getEmail() != null ? membre.getEmail() : "");
|
||||
values.add(membre.getTelephone() != null ? membre.getTelephone() : "");
|
||||
}
|
||||
if (colonnesExport.contains("ADHESION") || colonnesExport.isEmpty()) {
|
||||
if (membre.getDateAdhesion() != null) {
|
||||
values.add(formaterDates ? membre.getDateAdhesion().format(DATE_FORMATTER) : membre.getDateAdhesion().toString());
|
||||
} else {
|
||||
values.add("");
|
||||
}
|
||||
values.add(membre.getStatut() != null ? membre.getStatut().toString() : "");
|
||||
}
|
||||
if (colonnesExport.contains("ORGANISATION") || colonnesExport.isEmpty()) {
|
||||
values.add(membre.getAssociationNom() != null ? membre.getAssociationNom() : "");
|
||||
}
|
||||
|
||||
printer.printRecord(values);
|
||||
}
|
||||
|
||||
printer.flush();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un modèle Excel pour l'import
|
||||
*/
|
||||
public byte[] genererModeleImport() throws IOException {
|
||||
try (Workbook workbook = new XSSFWorkbook()) {
|
||||
Sheet sheet = workbook.createSheet("Modèle");
|
||||
|
||||
// En-têtes
|
||||
Row headerRow = sheet.createRow(0);
|
||||
headerRow.createCell(0).setCellValue("Nom");
|
||||
headerRow.createCell(1).setCellValue("Prénom");
|
||||
headerRow.createCell(2).setCellValue("Email");
|
||||
headerRow.createCell(3).setCellValue("Téléphone");
|
||||
headerRow.createCell(4).setCellValue("Date naissance");
|
||||
headerRow.createCell(5).setCellValue("Date adhésion");
|
||||
headerRow.createCell(6).setCellValue("Adresse");
|
||||
headerRow.createCell(7).setCellValue("Profession");
|
||||
headerRow.createCell(8).setCellValue("Type membre");
|
||||
|
||||
// Exemple de ligne
|
||||
Row exampleRow = sheet.createRow(1);
|
||||
exampleRow.createCell(0).setCellValue("DUPONT");
|
||||
exampleRow.createCell(1).setCellValue("Jean");
|
||||
exampleRow.createCell(2).setCellValue("jean.dupont@example.com");
|
||||
exampleRow.createCell(3).setCellValue("+225 07 12 34 56 78");
|
||||
exampleRow.createCell(4).setCellValue("15/01/1990");
|
||||
exampleRow.createCell(5).setCellValue("01/01/2024");
|
||||
exampleRow.createCell(6).setCellValue("Abidjan, Cocody");
|
||||
exampleRow.createCell(7).setCellValue("Ingénieur");
|
||||
exampleRow.createCell(8).setCellValue("ACTIF");
|
||||
|
||||
// Auto-size columns
|
||||
for (int i = 0; i < 9; i++) {
|
||||
sheet.autoSizeColumn(i);
|
||||
}
|
||||
|
||||
// Écrire dans un ByteArrayOutputStream
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
workbook.write(outputStream);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe pour le résultat de l'import
|
||||
*/
|
||||
public static class ResultatImport {
|
||||
public int totalLignes;
|
||||
public int lignesTraitees;
|
||||
public int lignesErreur;
|
||||
public List<String> erreurs;
|
||||
public List<MembreDTO> membresImportes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
@@ -34,6 +35,9 @@ public class MembreService {
|
||||
|
||||
@Inject MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
MembreImportExportService membreImportExportService;
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager entityManager;
|
||||
|
||||
@@ -341,7 +345,7 @@ public class MembreService {
|
||||
long totalElements = countQueryTyped.getSingleResult();
|
||||
|
||||
if (totalElements == 0) {
|
||||
return MembreSearchResultDTO.empty(criteria);
|
||||
return MembreSearchResultDTO.empty(criteria, page.size, page.index);
|
||||
}
|
||||
|
||||
// Ajout du tri et pagination
|
||||
@@ -464,16 +468,16 @@ public class MembreService {
|
||||
parameters.put("organisationIds", criteria.getOrganisationIds());
|
||||
}
|
||||
|
||||
// Filtre par rôles (recherche dans le champ roles)
|
||||
// Filtre par rôles (recherche via la relation MembreRole -> Role)
|
||||
if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) {
|
||||
StringBuilder roleCondition = new StringBuilder(" AND (");
|
||||
for (int i = 0; i < criteria.getRoles().size(); i++) {
|
||||
if (i > 0) roleCondition.append(" OR ");
|
||||
roleCondition.append("m.roles LIKE :role").append(i);
|
||||
parameters.put("role" + i, "%" + criteria.getRoles().get(i) + "%");
|
||||
}
|
||||
roleCondition.append(")");
|
||||
queryBuilder.append(roleCondition);
|
||||
// Utiliser EXISTS avec une sous-requête pour vérifier les rôles
|
||||
queryBuilder.append(" AND EXISTS (");
|
||||
queryBuilder.append(" SELECT 1 FROM MembreRole mr WHERE mr.membre = m");
|
||||
queryBuilder.append(" AND mr.actif = true");
|
||||
queryBuilder.append(" AND mr.role.code IN :roleCodes");
|
||||
queryBuilder.append(")");
|
||||
// Convertir les noms de rôles en codes (supposant que criteria.getRoles() contient des codes)
|
||||
parameters.put("roleCodes", criteria.getRoles());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +488,10 @@ public class MembreService {
|
||||
}
|
||||
|
||||
return sort.getColumns().stream()
|
||||
.map(column -> "m." + column.getName() + " " + column.getDirection().name())
|
||||
.map(column -> {
|
||||
String direction = column.getDirection() == Sort.Direction.Descending ? "DESC" : "ASC";
|
||||
return "m." + column.getName() + " " + direction;
|
||||
})
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@@ -633,4 +640,101 @@ public class MembreService {
|
||||
|
||||
return csv.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe des membres depuis un fichier Excel ou CSV
|
||||
*/
|
||||
public MembreImportExportService.ResultatImport importerMembres(
|
||||
InputStream fileInputStream,
|
||||
String fileName,
|
||||
UUID organisationId,
|
||||
String typeMembreDefaut,
|
||||
boolean mettreAJourExistants,
|
||||
boolean ignorerErreurs) {
|
||||
return membreImportExportService.importerMembres(
|
||||
fileInputStream, fileName, organisationId, typeMembreDefaut, mettreAJourExistants, ignorerErreurs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte des membres vers Excel
|
||||
*/
|
||||
public byte[] exporterVersExcel(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates, boolean inclureStatistiques, String motDePasse) {
|
||||
try {
|
||||
return membreImportExportService.exporterVersExcel(membres, colonnesExport, inclureHeaders, formaterDates, inclureStatistiques, motDePasse);
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export Excel");
|
||||
throw new RuntimeException("Erreur lors de l'export Excel: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporte des membres vers CSV
|
||||
*/
|
||||
public byte[] exporterVersCSV(List<MembreDTO> membres, List<String> colonnesExport, boolean inclureHeaders, boolean formaterDates) {
|
||||
try {
|
||||
return membreImportExportService.exporterVersCSV(membres, colonnesExport, inclureHeaders, formaterDates);
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de l'export CSV");
|
||||
throw new RuntimeException("Erreur lors de l'export CSV: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un modèle Excel pour l'import
|
||||
*/
|
||||
public byte[] genererModeleImport() {
|
||||
try {
|
||||
return membreImportExportService.genererModeleImport();
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la génération du modèle");
|
||||
throw new RuntimeException("Erreur lors de la génération du modèle: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les membres pour l'export selon les filtres
|
||||
*/
|
||||
public List<MembreDTO> listerMembresPourExport(
|
||||
UUID associationId,
|
||||
String statut,
|
||||
String type,
|
||||
String dateAdhesionDebut,
|
||||
String dateAdhesionFin) {
|
||||
|
||||
List<Membre> membres;
|
||||
|
||||
if (associationId != null) {
|
||||
TypedQuery<Membre> query = entityManager.createQuery(
|
||||
"SELECT m FROM Membre m WHERE m.organisation.id = :associationId", Membre.class);
|
||||
query.setParameter("associationId", associationId);
|
||||
membres = query.getResultList();
|
||||
} else {
|
||||
membres = membreRepository.listAll();
|
||||
}
|
||||
|
||||
// Filtrer par statut
|
||||
if (statut != null && !statut.isEmpty()) {
|
||||
boolean actif = "ACTIF".equals(statut);
|
||||
membres = membres.stream()
|
||||
.filter(m -> m.getActif() == actif)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Filtrer par dates d'adhésion
|
||||
if (dateAdhesionDebut != null && !dateAdhesionDebut.isEmpty()) {
|
||||
LocalDate dateDebut = LocalDate.parse(dateAdhesionDebut);
|
||||
membres = membres.stream()
|
||||
.filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isBefore(dateDebut))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
if (dateAdhesionFin != null && !dateAdhesionFin.isEmpty()) {
|
||||
LocalDate dateFin = LocalDate.parse(dateAdhesionFin);
|
||||
membres = membres.stream()
|
||||
.filter(m -> m.getDateAdhesion() != null && !m.getDateAdhesion().isAfter(dateFin))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return convertToDTOList(membres);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,77 @@
|
||||
# Configuration UnionFlow Server - Profil Production
|
||||
# Ce fichier est chargé automatiquement quand le profil 'prod' est actif
|
||||
# Configuration UnionFlow Server - PRODUCTION
|
||||
# Ce fichier est utilisé avec le profil Quarkus "prod"
|
||||
|
||||
# Configuration Hibernate pour production
|
||||
quarkus.hibernate-orm.database.generation=validate
|
||||
# Configuration HTTP
|
||||
quarkus.http.port=8085
|
||||
quarkus.http.host=0.0.0.0
|
||||
|
||||
# Configuration CORS - Production (strict)
|
||||
quarkus.http.cors=true
|
||||
quarkus.http.cors.origins=${CORS_ORIGINS:https://unionflow.lions.dev,https://security.lions.dev}
|
||||
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
|
||||
quarkus.http.cors.headers=Content-Type,Authorization
|
||||
quarkus.http.cors.allow-credentials=true
|
||||
|
||||
# Configuration Base de données PostgreSQL - Production
|
||||
quarkus.datasource.db-kind=postgresql
|
||||
quarkus.datasource.username=${DB_USERNAME:unionflow}
|
||||
quarkus.datasource.password=${DB_PASSWORD}
|
||||
quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/unionflow}
|
||||
quarkus.datasource.jdbc.min-size=5
|
||||
quarkus.datasource.jdbc.max-size=20
|
||||
|
||||
# Configuration Hibernate - Production (IMPORTANT: update, pas drop-and-create)
|
||||
quarkus.hibernate-orm.database.generation=update
|
||||
quarkus.hibernate-orm.log.sql=false
|
||||
quarkus.hibernate-orm.jdbc.timezone=UTC
|
||||
quarkus.hibernate-orm.packages=dev.lions.unionflow.server.entity
|
||||
quarkus.hibernate-orm.metrics.enabled=false
|
||||
|
||||
# Configuration logging pour production
|
||||
quarkus.log.console.level=WARN
|
||||
quarkus.log.category."dev.lions.unionflow".level=INFO
|
||||
quarkus.log.category.root.level=WARN
|
||||
# Configuration Flyway - Production (ACTIVÉ)
|
||||
quarkus.flyway.migrate-at-start=true
|
||||
quarkus.flyway.baseline-on-migrate=true
|
||||
quarkus.flyway.baseline-version=1.0.0
|
||||
|
||||
# Configuration Keycloak pour production
|
||||
quarkus.oidc.auth-server-url=${KEYCLOAK_SERVER_URL:http://localhost:8180/realms/unionflow}
|
||||
quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:unionflow-server}
|
||||
# Configuration Keycloak OIDC - Production
|
||||
quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/unionflow}
|
||||
quarkus.oidc.client-id=unionflow-server
|
||||
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
|
||||
quarkus.oidc.tls.verification=required
|
||||
quarkus.oidc.application-type=service
|
||||
|
||||
# Configuration Keycloak Policy Enforcer
|
||||
quarkus.keycloak.policy-enforcer.enable=false
|
||||
quarkus.keycloak.policy-enforcer.lazy-load-paths=true
|
||||
quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE
|
||||
|
||||
# Chemins publics (non protégés)
|
||||
quarkus.http.auth.permission.public.paths=/health,/q/*,/favicon.ico
|
||||
quarkus.http.auth.permission.public.policy=permit
|
||||
|
||||
# Configuration OpenAPI - Production (Swagger désactivé ou protégé)
|
||||
quarkus.smallrye-openapi.info-title=UnionFlow Server API
|
||||
quarkus.smallrye-openapi.info-version=1.0.0
|
||||
quarkus.smallrye-openapi.info-description=API REST pour la gestion d'union avec authentification Keycloak
|
||||
quarkus.smallrye-openapi.servers=https://api.lions.dev/unionflow
|
||||
|
||||
# Configuration Swagger UI - Production (DÉSACTIVÉ pour sécurité)
|
||||
quarkus.swagger-ui.always-include=false
|
||||
|
||||
# Configuration santé
|
||||
quarkus.smallrye-health.root-path=/health
|
||||
|
||||
# Configuration logging - Production
|
||||
quarkus.log.console.enable=true
|
||||
quarkus.log.console.level=INFO
|
||||
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
|
||||
quarkus.log.category."dev.lions.unionflow".level=INFO
|
||||
quarkus.log.category."org.hibernate".level=WARN
|
||||
quarkus.log.category."io.quarkus".level=INFO
|
||||
quarkus.log.category."org.jboss.resteasy".level=WARN
|
||||
|
||||
# Configuration Wave Money - Production
|
||||
wave.api.key=${WAVE_API_KEY:}
|
||||
wave.api.secret=${WAVE_API_SECRET:}
|
||||
wave.api.base.url=${WAVE_API_BASE_URL:https://api.wave.com/v1}
|
||||
wave.environment=${WAVE_ENVIRONMENT:production}
|
||||
wave.webhook.secret=${WAVE_WEBHOOK_SECRET:}
|
||||
|
||||
@@ -5,16 +5,27 @@
|
||||
quarkus.datasource.db-kind=h2
|
||||
quarkus.datasource.username=sa
|
||||
quarkus.datasource.password=
|
||||
quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
|
||||
quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
|
||||
|
||||
# Configuration Hibernate pour tests
|
||||
quarkus.hibernate-orm.database.generation=drop-and-create
|
||||
# Désactiver complètement l'exécution des scripts SQL au démarrage
|
||||
quarkus.hibernate-orm.sql-load-script-source=none
|
||||
# Empêcher Hibernate d'exécuter les scripts SQL automatiquement
|
||||
# Note: Ne pas définir quarkus.hibernate-orm.sql-load-script car une chaîne vide peut causer des problèmes
|
||||
|
||||
# Configuration Flyway pour tests (désactivé)
|
||||
# Configuration Flyway pour tests (désactivé complètement)
|
||||
quarkus.flyway.migrate-at-start=false
|
||||
quarkus.flyway.enabled=false
|
||||
quarkus.flyway.baseline-on-migrate=false
|
||||
# Note: Ne pas définir quarkus.flyway.locations car une chaîne vide cause une erreur de configuration
|
||||
|
||||
# Configuration Keycloak pour tests (désactivé)
|
||||
quarkus.oidc.tenant-enabled=false
|
||||
quarkus.keycloak.policy-enforcer.enable=false
|
||||
|
||||
# Configuration HTTP pour tests
|
||||
quarkus.http.port=0
|
||||
quarkus.http.test-port=0
|
||||
|
||||
|
||||
|
||||
@@ -122,90 +122,9 @@ BEGIN
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Insertion de données de test pour le développement
|
||||
INSERT INTO organisations (
|
||||
nom, nom_court, type_organisation, statut, description,
|
||||
email, telephone, adresse, ville, region, pays,
|
||||
objectifs, activites_principales, nombre_membres,
|
||||
organisation_publique, accepte_nouveaux_membres,
|
||||
cree_par
|
||||
) VALUES
|
||||
(
|
||||
'Lions Club Abidjan Plateau',
|
||||
'LC Plateau',
|
||||
'LIONS_CLUB',
|
||||
'ACTIVE',
|
||||
'Lions Club du district 403 A1, zone Plateau d''Abidjan',
|
||||
'plateau@lionsclub-ci.org',
|
||||
'+225 27 20 21 22 23',
|
||||
'Immeuble SCIAM, Boulevard de la République',
|
||||
'Abidjan',
|
||||
'Lagunes',
|
||||
'Côte d''Ivoire',
|
||||
'Servir la communauté par des actions humanitaires et sociales',
|
||||
'Actions de santé, éducation, environnement et aide aux démunis',
|
||||
45,
|
||||
true,
|
||||
true,
|
||||
'system'
|
||||
),
|
||||
(
|
||||
'Lions Club Abidjan Cocody',
|
||||
'LC Cocody',
|
||||
'LIONS_CLUB',
|
||||
'ACTIVE',
|
||||
'Lions Club du district 403 A1, zone Cocody',
|
||||
'cocody@lionsclub-ci.org',
|
||||
'+225 27 22 44 55 66',
|
||||
'Riviera Golf, Cocody',
|
||||
'Abidjan',
|
||||
'Lagunes',
|
||||
'Côte d''Ivoire',
|
||||
'Servir la communauté par des actions humanitaires et sociales',
|
||||
'Actions de santé, éducation, environnement et aide aux démunis',
|
||||
38,
|
||||
true,
|
||||
true,
|
||||
'system'
|
||||
),
|
||||
(
|
||||
'Association des Femmes Entrepreneures CI',
|
||||
'AFECI',
|
||||
'ASSOCIATION',
|
||||
'ACTIVE',
|
||||
'Association pour la promotion de l''entrepreneuriat féminin en Côte d''Ivoire',
|
||||
'contact@afeci.org',
|
||||
'+225 05 06 07 08 09',
|
||||
'Marcory Zone 4C',
|
||||
'Abidjan',
|
||||
'Lagunes',
|
||||
'Côte d''Ivoire',
|
||||
'Promouvoir l''entrepreneuriat féminin et l''autonomisation des femmes',
|
||||
'Formation, accompagnement, financement de projets féminins',
|
||||
120,
|
||||
true,
|
||||
true,
|
||||
'system'
|
||||
),
|
||||
(
|
||||
'Coopérative Agricole du Nord',
|
||||
'COOP-NORD',
|
||||
'COOPERATIVE',
|
||||
'ACTIVE',
|
||||
'Coopérative des producteurs agricoles du Nord de la Côte d''Ivoire',
|
||||
'info@coop-nord.ci',
|
||||
'+225 09 10 11 12 13',
|
||||
'Korhogo Centre',
|
||||
'Korhogo',
|
||||
'Savanes',
|
||||
'Côte d''Ivoire',
|
||||
'Améliorer les conditions de vie des producteurs agricoles',
|
||||
'Production, transformation et commercialisation de produits agricoles',
|
||||
250,
|
||||
true,
|
||||
true,
|
||||
'system'
|
||||
);
|
||||
-- IMPORTANT: Aucune donnée fictive n'est insérée dans ce script de migration.
|
||||
-- Les données doivent être insérées manuellement via l'interface d'administration
|
||||
-- ou via des scripts de migration séparés si nécessaire pour la production.
|
||||
|
||||
-- Mise à jour des statistiques de la base de données
|
||||
ANALYZE organisations;
|
||||
|
||||
@@ -1,128 +1,10 @@
|
||||
-- Script d'insertion de données initiales pour UnionFlow (avec UUID)
|
||||
-- Script d'insertion de données initiales pour UnionFlow
|
||||
-- Ce fichier est exécuté automatiquement par Hibernate au démarrage
|
||||
-- Utilisé uniquement en mode développement (quarkus.hibernate-orm.database.generation=drop-and-create)
|
||||
-- NOTE: Les IDs sont maintenant des UUID générés automatiquement
|
||||
|
||||
-- Insertion d'organisations initiales avec UUIDs générés
|
||||
INSERT INTO organisations (id, nom, nom_court, type_organisation, statut, description,
|
||||
email, telephone, adresse, ville, region, pays,
|
||||
objectifs, activites_principales, nombre_membres,
|
||||
organisation_publique, accepte_nouveaux_membres, cree_par,
|
||||
actif, date_creation, niveau_hierarchique, nombre_administrateurs, cotisation_obligatoire) VALUES
|
||||
(
|
||||
gen_random_uuid(),
|
||||
'Lions Club Abidjan Plateau',
|
||||
'LC Plateau',
|
||||
'LIONS_CLUB',
|
||||
'ACTIVE',
|
||||
'Lions Club du district 403 A1, zone Plateau d''Abidjan',
|
||||
'plateau@lionsclub-ci.org',
|
||||
'+225 27 20 21 22 23',
|
||||
'Immeuble SCIAM, Boulevard de la République',
|
||||
'Abidjan',
|
||||
'Lagunes',
|
||||
'Côte d''Ivoire',
|
||||
'Servir la communauté par des actions humanitaires et sociales',
|
||||
'Actions de santé, éducation, environnement et aide aux démunis',
|
||||
45,
|
||||
true,
|
||||
true,
|
||||
'system',
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
),
|
||||
(
|
||||
gen_random_uuid(),
|
||||
'Lions Club Abidjan Cocody',
|
||||
'LC Cocody',
|
||||
'LIONS_CLUB',
|
||||
'ACTIVE',
|
||||
'Lions Club du district 403 A1, zone Cocody',
|
||||
'cocody@lionsclub-ci.org',
|
||||
'+225 27 22 44 55 66',
|
||||
'Riviera Golf, Cocody',
|
||||
'Abidjan',
|
||||
'Lagunes',
|
||||
'Côte d''Ivoire',
|
||||
'Servir la communauté par des actions humanitaires et sociales',
|
||||
'Actions de santé, éducation, environnement et aide aux démunis',
|
||||
38,
|
||||
true,
|
||||
true,
|
||||
'system',
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
),
|
||||
(
|
||||
gen_random_uuid(),
|
||||
'Association des Femmes Entrepreneures CI',
|
||||
'AFECI',
|
||||
'ASSOCIATION',
|
||||
'ACTIVE',
|
||||
'Association pour la promotion de l''entrepreneuriat féminin en Côte d''Ivoire',
|
||||
'contact@afeci.org',
|
||||
'+225 05 06 07 08 09',
|
||||
'Marcory Zone 4C',
|
||||
'Abidjan',
|
||||
'Lagunes',
|
||||
'Côte d''Ivoire',
|
||||
'Promouvoir l''entrepreneuriat féminin et l''autonomisation des femmes',
|
||||
'Formation, accompagnement, financement de projets féminins',
|
||||
120,
|
||||
true,
|
||||
true,
|
||||
'system',
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
),
|
||||
(
|
||||
gen_random_uuid(),
|
||||
'Coopérative Agricole du Nord',
|
||||
'COOP-NORD',
|
||||
'COOPERATIVE',
|
||||
'ACTIVE',
|
||||
'Coopérative des producteurs agricoles du Nord de la Côte d''Ivoire',
|
||||
'info@coop-nord.ci',
|
||||
'+225 09 10 11 12 13',
|
||||
'Korhogo Centre',
|
||||
'Korhogo',
|
||||
'Savanes',
|
||||
'Côte d''Ivoire',
|
||||
'Améliorer les conditions de vie des producteurs agricoles',
|
||||
'Production, transformation et commercialisation de produits agricoles',
|
||||
250,
|
||||
true,
|
||||
true,
|
||||
'system',
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
-- Insertion de membres initiaux (avec UUIDs générés et références aux organisations)
|
||||
-- On utilise des sous-requêtes pour récupérer les IDs des organisations par leur nom
|
||||
INSERT INTO membres (id, numero_membre, nom, prenom, email, telephone, date_naissance, date_adhesion, actif, date_creation, organisation_id) VALUES
|
||||
(gen_random_uuid(), 'MBR001', 'Kouassi', 'Jean-Baptiste', 'jb.kouassi@email.ci', '+225071234567', '1985-03-15', '2023-01-15', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Plateau' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR002', 'Traoré', 'Aminata', 'aminata.traore@email.ci', '+225059876543', '1990-07-22', '2023-02-10', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Plateau' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR003', 'Bamba', 'Seydou', 'seydou.bamba@email.ci', '+225012345678', '1988-11-08', '2023-03-05', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Cocody' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR004', 'Ouattara', 'Fatoumata', 'fatoumata.ouattara@email.ci', '+225078765432', '1992-05-18', '2023-04-12', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Lions Club Abidjan Cocody' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR005', 'Koné', 'Ibrahim', 'ibrahim.kone@email.ci', '+225051122334', '1987-09-30', '2023-05-20', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Association des Femmes Entrepreneures CI' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR006', 'Diabaté', 'Mariam', 'mariam.diabate@email.ci', '+225015566778', '1991-12-03', '2023-06-08', false, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Association des Femmes Entrepreneures CI' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR007', 'Sangaré', 'Moussa', 'moussa.sangare@email.ci', '+225079988776', '1989-04-25', '2023-07-15', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Coopérative Agricole du Nord' LIMIT 1)),
|
||||
(gen_random_uuid(), 'MBR008', 'Coulibaly', 'Awa', 'awa.coulibaly@email.ci', '+225054433221', '1993-08-14', '2023-08-22', true, '2024-01-01 10:00:00', (SELECT id FROM organisations WHERE nom = 'Coopérative Agricole du Nord' LIMIT 1));
|
||||
|
||||
-- Note: Les insertions de cotisations et événements avec des références aux membres
|
||||
-- nécessitent de récupérer les UUIDs réels des membres insérés ci-dessus.
|
||||
-- Pour le développement, ces données peuvent être insérées via l'application ou
|
||||
-- via des scripts de test plus complexes qui récupèrent les UUIDs générés.
|
||||
--
|
||||
-- IMPORTANT: Ce fichier ne doit PAS contenir de données fictives pour la production.
|
||||
-- Les données doivent être insérées manuellement via l'interface d'administration
|
||||
-- ou via des scripts de migration Flyway si nécessaire.
|
||||
--
|
||||
-- Ce fichier est laissé vide intentionnellement pour éviter l'insertion automatique
|
||||
-- de données fictives lors du démarrage du serveur.
|
||||
|
||||
@@ -104,6 +104,7 @@ class MembreResourceAdvancedSearchTest {
|
||||
void testAdvancedSearchPagination() {
|
||||
MembreSearchCriteria criteria =
|
||||
MembreSearchCriteria.builder()
|
||||
.statut("ACTIF") // Ajouter un critère valide
|
||||
.includeInactifs(true) // Inclure tous les membres
|
||||
.build();
|
||||
|
||||
@@ -286,7 +287,11 @@ class MembreResourceAdvancedSearchTest {
|
||||
roles = {"SUPER_ADMIN"})
|
||||
@DisplayName("POST /api/membres/search/advanced doit retourner des statistiques complètes")
|
||||
void testAdvancedSearchStatistics() {
|
||||
MembreSearchCriteria criteria = MembreSearchCriteria.builder().includeInactifs(true).build();
|
||||
MembreSearchCriteria criteria =
|
||||
MembreSearchCriteria.builder()
|
||||
.statut("ACTIF") // Ajouter un critère valide
|
||||
.includeInactifs(true)
|
||||
.build();
|
||||
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
|
||||
@@ -45,6 +45,7 @@ class MembreServiceAdvancedSearchTest {
|
||||
.nom("Organisation Test")
|
||||
.typeOrganisation("ASSOCIATION")
|
||||
.statut("ACTIF")
|
||||
.email("test@organisation.com")
|
||||
.build();
|
||||
testOrganisation.setDateCreation(LocalDateTime.now());
|
||||
testOrganisation.setActif(true);
|
||||
@@ -104,13 +105,21 @@ class MembreServiceAdvancedSearchTest {
|
||||
if (testMembres != null) {
|
||||
testMembres.forEach(membre -> {
|
||||
if (membre.getId() != null) {
|
||||
membreRepository.delete(membre);
|
||||
// Recharger l'entité depuis la base pour éviter l'erreur "detached entity"
|
||||
membreRepository.findByIdOptional(membre.getId()).ifPresent(m -> {
|
||||
// Utiliser deleteById pour éviter les problèmes avec les entités détachées
|
||||
membreRepository.deleteById(m.getId());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (testOrganisation != null && testOrganisation.getId() != null) {
|
||||
organisationRepository.delete(testOrganisation);
|
||||
// Recharger l'entité depuis la base pour éviter l'erreur "detached entity"
|
||||
organisationRepository.findByIdOptional(testOrganisation.getId()).ifPresent(o -> {
|
||||
// Utiliser deleteById pour éviter les problèmes avec les entités détachées
|
||||
organisationRepository.deleteById(o.getId());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user