Update client submodule to latest commit (removed lions-user-manager dependency)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import jakarta.ws.rs.FormParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
|
||||
import org.jboss.resteasy.reactive.PartType;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MembreImportMultipartForm {
|
||||
@FormParam("file")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
public byte[] file;
|
||||
|
||||
@FormParam("fileName")
|
||||
public String fileName;
|
||||
|
||||
@FormParam("organisationId")
|
||||
public UUID organisationId;
|
||||
|
||||
@FormParam("typeMembreDefaut")
|
||||
public String typeMembreDefaut;
|
||||
|
||||
@FormParam("mettreAJourExistants")
|
||||
public boolean mettreAJourExistants;
|
||||
|
||||
@FormParam("ignorerErreurs")
|
||||
public boolean ignorerErreurs;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.service.MembreService;
|
||||
import dev.lions.unionflow.client.service.AssociationService;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO;
|
||||
import dev.lions.unionflow.client.dto.AssociationDTO;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Named("membreExportBean")
|
||||
@ViewScoped
|
||||
@Getter
|
||||
@Setter
|
||||
public class MembreExportBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOGGER = Logger.getLogger(MembreExportBean.class.getName());
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
MembreService membreService;
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
AssociationService associationService;
|
||||
|
||||
// Configuration de l'export
|
||||
private String formatExport = "EXCEL";
|
||||
private String scopeExport = "TOUS";
|
||||
private List<String> colonnesExport = new ArrayList<>();
|
||||
|
||||
// Filtres
|
||||
private String statutFilter = "";
|
||||
private String typeFilter = "";
|
||||
private UUID organisationId;
|
||||
private LocalDate dateAdhesionDebut;
|
||||
private LocalDate dateAdhesionFin;
|
||||
|
||||
// Options d'export
|
||||
private boolean inclureHeaders = true;
|
||||
private boolean formaterDates = true;
|
||||
private boolean inclureStatistiques = false;
|
||||
private boolean chiffrerDonnees = false;
|
||||
private String motDePasseExport = "";
|
||||
|
||||
// Organisations disponibles
|
||||
private List<OrganisationDTO> organisationsDisponibles = new ArrayList<>();
|
||||
|
||||
// Statistiques
|
||||
private int totalMembres = 0;
|
||||
private int membresActifs = 0;
|
||||
private int membresInactifs = 0;
|
||||
private int nombreMembresAExporter = 0;
|
||||
|
||||
// Historique des exports
|
||||
private List<ExportHistorique> historiqueExports = new ArrayList<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
chargerOrganisations();
|
||||
chargerStatistiques();
|
||||
initialiserColonnesExport();
|
||||
}
|
||||
|
||||
private void chargerOrganisations() {
|
||||
organisationsDisponibles = new ArrayList<>();
|
||||
try {
|
||||
List<AssociationDTO> associations = associationService.listerToutes(0, 1000);
|
||||
for (AssociationDTO assoc : associations) {
|
||||
OrganisationDTO org = new OrganisationDTO();
|
||||
org.setId(assoc.getId());
|
||||
org.setNom(assoc.getNom());
|
||||
org.setVille(assoc.getVille());
|
||||
organisationsDisponibles.add(org);
|
||||
}
|
||||
LOGGER.info("Chargement de " + organisationsDisponibles.size() + " organisations disponibles");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des organisations: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void chargerStatistiques() {
|
||||
try {
|
||||
MembreService.StatistiquesMembreDTO stats = membreService.obtenirStatistiques();
|
||||
if (stats != null) {
|
||||
totalMembres = stats.getTotalMembres() != null ? stats.getTotalMembres().intValue() : 0;
|
||||
membresActifs = stats.getMembresActifs() != null ? stats.getMembresActifs().intValue() : 0;
|
||||
membresInactifs = stats.getMembresInactifs() != null ? stats.getMembresInactifs().intValue() : 0;
|
||||
}
|
||||
actualiserCompteur();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des statistiques: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void initialiserColonnesExport() {
|
||||
colonnesExport = new ArrayList<>();
|
||||
colonnesExport.add("PERSO");
|
||||
colonnesExport.add("CONTACT");
|
||||
colonnesExport.add("ADHESION");
|
||||
}
|
||||
|
||||
public void actualiserCompteur() {
|
||||
actualiserCompteur(null);
|
||||
}
|
||||
|
||||
public void actualiserCompteur(jakarta.faces.event.AjaxBehaviorEvent event) {
|
||||
try {
|
||||
// Appel au backend pour obtenir le comptage exact selon les filtres
|
||||
String statut = null;
|
||||
if ("ACTIFS".equals(scopeExport)) {
|
||||
statut = "ACTIF";
|
||||
} else if ("INACTIFS".equals(scopeExport)) {
|
||||
statut = "INACTIF";
|
||||
} else if (statutFilter != null && !statutFilter.isEmpty()) {
|
||||
statut = statutFilter;
|
||||
}
|
||||
|
||||
String dateAdhesionDebutStr = dateAdhesionDebut != null ? dateAdhesionDebut.toString() : null;
|
||||
String dateAdhesionFinStr = dateAdhesionFin != null ? dateAdhesionFin.toString() : null;
|
||||
|
||||
Long count = membreService.compterMembresPourExport(
|
||||
organisationId,
|
||||
statut,
|
||||
typeFilter != null && !typeFilter.isEmpty() ? typeFilter : null,
|
||||
dateAdhesionDebutStr,
|
||||
dateAdhesionFinStr
|
||||
);
|
||||
|
||||
nombreMembresAExporter = count != null ? count.intValue() : 0;
|
||||
|
||||
LOGGER.info("Comptage des membres pour export: " + nombreMembresAExporter);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'actualisation du compteur: " + e.getMessage());
|
||||
// Fallback sur estimation basée sur les statistiques
|
||||
if ("TOUS".equals(scopeExport)) {
|
||||
nombreMembresAExporter = totalMembres;
|
||||
} else if ("ACTIFS".equals(scopeExport)) {
|
||||
nombreMembresAExporter = membresActifs;
|
||||
} else if ("INACTIFS".equals(scopeExport)) {
|
||||
nombreMembresAExporter = membresInactifs;
|
||||
} else {
|
||||
nombreMembresAExporter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exporterMembres() {
|
||||
if (colonnesExport == null || colonnesExport.isEmpty()) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention",
|
||||
"Veuillez sélectionner au moins une catégorie de colonnes à exporter"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (nombreMembresAExporter == 0) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention",
|
||||
"Aucun membre ne correspond aux critères sélectionnés"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.info("Export des membres: format=" + formatExport + ", nombre=" + nombreMembresAExporter);
|
||||
|
||||
String dateAdhesionDebutStr = dateAdhesionDebut != null ? dateAdhesionDebut.toString() : null;
|
||||
String dateAdhesionFinStr = dateAdhesionFin != null ? dateAdhesionFin.toString() : null;
|
||||
|
||||
// Générer un mot de passe aléatoire si le chiffrement est demandé
|
||||
String motDePasse = null;
|
||||
if (chiffrerDonnees) {
|
||||
if (motDePasseExport != null && !motDePasseExport.trim().isEmpty()) {
|
||||
motDePasse = motDePasseExport;
|
||||
} else {
|
||||
// Générer un mot de passe aléatoire de 12 caractères
|
||||
motDePasse = genererMotDePasseAleatoire();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] exportData = membreService.exporterExcel(
|
||||
formatExport,
|
||||
organisationId,
|
||||
statutFilter,
|
||||
typeFilter,
|
||||
dateAdhesionDebutStr,
|
||||
dateAdhesionFinStr,
|
||||
colonnesExport,
|
||||
inclureHeaders,
|
||||
formaterDates,
|
||||
inclureStatistiques && "EXCEL".equals(formatExport), // Statistiques uniquement pour Excel
|
||||
motDePasse
|
||||
);
|
||||
|
||||
FacesContext facesContext = FacesContext.getCurrentInstance();
|
||||
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
|
||||
|
||||
response.reset();
|
||||
String contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
String extension = "xlsx";
|
||||
|
||||
if ("CSV".equals(formatExport)) {
|
||||
contentType = "text/csv";
|
||||
extension = "csv";
|
||||
}
|
||||
|
||||
response.setContentType(contentType);
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"membres_export_" +
|
||||
LocalDate.now() + "." + extension + "\"");
|
||||
response.setContentLength(exportData.length);
|
||||
|
||||
response.getOutputStream().write(exportData);
|
||||
response.getOutputStream().flush();
|
||||
facesContext.responseComplete();
|
||||
|
||||
// Ajouter à l'historique
|
||||
ExportHistorique historique = new ExportHistorique();
|
||||
historique.setDate(LocalDateTime.now());
|
||||
historique.setFormat(formatExport);
|
||||
historique.setNombreMembres(nombreMembresAExporter);
|
||||
historique.setTaille(formatTaille(exportData.length));
|
||||
historiqueExports.add(0, historique); // Ajouter au début
|
||||
|
||||
// Afficher le mot de passe si le chiffrement était demandé
|
||||
if (chiffrerDonnees && motDePasse != null) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Fichier protégé",
|
||||
"Le fichier a été protégé par un mot de passe. " +
|
||||
"Mot de passe: " + motDePasse +
|
||||
" (Note: Le fichier est protégé contre la modification, mais peut toujours être ouvert)"));
|
||||
}
|
||||
|
||||
LOGGER.info("Export généré et téléchargé: " + exportData.length + " bytes");
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Erreur lors du téléchargement de l'export: " + e.getMessage());
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Impossible de télécharger l'export: " + e.getMessage()));
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'export: " + e.getMessage());
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Impossible d'exporter les membres: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private String formatTaille(long bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + " B";
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return String.format("%.2f KB", bytes / 1024.0);
|
||||
} else {
|
||||
return String.format("%.2f MB", bytes / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
public void telechargerExport(ExportHistorique export) {
|
||||
// L'historique est stocké localement dans la session, pas de téléchargement depuis le serveur
|
||||
LOGGER.info("Export historique consulté: " + export.getDate() + " - " + export.getFormat());
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information",
|
||||
"L'export du " + export.getDate().format(java.time.format.DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")) +
|
||||
" n'est plus disponible. Veuillez générer un nouvel export."));
|
||||
}
|
||||
|
||||
private String genererMotDePasseAleatoire() {
|
||||
String caracteres = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&*";
|
||||
java.util.Random random = new java.util.Random();
|
||||
StringBuilder motDePasse = new StringBuilder(12);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
motDePasse.append(caracteres.charAt(random.nextInt(caracteres.length())));
|
||||
}
|
||||
return motDePasse.toString();
|
||||
}
|
||||
|
||||
public void reinitialiser() {
|
||||
formatExport = "EXCEL";
|
||||
scopeExport = "TOUS";
|
||||
colonnesExport = new ArrayList<>();
|
||||
colonnesExport.add("PERSO");
|
||||
colonnesExport.add("CONTACT");
|
||||
colonnesExport.add("ADHESION");
|
||||
statutFilter = "";
|
||||
typeFilter = "";
|
||||
organisationId = null;
|
||||
dateAdhesionDebut = null;
|
||||
dateAdhesionFin = null;
|
||||
inclureHeaders = true;
|
||||
formaterDates = true;
|
||||
inclureStatistiques = false;
|
||||
chiffrerDonnees = false;
|
||||
motDePasseExport = "";
|
||||
actualiserCompteur();
|
||||
}
|
||||
|
||||
// Classe interne pour l'historique des exports
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ExportHistorique implements Serializable {
|
||||
private LocalDateTime date;
|
||||
private String format;
|
||||
private int nombreMembres;
|
||||
private String taille;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.service.MembreService;
|
||||
import dev.lions.unionflow.client.service.MembreImportMultipartForm;
|
||||
import dev.lions.unionflow.client.service.AssociationService;
|
||||
import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO;
|
||||
import dev.lions.unionflow.client.dto.AssociationDTO;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.primefaces.event.FileUploadEvent;
|
||||
import org.primefaces.model.file.UploadedFile;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Named("membreImportBean")
|
||||
@ViewScoped
|
||||
@Getter
|
||||
@Setter
|
||||
public class MembreImportBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOGGER = Logger.getLogger(MembreImportBean.class.getName());
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
MembreService membreService;
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
AssociationService associationService;
|
||||
|
||||
// Fichier à importer
|
||||
private UploadedFile fichierImport;
|
||||
|
||||
// Options d'import
|
||||
private boolean mettreAJourExistants = false;
|
||||
private boolean ignorerErreurs = false;
|
||||
private UUID organisationId;
|
||||
private String typeMembreDefaut = "";
|
||||
|
||||
// Organisations disponibles
|
||||
private List<OrganisationDTO> organisationsDisponibles = new ArrayList<>();
|
||||
|
||||
// Résultat de l'import
|
||||
private ResultatImport resultatImport;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
chargerOrganisations();
|
||||
}
|
||||
|
||||
private void chargerOrganisations() {
|
||||
organisationsDisponibles = new ArrayList<>();
|
||||
try {
|
||||
List<AssociationDTO> associations = associationService.listerToutes(0, 1000);
|
||||
for (AssociationDTO assoc : associations) {
|
||||
OrganisationDTO org = new OrganisationDTO();
|
||||
org.setId(assoc.getId());
|
||||
org.setNom(assoc.getNom());
|
||||
org.setVille(assoc.getVille());
|
||||
organisationsDisponibles.add(org);
|
||||
}
|
||||
LOGGER.info("Chargement de " + organisationsDisponibles.size() + " organisations disponibles");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des organisations: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère l'upload du fichier (appelé par PrimeFaces FileUpload)
|
||||
*/
|
||||
public void handleFileUpload(FileUploadEvent event) {
|
||||
fichierImport = event.getFile();
|
||||
LOGGER.info("Fichier sélectionné: " + (fichierImport != null ? fichierImport.getFileName() : "null"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance l'import des membres
|
||||
*/
|
||||
public void importerMembres() {
|
||||
if (fichierImport == null || fichierImport.getFileName() == null || fichierImport.getFileName().isEmpty()) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention",
|
||||
"Veuillez sélectionner un fichier à importer"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.info("Import du fichier: " + fichierImport.getFileName());
|
||||
|
||||
byte[] fileContent = fichierImport.getContent();
|
||||
String fileName = fichierImport.getFileName();
|
||||
|
||||
// Créer le formulaire multipart
|
||||
MembreImportMultipartForm form = new MembreImportMultipartForm();
|
||||
form.file = fileContent;
|
||||
form.fileName = fileName;
|
||||
form.organisationId = organisationId;
|
||||
form.typeMembreDefaut = typeMembreDefaut != null && !typeMembreDefaut.isEmpty() ? typeMembreDefaut : "ACTIF";
|
||||
form.mettreAJourExistants = mettreAJourExistants;
|
||||
form.ignorerErreurs = ignorerErreurs;
|
||||
|
||||
// Appeler le service REST
|
||||
MembreService.ResultatImportDTO result = membreService.importerDonnees(form);
|
||||
|
||||
// Convertir le résultat
|
||||
resultatImport = new ResultatImport();
|
||||
resultatImport.setTotalTraite(result.getTotalLignes() != null ? result.getTotalLignes() : 0);
|
||||
resultatImport.setReussis(result.getLignesTraitees() != null ? result.getLignesTraitees() : 0);
|
||||
resultatImport.setEchecs(result.getLignesErreur() != null ? result.getLignesErreur() : 0);
|
||||
resultatImport.setIgnores(0);
|
||||
|
||||
// Convertir les erreurs
|
||||
List<ErreurImport> erreursList = new ArrayList<>();
|
||||
if (result.getErreurs() != null) {
|
||||
for (int i = 0; i < result.getErreurs().size(); i++) {
|
||||
ErreurImport erreur = new ErreurImport();
|
||||
erreur.setLigne(i + 1);
|
||||
erreur.setMessage(result.getErreurs().get(i));
|
||||
erreursList.add(erreur);
|
||||
}
|
||||
}
|
||||
resultatImport.setErreurs(erreursList);
|
||||
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès",
|
||||
"Import terminé: " + resultatImport.getReussis() + " membres importés avec succès" +
|
||||
(resultatImport.getEchecs() > 0 ? ", " + resultatImport.getEchecs() + " erreurs" : "")));
|
||||
|
||||
// Réinitialiser le fichier
|
||||
fichierImport = null;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'import: " + e.getMessage());
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Détails de l'erreur d'import", e);
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Impossible d'importer le fichier: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public void telechargerModele() {
|
||||
try {
|
||||
LOGGER.info("Téléchargement du modèle d'import");
|
||||
|
||||
byte[] modele = membreService.telechargerModeleImport();
|
||||
|
||||
FacesContext facesContext = FacesContext.getCurrentInstance();
|
||||
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
|
||||
|
||||
response.reset();
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"modele_import_membres.xlsx\"");
|
||||
response.setContentLength(modele.length);
|
||||
|
||||
response.getOutputStream().write(modele);
|
||||
response.getOutputStream().flush();
|
||||
facesContext.responseComplete();
|
||||
|
||||
LOGGER.info("Modèle d'import téléchargé");
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Erreur lors du téléchargement du modèle: " + e.getMessage());
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Impossible de télécharger le modèle: " + e.getMessage()));
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du téléchargement du modèle: " + e.getMessage());
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Impossible de télécharger le modèle: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public void reinitialiser() {
|
||||
fichierImport = null;
|
||||
mettreAJourExistants = false;
|
||||
ignorerErreurs = false;
|
||||
organisationId = null;
|
||||
typeMembreDefaut = "";
|
||||
resultatImport = null;
|
||||
}
|
||||
|
||||
// Classe interne pour le résultat de l'import
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ResultatImport implements Serializable {
|
||||
private int totalTraite;
|
||||
private int reussis;
|
||||
private int echecs;
|
||||
private int ignores;
|
||||
private List<ErreurImport> erreurs = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ErreurImport implements Serializable {
|
||||
private int ligne;
|
||||
private String message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
<!DOCTYPE html>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{membreExportBean}"/>
|
||||
<ui:define name="title">Export des Membres - UnionFlow</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-download text-blue-500" />
|
||||
<ui:param name="title" value="Export des Membres" />
|
||||
<ui:param name="description" value="Exportez les données des membres dans différents formats" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formActionsEntete">
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Retour" />
|
||||
<ui:param name="icon" value="pi pi-arrow-left" />
|
||||
<ui:param name="outcome" value="/pages/secure/membre/liste" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<!-- Statistiques -->
|
||||
<div class="grid mb-3">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Membres" />
|
||||
<ui:param name="value" value="#{membreExportBean.totalMembres}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="colSize" value="col-12 md:col-4" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Membres Actifs" />
|
||||
<ui:param name="value" value="#{membreExportBean.membresActifs}" />
|
||||
<ui:param name="icon" value="pi-check-circle" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="colSize" value="col-12 md:col-4" />
|
||||
</ui:include>
|
||||
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Membres Inactifs" />
|
||||
<ui:param name="value" value="#{membreExportBean.membresInactifs}" />
|
||||
<ui:param name="icon" value="pi-times-circle" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="colSize" value="col-12 md:col-4" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire d'export -->
|
||||
<div class="card">
|
||||
<h:form id="formExport">
|
||||
<h5 class="mb-4">Configuration de l'export</h5>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="formatExport" value="Format d'export *" />
|
||||
<p:selectOneMenu id="formatExport" value="#{membreExportBean.formatExport}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Excel (.xlsx)" itemValue="EXCEL" />
|
||||
<f:selectItem itemLabel="CSV (.csv)" itemValue="CSV" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="scopeExport" value="Portée de l'export *" />
|
||||
<p:selectOneMenu id="scopeExport" value="#{membreExportBean.scopeExport}" required="true" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les membres" itemValue="TOUS" />
|
||||
<f:selectItem itemLabel="Membres actifs uniquement" itemValue="ACTIFS" />
|
||||
<f:selectItem itemLabel="Membres inactifs uniquement" itemValue="INACTIFS" />
|
||||
<f:selectItem itemLabel="Membres sélectionnés" itemValue="SELECTION" />
|
||||
<p:ajax event="change" listener="#{membreExportBean.actualiserCompteur}" update=":formExport" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Colonnes à exporter" />
|
||||
<ui:define name="content">
|
||||
<div class="field">
|
||||
<p:outputLabel for="colonnesExport" value="Sélectionnez les colonnes à inclure *" />
|
||||
<p:selectCheckboxMenu id="colonnesExport"
|
||||
value="#{membreExportBean.colonnesExport}"
|
||||
multiple="true"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Informations personnelles (Nom, Prénom, Date naissance, Genre)" itemValue="PERSO" />
|
||||
<f:selectItem itemLabel="Coordonnées (Email, Téléphone, Adresse)" itemValue="CONTACT" />
|
||||
<f:selectItem itemLabel="Informations adhésion (Date adhésion, Type membre, Statut)" itemValue="ADHESION" />
|
||||
<f:selectItem itemLabel="Cotisations (Statut cotisations, Dernier paiement)" itemValue="COTISATIONS" />
|
||||
<f:selectItem itemLabel="Participation événements (Taux participation, Événements)" itemValue="EVENEMENTS" />
|
||||
<f:selectItem itemLabel="Organisation (Entité, Ville)" itemValue="ORGANISATION" />
|
||||
<f:selectItem itemLabel="Famille (Membres de famille déclarés)" itemValue="FAMILLE" />
|
||||
</p:selectCheckboxMenu>
|
||||
<small class="text-600">Sélectionnez au moins une catégorie de colonnes</small>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Filtres optionnels" />
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field">
|
||||
<p:outputLabel for="statutFilter" value="Statut" />
|
||||
<p:selectOneMenu id="statutFilter" value="#{membreExportBean.statutFilter}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les statuts" itemValue="" />
|
||||
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
|
||||
<f:selectItem itemLabel="Inactif" itemValue="INACTIF" />
|
||||
<f:selectItem itemLabel="Suspendu" itemValue="SUSPENDU" />
|
||||
<f:selectItem itemLabel="Radié" itemValue="RADIE" />
|
||||
<p:ajax event="change" listener="#{membreExportBean.actualiserCompteur}" update=":formExport" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field">
|
||||
<p:outputLabel for="typeFilter" value="Type de membre" />
|
||||
<p:selectOneMenu id="typeFilter" value="#{membreExportBean.typeFilter}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les types" itemValue="" />
|
||||
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
|
||||
<f:selectItem itemLabel="Associé" itemValue="ASSOCIE" />
|
||||
<f:selectItem itemLabel="Bienfaiteur" itemValue="BIENFAITEUR" />
|
||||
<f:selectItem itemLabel="Honoraire" itemValue="HONORAIRE" />
|
||||
<p:ajax event="change" listener="#{membreExportBean.actualiserCompteur}" update=":formExport" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field">
|
||||
<p:outputLabel for="organisationFilter" value="Organisation" />
|
||||
<p:selectOneMenu id="organisationFilter" value="#{membreExportBean.organisationId}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Toutes les organisations" itemValue="" />
|
||||
<f:selectItems value="#{membreExportBean.organisationsDisponibles}"
|
||||
var="org"
|
||||
itemLabel="#{org.nom} (#{org.ville})"
|
||||
itemValue="#{org.id}" />
|
||||
<p:ajax event="change" listener="#{membreExportBean.actualiserCompteur}" update=":formExport" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="dateAdhesionDebut" value="Adhésion après le" />
|
||||
<p:calendar id="dateAdhesionDebut"
|
||||
value="#{membreExportBean.dateAdhesionDebut}"
|
||||
showIcon="true"
|
||||
navigator="true"
|
||||
locale="fr"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full">
|
||||
<p:ajax event="dateSelect" listener="#{membreExportBean.actualiserCompteur}" update=":formExport" />
|
||||
</p:calendar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="dateAdhesionFin" value="Adhésion avant le" />
|
||||
<p:calendar id="dateAdhesionFin"
|
||||
value="#{membreExportBean.dateAdhesionFin}"
|
||||
showIcon="true"
|
||||
navigator="true"
|
||||
locale="fr"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full">
|
||||
<p:ajax event="dateSelect" listener="#{membreExportBean.actualiserCompteur}" update=":formExport" />
|
||||
</p:calendar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<!-- Options d'export -->
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Options d'export" />
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:selectBooleanCheckbox id="inclureHeaders" value="#{membreExportBean.inclureHeaders}" />
|
||||
<p:outputLabel for="inclureHeaders" value="Inclure les en-têtes de colonnes" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:selectBooleanCheckbox id="formaterDates" value="#{membreExportBean.formaterDates}" />
|
||||
<p:outputLabel for="formaterDates" value="Formater les dates (DD/MM/YYYY)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:selectBooleanCheckbox id="inclureStatistiques" value="#{membreExportBean.inclureStatistiques}" />
|
||||
<p:outputLabel for="inclureStatistiques" value="Inclure un onglet statistiques (Excel uniquement)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:selectBooleanCheckbox id="chiffrerDonnees" value="#{membreExportBean.chiffrerDonnees}" />
|
||||
<p:outputLabel for="chiffrerDonnees" value="Chiffrer le fichier exporté" />
|
||||
<small class="text-600 block mt-1">Le fichier sera protégé par un mot de passe (généré automatiquement ou personnalisé ci-dessous)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12" rendered="#{membreExportBean.chiffrerDonnees}">
|
||||
<div class="field">
|
||||
<p:outputLabel for="motDePasseExport" value="Mot de passe personnalisé (optionnel)" />
|
||||
<p:password id="motDePasseExport" value="#{membreExportBean.motDePasseExport}"
|
||||
placeholder="Laisser vide pour générer automatiquement"
|
||||
styleClass="w-full" />
|
||||
<small class="text-600 block mt-1">Si vide, un mot de passe aléatoire sera généré et affiché après l'export</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<!-- Aperçu du nombre de membres à exporter -->
|
||||
<div class="surface-50 p-3 border-round mb-4">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<div class="font-medium mb-1">Nombre de membres à exporter :</div>
|
||||
<div class="text-600">#{membreExportBean.nombreMembresAExporter} membre(s) correspond(ent) aux critères sélectionnés</div>
|
||||
</div>
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="Actualiser le compteur" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreExportBean.actualiserCompteur}" />
|
||||
<ui:param name="update" value=":formExport" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="ui-button-sm" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="Générer l'export" />
|
||||
<ui:param name="icon" value="pi pi-download" />
|
||||
<ui:param name="action" value="#{membreExportBean.exporterMembres}" />
|
||||
<ui:param name="update" value="none" />
|
||||
<ui:param name="disabled" value="#{membreExportBean.colonnesExport == null or membreExportBean.colonnesExport.isEmpty() or membreExportBean.nombreMembresAExporter == 0}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreExportBean.reinitialiser}" />
|
||||
<ui:param name="update" value=":formExport" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<!-- Historique des exports -->
|
||||
<div class="card mt-3">
|
||||
<h5 class="mb-3">Historique des exports</h5>
|
||||
<p:dataTable value="#{membreExportBean.historiqueExports}" var="export"
|
||||
styleClass="p-datatable-sm"
|
||||
emptyMessage="Aucun export effectué">
|
||||
<p:column headerText="Date">
|
||||
<h:outputText value="#{export.date}">
|
||||
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" type="localDateTime" />
|
||||
</h:outputText>
|
||||
</p:column>
|
||||
<p:column headerText="Format">
|
||||
<p:tag value="#{export.format}" severity="info" />
|
||||
</p:column>
|
||||
<p:column headerText="Nombre de membres">
|
||||
<h:outputText value="#{export.nombreMembres}" />
|
||||
</p:column>
|
||||
<p:column headerText="Taille">
|
||||
<h:outputText value="#{export.taille}" />
|
||||
</p:column>
|
||||
<p:column headerText="Actions">
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="" />
|
||||
<ui:param name="icon" value="pi pi-download" />
|
||||
<ui:param name="action" value="#{membreExportBean.telechargerExport(export)}" />
|
||||
<ui:param name="update" value="none" />
|
||||
<ui:param name="title" value="Télécharger" />
|
||||
<ui:param name="styleClass" value="ui-button-sm" />
|
||||
</ui:include>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</div>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
<!DOCTYPE html>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{membreImportBean}"/>
|
||||
<ui:define name="title">Import en Masse des Membres - UnionFlow</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-upload text-green-500" />
|
||||
<ui:param name="title" value="Import en Masse des Membres" />
|
||||
<ui:param name="description" value="Importez plusieurs membres à la fois depuis un fichier Excel" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formActionsEntete">
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Retour" />
|
||||
<ui:param name="icon" value="pi pi-arrow-left" />
|
||||
<ui:param name="outcome" value="/pages/secure/membre/liste" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<!-- Instructions -->
|
||||
<div class="card mb-3">
|
||||
<div class="flex align-items-start">
|
||||
<i class="pi pi-info-circle text-blue-500 text-2xl mr-3"></i>
|
||||
<div class="flex-1">
|
||||
<h5 class="mt-0 mb-2">Instructions d'import</h5>
|
||||
<p class="text-600 mb-3">
|
||||
Téléchargez le modèle Excel, remplissez-le avec les données des membres, puis importez-le ici.
|
||||
</p>
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<h6 class="mb-2">Format du fichier :</h6>
|
||||
<ul class="text-600 text-sm m-0 pl-3">
|
||||
<li>Format Excel (.xlsx) ou CSV (.csv)</li>
|
||||
<li>Maximum 1000 lignes par import</li>
|
||||
<li>Taille maximale : 10 MB</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<h6 class="mb-2">Colonnes requises :</h6>
|
||||
<ul class="text-600 text-sm m-0 pl-3">
|
||||
<li>Nom, Prénom (obligatoires)</li>
|
||||
<li>Email, Téléphone (obligatoires)</li>
|
||||
<li>Date de naissance, Adresse</li>
|
||||
<li>Profession, Type membre</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire d'import -->
|
||||
<div class="card">
|
||||
<h:form id="formImport" enctype="multipart/form-data">
|
||||
<h5 class="mb-4">Fichier à importer</h5>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-8">
|
||||
<div class="field">
|
||||
<p:outputLabel for="fichierImport" value="Fichier Excel/CSV *" />
|
||||
<p:fileUpload id="fichierImport"
|
||||
mode="advanced"
|
||||
dragDropSupport="true"
|
||||
skinSimple="false"
|
||||
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,text/csv"
|
||||
fileLimit="1"
|
||||
sizeLimit="10485760"
|
||||
uploadLabel="Importer"
|
||||
cancelLabel="Annuler"
|
||||
chooseLabel="Sélectionner le fichier"
|
||||
invalidFileMessage="Type de fichier non supporté"
|
||||
fileLimitMessage="Un seul fichier autorisé"
|
||||
invalidSizeMessage="Taille de fichier trop importante (max 10MB)"
|
||||
fileUploadListener="#{membreImportBean.handleFileUpload}"
|
||||
update=":formImport"
|
||||
styleClass="w-full" />
|
||||
<small class="text-600">Formats acceptés : .xlsx, .xls, .csv - Maximum 10 MB</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field">
|
||||
<p:outputLabel />
|
||||
<div class="flex flex-column gap-2">
|
||||
<ui:include src="/templates/components/buttons/button-info.xhtml">
|
||||
<ui:param name="value" value="Télécharger modèle" />
|
||||
<ui:param name="icon" value="pi pi-download" />
|
||||
<ui:param name="action" value="#{membreImportBean.telechargerModele}" />
|
||||
<ui:param name="update" value="none" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
<ui:param name="styleClass" value="w-full" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Options d'import" />
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:selectBooleanCheckbox id="mettreAJourExistants" value="#{membreImportBean.mettreAJourExistants}" />
|
||||
<p:outputLabel for="mettreAJourExistants" value="Mettre à jour les membres existants" />
|
||||
<small class="text-600 block mt-1">Si coché, les membres existants (même email) seront mis à jour</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:selectBooleanCheckbox id="ignorerErreurs" value="#{membreImportBean.ignorerErreurs}" />
|
||||
<p:outputLabel for="ignorerErreurs" value="Ignorer les lignes en erreur" />
|
||||
<small class="text-600 block mt-1">Continuer l'import même si certaines lignes contiennent des erreurs</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="organisationImport" value="Organisation par défaut" />
|
||||
<p:selectOneMenu id="organisationImport" value="#{membreImportBean.organisationId}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner une organisation..." itemValue="" />
|
||||
<f:selectItems value="#{membreImportBean.organisationsDisponibles}"
|
||||
var="org"
|
||||
itemLabel="#{org.nom} (#{org.ville})"
|
||||
itemValue="#{org.id}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="typeMembreImport" value="Type de membre par défaut" />
|
||||
<p:selectOneMenu id="typeMembreImport" value="#{membreImportBean.typeMembreDefaut}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItem itemLabel="Actif" itemValue="ACTIF" />
|
||||
<f:selectItem itemLabel="Associé" itemValue="ASSOCIE" />
|
||||
<f:selectItem itemLabel="Bienfaiteur" itemValue="BIENFAITEUR" />
|
||||
<f:selectItem itemLabel="Honoraire" itemValue="HONORAIRE" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<!-- Résultats de l'import -->
|
||||
<div class="mt-4" rendered="#{membreImportBean.resultatImport != null}">
|
||||
<ui:decorate template="/templates/components/forms/form-section.xhtml">
|
||||
<ui:param name="title" value="Résultat de l'import" />
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total traité" />
|
||||
<ui:param name="value" value="#{membreImportBean.resultatImport.totalTraite}" />
|
||||
<ui:param name="icon" value="pi-file" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="colSize" value="col-12" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Réussis" />
|
||||
<ui:param name="value" value="#{membreImportBean.resultatImport.reussis}" />
|
||||
<ui:param name="icon" value="pi-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="colSize" value="col-12" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Échecs" />
|
||||
<ui:param name="value" value="#{membreImportBean.resultatImport.echecs}" />
|
||||
<ui:param name="icon" value="pi-times" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
<ui:param name="colSize" value="col-12" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Ignorés" />
|
||||
<ui:param name="value" value="#{membreImportBean.resultatImport.ignores}" />
|
||||
<ui:param name="icon" value="pi-eye-slash" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="colSize" value="col-12" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3" rendered="#{not empty membreImportBean.resultatImport.erreurs}">
|
||||
<h6 class="mb-2">Détails des erreurs :</h6>
|
||||
<div class="surface-50 p-3 border-round">
|
||||
<ui:repeat value="#{membreImportBean.resultatImport.erreurs}" var="erreur">
|
||||
<div class="mb-2">
|
||||
<span class="font-medium">Ligne #{erreur.ligne}:</span>
|
||||
<span class="text-red-500 ml-2">#{erreur.message}</span>
|
||||
</div>
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-2 mt-4">
|
||||
<ui:include src="/templates/components/buttons/button-success.xhtml">
|
||||
<ui:param name="value" value="Lancer l'import" />
|
||||
<ui:param name="icon" value="pi pi-upload" />
|
||||
<ui:param name="action" value="#{membreImportBean.importerMembres}" />
|
||||
<ui:param name="update" value=":formImport" />
|
||||
<ui:param name="disabled" value="#{membreImportBean.fichierImport == null}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/buttons/button-secondary.xhtml">
|
||||
<ui:param name="value" value="Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{membreImportBean.reinitialiser}" />
|
||||
<ui:param name="update" value=":formImport" />
|
||||
<ui:param name="outlined" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://java.sun.com/jsf/html"
|
||||
xmlns:f="http://java.sun.com/jsf/core"
|
||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Carte KPI (Indicateur de Performance)
|
||||
|
||||
Paramètres:
|
||||
- title: Titre du KPI (requis)
|
||||
- value: Valeur principale à afficher (requis)
|
||||
- icon: Classe d'icône PrimeIcons (requis, ex: pi-users)
|
||||
- iconColor: Couleur de l'icône (requis, ex: blue-600, green-600, purple-600, orange-600)
|
||||
- growthValue: Valeur de croissance (optionnel, ex: "12.5", "5", "+10")
|
||||
- growthLabel: Libellé de la croissance (optionnel, ex: "ce mois", "vs mois dernier")
|
||||
- growthType: Type de croissance - "percentage" (défaut) ou "number" ou "status"
|
||||
- showGrowth: Afficher la section croissance (défaut: true si growthValue fourni)
|
||||
- noDataLabel: Libellé si pas de données (optionnel, défaut: "Données non disponibles")
|
||||
- progressValue: Valeur pour la barre de progression 0-100 (optionnel)
|
||||
- showProgress: Afficher la barre de progression (défaut: true si progressValue fourni)
|
||||
- statusIcon: Icône de statut au lieu de croissance (optionnel, ex: "pi-check-circle")
|
||||
- statusLabel: Libellé de statut (optionnel, ex: "En ligne")
|
||||
- statusValue: Valeur de statut (optionnel, ex: "5 actifs")
|
||||
- colSize: Taille de colonne (optionnel, défaut: "col-12 md:col-6 lg:col-3")
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. KPI simple avec croissance en pourcentage:
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Membres Actifs" />
|
||||
<ui:param name="value" value="#{bean.totalMembres}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="growthValue" value="#{bean.croissanceMembres}" />
|
||||
<ui:param name="growthLabel" value="ce mois" />
|
||||
<ui:param name="progressValue" value="#{bean.pourcentageMembres}" />
|
||||
</ui:include>
|
||||
|
||||
2. KPI avec croissance en nombre:
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Organisations" />
|
||||
<ui:param name="value" value="#{bean.totalEntites}" />
|
||||
<ui:param name="icon" value="pi-sitemap" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="growthValue" value="#{bean.nouvellesEntites}" />
|
||||
<ui:param name="growthLabel" value="nouvelles" />
|
||||
<ui:param name="growthType" value="number" />
|
||||
<ui:param name="progressValue" value="#{bean.pourcentageOrganisations}" />
|
||||
</ui:include>
|
||||
|
||||
3. KPI avec statut au lieu de croissance:
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Activité du Jour" />
|
||||
<ui:param name="value" value="#{bean.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="#{bean.utilisateursActifs} actifs" />
|
||||
<ui:param name="progressValue" value="#{bean.pourcentageActivite}" />
|
||||
</ui:include>
|
||||
|
||||
4. KPI sans croissance ni progression:
|
||||
<ui:include src="/templates/components/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total" />
|
||||
<ui:param name="value" value="#{bean.total}" />
|
||||
<ui:param name="icon" value="pi-calendar" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="showGrowth" value="false" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
<ui:param name="colSize" value="col-12 md:col-6 lg:col-3" />
|
||||
<ui:param name="showGrowth" value="#{not empty growthValue}" />
|
||||
<ui:param name="showProgress" value="#{not empty progressValue}" />
|
||||
<ui:param name="growthType" value="percentage" />
|
||||
<ui:param name="noDataLabel" value="Données non disponibles" />
|
||||
|
||||
<div class="field #{colSize}">
|
||||
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200">
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<!-- Header: Titre et Icône -->
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">#{title}</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Valeur principale -->
|
||||
<div class="text-900 font-bold text-2xl mb-3">#{value}</div>
|
||||
|
||||
<!-- Section Croissance ou Statut -->
|
||||
<c:choose>
|
||||
<!-- Mode Statut (statusIcon fourni) -->
|
||||
<c:when test="#{not empty statusIcon}">
|
||||
<div class="flex align-items-center mb-2" rendered="#{not empty statusValue and statusValue != '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">#{statusLabel}</span>
|
||||
<span class="text-500 text-xs">#{statusValue}</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{empty statusValue or statusValue == '0'}">
|
||||
Aucun utilisateur actif
|
||||
</div>
|
||||
</c:when>
|
||||
<!-- Mode Croissance -->
|
||||
<c:otherwise>
|
||||
<c:choose>
|
||||
<!-- Croissance en nombre -->
|
||||
<c:when test="#{growthType == 'number'}">
|
||||
<div class="flex align-items-center mb-2" rendered="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}</span>
|
||||
<span class="text-500 text-xs">#{growthLabel}</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{not showGrowth or empty growthValue or growthValue == '0' or growthValue == '0.0'}">
|
||||
#{noDataLabel}
|
||||
</div>
|
||||
</c:when>
|
||||
<!-- Croissance en pourcentage (défaut) -->
|
||||
<c:otherwise>
|
||||
<div class="flex align-items-center mb-2" rendered="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}%</span>
|
||||
<span class="text-500 text-xs">#{growthLabel}</span>
|
||||
</div>
|
||||
<div class="text-500 text-xs" rendered="#{not showGrowth or empty growthValue or growthValue == '0' or growthValue == '0.0'}">
|
||||
#{noDataLabel}
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
<!-- Barre de progression -->
|
||||
<p:progressBar value="#{progressValue}"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%;"
|
||||
rendered="#{showProgress}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Champ de détail en ligne (label à gauche, valeur à droite)
|
||||
Usage:
|
||||
<ui:include src="/templates/components/forms/detail-field-row.xhtml">
|
||||
<ui:param name="label" value="Nom complet" />
|
||||
<ui:param name="value" value="#{bean.property}" />
|
||||
<ui:param name="valueClass" value="font-bold text-primary" />
|
||||
<ui:param name="suffix" value="(optionnel)" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
<div class="flex justify-content-between align-items-center mb-2">
|
||||
<span class="font-medium text-900">#{label}:</span>
|
||||
<div class="text-right">
|
||||
<span class="#{valueClass}">#{value}</span>
|
||||
<small class="text-600 ml-1" rendered="#{not empty suffix}">#{suffix}</small>
|
||||
</div>
|
||||
</div>
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Photo de profil avec upload
|
||||
Paramètres:
|
||||
- photoId: ID du panelGroup pour la photo (requis)
|
||||
- photoUrl: URL de la photo (optionnel)
|
||||
- initiales: Initiales à afficher si pas de photo (requis)
|
||||
- formId: ID du formulaire pour l'upload (requis)
|
||||
- listener: Méthode bean pour gérer l'upload (requis)
|
||||
- update: Composants à mettre à jour après upload (requis)
|
||||
- size: Taille de la photo en px (optionnel, défaut: 120)
|
||||
-->
|
||||
|
||||
<ui:param name="size" value="120" />
|
||||
|
||||
<h:panelGroup id="#{photoId}">
|
||||
<div class="border-circle overflow-hidden" style="width: #{size}px; height: #{size}px;">
|
||||
<h:graphicImage value="#{photoUrl}"
|
||||
style="width: 100%; height: 100%; object-fit: cover;"
|
||||
rendered="#{photoUrl != null}" />
|
||||
<div class="bg-primary text-white flex align-items-center justify-content-center w-full h-full"
|
||||
rendered="#{photoUrl == null}">
|
||||
<span style="font-size: #{size / 2}px;">#{initiales}</span>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
<h:form id="#{formId}" enctype="multipart/form-data">
|
||||
<p:fileUpload mode="simple" skinSimple="true"
|
||||
label="Changer photo" chooseLabel="Modifier"
|
||||
accept="image/*" maxFileSize="2000000"
|
||||
listener="#{listener}"
|
||||
update="#{update}"
|
||||
styleClass="mt-2 w-full" />
|
||||
</h:form>
|
||||
</ui:composition>
|
||||
|
||||
Reference in New Issue
Block a user