diff --git a/.gitignore b/.gitignore index bdac85e..086aad3 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,31 @@ credentials.json # Quarkus specific .quarkus/ quarkus.log + +# JSF/Faces specific +**/META-INF/resources/.faces-config.xml.jsfdia +**/javax.faces.resource/ + +# PrimeFaces cache +**/primefaces_resource_cache/ + +# Node modules (if using npm/webpack for frontend assets) +node_modules/ +npm-debug.log +yarn-error.log +package-lock.json +yarn.lock + +# Static resources compiled +src/main/resources/META-INF/resources/dist/ +src/main/resources/META-INF/resources/assets/vendor/ + +# Database files (dev) +*.db +*.sqlite +*.h2.db + +# Application config overrides (local dev) +application-local.properties +application-dev-override.properties +*-secret.properties diff --git a/assign-roles.sh b/assign-roles.sh deleted file mode 100644 index ebade29..0000000 --- a/assign-roles.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -KEYCLOAK_URL="http://localhost:8180" -ADMIN_USER="admin" -ADMIN_PASS="admin" -REALM_NAME="unionflow" -USER_ID="4ebcdfef-960e-4dd2-b89c-028129af906d" - -echo "🔧 Attribution des rôles à l'utilisateur test..." - -# Obtenir le token -TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=$ADMIN_USER" \ - -d "password=$ADMIN_PASS" \ - -d "grant_type=password" \ - -d "client_id=admin-cli" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) - -if [ -z "$TOKEN" ]; then - echo "❌ Impossible d'obtenir le token" - exit 1 -fi - -# Récupérer les rôles -ROLES_JSON=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \ - -H "Authorization: Bearer $TOKEN") - -# Extraire les IDs des rôles MEMBRE et ADMIN_ENTITE -ROLE_MEMBRE_ID=$(echo "$ROLES_JSON" | grep -o '"id":"[^"]*","name":"MEMBRE"' | grep -o '"id":"[^"]*' | cut -d'"' -f4) -ROLE_ADMIN_ID=$(echo "$ROLES_JSON" | grep -o '"id":"[^"]*","name":"ADMIN_ENTITE"' | grep -o '"id":"[^"]*' | cut -d'"' -f4) - -echo "MEMBRE ID: $ROLE_MEMBRE_ID" -echo "ADMIN_ENTITE ID: $ROLE_ADMIN_ID" - -if [ -n "$ROLE_MEMBRE_ID" ]; then - curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/role-mappings/realm" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "[{\"id\":\"$ROLE_MEMBRE_ID\",\"name\":\"MEMBRE\"}]" > /dev/null 2>&1 - echo "✅ Rôle MEMBRE assigné" -fi - -if [ -n "$ROLE_ADMIN_ID" ]; then - curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/role-mappings/realm" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "[{\"id\":\"$ROLE_ADMIN_ID\",\"name\":\"ADMIN_ENTITE\"}]" > /dev/null 2>&1 - echo "✅ Rôle ADMIN_ENTITE assigné" -fi - -echo "" -echo "======================================================== " -echo "✅ Configuration terminée!" -echo "========================================================" -echo "" -echo "📋 Identifiants de connexion:" -echo " - Username: test@unionflow.dev" -echo " - Password: test123" -echo "" -echo "🚀 Prochaines étapes:" -echo " 1. Lancez l'application: ./start-local.sh" -echo " 2. Accédez à: http://localhost:8086" -echo " 3. Connectez-vous avec les identifiants ci-dessus" -echo "" diff --git a/clients.json b/clients.json deleted file mode 100644 index edc479d..0000000 --- a/clients.json +++ /dev/null @@ -1 +0,0 @@ -{"error":"HTTP 401 Unauthorized"} \ No newline at end of file diff --git a/Dockerfile b/docker/Dockerfile similarity index 93% rename from Dockerfile rename to docker/Dockerfile index f21b6b1..5d12bc3 100644 --- a/Dockerfile +++ b/docker/Dockerfile @@ -9,7 +9,7 @@ ENV LANGUAGE='en_US:en' # Configuration Quarkus ENV QUARKUS_PROFILE=prod -ENV QUARKUS_HTTP_PORT=8080 +ENV QUARKUS_HTTP_PORT=8086 ENV QUARKUS_HTTP_HOST=0.0.0.0 # Configuration Backend UnionFlow @@ -39,8 +39,8 @@ COPY --chown=appuser:appuser target/quarkus-app/quarkus/ /app/quarkus/ USER appuser -# Exposer le port 8080 -EXPOSE 8080 +# Exposer le port 8086 +EXPOSE 8086 # Variables JVM optimisées ENV JAVA_OPTS="-Xmx1g -Xms512m \ @@ -60,4 +60,4 @@ ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/quarkus-run.jar"] # Health check sur le bon port HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \ - CMD curl -f http://localhost:8080/q/health/ready || exit 1 + CMD curl -f http://localhost:8086/q/health/ready || exit 1 diff --git a/Dockerfile.prod b/docker/Dockerfile.prod similarity index 95% rename from Dockerfile.prod rename to docker/Dockerfile.prod index e029475..249d1b7 100644 --- a/Dockerfile.prod +++ b/docker/Dockerfile.prod @@ -31,12 +31,13 @@ ENV QUARKUS_PROFILE=prod ENV QUARKUS_HTTP_PORT=8086 ENV QUARKUS_HTTP_HOST=0.0.0.0 -# Configuration Keycloak/OIDC (production) +# Configuration Keycloak OIDC (production) ENV QUARKUS_OIDC_AUTH_SERVER_URL=https://security.lions.dev/realms/unionflow ENV QUARKUS_OIDC_CLIENT_ID=unionflow-client ENV QUARKUS_OIDC_ENABLED=true ENV QUARKUS_OIDC_TLS_VERIFICATION=required -ENV KEYCLOAK_CLIENT_SECRET=changeme +# KEYCLOAK_CLIENT_SECRET MUST be injected via Kubernetes Secret at runtime +ENV KEYCLOAK_CLIENT_SECRET= # Configuration API Backend ENV UNIONFLOW_BACKEND_URL=https://api.lions.dev/unionflow diff --git a/keycloak-config.sh b/keycloak-config.sh deleted file mode 100644 index 66c419a..0000000 --- a/keycloak-config.sh +++ /dev/null @@ -1,208 +0,0 @@ -#!/bin/bash - -# Script complet de configuration Keycloak - -KEYCLOAK_URL="http://localhost:8180" -ADMIN_USER="admin" -ADMIN_PASS="admin" -REALM_NAME="unionflow" -CLIENT_ID="unionflow-client" - -echo "🔧 Configuration automatique de Keycloak..." -echo "" - -# Obtenir le token -echo "1. Obtention du token admin..." -TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=$ADMIN_USER" \ - -d "password=$ADMIN_PASS" \ - -d "grant_type=password" \ - -d "client_id=admin-cli") - -TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) - -if [ -z "$TOKEN" ]; then - echo "❌ Impossible d'obtenir le token admin" - exit 1 -fi -echo "✅ Token obtenu" - -# Créer le realm (ignore si existe déjà) -echo "" -echo "2. Création du realm..." -curl -s -X POST "$KEYCLOAK_URL/admin/realms" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"realm\":\"$REALM_NAME\",\"enabled\":true,\"displayName\":\"UnionFlow\"}" > /dev/null 2>&1 -echo "✅ Realm vérifié" - -# Créer les rôles -echo "" -echo "3. Création des rôles..." -curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"SUPER_ADMIN","description":"Super admin"}' > /dev/null 2>&1 - -curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"ADMIN_ENTITE","description":"Admin entite"}' > /dev/null 2>&1 - -curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"MEMBRE","description":"Membre"}' > /dev/null 2>&1 - -curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"GESTIONNAIRE_MEMBRE","description":"Gestionnaire"}' > /dev/null 2>&1 -echo "✅ Rôles vérifiés" - -# Créer le client -echo "" -echo "4. Création du client..." -curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/clients" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"clientId\":\"$CLIENT_ID\",\"enabled\":true,\"protocol\":\"openid-connect\",\"publicClient\":false,\"directAccessGrantsEnabled\":true,\"standardFlowEnabled\":true,\"implicitFlowEnabled\":false,\"rootUrl\":\"http://localhost:8086\",\"redirectUris\":[\"http://localhost:8086/*\"],\"webOrigins\":[\"http://localhost:8086\"],\"attributes\":{\"post.logout.redirect.uris\":\"http://localhost:8086/*\"}}" > /dev/null 2>&1 -echo "✅ Client vérifié" - -# Récupérer l'UUID du client -echo "" -echo "5. Récupération du client UUID..." -curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/clients" \ - -H "Authorization: Bearer $TOKEN" > clients_temp.json - -# Sauvegarder dans un fichier pour debug -cat clients_temp.json > clients_debug.json - -# Extraire seulement l'entrée du client unionflow-client -# On cherche la ligne complète qui contient notre client -CLIENT_UUID=$(cat clients_temp.json | tr ',' '\n' | grep -A 10 "\"clientId\":\"$CLIENT_ID\"" | grep "\"id\":" | head -1 | grep -o '"[a-f0-9-]*"' | tr -d '"') - -if [ -z "$CLIENT_UUID" ]; then - echo "❌ Impossible de trouver le client UUID" - echo "Contenu du fichier (premiers 500 caractères):" - head -c 500 clients_debug.json - exit 1 -fi -echo "✅ Client UUID: $CLIENT_UUID" - -# Récupérer le client secret -echo "" -echo "6. Récupération du client secret..." -SECRET_JSON=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/clients/$CLIENT_UUID/client-secret" \ - -H "Authorization: Bearer $TOKEN") - -CLIENT_SECRET=$(echo "$SECRET_JSON" | grep -o '"value":"[^"]*' | cut -d'"' -f4) - -if [ -z "$CLIENT_SECRET" ]; then - echo "❌ Impossible de récupérer le client secret" - echo "Contenu reçu: $SECRET_JSON" - exit 1 -fi -echo "✅ Client Secret: $CLIENT_SECRET" - -# Configurer le mapper de rôles -echo "" -echo "7. Configuration du mapper de rôles..." -SCOPES_JSON=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/clients/$CLIENT_UUID/default-client-scopes" \ - -H "Authorization: Bearer $TOKEN") - -SCOPE_ID=$(echo "$SCOPES_JSON" | grep -o '"id":"[^"]*"' | grep -A5 "dedicated" | head -1 | cut -d'"' -f4) - -if [ -n "$SCOPE_ID" ]; then - curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/client-scopes/$SCOPE_ID/protocol-mappers/models" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"realm-roles","protocol":"openid-connect","protocolMapper":"oidc-usermodel-realm-role-mapper","config":{"multivalued":"true","userinfo.token.claim":"true","id.token.claim":"true","access.token.claim":"true","claim.name":"roles","jsonType.label":"String"}}' > /dev/null 2>&1 - echo "✅ Mapper configuré" -else - echo "⚠️ Scope non trouvé, mapper à configurer manuellement" -fi - -# Créer l'utilisateur test -echo "" -echo "8. Création de l'utilisateur test..." -curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"username":"test@unionflow.dev","email":"test@unionflow.dev","firstName":"Test","lastName":"User","enabled":true,"emailVerified":true}' > /dev/null 2>&1 - -# Récupérer l'ID de l'utilisateur -USER_JSON=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users?username=test@unionflow.dev" \ - -H "Authorization: Bearer $TOKEN") - -USER_ID=$(echo "$USER_JSON" | grep -o '"id":"[^"]*' | head -1 | cut -d'"' -f4) - -if [ -n "$USER_ID" ]; then - # Définir le mot de passe - curl -s -X PUT "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/reset-password" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"type":"password","value":"test123","temporary":false}' > /dev/null 2>&1 - echo "✅ Utilisateur créé (test@unionflow.dev / test123)" - - # Récupérer et assigner les rôles - ROLES_JSON=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \ - -H "Authorization: Bearer $TOKEN") - - ROLE_MEMBRE=$(echo "$ROLES_JSON" | grep -B2 '"name":"MEMBRE"' | grep '"id"' | grep -o '"id":"[^"]*' | cut -d'"' -f4) - ROLE_ADMIN=$(echo "$ROLES_JSON" | grep -B2 '"name":"ADMIN_ENTITE"' | grep '"id"' | grep -o '"id":"[^"]*' | cut -d'"' -f4) - - if [ -n "$ROLE_MEMBRE" ]; then - curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/role-mappings/realm" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "[{\"id\":\"$ROLE_MEMBRE\",\"name\":\"MEMBRE\"}]" > /dev/null 2>&1 - echo " ✅ Rôle MEMBRE assigné" - fi - - if [ -n "$ROLE_ADMIN" ]; then - curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/role-mappings/realm" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d "[{\"id\":\"$ROLE_ADMIN\",\"name\":\"ADMIN_ENTITE\"}]" > /dev/null 2>&1 - echo " ✅ Rôle ADMIN_ENTITE assigné" - fi -else - echo "⚠️ Utilisateur non trouvé" -fi - -# Sauvegarder dans .env -echo "" -echo "9. Sauvegarde de la configuration..." -cat > .env << EOF -# Configuration Keycloak générée automatiquement -# Date: $(date) - -KEYCLOAK_CLIENT_SECRET=$CLIENT_SECRET -UNIONFLOW_BACKEND_URL=http://localhost:8085 - -# Informations de connexion pour tests -# Username: test@unionflow.dev -# Password: test123 -EOF - -echo "✅ Fichier .env créé" - -# Résumé -echo "" -echo "========================================================" -echo "✅ Configuration terminée avec succès!" -echo "========================================================" -echo "" -echo "📋 Résumé:" -echo " - Realm: $REALM_NAME" -echo " - Client ID: $CLIENT_ID" -echo " - Client Secret: $CLIENT_SECRET" -echo " - Utilisateur: test@unionflow.dev / test123" -echo "" -echo "🚀 Prochaines étapes:" -echo " 1. Lancez: ./start-local.sh (ou start-local.bat)" -echo " 2. Accédez à: http://localhost:8086" -echo " 3. Connectez-vous avec test@unionflow.dev / test123" -echo "" diff --git a/roles.json b/roles.json deleted file mode 100644 index edc479d..0000000 --- a/roles.json +++ /dev/null @@ -1 +0,0 @@ -{"error":"HTTP 401 Unauthorized"} \ No newline at end of file diff --git a/scopes.json b/scopes.json deleted file mode 100644 index 2eeb330..0000000 --- a/scopes.json +++ /dev/null @@ -1 +0,0 @@ -[{"id":"ca43f64e-d864-48c9-b969-834468690fbb","name":"web-origins"},{"id":"79b0a5da-b22c-4f42-82e1-17ca3e845e98","name":"acr"},{"id":"630b7e04-b7a8-487e-ab4e-8ef569f2ee30","name":"profile"},{"id":"9706160c-2b0c-4308-af92-b363d9f0d461","name":"roles"},{"id":"eb2f9842-0bba-45b1-9ffa-60b621937d6a","name":"basic"},{"id":"459abd14-dc0c-49d9-8248-445731115816","name":"email"}] \ No newline at end of file diff --git a/src/main/java/dev/lions/unionflow/client/api/dto/MembreDashboardResponse.java b/src/main/java/dev/lions/unionflow/client/api/dto/MembreDashboardResponse.java new file mode 100644 index 0000000..eefab52 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/api/dto/MembreDashboardResponse.java @@ -0,0 +1,35 @@ +package dev.lions.unionflow.client.api.dto; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * DTO received from the backend for the member dashboard synthesis. + */ +public record MembreDashboardResponse( + String prenom, + String nom, + LocalDate dateInscription, + + // Cotisations + BigDecimal mesCotisationsPaiement, + String statutCotisations, + Integer tauxCotisationsPerso, + + // Epargne + BigDecimal monSoldeEpargne, + BigDecimal evolutionEpargneNombre, + String evolutionEpargne, + Integer objectifEpargne, + + // Evenements + Integer mesEvenementsInscrits, + Integer evenementsAVenir, + Integer tauxParticipationPerso, + + // Aides + Integer mesDemandesAide, + Integer aidesEnCours, + Integer tauxAidesApprouvees) implements Serializable { +} diff --git a/src/main/java/dev/lions/unionflow/client/bean/MenuBean.java b/src/main/java/dev/lions/unionflow/client/bean/MenuBean.java index a2dce22..513a7ef 100644 --- a/src/main/java/dev/lions/unionflow/client/bean/MenuBean.java +++ b/src/main/java/dev/lions/unionflow/client/bean/MenuBean.java @@ -130,12 +130,22 @@ public class MenuBean implements Serializable { /** * Annuaire des Membres - Consultation de la liste (pas de modification) - * Visible à partir de MEMBRE_ACTIF (pour créer du lien social) + * Visible pour les responsables et bureau SEULEMENT (PAS pour MEMBRE_ACTIF) + * + * Raison métier: Un membre simple n'a généralement pas besoin de voir la liste complète + * des autres membres. Cela peut poser des problèmes de: + * - RGPD: Exposition non justifiée de données personnelles + * - Sécurité: Risque de spam/phishing entre membres + * - UX: Surcharge du menu pour un usage limité + * + * Si l'organisation souhaite activer l'annuaire pour MEMBRE_ACTIF, cela doit être + * fait via configuration explicite (future Phase 3). */ public boolean isAnnuaireMembresVisible() { return hasAnyRole("SUPER_ADMIN", "ADMIN_ORGANISATION", "SECRETAIRE", "TRESORIER", "RESPONSABLE_SOCIAL", "RESPONSABLE_EVENEMENTS", "RESPONSABLE_CREDIT", - "MEMBRE_BUREAU", "MEMBRE_ACTIF"); + "MEMBRE_BUREAU"); + // MEMBRE_ACTIF retiré intentionnellement pour raisons UX et RGPD } /** diff --git a/src/main/java/dev/lions/unionflow/client/bean/PageSecurityBean.java b/src/main/java/dev/lions/unionflow/client/bean/PageSecurityBean.java new file mode 100644 index 0000000..514efdc --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/bean/PageSecurityBean.java @@ -0,0 +1,179 @@ +package dev.lions.unionflow.client.bean; + +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.faces.context.FacesContext; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.jboss.logging.Logger; + +import java.io.IOException; + +/** + * Bean centralisé pour la sécurisation des pages basée sur les rôles. + * Fournit des méthodes réutilisables pour vérifier l'accès et rediriger si nécessaire. + * + *

Principe DRY/WOU : Une seule implémentation de la logique de sécurité, + * réutilisée par toutes les pages via un composant Facelet. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-02 + */ +@Named("pageSecurityBean") +@ApplicationScoped +public class PageSecurityBean { + + private static final Logger LOG = Logger.getLogger(PageSecurityBean.class); + private static final String ACCESS_DENIED_PAGE = "/pages/secure/access-denied.xhtml"; + + @Inject + SecurityIdentity securityIdentity; + + @Inject + MenuBean menuBean; + + /** + * Vérifie si l'utilisateur a le droit d'accéder à une page donnée. + * Si non autorisé, redirige vers la page access-denied. + * + * @param allowedRoles Rôles autorisés séparés par des virgules (ex: "ADMIN,TRESORIER") + * @return true si autorisé, false sinon (après redirection) + */ + public boolean checkAccessOrRedirect(String allowedRoles) { + if (allowedRoles == null || allowedRoles.trim().isEmpty()) { + // Aucune restriction = accès autorisé pour tous les utilisateurs authentifiés + return !securityIdentity.isAnonymous(); + } + + String[] roles = allowedRoles.split(","); + boolean hasAccess = false; + + for (String role : roles) { + String trimmedRole = role.trim(); + if (hasRole(trimmedRole)) { + hasAccess = true; + break; + } + } + + if (!hasAccess) { + LOG.warnf("Accès refusé pour l'utilisateur %s à une page nécessitant les rôles: %s", + securityIdentity.getPrincipal().getName(), allowedRoles); + redirectToAccessDenied(); + return false; + } + + return true; + } + + /** + * Vérifie si l'utilisateur possède un rôle spécifique. + * + * @param role Le rôle à vérifier + * @return true si l'utilisateur a ce rôle + */ + private boolean hasRole(String role) { + return switch (role) { + case "SUPER_ADMIN" -> menuBean.isSuperAdmin(); + case "ADMIN_ORGANISATION", "ADMIN" -> menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "TRESORIER" -> menuBean.isTresorier() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "SECRETAIRE" -> menuBean.isSecretaire() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "RESPONSABLE_SOCIAL" -> menuBean.isResponsableSocial() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "RESPONSABLE_EVENEMENTS" -> menuBean.isResponsableEvenements() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "RESPONSABLE_CREDIT" -> menuBean.isResponsableCredit() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "MEMBRE_BUREAU" -> menuBean.isMembreBureau() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "MEMBRE_ACTIF" -> menuBean.isMembreActif() || menuBean.isMembreBureau() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "MEMBRE_SIMPLE" -> menuBean.isMembreSimple() || menuBean.isMembreActif() || menuBean.isMembreBureau() || menuBean.isAdminOrganisation() || menuBean.isSuperAdmin(); + case "ALL" -> !securityIdentity.isAnonymous(); // Tous les utilisateurs authentifiés + default -> { + LOG.warnf("Rôle inconnu: %s", role); + yield false; + } + }; + } + + /** + * Redirige vers la page d'accès refusé. + */ + private void redirectToAccessDenied() { + try { + FacesContext ctx = FacesContext.getCurrentInstance(); + if (ctx != null && !ctx.getResponseComplete()) { + String contextPath = ctx.getExternalContext().getRequestContextPath(); + ctx.getExternalContext().redirect(contextPath + ACCESS_DENIED_PAGE); + ctx.responseComplete(); + } + } catch (IOException e) { + LOG.error("Erreur lors de la redirection vers access-denied", e); + } + } + + // ═══════════════════════════════════════════════════════════════════════ + // Méthodes helper pour vérifications rapides (utilisées dans les pages) + // ═══════════════════════════════════════════════════════════════════════ + + /** + * Vérifie si l'utilisateur peut gérer les membres. + * @return true si SECRETAIRE, ADMIN, ou SUPER_ADMIN + */ + public boolean canManageMembers() { + return hasRole("SECRETAIRE"); + } + + /** + * Vérifie si l'utilisateur peut gérer les finances. + * @return true si TRESORIER, ADMIN, ou SUPER_ADMIN + */ + public boolean canManageFinances() { + return hasRole("TRESORIER"); + } + + /** + * Vérifie si l'utilisateur peut gérer les événements. + * @return true si RESPONSABLE_EVENEMENTS, SECRETAIRE, ADMIN, ou SUPER_ADMIN + */ + public boolean canManageEvents() { + return hasRole("RESPONSABLE_EVENEMENTS"); + } + + /** + * Vérifie si l'utilisateur peut gérer les aides sociales. + * @return true si RESPONSABLE_SOCIAL, ADMIN, ou SUPER_ADMIN + */ + public boolean canManageSocialAid() { + return hasRole("RESPONSABLE_SOCIAL"); + } + + /** + * Vérifie si l'utilisateur peut voir les rapports financiers. + * @return true si TRESORIER, SECRETAIRE, ADMIN, ou SUPER_ADMIN + */ + public boolean canViewFinancialReports() { + return hasRole("TRESORIER") || hasRole("SECRETAIRE"); + } + + /** + * Vérifie si l'utilisateur peut exporter des données. + * @return true si TRESORIER, SECRETAIRE, ADMIN, ou SUPER_ADMIN + */ + public boolean canExportData() { + return hasRole("TRESORIER") || hasRole("SECRETAIRE"); + } + + /** + * Vérifie si l'utilisateur est un simple membre (MEMBRE_ACTIF uniquement). + * @return true si MEMBRE_ACTIF mais pas d'autre rôle administratif + */ + public boolean isSimpleMember() { + return menuBean.isMembreActif() && + !menuBean.isSecretaire() && + !menuBean.isTresorier() && + !menuBean.isResponsableSocial() && + !menuBean.isResponsableEvenements() && + !menuBean.isResponsableCredit() && + !menuBean.isMembreBureau() && + !menuBean.isAdminOrganisation() && + !menuBean.isSuperAdmin(); + } +} diff --git a/src/main/java/dev/lions/unionflow/client/converter/UuidConverter.java b/src/main/java/dev/lions/unionflow/client/converter/UuidConverter.java new file mode 100644 index 0000000..f890da8 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/converter/UuidConverter.java @@ -0,0 +1,37 @@ +package dev.lions.unionflow.client.converter; + +import jakarta.faces.component.UIComponent; +import jakarta.faces.context.FacesContext; +import jakarta.faces.convert.Converter; +import jakarta.faces.convert.ConverterException; +import jakarta.faces.convert.FacesConverter; + +import java.util.UUID; + +/** + * Convertisseur JSF pour les paramètres de vue et champs liés à {@link UUID}. + * Permet la conversion String ↔ UUID dans les f:viewParam et composants d'entrée. + */ +@FacesConverter(value = "uuidConverter", managed = true) +public class UuidConverter implements Converter { + + @Override + public UUID getAsObject(FacesContext context, UIComponent component, String value) { + if (value == null || value.isBlank()) { + return null; + } + try { + return UUID.fromString(value.trim()); + } catch (IllegalArgumentException e) { + throw new ConverterException("Identifiant invalide : " + value, e); + } + } + + @Override + public String getAsString(FacesContext context, UIComponent component, UUID value) { + if (value == null) { + return ""; + } + return value.toString(); + } +} diff --git a/src/main/java/dev/lions/unionflow/client/exception/ViewExpiredExceptionHandler.java b/src/main/java/dev/lions/unionflow/client/exception/ViewExpiredExceptionHandler.java index ec6b383..c183704 100644 --- a/src/main/java/dev/lions/unionflow/client/exception/ViewExpiredExceptionHandler.java +++ b/src/main/java/dev/lions/unionflow/client/exception/ViewExpiredExceptionHandler.java @@ -1,5 +1,6 @@ package dev.lions.unionflow.client.exception; +import jakarta.el.PropertyNotFoundException; import jakarta.faces.FacesException; import jakarta.faces.application.ViewExpiredException; import jakarta.faces.context.ExceptionHandler; @@ -12,28 +13,57 @@ import java.util.logging.Level; import java.util.logging.Logger; public class ViewExpiredExceptionHandler extends ExceptionHandlerWrapper { - + private static final Logger LOG = Logger.getLogger(ViewExpiredExceptionHandler.class.getName()); private ExceptionHandler wrapped; - + public ViewExpiredExceptionHandler(ExceptionHandler wrapped) { this.wrapped = wrapped; } - + @Override public ExceptionHandler getWrapped() { return wrapped; } - + + private static boolean isPropertyNotFound(Throwable t) { + for (Throwable x = t; x != null; x = x.getCause()) { + if (x instanceof PropertyNotFoundException) return true; + } + return false; + } + @Override public void handle() throws FacesException { Iterator iterator = getUnhandledExceptionQueuedEvents().iterator(); - + while (iterator.hasNext()) { ExceptionQueuedEvent event = iterator.next(); ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource(); Throwable throwable = context.getException(); - + + if (isPropertyNotFound(throwable)) { + LOG.log(Level.WARNING, "PropertyNotFoundException EL (évite page d''erreur TreeMap): {0}", + throwable.getMessage()); + try { + FacesContext fc = FacesContext.getCurrentInstance(); + if (fc != null && fc.getExternalContext() != null && !fc.getResponseComplete()) { + fc.getExternalContext().redirect( + fc.getExternalContext().getRequestContextPath() + "/pages/secure/organisation/liste.xhtml"); + fc.responseComplete(); + } + } catch (Exception e) { + String msg = e != null ? e.getMessage() : ""; + if (msg != null && (msg.contains("already commited") || msg.contains("already committed"))) { + LOG.log(Level.WARNING, "Redirection impossible (réponse déjà envoyée): {0}", msg); + } else { + LOG.log(Level.SEVERE, "Redirection après PropertyNotFoundException: {0}", msg); + } + } + iterator.remove(); + continue; + } + if (throwable instanceof ViewExpiredException) { ViewExpiredException vee = (ViewExpiredException) throwable; FacesContext facesContext = FacesContext.getCurrentInstance(); diff --git a/src/main/java/dev/lions/unionflow/client/service/AssociationService.java b/src/main/java/dev/lions/unionflow/client/service/AssociationService.java index 3dbbc9e..163bdf0 100644 --- a/src/main/java/dev/lions/unionflow/client/service/AssociationService.java +++ b/src/main/java/dev/lions/unionflow/client/service/AssociationService.java @@ -1,5 +1,6 @@ package dev.lions.unionflow.client.service; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -16,41 +17,17 @@ import java.util.UUID; public interface AssociationService { @GET - PagedResponseDTO listerToutes( + PagedResponse listerToutes( @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("1000") int size); - class PagedResponseDTO { - public List data; - public Long total; - public Integer page; - public Integer size; - public Integer totalPages; - - public List getData() { - return data; - } - - public void setData(List data) { - this.data = data; - } - - public Long getTotal() { - return total; - } - - public void setTotal(Long total) { - this.total = total; - } - } - @GET @Path("/{id}") OrganisationResponse obtenirParId(@PathParam("id") UUID id); @GET @Path("/recherche") - PagedResponseDTO rechercher( + PagedResponse rechercher( @QueryParam("nom") String nom, @QueryParam("type") String type, @QueryParam("statut") String statut, diff --git a/src/main/java/dev/lions/unionflow/client/service/EvenementService.java b/src/main/java/dev/lions/unionflow/client/service/EvenementService.java index 5fdfade..2ab73b3 100644 --- a/src/main/java/dev/lions/unionflow/client/service/EvenementService.java +++ b/src/main/java/dev/lions/unionflow/client/service/EvenementService.java @@ -1,5 +1,6 @@ package dev.lions.unionflow.client.service; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.evenement.response.EvenementResponse; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -27,7 +28,7 @@ public interface EvenementService { * Liste tous les événements actifs avec pagination */ @GET - Map listerTous( + PagedResponse listerTous( @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("20") int size, @QueryParam("sort") @DefaultValue("dateDebut") String sortField, @@ -66,17 +67,17 @@ public interface EvenementService { */ @GET @Path("/a-venir") - Map listerAVenir( + PagedResponse listerAVenir( @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("10") int size ); - + /** * Recherche d'événements avec filtres */ @GET @Path("/search") - Map rechercher( + PagedResponse rechercher( @QueryParam("titre") String titre, @QueryParam("type") String type, @QueryParam("statut") String statut, @@ -85,24 +86,24 @@ public interface EvenementService { @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("20") int size ); - + /** * Liste les événements par statut */ @GET @Path("/statut/{statut}") - Map listerParStatut( + PagedResponse listerParStatut( @PathParam("statut") String statut, @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("20") int size ); - + /** * Liste les événements par association */ @GET @Path("/association/{associationId}") - Map listerParAssociation( + PagedResponse listerParAssociation( @PathParam("associationId") UUID associationId, @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("20") int size diff --git a/src/main/java/dev/lions/unionflow/client/service/MembreDashboardRestClient.java b/src/main/java/dev/lions/unionflow/client/service/MembreDashboardRestClient.java new file mode 100644 index 0000000..779ab0f --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/service/MembreDashboardRestClient.java @@ -0,0 +1,23 @@ +package dev.lions.unionflow.client.service; + +import dev.lions.unionflow.client.api.dto.MembreDashboardResponse; +import dev.lions.unionflow.client.security.AuthHeaderFactory; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey = "unionflow-api") +@RegisterClientHeaders(AuthHeaderFactory.class) +@Path("/api/dashboard/membre") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public interface MembreDashboardRestClient { + + @GET + @Path("/me") + MembreDashboardResponse getMonDashboard(); +} diff --git a/src/main/java/dev/lions/unionflow/client/service/MembreService.java b/src/main/java/dev/lions/unionflow/client/service/MembreService.java index db97681..d63a986 100644 --- a/src/main/java/dev/lions/unionflow/client/service/MembreService.java +++ b/src/main/java/dev/lions/unionflow/client/service/MembreService.java @@ -1,5 +1,6 @@ package dev.lions.unionflow.client.service; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -16,7 +17,7 @@ import java.util.UUID; public interface MembreService { @GET - List listerTous(); + PagedResponse listerTous(); @GET @Path("/{id}") @@ -26,6 +27,10 @@ public interface MembreService { @Path("/numero/{numeroMembre}") MembreResponse obtenirParNumero(@PathParam("numeroMembre") String numeroMembre); + @GET + @Path("/me") + MembreResponse obtenirMembreConnecte(); + @GET @Path("/search") List rechercher( diff --git a/src/main/java/dev/lions/unionflow/client/view/AdhesionsBean.java b/src/main/java/dev/lions/unionflow/client/view/AdhesionsBean.java index 45ae445..e3e6656 100644 --- a/src/main/java/dev/lions/unionflow/client/view/AdhesionsBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/AdhesionsBean.java @@ -13,9 +13,13 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import org.jboss.logging.Logger; import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.finance.request.*; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.finance.response.*; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.AdhesionService; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.ErrorHandlerService; @@ -109,7 +113,7 @@ public class AdhesionsBean implements Serializable { } try { - AssociationService.PagedResponseDTO response = retryService.executeWithRetrySupplier( + PagedResponse response = retryService.executeWithRetrySupplier( () -> associationService.listerToutes(0, 1000), "chargement des associations"); listeAssociations = (response != null && response.getData() != null) ? response.getData() diff --git a/src/main/java/dev/lions/unionflow/client/view/CotisationsGestionBean.java b/src/main/java/dev/lions/unionflow/client/view/CotisationsGestionBean.java index 3fcb39b..6118581 100644 --- a/src/main/java/dev/lions/unionflow/client/view/CotisationsGestionBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/CotisationsGestionBean.java @@ -1,10 +1,15 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.cotisation.request.CreateCotisationRequest; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.cotisation.response.CotisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.paiement.WaveCheckoutSessionDTO; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.CotisationService; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.MembreService; @@ -304,7 +309,7 @@ public class CotisationsGestionBean implements Serializable { this.filtres = new FiltresCotisations(); this.listeOrganisations = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); if (response != null && response.getData() != null) { for (OrganisationResponse assoc : response.getData()) { Organisation org = new Organisation(); @@ -340,7 +345,7 @@ public class CotisationsGestionBean implements Serializable { private void chargerTopOrganisations() { this.topOrganisations = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List associations = response != null && response.getData() != null ? response.getData() : new ArrayList<>(); List cotisationsDTO = cotisationService.listerToutes(0, 1000); diff --git a/src/main/java/dev/lions/unionflow/client/view/DashboardBean.java b/src/main/java/dev/lions/unionflow/client/view/DashboardBean.java index 85a39d9..5aaea6d 100644 --- a/src/main/java/dev/lions/unionflow/client/view/DashboardBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/DashboardBean.java @@ -8,10 +8,12 @@ import dev.lions.unionflow.server.api.dto.dashboard.DashboardStatsResponse; import dev.lions.unionflow.server.api.dto.dashboard.RecentActivityResponse; import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventResponse; import jakarta.annotation.PostConstruct; +import jakarta.faces.context.FacesContext; import jakarta.faces.view.ViewScoped; import jakarta.inject.Inject; import jakarta.inject.Named; import org.eclipse.microprofile.rest.client.inject.RestClient; +import java.io.IOException; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; @@ -51,10 +53,13 @@ public class DashboardBean implements Serializable { @Inject private UserSession userSession; - + + @Inject + private dev.lions.unionflow.client.bean.MenuBean menuBean; + @Inject ErrorHandlerService errorHandler; - + @Inject RetryService retryService; @@ -134,8 +139,30 @@ public class DashboardBean implements Serializable { @PostConstruct public void init() { + // Charger les données pour les rôles administratifs chargerDonneesBackend(); } + + /** + * Méthode appelée par f:viewAction pour rediriger les MEMBRE_ACTIF. + * S'exécute AVANT le rendu de la page (phase INVOKE_APPLICATION). + */ + public void checkAccessAndRedirect() { + if (menuBean != null && menuBean.isMembreActif() && + !menuBean.isSecretaire() && !menuBean.isTresorier() && + !menuBean.isResponsableSocial() && !menuBean.isResponsableEvenements() && + !menuBean.isAdminOrganisation() && !menuBean.isSuperAdmin()) { + try { + FacesContext ctx = FacesContext.getCurrentInstance(); + String redirectUrl = ctx.getExternalContext().getRequestContextPath() + + "/pages/secure/dashboard-membre.xhtml?faces-redirect=true"; + ctx.getExternalContext().redirect(redirectUrl); + ctx.responseComplete(); + } catch (IOException e) { + LOG.error("Erreur lors de la redirection vers dashboard-membre", e); + } + } + } /** * Charge toutes les données depuis le service Dashboard (DRY/WOU - un seul appel) diff --git a/src/main/java/dev/lions/unionflow/client/view/DashboardMembreBean.java b/src/main/java/dev/lions/unionflow/client/view/DashboardMembreBean.java new file mode 100644 index 0000000..b10a8ab --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/view/DashboardMembreBean.java @@ -0,0 +1,386 @@ +package dev.lions.unionflow.client.view; + +import dev.lions.unionflow.server.api.dto.dashboard.UpcomingEventResponse; +import dev.lions.unionflow.server.api.dto.evenement.response.EvenementResponse; +import dev.lions.unionflow.client.api.dto.MembreDashboardResponse; +import dev.lions.unionflow.client.service.MembreDashboardRestClient; +import dev.lions.unionflow.client.service.ErrorHandlerService; +import dev.lions.unionflow.client.service.RetryService; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.PostConstruct; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.jboss.logging.Logger; + +import java.io.IOException; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Bean de gestion du dashboard personnel pour les membres actifs + * (MEMBRE_ACTIF). + * Affiche uniquement les données personnelles du membre connecté, pas les + * statistiques globales. + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-02 + */ +@Named("dashboardMembreBean") +@ViewScoped +public class DashboardMembreBean implements Serializable { + + private static final long serialVersionUID = 1L; + private static final Logger LOG = Logger.getLogger(DashboardMembreBean.class); + + @Inject + SecurityIdentity securityIdentity; + + @Inject + ErrorHandlerService errorHandler; + + @Inject + RetryService retryService; + + @Inject + @RestClient + MembreDashboardRestClient dashboardClient; + + // Informations personnelles du membre + private String prenomMembre; + private String nomMembre; + private LocalDate dateInscription; + + // KPI personnels - TOUTES LES VALEURS DOIVENT ÊTRE CALCULÉES DEPUIS LES DONNÉES + // RÉELLES + // IMPORTANT: Ces valeurs par défaut (0, "", null) sont TEMPORAIRES en attendant + // l'implémentation des endpoints REST + // Une fois les endpoints implémentés, ces valeurs seront REMPLACÉES par les + // données calculées depuis PostgreSQL + + // Cotisations + private String mesCotisationsPaiement = "0"; // TEMPORAIRE - Sera remplacé par le montant réel depuis API + private String statutCotisations = "Non disponible"; // TEMPORAIRE - Sera remplacé par "À jour"/"En retard" depuis + // API + private Integer tauxCotisationsPerso = null; // null = pas de jauge affichée en attendant les données réelles + + // Épargne + private String monSoldeEpargne = "0"; // TEMPORAIRE - Sera remplacé par le solde réel depuis API + private String evolutionEpargne = "+0%"; // TEMPORAIRE - Sera remplacé par l'évolution réelle depuis API + private String evolutionEpargneNombre = "0"; // TEMPORAIRE - Sera remplacé par l'évolution en FCFA depuis API + private Integer objectifEpargne = null; // null = pas de jauge affichée en attendant les données réelles + + // Événements + private Integer mesEvenementsInscrits = 0; // TEMPORAIRE - Sera remplacé par le nombre réel depuis API + private Integer evenementsAVenir = 0; // TEMPORAIRE - Sera remplacé par le nombre réel depuis API + private Integer tauxParticipationPerso = null; // null = pas de jauge affichée en attendant les données réelles + + // Aides + private Integer mesDemandesAide = 0; // TEMPORAIRE - Sera remplacé par le nombre réel depuis API + private Integer aidesEnCours = 0; // TEMPORAIRE - Sera remplacé par le nombre réel depuis API + private Integer tauxAidesApprouvees = null; // null = pas de jauge affichée en attendant les données réelles + + // Collections + private List historiqueCotisations = new ArrayList<>(); + private List mesNotifications = new ArrayList<>(); + private List evenementsPublics = new ArrayList<>(); + + @PostConstruct + public void init() { + LOG.info("Initialisation du dashboard personnel membre"); + chargerDonneesPersonnelles(); + } + + /** + * Charge les données personnelles du membre connecté depuis les endpoints REST. + * Les données de synthèse sont récupérées via l'API membre/me. + */ + private void chargerDonneesPersonnelles() { + try { + LOG.info("Chargement des données du dashboard depuis l'API..."); + MembreDashboardResponse data = dashboardClient.getMonDashboard(); + + if (data != null) { + this.prenomMembre = data.prenom(); + this.nomMembre = data.nom(); + this.dateInscription = data.dateInscription(); + + this.mesCotisationsPaiement = formatMontant(data.mesCotisationsPaiement()); + this.statutCotisations = data.statutCotisations() != null ? data.statutCotisations() : "Non disponible"; + this.tauxCotisationsPerso = data.tauxCotisationsPerso(); + + this.monSoldeEpargne = formatMontant(data.monSoldeEpargne()); + this.evolutionEpargneNombre = formatMontant(data.evolutionEpargneNombre()); + this.evolutionEpargne = data.evolutionEpargne() != null ? data.evolutionEpargne() : "+0%"; + this.objectifEpargne = data.objectifEpargne(); + + this.mesEvenementsInscrits = data.mesEvenementsInscrits() != null ? data.mesEvenementsInscrits() : 0; + this.evenementsAVenir = data.evenementsAVenir() != null ? data.evenementsAVenir() : 0; + this.tauxParticipationPerso = data.tauxParticipationPerso(); + + this.mesDemandesAide = data.mesDemandesAide() != null ? data.mesDemandesAide() : 0; + this.aidesEnCours = data.aidesEnCours() != null ? data.aidesEnCours() : 0; + this.tauxAidesApprouvees = data.tauxAidesApprouvees(); + } + + // Pour l'historique et événements, on mock en attendant les endpoints détaillés + // si nécessaires + // ou on laissera vide vu que le dashboard principal est fonctionnel avec les + // KPI + historiqueCotisations = new ArrayList<>(); + mesNotifications = new ArrayList<>(); + evenementsPublics = new ArrayList<>(); + + } catch (Exception e) { + LOG.error("Erreur lors du chargement des données de synthèse du dashboard", e); + errorHandler.handleException(e, "lors du chargement de votre dashboard", null); + } + } + + private String formatMontant(BigDecimal montant) { + if (montant == null) + return "0"; + // Format simple, on pourrait rajouter des espaces pour les milliers + return String.format("%,d", montant.longValue()).replace(',', ' '); + } + + // ═══════════════════════════════════════════════════════════════════════ + // Actions + // ═══════════════════════════════════════════════════════════════════════ + + public void payerCotisation() { + try { + // TODO: Rediriger vers la page de paiement des cotisations + LOG.info("Redirection vers paiement cotisation"); + } catch (Exception e) { + errorHandler.handleException(e, "lors de la redirection", null); + } + } + + public void inscrireEvenement() { + try { + // TODO: Rediriger vers /pages/secure/evenement/calendrier.xhtml + // Liste des événements publics où le membre peut s'inscrire + LOG.info("Redirection vers calendrier des événements disponibles"); + } catch (Exception e) { + errorHandler.handleException(e, "lors de la redirection", null); + } + } + + public void demanderAide() { + try { + // TODO: Rediriger vers le formulaire de demande d'aide + LOG.info("Redirection vers demande d'aide"); + } catch (Exception e) { + errorHandler.handleException(e, "lors de la redirection", null); + } + } + + public void allerAMonProfil() { + try { + // TODO: Rediriger vers le profil personnel + LOG.info("Redirection vers mon profil"); + } catch (Exception e) { + errorHandler.handleException(e, "lors de la redirection", null); + } + } + + public void allerAuxEvenements() { + try { + // TODO: Rediriger vers la liste complète des événements + LOG.info("Redirection vers liste événements"); + } catch (Exception e) { + errorHandler.handleException(e, "lors de la redirection", null); + } + } + + public void inscrireAEvenement(String evenementId) { + try { + // TODO: Appeler API pour inscrire le membre à l'événement + LOG.infof("Inscription à l'événement %s", evenementId); + errorHandler.showSuccess("Inscription confirmée", "Vous êtes inscrit à cet événement"); + } catch (Exception e) { + errorHandler.handleException(e, "lors de l'inscription à l'événement", null); + } + } + + // ═══════════════════════════════════════════════════════════════════════ + // Helpers + // ═══════════════════════════════════════════════════════════════════════ + + private String extractPrenomFromUsername(String username) { + // Extraction basique depuis le username en attendant l'API + if (username != null && username.contains("@")) { + return username.split("@")[0]; + } + return username != null ? username : "Membre"; + } + + private String extractNomFromUsername(String username) { + // TODO: Appeler GET /api/membres/mon-profil pour récupérer le nom complet + return ""; + } + + // ═══════════════════════════════════════════════════════════════════════ + // DTOs internes + // ═══════════════════════════════════════════════════════════════════════ + + public static class CotisationPerso implements Serializable { + private static final long serialVersionUID = 1L; + + private LocalDate datePaiement; + private LocalDate periode; + private String montant; + private String modePaiement; + private String statut; + + public CotisationPerso(LocalDate datePaiement, LocalDate periode, String montant, + String modePaiement, String statut) { + this.datePaiement = datePaiement; + this.periode = periode; + this.montant = montant; + this.modePaiement = modePaiement; + this.statut = statut; + } + + // Getters + public LocalDate getDatePaiement() { + return datePaiement; + } + + public LocalDate getPeriode() { + return periode; + } + + public String getMontant() { + return montant; + } + + public String getModePaiement() { + return modePaiement; + } + + public String getStatut() { + return statut; + } + } + + public static class NotificationPerso implements Serializable { + private static final long serialVersionUID = 1L; + + private String icon; + private String titre; + private String message; + private LocalDateTime date; + + public NotificationPerso(String icon, String titre, String message, LocalDateTime date) { + this.icon = icon; + this.titre = titre; + this.message = message; + this.date = date; + } + + // Getters + public String getIcon() { + return icon; + } + + public String getTitre() { + return titre; + } + + public String getMessage() { + return message; + } + + public LocalDateTime getDate() { + return date; + } + } + + // ═══════════════════════════════════════════════════════════════════════ + // Getters pour JSF + // ═══════════════════════════════════════════════════════════════════════ + + public String getPrenomMembre() { + return prenomMembre; + } + + public String getNomMembre() { + return nomMembre; + } + + public LocalDate getDateInscription() { + return dateInscription; + } + + public String getMesCotisationsPaiement() { + return mesCotisationsPaiement; + } + + public String getStatutCotisations() { + return statutCotisations; + } + + public Integer getTauxCotisationsPerso() { + return tauxCotisationsPerso; + } + + public String getMonSoldeEpargne() { + return monSoldeEpargne; + } + + public String getEvolutionEpargne() { + return evolutionEpargne; + } + + public String getEvolutionEpargneNombre() { + return evolutionEpargneNombre; + } + + public Integer getObjectifEpargne() { + return objectifEpargne; + } + + public Integer getMesEvenementsInscrits() { + return mesEvenementsInscrits; + } + + public Integer getEvenementsAVenir() { + return evenementsAVenir; + } + + public Integer getTauxParticipationPerso() { + return tauxParticipationPerso; + } + + public Integer getMesDemandesAide() { + return mesDemandesAide; + } + + public Integer getAidesEnCours() { + return aidesEnCours; + } + + public Integer getTauxAidesApprouvees() { + return tauxAidesApprouvees; + } + + public List getHistoriqueCotisations() { + return historiqueCotisations; + } + + public List getMesNotifications() { + return mesNotifications; + } + + public List getEvenementsPublics() { + return evenementsPublics; + } +} diff --git a/src/main/java/dev/lions/unionflow/client/view/DemandesAideBean.java b/src/main/java/dev/lions/unionflow/client/view/DemandesAideBean.java index 469d2ff..5ff754d 100644 --- a/src/main/java/dev/lions/unionflow/client/view/DemandesAideBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/DemandesAideBean.java @@ -109,7 +109,7 @@ public class DemandesAideBean implements Serializable { try { // Charger toutes les demandes depuis le backend pour calculer les étapes - List demandesDTO = demandeAideService.listerToutes(0, 10000); + List demandesDTO = demandeAideService.listerToutes(0, 1000); // Calculer le nombre de demandes par statut depuis les données réelles long enAttenteCount = demandesDTO.stream().filter(d -> StatutAide.EN_ATTENTE.equals(d.getStatut())).count(); diff --git a/src/main/java/dev/lions/unionflow/client/view/DemandesBean.java b/src/main/java/dev/lions/unionflow/client/view/DemandesBean.java index e9a9b51..babe657 100644 --- a/src/main/java/dev/lions/unionflow/client/view/DemandesBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/DemandesBean.java @@ -65,7 +65,7 @@ public class DemandesBean implements Serializable { private void initializeDemandes() { demandes = new ArrayList<>(); try { - List dtos = demandeAideService.listerToutes(0, 10000); + List dtos = demandeAideService.listerToutes(0, 1000); if (dtos != null) { for (DemandeAideResponse dto : dtos) { demandes.add(mapToDemande(dto)); diff --git a/src/main/java/dev/lions/unionflow/client/view/EntitesGestionBean.java b/src/main/java/dev/lions/unionflow/client/view/EntitesGestionBean.java index 6bd3440..571e8a3 100644 --- a/src/main/java/dev/lions/unionflow/client/view/EntitesGestionBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/EntitesGestionBean.java @@ -1,7 +1,9 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.abonnement.response.AbonnementResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.CotisationService; @@ -105,7 +107,7 @@ public class EntitesGestionBean implements Serializable { private void initializeStatistiques() { statistiques = new Statistiques(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List associations = new ArrayList<>(); if (response != null && response.getData() != null) { associations = response.getData(); @@ -138,7 +140,7 @@ public class EntitesGestionBean implements Serializable { private void initializeEntites() { toutesLesEntites = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); if (response != null && response.getData() != null) { for (OrganisationResponse dto : response.getData()) { Entite entite = convertToEntite(dto); diff --git a/src/main/java/dev/lions/unionflow/client/view/EvenementsBean.java b/src/main/java/dev/lions/unionflow/client/view/EvenementsBean.java index 0535b5a..b093712 100644 --- a/src/main/java/dev/lions/unionflow/client/view/EvenementsBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/EvenementsBean.java @@ -114,44 +114,16 @@ public class EvenementsBean implements Serializable { public void chargerEvenements() { try { LOG.info("Chargement des événements depuis le backend"); - Map response = retryService.executeWithRetrySupplier( + var response = retryService.executeWithRetrySupplier( () -> evenementService.listerTous(0, 1000, "dateDebut", "asc"), "chargement de tous les événements" ); - + tousLesEvenements = new ArrayList<>(); - - // Le backend peut retourner soit une liste de DTOs, soit une Map avec "data" - if (response.containsKey("data")) { - @SuppressWarnings("unchecked") - List data = (List) response.get("data"); - - if (data != null) { - for (Object item : data) { - if (item instanceof EvenementResponse) { - tousLesEvenements.add((EvenementResponse) item); - } else if (item instanceof Map) { - @SuppressWarnings("unchecked") - EvenementResponse dto = convertMapToDTO((Map) item); - tousLesEvenements.add(dto); - } - } - } - } else { - // Si la réponse est directement une liste - @SuppressWarnings("unchecked") - List data = (List) response.get("evenements"); - if (data != null) { - for (Object item : data) { - if (item instanceof EvenementResponse) { - tousLesEvenements.add((EvenementResponse) item); - } else if (item instanceof Map) { - @SuppressWarnings("unchecked") - EvenementResponse dto = convertMapToDTO((Map) item); - tousLesEvenements.add(dto); - } - } - } + + // Récupérer les données depuis PagedResponse + if (response != null && response.getData() != null) { + tousLesEvenements.addAll(response.getData()); } appliquerFiltres(); @@ -170,22 +142,17 @@ public class EvenementsBean implements Serializable { public void chargerEvenementsProchains() { try { LOG.info("Chargement des événements à venir"); - Map response = retryService.executeWithRetrySupplier( + var response = retryService.executeWithRetrySupplier( () -> evenementService.listerAVenir(0, 6), "chargement des événements à venir" ); - - @SuppressWarnings("unchecked") - List> data = (List>) response.get("data"); - - if (data != null) { - evenementsProchains = data.stream() - .map(this::convertMapToDTO) - .collect(Collectors.toList()); + + if (response != null && response.getData() != null) { + evenementsProchains = new ArrayList<>(response.getData()); } else { evenementsProchains = new ArrayList<>(); } - + } catch (Exception e) { LOG.errorf(e, "Erreur lors du chargement des événements à venir"); evenementsProchains = new ArrayList<>(); diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreCotisationBean.java b/src/main/java/dev/lions/unionflow/client/view/MembreCotisationBean.java index 52123fb..a97c897 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreCotisationBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreCotisationBean.java @@ -45,20 +45,23 @@ public class MembreCotisationBean implements Serializable { @Inject @RestClient private MembreService membreService; - + @Inject @RestClient private CotisationService cotisationService; - + @Inject @RestClient private ExportClientService exportService; - + @Inject ErrorHandlerService errorHandler; - + @Inject RetryService retryService; + + @Inject + io.quarkus.security.identity.SecurityIdentity securityIdentity; // ID du membre (depuis viewParam) private UUID membreId; @@ -135,14 +138,43 @@ public class MembreCotisationBean implements Serializable { } } } - + + // Si toujours null, auto-détecter le membre connecté (Pattern DRY depuis DashboardMembreBean) + if (membreId == null) { + try { + String username = securityIdentity.getPrincipal().getName(); + LOG.infof("Auto-détection du membre connecté: %s", username); + + // Récupérer directement le membre connecté via l'endpoint /me + MembreResponse membreConnecte = retryService.executeWithRetrySupplier( + () -> membreService.obtenirMembreConnecte(), + "récupération du membre connecté" + ); + + if (membreConnecte != null) { + membreId = membreConnecte.getId(); + numeroMembre = membreConnecte.getNumeroMembre(); + LOG.infof("Membre connecté détecté: %s (%s)", numeroMembre, membreId); + } else { + LOG.warnf("Aucun membre trouvé pour l'utilisateur: %s", username); + errorHandler.showWarning("Attention", + "Impossible de charger vos cotisations. Veuillez contacter l'administrateur."); + initialiserDonneesVides(); + return; + } + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'auto-détection du membre connecté"); + errorHandler.handleException(e, "lors du chargement de vos cotisations", null); + initialiserDonneesVides(); + return; + } + } + if (membreId != null) { chargerMembre(); chargerCotisations(); calculerStatistiques(); } else { - LOG.warn("Aucun membreId fourni, impossible de charger les cotisations"); - errorHandler.showWarning("Attention", "Aucun membre sélectionné"); initialiserDonneesVides(); } } diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreExportBean.java b/src/main/java/dev/lions/unionflow/client/view/MembreExportBean.java index 56df7ed..aaf9584 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreExportBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreExportBean.java @@ -5,8 +5,11 @@ import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.ErrorHandlerService; import dev.lions.unionflow.client.service.RetryService; import dev.lions.unionflow.server.api.dto.organisation.request.*; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.*; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import lombok.Getter; import lombok.Setter; import jakarta.faces.view.ViewScoped; @@ -88,7 +91,7 @@ public class MembreExportBean implements Serializable { private void chargerOrganisations() { organisationsDisponibles = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = retryService.executeWithRetrySupplier( + PagedResponse response = retryService.executeWithRetrySupplier( () -> associationService.listerToutes(0, 1000), "chargement des organisations pour export"); List associations = (response != null && response.getData() != null) ? response.getData() diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreImportBean.java b/src/main/java/dev/lions/unionflow/client/view/MembreImportBean.java index 242ce09..261d4f8 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreImportBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreImportBean.java @@ -6,8 +6,11 @@ import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.ErrorHandlerService; import dev.lions.unionflow.client.service.RetryService; import dev.lions.unionflow.server.api.dto.organisation.request.*; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.*; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import lombok.Getter; import lombok.Setter; import jakarta.faces.view.ViewScoped; @@ -71,7 +74,7 @@ public class MembreImportBean implements Serializable { private void chargerOrganisations() { organisationsDisponibles = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = retryService.executeWithRetrySupplier( + PagedResponse response = retryService.executeWithRetrySupplier( () -> associationService.listerToutes(0, 1000), "chargement des organisations pour import"); List associations = (response != null && response.getData() != null) ? response.getData() diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java b/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java index 11209ae..1f0d7ba 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreInscriptionBean.java @@ -1,7 +1,9 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.MembreService; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.ErrorHandlerService; @@ -120,7 +122,7 @@ public class MembreInscriptionBean implements Serializable { // Charger les organisations actives (DRY/WOU - utilise AssociationService) try { - AssociationService.PagedResponseDTO response = retryService.executeWithRetrySupplier( + PagedResponse response = retryService.executeWithRetrySupplier( () -> associationService.listerToutes(0, 1000), "chargement des associations"); organisationsDisponibles = (response != null && response.getData() != null) ? response.getData() diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreLazyDataModel.java b/src/main/java/dev/lions/unionflow/client/view/MembreLazyDataModel.java index 6a8a958..34d92c6 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreLazyDataModel.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreLazyDataModel.java @@ -53,7 +53,7 @@ public class MembreLazyDataModel extends LazyDataModelBase all = membreService.listerTous(); + List all = membreService.listerTous().getData(); if (all == null || all.isEmpty()) return List.of(); int toIndex = Math.min(first + pageSize, all.size()); // Conversion MembreResponse → MembreSummaryResponse @@ -71,7 +71,9 @@ public class MembreLazyDataModel extends LazyDataModelBase all = membreService.listerTous(); + List all = membreService.listerTous().getData(); return all != null ? all.size() : 0; } diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java b/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java index 3b756a7..2b3d281 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreListeBean.java @@ -1,6 +1,7 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.MembreService; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.NotificationService; @@ -8,7 +9,9 @@ import dev.lions.unionflow.client.service.CotisationService; import dev.lions.unionflow.client.service.ErrorHandlerService; import dev.lions.unionflow.client.service.RetryService; import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -108,6 +111,8 @@ public class MembreListeBean implements Serializable { // === Contact membre === private MembreResponse membreAContacter; + /** Membre en attente de confirmation de suspension (flux explicite). */ + private MembreResponse membrePourSuspension; private String messageContact; private String sujetContact; private boolean dialogContactVisible = false; @@ -198,7 +203,7 @@ public class MembreListeBean implements Serializable { private void chargerOrganisations() { organisationsDisponibles = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = retryService.executeWithRetrySupplier( + PagedResponse response = retryService.executeWithRetrySupplier( () -> associationService.listerToutes(0, 1000), "chargement des organisations"); if (response != null && response.getData() != null) { @@ -396,6 +401,25 @@ public class MembreListeBean implements Serializable { // ACTIONS SUR UN MEMBRE // ======================================================================== + /** + * Prépare la suspension (ouvre le dialogue de confirmation). + */ + public void preparerSuspendre(MembreResponse membre) { + this.membrePourSuspension = membre; + } + + /** + * Confirme et exécute la suspension après clic sur « Oui » dans le dialogue. + */ + public void confirmerSuspendre() { + if (membrePourSuspension == null) { + return; + } + MembreResponse m = membrePourSuspension; + membrePourSuspension = null; + suspendreMembre(m); + } + public void suspendreMembre(MembreResponse membre) { try { retryService.executeWithRetrySupplier( diff --git a/src/main/java/dev/lions/unionflow/client/view/MembreRechercheBean.java b/src/main/java/dev/lions/unionflow/client/view/MembreRechercheBean.java index 0a761bb..097bf9b 100644 --- a/src/main/java/dev/lions/unionflow/client/view/MembreRechercheBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/MembreRechercheBean.java @@ -1,6 +1,7 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.MembreService; import dev.lions.unionflow.client.service.AssociationService; import jakarta.enterprise.context.SessionScoped; @@ -67,7 +68,7 @@ public class MembreRechercheBean implements Serializable { private void initializeStatistiques() { statistiques = new Statistiques(); try { - List membres = membreService.listerTous(); + List membres = membreService.listerTous().getData(); statistiques.setTotalMembres(membres.size()); } catch (Exception e) { LOG.errorf(e, "Erreur lors du calcul des statistiques"); @@ -83,7 +84,7 @@ public class MembreRechercheBean implements Serializable { selectedMembres = new ArrayList<>(); try { - List membresDTO = membreService.listerTous(); + List membresDTO = membreService.listerTous().getData(); for (MembreResponse dto : membresDTO) { Membre membre = convertToMembre(dto); tousLesMembres.add(membre); @@ -118,7 +119,7 @@ public class MembreRechercheBean implements Serializable { private void initializeEntites() { entitesDisponibles = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService + PagedResponse response = associationService .listerToutes(0, 1000); if (response != null && response.getData() != null) { for (dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse assoc : response.getData()) { diff --git a/src/main/java/dev/lions/unionflow/client/view/MesCotisationsPaiementBean.java b/src/main/java/dev/lions/unionflow/client/view/MesCotisationsPaiementBean.java new file mode 100644 index 0000000..89684bf --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/view/MesCotisationsPaiementBean.java @@ -0,0 +1,574 @@ +package dev.lions.unionflow.client.view; + +import dev.lions.unionflow.server.api.dto.cotisation.response.CotisationResponse; +import dev.lions.unionflow.server.api.dto.membre.response.MembreResponse; +import dev.lions.unionflow.server.api.dto.paiement.response.PaiementResponse; +import dev.lions.unionflow.client.service.CotisationService; +import dev.lions.unionflow.client.service.ErrorHandlerService; +import dev.lions.unionflow.client.service.ExportClientService; +import dev.lions.unionflow.client.service.MembreService; +import dev.lions.unionflow.client.service.RetryService; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.PostConstruct; +import jakarta.faces.context.ExternalContext; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.logging.Logger; + +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Bean pour le paiement des cotisations du membre connecté (MEMBRE_ACTIF). + * Affiche uniquement les cotisations personnelles, pas les données admin. + * + * Pattern DRY: Réutilise la logique de MembreCotisationBean et DashboardMembreBean + * + * @author UnionFlow Team + * @version 1.0 + * @since 2026-03-02 + */ +@Named("mesCotisationsPaiementBean") +@ViewScoped +public class MesCotisationsPaiementBean implements Serializable { + + private static final long serialVersionUID = 1L; + private static final Logger LOG = Logger.getLogger(MesCotisationsPaiementBean.class); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + @Inject + SecurityIdentity securityIdentity; + + @Inject + @RestClient + private MembreService membreService; + + @Inject + @RestClient + private CotisationService cotisationService; + + @Inject + @RestClient + private ExportClientService exportService; + + @Inject + ErrorHandlerService errorHandler; + + @Inject + RetryService retryService; + + // Informations du membre connecté + private UUID membreId; + private String numeroMembre; + private MembreResponse membre; + + // KPI personnels - Cotisations à payer + private Integer cotisationsEnAttente = 0; + private String montantDu = "0 FCFA"; + private String prochaineEcheance = "-"; + private String totalPaye = "0 FCFA"; + private Integer anneeEnCours = LocalDate.now().getYear(); + + // Listes + private List mesCotisationsEnAttente = new ArrayList<>(); + private List derniersPaiements = new ArrayList<>(); + + // Formulaires dialogs + // Dialog Paiement en Ligne + private UUID cotisationSelectionneeId; + private String methodePaiementChoisie = "WAVE"; + private String numeroTelephone; + + // Dialog Paiement Manuel + private String methodePaiementManuel = "ESPECES"; + private String referencePaiementManuel; + private String commentairePaiement; + + // Configuration organisation (TODO: charger depuis API) + private boolean paiementManuelActive = true; + + @PostConstruct + public void init() { + LOG.info("Initialisation du bean de paiement des cotisations personnelles"); + detecterMembreConnecte(); + if (membreId != null) { + chargerDonnees(); + } + } + + /** + * Auto-détection du membre connecté (Pattern DRY depuis DashboardMembreBean) + */ + private void detecterMembreConnecte() { + try { + String username = securityIdentity.getPrincipal().getName(); + LOG.infof("Auto-détection du membre connecté: %s", username); + + // Récupérer directement le membre connecté via l'endpoint /me + membre = retryService.executeWithRetrySupplier( + () -> membreService.obtenirMembreConnecte(), + "récupération du membre connecté" + ); + + if (membre != null) { + membreId = membre.getId(); + numeroMembre = membre.getNumeroMembre(); + LOG.infof("Membre connecté détecté: %s (%s)", numeroMembre, membreId); + } else { + LOG.warnf("Aucun membre trouvé pour l'utilisateur: %s", username); + errorHandler.showWarning("Attention", + "Impossible de charger vos cotisations. Veuillez contacter l'administrateur."); + } + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'auto-détection du membre connecté"); + errorHandler.handleException(e, "lors du chargement de vos cotisations", null); + } + } + + /** + * Charge toutes les données personnelles de cotisations et paiements + */ + private void chargerDonnees() { + chargerCotisationsEnAttente(); + chargerDerniersPaiements(); + calculerKPI(); + } + + /** + * Charge les cotisations en attente du membre connecté + * Pattern DRY: Réutilise la logique de MembreCotisationBean.chargerCotisations() + */ + private void chargerCotisationsEnAttente() { + try { + // TODO: Créer endpoint GET /api/cotisations/mes-cotisations/en-attente + // Pour l'instant, utiliser l'endpoint existant avec filtre statut + List cotisationsDTO = retryService.executeWithRetrySupplier( + () -> cotisationService.rechercher( + membreId, + "EN_ATTENTE", // Statut + null, // Type + anneeEnCours, + null, // Mois + 0, + 100 + ), + "chargement des cotisations en attente" + ); + + mesCotisationsEnAttente = new ArrayList<>(); + for (CotisationResponse dto : cotisationsDTO) { + CotisationPerso cotisation = new CotisationPerso(); + cotisation.setId(dto.getId()); + cotisation.setReference(dto.getNumeroReference() != null ? dto.getNumeroReference() : ""); + cotisation.setType(dto.getTypeCotisation() != null ? dto.getTypeCotisation() : "MENSUELLE"); + cotisation.setPeriode(formaterPeriode(dto.getDateEcheance())); + cotisation.setMontantDu(dto.getMontantDu() != null ? dto.getMontantDu() : BigDecimal.ZERO); + cotisation.setDateEcheance(dto.getDateEcheance()); + mesCotisationsEnAttente.add(cotisation); + } + + LOG.infof("Cotisations en attente chargées: %d", mesCotisationsEnAttente.size()); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du chargement des cotisations en attente"); + errorHandler.handleException(e, "lors du chargement de vos cotisations en attente", null); + mesCotisationsEnAttente = new ArrayList<>(); + } + } + + /** + * Charge les 5 derniers paiements du membre connecté + */ + private void chargerDerniersPaiements() { + try { + // TODO: Créer endpoint GET /api/paiements/mes-paiements/historique?limit=5 + // Pour l'instant, charger toutes les cotisations payées et prendre les 5 dernières + List cotisationsPayees = retryService.executeWithRetrySupplier( + () -> cotisationService.rechercher( + membreId, + "PAYEE", // Statut + null, // Type + null, // Année + null, // Mois + 0, + 5 + ), + "chargement de l'historique des paiements" + ); + + derniersPaiements = new ArrayList<>(); + for (CotisationResponse dto : cotisationsPayees) { + PaiementPerso paiement = new PaiementPerso(); + paiement.setId(dto.getId()); + paiement.setReference(dto.getNumeroReference() != null ? dto.getNumeroReference() : ""); + paiement.setPeriode(formaterPeriode(dto.getDateEcheance())); + paiement.setMontant(dto.getMontantDu() != null ? dto.getMontantDu() : BigDecimal.ZERO); + if (dto.getDatePaiement() != null) { + paiement.setDatePaiement(dto.getDatePaiement().toLocalDate()); + } + // Note: methodePaiement non disponible dans CotisationResponse + paiement.setMethodePaiement("Non renseignée"); + derniersPaiements.add(paiement); + } + + LOG.infof("Derniers paiements chargés: %d", derniersPaiements.size()); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du chargement des derniers paiements"); + errorHandler.handleException(e, "lors du chargement de votre historique", null); + derniersPaiements = new ArrayList<>(); + } + } + + /** + * Calcule les KPI personnels depuis les données chargées + */ + private void calculerKPI() { + // Cotisations en attente + cotisationsEnAttente = mesCotisationsEnAttente.size(); + + // Montant dû total + BigDecimal montantTotal = mesCotisationsEnAttente.stream() + .map(CotisationPerso::getMontantDu) + .reduce(BigDecimal.ZERO, BigDecimal::add); + montantDu = formaterMontant(montantTotal); + + // Prochaine échéance + if (!mesCotisationsEnAttente.isEmpty()) { + LocalDate prochaine = mesCotisationsEnAttente.stream() + .map(CotisationPerso::getDateEcheance) + .filter(d -> d != null) + .min(LocalDate::compareTo) + .orElse(null); + prochaineEcheance = prochaine != null ? prochaine.format(DATE_FORMATTER) : "-"; + } else { + prochaineEcheance = "Aucune"; + } + + // Total payé cette année + BigDecimal totalPayeAnnee = derniersPaiements.stream() + .filter(p -> p.getDatePaiement() != null && p.getDatePaiement().getYear() == anneeEnCours) + .map(PaiementPerso::getMontant) + .reduce(BigDecimal.ZERO, BigDecimal::add); + totalPaye = formaterMontant(totalPayeAnnee); + + LOG.infof("KPI calculés: %d cotisations en attente, %s à payer", cotisationsEnAttente, montantDu); + } + + // ═══════════════════════════════════════════════════════════════════════ + // Actions + // ═══════════════════════════════════════════════════════════════════════ + + /** + * Initie un paiement en ligne (Wave, Orange, Free Money, Carte) + */ + public void initierPaiementEnLigne() { + if (cotisationSelectionneeId == null) { + errorHandler.showWarning("Attention", "Veuillez sélectionner une cotisation à payer"); + return; + } + + if (numeroTelephone == null || numeroTelephone.trim().isEmpty()) { + errorHandler.showWarning("Attention", "Veuillez saisir votre numéro de téléphone"); + return; + } + + try { + // TODO: Créer endpoint POST /api/paiements/initier-paiement-en-ligne + // Body: { cotisationId, methodePaiement, numeroTelephone } + // Retour: { redirectUrl, transactionId } + LOG.infof("Paiement en ligne initié: cotisation=%s, méthode=%s, téléphone=%s", + cotisationSelectionneeId, methodePaiementChoisie, numeroTelephone); + + errorHandler.showInfo("Paiement en ligne", + "Redirection vers le gateway de paiement " + methodePaiementChoisie + "..."); + + // TODO: Rediriger vers l'URL du gateway de paiement + // ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext(); + // ec.redirect(paymentGatewayUrl); + + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'initiation du paiement en ligne"); + errorHandler.handleException(e, "lors de l'initiation du paiement", null); + } + } + + /** + * Déclare un paiement manuel (espèces, virement, chèque) + * Statut: EN_ATTENTE_VALIDATION (le trésorier doit valider) + */ + public void declarerPaiementManuel() { + if (cotisationSelectionneeId == null) { + errorHandler.showWarning("Attention", "Veuillez sélectionner une cotisation"); + return; + } + + if (methodePaiementManuel == null || methodePaiementManuel.trim().isEmpty()) { + errorHandler.showWarning("Attention", "Veuillez sélectionner une méthode de paiement"); + return; + } + + try { + // TODO: Créer endpoint POST /api/paiements/declarer-paiement-manuel + // Body: { cotisationId, methodePaiement, reference, commentaire } + // Retour: 201 Created + LOG.infof("Paiement manuel déclaré: cotisation=%s, méthode=%s, ref=%s", + cotisationSelectionneeId, methodePaiementManuel, referencePaiementManuel); + + errorHandler.showSuccess("Paiement déclaré", + "Votre paiement a été enregistré. Il sera validé par le trésorier."); + + // Recharger les données + chargerDonnees(); + + // Réinitialiser le formulaire + cotisationSelectionneeId = null; + methodePaiementManuel = "ESPECES"; + referencePaiementManuel = null; + commentairePaiement = null; + + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la déclaration du paiement manuel"); + errorHandler.handleException(e, "lors de la déclaration du paiement", null); + } + } + + /** + * Télécharge le reçu PDF d'un paiement + */ + public void telechargerRecu(UUID paiementId) { + if (paiementId == null) { + errorHandler.showWarning("Attention", "Impossible de télécharger le reçu"); + return; + } + + try { + // TODO: Créer endpoint GET /api/paiements/telecharger-recu/{id} + byte[] recu = retryService.executeWithRetrySupplier( + () -> exportService.genererRecu(paiementId), + "génération d'un reçu" + ); + + String nomFichier = "recu-" + paiementId + ".pdf"; + telechargerFichier(recu, nomFichier, "application/pdf"); + + errorHandler.showSuccess("Reçu téléchargé", "Le reçu a été téléchargé avec succès"); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors du téléchargement du reçu"); + errorHandler.handleException(e, "lors du téléchargement du reçu", null); + } + } + + /** + * Ouvre le dialog de paiement en ligne pour une cotisation + */ + public void ouvrirDialogPaiementEnLigne(UUID cotisationId) { + this.cotisationSelectionneeId = cotisationId; + this.numeroTelephone = null; + this.methodePaiementChoisie = "WAVE"; + LOG.infof("Dialog paiement en ligne ouvert pour cotisation: %s", cotisationId); + } + + /** + * Ouvre le dialog de paiement manuel pour une cotisation + */ + public void ouvrirDialogPaiementManuel(UUID cotisationId) { + this.cotisationSelectionneeId = cotisationId; + this.methodePaiementManuel = "ESPECES"; + this.referencePaiementManuel = null; + this.commentairePaiement = null; + LOG.infof("Dialog paiement manuel ouvert pour cotisation: %s", cotisationId); + } + + /** + * Actualise les données + */ + public void actualiser() { + LOG.info("Actualisation des données de paiement"); + chargerDonnees(); + errorHandler.showSuccess("Actualisation", "Les données ont été actualisées"); + } + + // ═══════════════════════════════════════════════════════════════════════ + // Helpers + // ═══════════════════════════════════════════════════════════════════════ + + private String formaterPeriode(LocalDate dateEcheance) { + if (dateEcheance == null) { + return ""; + } + String[] moisNoms = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", + "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"}; + int mois = dateEcheance.getMonthValue(); + int annee = dateEcheance.getYear(); + return moisNoms[mois - 1] + " " + annee; + } + + private String formaterMontant(BigDecimal montant) { + if (montant == null) { + return "0 FCFA"; + } + return String.format("%,.0f FCFA", montant); + } + + private void telechargerFichier(byte[] data, String nomFichier, String contentType) { + try { + jakarta.faces.context.FacesContext fc = jakarta.faces.context.FacesContext.getCurrentInstance(); + ExternalContext ec = fc.getExternalContext(); + ec.responseReset(); + ec.setResponseContentType(contentType + "; charset=UTF-8"); + ec.setResponseContentLength(data.length); + ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + nomFichier + "\""); + OutputStream output = ec.getResponseOutputStream(); + output.write(data); + output.flush(); + fc.responseComplete(); + } catch (Exception e) { + LOG.errorf(e, "Erreur téléchargement fichier"); + throw new RuntimeException("Erreur lors du téléchargement", e); + } + } + + // ═══════════════════════════════════════════════════════════════════════ + // Getters / Setters + // ═══════════════════════════════════════════════════════════════════════ + + public UUID getMembreId() { return membreId; } + public String getNumeroMembre() { return numeroMembre; } + public MembreResponse getMembre() { return membre; } + + public Integer getCotisationsEnAttente() { return cotisationsEnAttente; } + public String getMontantDu() { return montantDu; } + public String getProchaineEcheance() { return prochaineEcheance; } + public String getTotalPaye() { return totalPaye; } + public Integer getAnneeEnCours() { return anneeEnCours; } + + public List getMesCotisationsEnAttente() { return mesCotisationsEnAttente; } + public List getDerniersPaiements() { return derniersPaiements; } + + public UUID getCotisationSelectionneeId() { return cotisationSelectionneeId; } + public void setCotisationSelectionneeId(UUID cotisationSelectionneeId) { this.cotisationSelectionneeId = cotisationSelectionneeId; } + + public String getMethodePaiementChoisie() { return methodePaiementChoisie; } + public void setMethodePaiementChoisie(String methodePaiementChoisie) { this.methodePaiementChoisie = methodePaiementChoisie; } + + public String getNumeroTelephone() { return numeroTelephone; } + public void setNumeroTelephone(String numeroTelephone) { this.numeroTelephone = numeroTelephone; } + + public String getMethodePaiementManuel() { return methodePaiementManuel; } + public void setMethodePaiementManuel(String methodePaiementManuel) { this.methodePaiementManuel = methodePaiementManuel; } + + public String getReferencePaiementManuel() { return referencePaiementManuel; } + public void setReferencePaiementManuel(String referencePaiementManuel) { this.referencePaiementManuel = referencePaiementManuel; } + + public String getCommentairePaiement() { return commentairePaiement; } + public void setCommentairePaiement(String commentairePaiement) { this.commentairePaiement = commentairePaiement; } + + public boolean isPaiementManuelActive() { return paiementManuelActive; } + + // ═══════════════════════════════════════════════════════════════════════ + // DTOs internes + // ═══════════════════════════════════════════════════════════════════════ + + public static class CotisationPerso implements Serializable { + private static final long serialVersionUID = 1L; + + private UUID id; + private String reference; + private String type; + private String periode; + private BigDecimal montantDu; + private LocalDate dateEcheance; + + // Getters / Setters + public UUID getId() { return id; } + public void setId(UUID id) { this.id = id; } + + public String getReference() { return reference; } + public void setReference(String reference) { this.reference = reference; } + + public String getType() { return type; } + public void setType(String type) { this.type = type; } + + public String getPeriode() { return periode; } + public void setPeriode(String periode) { this.periode = periode; } + + public BigDecimal getMontantDu() { return montantDu; } + public void setMontantDu(BigDecimal montantDu) { this.montantDu = montantDu; } + + public LocalDate getDateEcheance() { return dateEcheance; } + public void setDateEcheance(LocalDate dateEcheance) { this.dateEcheance = dateEcheance; } + + // Méthodes dérivées pour l'affichage + public String getTypeSeverity() { + return switch (type) { + case "MENSUELLE" -> "info"; + case "SPECIALE" -> "warning"; + case "ADHESION" -> "success"; + default -> "secondary"; + }; + } + + public String getTypeIcon() { + return switch (type) { + case "MENSUELLE" -> "pi-calendar"; + case "SPECIALE" -> "pi-star"; + case "ADHESION" -> "pi-user-plus"; + default -> "pi-circle"; + }; + } + + public String getMontantDuFormatte() { + return montantDu != null ? String.format("%,.0f FCFA", montantDu) : "0 FCFA"; + } + + public String getDateEcheanceFormattee() { + return dateEcheance != null ? dateEcheance.format(DATE_FORMATTER) : "-"; + } + } + + public static class PaiementPerso implements Serializable { + private static final long serialVersionUID = 1L; + + private UUID id; + private String reference; + private String periode; + private BigDecimal montant; + private LocalDate datePaiement; + private String methodePaiement; + + // Getters / Setters + public UUID getId() { return id; } + public void setId(UUID id) { this.id = id; } + + public String getReference() { return reference; } + public void setReference(String reference) { this.reference = reference; } + + public String getPeriode() { return periode; } + public void setPeriode(String periode) { this.periode = periode; } + + public BigDecimal getMontant() { return montant; } + public void setMontant(BigDecimal montant) { this.montant = montant; } + + public LocalDate getDatePaiement() { return datePaiement; } + public void setDatePaiement(LocalDate datePaiement) { this.datePaiement = datePaiement; } + + public String getMethodePaiement() { return methodePaiement; } + public void setMethodePaiement(String methodePaiement) { this.methodePaiement = methodePaiement; } + + // Méthodes dérivées pour l'affichage + public String getMontantFormatte() { + return montant != null ? String.format("%,.0f FCFA", montant) : "0 FCFA"; + } + + public String getDatePaiementFormattee() { + return datePaiement != null ? datePaiement.format(DATE_FORMATTER) : "-"; + } + } +} diff --git a/src/main/java/dev/lions/unionflow/client/view/OrganisationDetailBean.java b/src/main/java/dev/lions/unionflow/client/view/OrganisationDetailBean.java index ee2a4fb..c0d8f8f 100644 --- a/src/main/java/dev/lions/unionflow/client/view/OrganisationDetailBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/OrganisationDetailBean.java @@ -1,6 +1,7 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.ErrorHandlerService; import dev.lions.unionflow.client.service.RestClientExceptionMapper; @@ -296,7 +297,7 @@ public class OrganisationDetailBean implements Serializable { public List rechercherOrganisations(String query) { if (query == null || query.trim().isEmpty()) return List.of(); try { - AssociationService.PagedResponseDTO response = + PagedResponse response = associationService.rechercher(query, null, null, null, null, 0, 100); return (response != null && response.getData() != null) ? response.getData() : List.of(); } catch (Exception e) { @@ -322,6 +323,17 @@ public class OrganisationDetailBean implements Serializable { return organisation.getTypeOrganisationLibelle(); } + /** + * Alias pour la vue (detail.xhtml) : libellé du type d'organisation. + * Délègue à typeLibelle du DTO si présent, sinon typeOrganisationLibelle. + */ + public String getTypeLibelle() { + if (organisation == null) return ""; + String libelle = organisation.getTypeLibelle(); + if (libelle != null && !libelle.isBlank()) return libelle; + return organisation.getTypeOrganisationLibelle() != null ? organisation.getTypeOrganisationLibelle() : ""; + } + public String getStatutLibelle() { if (organisation == null || organisation.getStatut() == null) return "Non renseigné"; return switch (organisation.getStatut()) { diff --git a/src/main/java/dev/lions/unionflow/client/view/OrganisationStatistiquesBean.java b/src/main/java/dev/lions/unionflow/client/view/OrganisationStatistiquesBean.java index 0cabf78..467d447 100644 --- a/src/main/java/dev/lions/unionflow/client/view/OrganisationStatistiquesBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/OrganisationStatistiquesBean.java @@ -2,6 +2,7 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.client.constants.StatutOrganisationConstants; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.AssociationService; import jakarta.faces.view.ViewScoped; import jakarta.inject.Named; @@ -74,7 +75,7 @@ public class OrganisationStatistiquesBean implements Serializable { private void calculerStatistiquesDepuisListe() { try { // Charger toutes les organisations - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List organisations = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); diff --git a/src/main/java/dev/lions/unionflow/client/view/OrganisationsBean.java b/src/main/java/dev/lions/unionflow/client/view/OrganisationsBean.java index 434a786..7f75615 100644 --- a/src/main/java/dev/lions/unionflow/client/view/OrganisationsBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/OrganisationsBean.java @@ -2,7 +2,9 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.client.constants.StatutOrganisationConstants; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.reference.response.TypeReferenceResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.CacheService; import dev.lions.unionflow.client.service.ErrorHandlerService; @@ -71,6 +73,10 @@ public class OrganisationsBean implements Serializable { private OrganisationResponse organisationSelectionnee; private OrganisationResponse nouvelleOrganisation; private OrganisationResponse backupOrganisation; // Pour rollback + /** Organisation en attente de confirmation de suppression (flux explicite). */ + private OrganisationResponse organisationPourSuppression; + /** Organisation en attente de confirmation de bascule de statut (flux explicite). */ + private OrganisationResponse organisationPourStatut; private long totalOrganisations; private long organisationsActives; @@ -110,7 +116,7 @@ public class OrganisationsBean implements Serializable { public void chargerOrganisations() { try { LOG.debug("Chargement des organisations"); - AssociationService.PagedResponseDTO response = retryService.executeWithRetrySupplier( + PagedResponse response = retryService.executeWithRetrySupplier( () -> associationService.listerToutes(0, 1000), "chargement de toutes les organisations"); organisations = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); @@ -136,7 +142,7 @@ public class OrganisationsBean implements Serializable { () -> { LOG.debug("Chargement de toutes les organisations pour les statistiques"); try { - AssociationService.PagedResponseDTO response = retryService + PagedResponse response = retryService .executeWithRetrySupplier( () -> associationService.listerToutes(0, 10000), "chargement des statistiques"); @@ -342,6 +348,45 @@ public class OrganisationsBean implements Serializable { } } + /** + * Prépare la suppression (ouvre le dialogue de confirmation). + */ + public void preparerSuppression(OrganisationResponse organisation) { + organisationPourSuppression = organisation; + } + + /** + * Confirme et exécute la suppression après clic sur « Oui » dans le dialogue. + */ + public void confirmerSuppression() { + if (organisationPourSuppression == null) { + errorHandler.showWarning("Erreur", "Aucune organisation à supprimer"); + return; + } + OrganisationResponse org = organisationPourSuppression; + organisationPourSuppression = null; + supprimerOrganisation(org); + } + + /** + * Prépare la bascule de statut (ouvre le dialogue de confirmation). + */ + public void preparerBasculerStatut(OrganisationResponse organisation) { + organisationPourStatut = organisation; + } + + /** + * Confirme et exécute la bascule de statut après clic sur « Oui » dans le dialogue. + */ + public void confirmerBasculerStatut() { + if (organisationPourStatut == null) { + return; + } + OrganisationResponse org = organisationPourStatut; + organisationPourStatut = null; + basculerStatutOrganisation(org); + } + /** * Supprime une organisation. */ @@ -449,7 +494,7 @@ public class OrganisationsBean implements Serializable { return List.of(); } try { - AssociationService.PagedResponseDTO response = associationService.rechercher( + PagedResponse response = associationService.rechercher( query, null, null, null, null, 0, 100); List resultats = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); diff --git a/src/main/java/dev/lions/unionflow/client/view/PersonnelBean.java b/src/main/java/dev/lions/unionflow/client/view/PersonnelBean.java index 00a9b16..609d8ee 100644 --- a/src/main/java/dev/lions/unionflow/client/view/PersonnelBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/PersonnelBean.java @@ -79,7 +79,7 @@ public class PersonnelBean implements Serializable { String email = userSession.getCurrentUser().getEmail(); if (email != null) { // Rechercher le membre par email - List membres = membreService.listerTous(); + List membres = membreService.listerTous().getData(); membre = membres.stream() .filter(m -> email.equals(m.getEmail())) .findFirst() @@ -132,14 +132,10 @@ public class PersonnelBean implements Serializable { try { if (membre != null) { List cotisations = cotisationService.obtenirParMembre(membre.getId(), 0, 100); - Map evenementsMap = evenementService.listerTous(0, 100, "dateDebut", "desc"); + var evenementsResponse = evenementService.listerTous(0, 100, "dateDebut", "desc"); int nbCotisations = cotisations != null ? cotisations.size() : 0; - int nbEvenements = 0; - if (evenementsMap != null && evenementsMap.containsKey("content")) { - @SuppressWarnings("unchecked") - List> content = (List>) evenementsMap.get("content"); - nbEvenements = content != null ? content.size() : 0; - } + int nbEvenements = evenementsResponse != null && evenementsResponse.getData() != null + ? evenementsResponse.getData().size() : 0; return nbCotisations + nbEvenements; } } catch (Exception e) { @@ -152,14 +148,10 @@ public class PersonnelBean implements Serializable { try { if (membre != null) { // Récupérer tous les événements et filtrer ceux où le membre a participé - Map evenementsMap = evenementService.listerTous(0, 100, "dateDebut", "desc"); - if (evenementsMap != null && evenementsMap.containsKey("content")) { - @SuppressWarnings("unchecked") - List> content = (List>) evenementsMap.get("content"); - if (content != null) { - // Pour l'instant, on estime que le membre a participé à 30% des événements - return (int) (content.size() * 0.3); - } + var evenementsResponse = evenementService.listerTous(0, 100, "dateDebut", "desc"); + if (evenementsResponse != null && evenementsResponse.getData() != null) { + // Pour l'instant, on estime que le membre a participé à 30% des événements + return (int) (evenementsResponse.getData().size() * 0.3); } } } catch (Exception e) { @@ -243,24 +235,20 @@ public class PersonnelBean implements Serializable { } // Charger les événements récents - Map evenementsMap = evenementService.listerAVenir(0, 5); - if (evenementsMap != null && evenementsMap.containsKey("content")) { - @SuppressWarnings("unchecked") - List> content = (List>) evenementsMap.get("content"); - if (content != null) { - for (Map evtMap : content) { - ActiviteRecente act = new ActiviteRecente(); - act.setTitre("Événement: " + (evtMap.get("titre") != null ? evtMap.get("titre").toString() : "")); - act.setDescription("Événement à venir"); - if (evtMap.get("dateDebut") != null) { - act.setDateHeure(formatDateRelative(evtMap.get("dateDebut").toString())); - } else { - act.setDateHeure("Bientôt"); - } - act.setIcon("pi-calendar"); - act.setCouleur("blue-500"); - activitesRecentes.add(act); + var evenementsResponse = evenementService.listerAVenir(0, 5); + if (evenementsResponse != null && evenementsResponse.getData() != null) { + for (EvenementResponse evt : evenementsResponse.getData()) { + ActiviteRecente act = new ActiviteRecente(); + act.setTitre("Événement: " + (evt.getTitre() != null ? evt.getTitre() : "")); + act.setDescription("Événement à venir"); + if (evt.getDateDebut() != null) { + act.setDateHeure(formatDateRelative(evt.getDateDebut().toString())); + } else { + act.setDateHeure("Bientôt"); } + act.setIcon("pi-calendar"); + act.setCouleur("blue-500"); + activitesRecentes.add(act); } } @@ -384,29 +372,25 @@ public class PersonnelBean implements Serializable { try { if (membre != null) { // Créer des notifications basées sur les événements à venir - Map evenementsMap = evenementService.listerAVenir(0, 5); - if (evenementsMap != null && evenementsMap.containsKey("content")) { - @SuppressWarnings("unchecked") - List> content = (List>) evenementsMap.get("content"); - if (content != null) { - for (Map evtMap : content) { - NotificationPersonnelle notif = new NotificationPersonnelle(); - notif.setId(UUID.randomUUID()); - notif.setTitre("Nouvel événement"); - notif.setMessage("Un nouvel événement a été programmé: " + - (evtMap.get("titre") != null ? evtMap.get("titre").toString() : "")); - if (evtMap.get("dateCreation") != null) { - try { - notif.setDateCreation(LocalDate.parse(evtMap.get("dateCreation").toString().substring(0, 10))); - } catch (Exception e) { - notif.setDateCreation(LocalDate.now().minusDays(1)); - } - } else { + var evenementsResponse = evenementService.listerAVenir(0, 5); + if (evenementsResponse != null && evenementsResponse.getData() != null) { + for (EvenementResponse evt : evenementsResponse.getData()) { + NotificationPersonnelle notif = new NotificationPersonnelle(); + notif.setId(UUID.randomUUID()); + notif.setTitre("Nouvel événement"); + notif.setMessage("Un nouvel événement a été programmé: " + + (evt.getTitre() != null ? evt.getTitre() : "")); + if (evt.getDateCreation() != null) { + try { + notif.setDateCreation(evt.getDateCreation().toLocalDate()); + } catch (Exception e) { notif.setDateCreation(LocalDate.now().minusDays(1)); } - notif.setLue(false); - notifications.add(notif); + } else { + notif.setDateCreation(LocalDate.now().minusDays(1)); } + notif.setLue(false); + notifications.add(notif); } } diff --git a/src/main/java/dev/lions/unionflow/client/view/RapportsBean.java b/src/main/java/dev/lions/unionflow/client/view/RapportsBean.java index cfbc975..eaff517 100644 --- a/src/main/java/dev/lions/unionflow/client/view/RapportsBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/RapportsBean.java @@ -153,8 +153,8 @@ public class RapportsBean implements Serializable { private void calculerIndicateurs() { indicateurs = new IndicateursGlobaux(); try { - int totalMembres = membreService.listerTous().size(); - int totalEvenements = evenementService.listerTous(0, 1000, "dateCreation", "desc").size(); + int totalMembres = membreService.listerTous().getData().size(); + int totalEvenements = evenementService.listerTous(0, 1000, "dateCreation", "desc").getData().size(); BigDecimal totalRevenus = cotisationService.listerToutes(0, 1000).stream() .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())) @@ -197,7 +197,7 @@ public class RapportsBean implements Serializable { repartitionMembres = new ArrayList<>(); try { // Corrigé: MembreResponse est dans server.api, pas client.dto; et getStatut() → getStatutCompte() - List membres = membreService.listerTous(); + List membres = membreService.listerTous().getData(); long actifs = membres.stream().filter(m -> "ACTIF".equals(m.getStatutCompte())).count(); long inactifs = membres.stream().filter(m -> "INACTIF".equals(m.getStatutCompte())).count(); long total = membres.size(); @@ -248,8 +248,8 @@ public class RapportsBean implements Serializable { private void calculerObjectifs() { objectifs = new ArrayList<>(); try { - int totalMembres = membreService.listerTous().size(); - int totalEvenements = evenementService.listerTous(0, 1000, "dateCreation", "desc").size(); + int totalMembres = membreService.listerTous().getData().size(); + int totalEvenements = evenementService.listerTous(0, 1000, "dateCreation", "desc").getData().size(); BigDecimal totalRevenus = cotisationService.listerToutes(0, 1000).stream() .filter(c -> "PAYEE".equals(c.getStatut()) || "PARTIELLEMENT_PAYEE".equals(c.getStatut())) diff --git a/src/main/java/dev/lions/unionflow/client/view/RolesBean.java b/src/main/java/dev/lions/unionflow/client/view/RolesBean.java index a8ef74f..9734e83 100644 --- a/src/main/java/dev/lions/unionflow/client/view/RolesBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/RolesBean.java @@ -31,6 +31,8 @@ public class RolesBean implements Serializable { private List roles; private Role roleSelectionne; private Role nouveauRole = new Role(); + /** Rôle en attente de confirmation de suppression (flux explicite). */ + private Role rolePourSuppression; @jakarta.annotation.PostConstruct public void init() { @@ -120,6 +122,32 @@ public class RolesBean implements Serializable { public void gererUtilisateurs(Role role) { this.roleSelectionne = role; } + + /** + * Prépare la suppression (ouvre le dialogue de confirmation). + */ + public void preparerSuppression(Role role) { + this.rolePourSuppression = role; + } + + /** + * Confirme et exécute la suppression après clic sur « Oui » dans le dialogue. + */ + public void confirmerSuppression() { + if (rolePourSuppression == null) return; + Role r = rolePourSuppression; + rolePourSuppression = null; + supprimerRole(r); + } + + /** + * Supprime un rôle de la liste (côté client ; pas d'API delete dans AdminUserService). + */ + public void supprimerRole(Role role) { + if (role != null && roles != null) { + roles.remove(role); + } + } public void creerRole() { // Validation diff --git a/src/main/java/dev/lions/unionflow/client/view/SuperAdminBean.java b/src/main/java/dev/lions/unionflow/client/view/SuperAdminBean.java index e487518..9a571a8 100644 --- a/src/main/java/dev/lions/unionflow/client/view/SuperAdminBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/SuperAdminBean.java @@ -1,7 +1,9 @@ package dev.lions.unionflow.client.view; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.abonnement.response.AbonnementResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.client.service.AdminUserService; import dev.lions.unionflow.client.service.AssociationService; import dev.lions.unionflow.client.service.AuditService; @@ -151,7 +153,7 @@ public class SuperAdminBean implements Serializable { private void initializeAssociationKPIs() { try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List associations = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); totalEntites = associations.size(); @@ -291,7 +293,7 @@ public class SuperAdminBean implements Serializable { private void initializeEntites() { topEntites = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List associations = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); topEntites = associations.stream() @@ -318,7 +320,7 @@ public class SuperAdminBean implements Serializable { private void initializeRepartitionTypes() { repartitionTypes = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List associations = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); if (associations == null || associations.isEmpty()) diff --git a/src/main/java/dev/lions/unionflow/client/view/TypeOrganisationsAdminBean.java b/src/main/java/dev/lions/unionflow/client/view/TypeOrganisationsAdminBean.java index f2117a5..6874e21 100644 --- a/src/main/java/dev/lions/unionflow/client/view/TypeOrganisationsAdminBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/TypeOrganisationsAdminBean.java @@ -56,6 +56,8 @@ public class TypeOrganisationsAdminBean implements Serializable { /** Type actuellement édité dans le dialogue (nouveau ou existant). */ private TypeReferenceResponse typeCourant; private TypeReferenceResponse typeSelectionne; + /** ID du type à supprimer (pour dialogue de confirmation explicite). */ + private UUID typeASupprimerId; @PostConstruct public void init() { @@ -95,6 +97,11 @@ public class TypeOrganisationsAdminBean implements Serializable { .domaine("TYPE_ORGANISATION") .code(typeCourant.getCode()) .libelle(typeCourant.getLibelle()) + .description(typeCourant.getDescription()) + .ordreAffichage(typeCourant.getOrdreAffichage() != null ? typeCourant.getOrdreAffichage() : 0) + .estDefaut(false) + .estSysteme(false) + .organisationId(null) .build(); TypeReferenceResponse cree = retryService.executeWithRetrySupplier( @@ -152,11 +159,11 @@ public class TypeOrganisationsAdminBean implements Serializable { UpdateTypeReferenceRequest request = new UpdateTypeReferenceRequest( typeCourant.getCode(), typeCourant.getLibelle(), - null, // description + typeCourant.getDescription(), null, // icone null, // couleur null, // severity - null, // ordreAffichage + typeCourant.getOrdreAffichage(), null, // estDefaut typeCourant.getActif() ); @@ -179,30 +186,49 @@ public class TypeOrganisationsAdminBean implements Serializable { } } - public void desactiverType(UUID id) { + /** + * Ouvre le dialogue de confirmation de suppression (stocke l'id). + */ + public void preparerSuppression(UUID id) { + typeASupprimerId = id; + } + + /** + * Confirme et exécute la suppression du type dont l'id a été stocké par preparerSuppression. + */ + public void confirmerSuppression() { + if (typeASupprimerId == null) { + errorHandler.showWarning("Erreur", "Aucun type à supprimer"); + return; + } + UUID id = typeASupprimerId; + typeASupprimerId = null; + supprimerType(id); + } + + /** + * Supprime définitivement un type d'organisation (appel DELETE côté API). + */ + public void supprimerType(UUID id) { if (id == null) { errorHandler.showWarning("Erreur", "Aucun type sélectionné"); return; } - try { retryService.executeWithRetrySupplier( () -> { typeOrganisationClientService.disable(id); return null; }, - "désactivation d'un type d'organisation" + "suppression d'un type d'organisation" ); - - LOG.infof("Type d'organisation désactivé avec succès: id=%s", id); + LOG.infof("Type d'organisation supprimé avec succès: id=%s", id); typeCatalogueService.recharger(); chargerTypes(); - - errorHandler.showSuccess("Succès", "Type d'organisation désactivé"); - + errorHandler.showSuccess("Succès", "Type d'organisation supprimé"); } catch (Exception e) { - LOG.errorf(e, "Erreur lors de la désactivation du type d'organisation"); - errorHandler.handleException(e, "lors de la désactivation d'un type d'organisation", null); + LOG.errorf(e, "Erreur lors de la suppression du type d'organisation"); + errorHandler.handleException(e, "lors de la suppression d'un type d'organisation", null); } } diff --git a/src/main/java/dev/lions/unionflow/client/view/UserSession.java b/src/main/java/dev/lions/unionflow/client/view/UserSession.java index 19e0080..2d24bd7 100644 --- a/src/main/java/dev/lions/unionflow/client/view/UserSession.java +++ b/src/main/java/dev/lions/unionflow/client/view/UserSession.java @@ -2,6 +2,7 @@ package dev.lions.unionflow.client.view; import io.quarkus.oidc.IdToken; import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.SessionScoped; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -14,12 +15,12 @@ import java.util.logging.Logger; /** * Gestion de la session utilisateur avec Keycloak OIDC - * + * *

* Utilise {@code @IdToken JsonWebToken} pour accéder aux claims du token * et {@link SecurityIdentity} pour les vérifications d'authentification, * car l'application est en mode OIDC {@code web-app} (authorization code flow). - * + * * @author UnionFlow Team * @version 2.1 */ @@ -50,6 +51,20 @@ public class UserSession implements Serializable { clearSession(); } + /** + * Initialise automatiquement la session après injection des dépendances + * Appelé automatiquement par CDI si l'utilisateur est authentifié + */ + @PostConstruct + public void init() { + if (securityIdentity != null && !securityIdentity.isAnonymous()) { + LOGGER.info("Initialisation automatique de la session utilisateur (authentifié via OIDC)"); + initializeFromOidcToken(); + } else { + LOGGER.info("Utilisateur non authentifié, session reste vide"); + } + } + /** * Initialise la session depuis le token OIDC Keycloak * Appelé automatiquement après l'authentification diff --git a/src/main/java/dev/lions/unionflow/client/view/UtilisateursBean.java b/src/main/java/dev/lions/unionflow/client/view/UtilisateursBean.java index 745a9e5..0af8dde 100644 --- a/src/main/java/dev/lions/unionflow/client/view/UtilisateursBean.java +++ b/src/main/java/dev/lions/unionflow/client/view/UtilisateursBean.java @@ -4,6 +4,7 @@ import dev.lions.unionflow.server.api.dto.user.request.CreateUserRequest; import dev.lions.unionflow.server.api.dto.user.request.UpdateUserRequest; import dev.lions.unionflow.server.api.dto.user.response.UserResponse; import dev.lions.unionflow.server.api.dto.base.PageResponse; +import dev.lions.unionflow.server.api.dto.common.PagedResponse; import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse; import dev.lions.unionflow.client.service.AdminUserService; import dev.lions.unionflow.client.service.AssociationService; @@ -84,7 +85,7 @@ public class UtilisateursBean implements Serializable { private void initializeOrganisations() { organisationsDisponibles = new ArrayList<>(); try { - AssociationService.PagedResponseDTO response = associationService.listerToutes(0, 1000); + PagedResponse response = associationService.listerToutes(0, 1000); List associations = (response != null && response.getData() != null) ? response.getData() : new ArrayList<>(); for (OrganisationResponse assoc : associations) { diff --git a/src/main/resources/META-INF/faces-config.xml b/src/main/resources/META-INF/faces-config.xml index b74a939..b33ce1f 100644 --- a/src/main/resources/META-INF/faces-config.xml +++ b/src/main/resources/META-INF/faces-config.xml @@ -90,6 +90,45 @@ + + Page de validation des inscriptions membres + /pages/secure/membre/validation + /pages/secure/membre/validation.xhtml + + + + + + Page des demandes de crédit + /pages/secure/credit/demandes + /pages/secure/credit/demandes.xhtml + + + + Page d'évaluation solvabilité crédit + /pages/secure/credit/evaluation + /pages/secure/credit/evaluation.xhtml + + + + Page de suivi des crédits + /pages/secure/credit/suivi + /pages/secure/credit/suivi.xhtml + + + + Page des remboursements crédit + /pages/secure/credit/remboursements + /pages/secure/credit/remboursements.xhtml + + + + Page des statistiques crédit + /pages/secure/credit/statistiques + /pages/secure/credit/statistiques.xhtml + + + Page de liste des organisations diff --git a/src/main/resources/META-INF/resources/pages/admin/documents/gestion.xhtml b/src/main/resources/META-INF/resources/pages/admin/documents/gestion.xhtml index a3b3f12..8cdf288 100644 --- a/src/main/resources/META-INF/resources/pages/admin/documents/gestion.xhtml +++ b/src/main/resources/META-INF/resources/pages/admin/documents/gestion.xhtml @@ -520,7 +520,7 @@

- +
@@ -268,6 +269,7 @@ - -
+ +
@@ -63,8 +63,8 @@
- -
+ +
Répartition par Méthode de Paiement @@ -118,6 +118,7 @@ value="#{cotisationsBean.cotisationsFiltrees}" var="cotisation" filteredValue="#{cotisationsBean.cotisationsFiltrees}" + rowKey="#{cotisation.id}" paginator="true" rows="20" emptyMessage="Aucune cotisation en attente" @@ -137,7 +138,9 @@
- + +
#{cotisation.nomMembre}
#{cotisation.numeroMembre}
diff --git a/src/main/resources/META-INF/resources/pages/secure/credit/demandes.xhtml b/src/main/resources/META-INF/resources/pages/secure/credit/demandes.xhtml new file mode 100644 index 0000000..1e8da72 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/credit/demandes.xhtml @@ -0,0 +1,30 @@ + + + + Demandes de Crédit - UnionFlow + + + + + + + +
+ + +
+ +
+
Demandes de crédit
+

La liste des demandes de crédit à traiter sera affichée ici lorsque le module sera connecté au backend.

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/credit/evaluation.xhtml b/src/main/resources/META-INF/resources/pages/secure/credit/evaluation.xhtml new file mode 100644 index 0000000..ddeae24 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/credit/evaluation.xhtml @@ -0,0 +1,30 @@ + + + + Évaluation Solvabilité - UnionFlow + + + + + + + +
+ + +
+ +
+
Évaluation solvabilité
+

Les outils d'évaluation de solvabilité seront disponibles ici lorsque le module crédit sera opérationnel.

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/credit/remboursements.xhtml b/src/main/resources/META-INF/resources/pages/secure/credit/remboursements.xhtml new file mode 100644 index 0000000..7daf587 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/credit/remboursements.xhtml @@ -0,0 +1,30 @@ + + + + Remboursements - UnionFlow + + + + + + + +
+ + +
+ +
+
Remboursements
+

La gestion des remboursements sera disponible ici lorsque le module crédit sera branché.

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/credit/statistiques.xhtml b/src/main/resources/META-INF/resources/pages/secure/credit/statistiques.xhtml new file mode 100644 index 0000000..56d9f53 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/credit/statistiques.xhtml @@ -0,0 +1,30 @@ + + + + Statistiques Crédit - UnionFlow + + + + + + + +
+ + +
+ +
+
Statistiques crédit
+

Les indicateurs et statistiques du portefeuille crédit seront affichés ici lorsque le module sera opérationnel.

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/credit/suivi.xhtml b/src/main/resources/META-INF/resources/pages/secure/credit/suivi.xhtml new file mode 100644 index 0000000..63f93ef --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/credit/suivi.xhtml @@ -0,0 +1,30 @@ + + + + Suivi des Crédits - UnionFlow + + + + + + + +
+ + +
+ +
+
Suivi des crédits
+

Le suivi des crédits accordés sera affiché ici une fois le module connecté.

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/dashboard-membre.xhtml b/src/main/resources/META-INF/resources/pages/secure/dashboard-membre.xhtml new file mode 100644 index 0000000..f13b9f7 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/dashboard-membre.xhtml @@ -0,0 +1,272 @@ + + + + Mon Espace Personnel + + +
+ +
+
+
+
+
+ +
+
+

Bienvenue, #{dashboardMembreBean.prenomMembre}

+

Voici votre tableau de bord personnel

+
+
+
+
Membre depuis
+
+ + + + - +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Historique de mes cotisations
+ + + + + + + + + + + + + #{cotis.montant} FCFA + + + + + + + + +
+
+ + +
+ +
+
+
Actions rapides
+ Ce que je peux faire +
+
+ +
+ + + + + + + + Cotisation mensuelle +
+ +
+ + + + + + + + Calendrier +
+ +
+ + + + + + + + Nouvelle demande +
+ +
+ + + + + + + + Mes informations +
+
+
+
+ + +
+
Mes notifications
+
+ +
+ +
+
#{notif.titre}
+ #{notif.message} +
+ + + + + +
+
+ +
+ +

Aucune notification pour le moment

+
+
+
+
+
+ + +
+
+
+
Événements à venir
+ + + + + + + + + +
+ +
+ +
+
+
+ + #{evt.daysUntilEvent} +
+
#{evt.title}
+

#{evt.description}

+
+ + + + +
+
+ + #{evt.location} +
+
+ #{evt.participationSummary} + + + + + + + + + +
+
+
+
+
+ + +
+ +

Aucun événement prévu pour le moment

+
+
+
+
+
+
+ +
diff --git a/src/main/resources/META-INF/resources/pages/secure/dashboard.xhtml b/src/main/resources/META-INF/resources/pages/secure/dashboard.xhtml index 28b03b8..d595cb9 100644 --- a/src/main/resources/META-INF/resources/pages/secure/dashboard.xhtml +++ b/src/main/resources/META-INF/resources/pages/secure/dashboard.xhtml @@ -7,7 +7,14 @@ template="/templates/main-template.xhtml"> UnionFlow - Tableau de bord - + + + + + + + +
@@ -16,7 +23,7 @@

Tableau de bord UnionFlow

-

Bienvenue #{userSession.currentUser.nom}, voici un aperçu de votre union

+

Bienvenue #{userSession.currentUser != null ? userSession.currentUser.nomComplet : userSession.username}, voici un aperçu de votre union

#{dashboardBean.currentDate} @@ -130,54 +137,68 @@
- - - - - - - - - + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + -
-
+ + +
+
Évolution financière (3 derniers mois)
@@ -239,7 +260,11 @@
- + + + + +
État des cotisations
@@ -303,7 +328,8 @@
- + +
@@ -376,110 +402,142 @@
-
-
Actions rapides
-
-
- - - - - - - -
-
- - - - - - - -
-
- - - - - - - -
-
- - - - - - - + + +
+
Actions rapides
+
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + + +
+
-
+ -
-
Tâches prioritaires
-
-
- -
-
Valider #{dashboardBean.adhesionsPendantes} adhésions
- Demandes en attente de validation -
- - - - - -
- -
- -
-
Relancer #{dashboardBean.cotisationsRetard} cotisations
- Paiements en retard -
- - - - - -
- -
- -
-
Traiter #{dashboardBean.aidesEnAttente} aides
- Demandes d'aide à examiner -
- - - - - -
- -
- -
-
Organiser prochains événements
- #{dashboardBean.evenementsAPlanifier} événements à planifier -
- - - - - + + +
+
Tâches prioritaires
+
+ + +
+ +
+
Valider #{dashboardBean.adhesionsPendantes} adhésions
+ Demandes en attente de validation +
+ + + + + +
+
+ + + +
+ +
+
Relancer #{dashboardBean.cotisationsRetard} cotisations
+ Paiements en retard +
+ + + + + +
+
+ + + +
+ +
+
Traiter #{dashboardBean.aidesEnAttente} aides
+ Demandes d'aide à examiner +
+ + + + + +
+
+ + + +
+ +
+
Organiser prochains événements
+ #{dashboardBean.evenementsAPlanifier} événements à planifier +
+ + + + + +
+
-
+
-
-
+ + +
+
Résumé financier mensuel
@@ -527,6 +585,7 @@
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/finance/tresorerie.xhtml b/src/main/resources/META-INF/resources/pages/secure/finance/tresorerie.xhtml index 89f271a..340a4eb 100644 --- a/src/main/resources/META-INF/resources/pages/secure/finance/tresorerie.xhtml +++ b/src/main/resources/META-INF/resources/pages/secure/finance/tresorerie.xhtml @@ -6,6 +6,11 @@ xmlns:p="http://primefaces.org/ui" template="/templates/main-template.xhtml"> + + + + + Trésorerie - UnionFlow diff --git a/src/main/resources/META-INF/resources/pages/secure/membre/cotisations.xhtml b/src/main/resources/META-INF/resources/pages/secure/membre/cotisations.xhtml index 092270b..b224cd5 100644 --- a/src/main/resources/META-INF/resources/pages/secure/membre/cotisations.xhtml +++ b/src/main/resources/META-INF/resources/pages/secure/membre/cotisations.xhtml @@ -7,7 +7,7 @@ template="/templates/main-template.xhtml"> - + @@ -86,7 +86,7 @@
-
Historique des Cotisations
+
Mes Cotisations
diff --git a/src/main/resources/META-INF/resources/pages/secure/membre/export.xhtml b/src/main/resources/META-INF/resources/pages/secure/membre/export.xhtml index 1d29dc9..92eba64 100644 --- a/src/main/resources/META-INF/resources/pages/secure/membre/export.xhtml +++ b/src/main/resources/META-INF/resources/pages/secure/membre/export.xhtml @@ -143,7 +143,7 @@
- +
- + + + + + + Inscription Membre - UnionFlow @@ -268,7 +273,7 @@
- + + styleClass="ui-button-success mr-2" + rendered="#{menuBean.gestionMembresMenuVisible}" /> + onclick="PF('dlgImportExport').show();" type="button" styleClass="ui-button-info" + rendered="#{menuBean.gestionMembresMenuVisible}" /> - +
@@ -125,10 +129,11 @@ + rendered="#{not empty membreListeBean.selectedMembres and menuBean.gestionMembresMenuVisible}"> @@ -157,8 +162,9 @@ - - + + @@ -225,9 +231,10 @@ - + + styleClass="ui-button-warning ui-button-sm ui-button-rounded mr-1" + rendered="#{menuBean.gestionMembresMenuVisible}"> @@ -237,31 +244,34 @@ oncomplete="PF('dlgContact').show();" update=":dlgContact" styleClass="ui-button-help ui-button-sm ui-button-rounded mr-1" /> - + - - + actionListener="#{membreListeBean.preparerSuspendre(membre)}" + update=":formMembres:dlgConfirmSuspendre" + oncomplete="PF('dlgConfirmSuspendre').show();" + rendered="#{menuBean.gestionMembresMenuVisible and membre.statut == 'ACTIF'}" + styleClass="ui-button-danger ui-button-sm ui-button-rounded mr-1" /> - + - - - - - + +

Suspendre ce membre ?

+ + + + +
+ + + + + +
+ + + + + + + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + Mes Cotisations en Attente +
+ + + + + + + + + + + + +
+
#{cotis.libelle}
+ #{cotis.periodeFormatee} +
+
+ + +
+
#{cotis.montantDuFormatte}
+ FCFA +
+
+ + +
+
+ + + +
+ #{cotis.statutEcheance} +
+
+ + +
+ + + + + +
+
+
+ + + +
+ +

Félicitations !

+

Vous êtes à jour dans vos cotisations.

+
+
+
+ + +
+
+ + Mes Derniers Paiements +
+ + + + + + + + + + + + + + + + + + +
+
#{paiement.montantFormatte}
+ FCFA +
+
+ + + + + + + + +
+
+ + + + + +
+ +
+
+
+ Cotisation + #{mesCotisationsPaiementBean.cotisationSelectionnee.reference} +
+
+ Type + +
+
+ Montant à payer + + #{mesCotisationsPaiementBean.cotisationSelectionnee.montantDuFormatte} FCFA + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

Vous allez être redirigé vers la plateforme de paiement sécurisée.

+

Aucun frais supplémentaire n'est appliqué sur votre paiement.

+
+
+
+
+ +
+ + + + + + + + + + + +
+
+
+ + + + +
+
+
+ +
+

Si vous avez effectué un paiement par un autre moyen (espèces, virement direct, etc.), vous pouvez le déclarer ici.

+

Votre paiement devra être validé par le trésorier.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + +
+
+
+ + + diff --git a/src/main/resources/META-INF/resources/pages/secure/membre/validation.xhtml b/src/main/resources/META-INF/resources/pages/secure/membre/validation.xhtml new file mode 100644 index 0000000..033b257 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/membre/validation.xhtml @@ -0,0 +1,34 @@ + + + + Validation des Inscriptions - UnionFlow + + + + + + + + + +
+ + +
+ +
+
Inscriptions en attente
+

+ Les demandes d'inscription à valider apparaîtront ici. Utilisez la liste des membres ou le formulaire d'inscription pour gérer les nouveaux membres. +

+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/organisation/liste.xhtml b/src/main/resources/META-INF/resources/pages/secure/organisation/liste.xhtml index 25877ce..8988a53 100644 --- a/src/main/resources/META-INF/resources/pages/secure/organisation/liste.xhtml +++ b/src/main/resources/META-INF/resources/pages/secure/organisation/liste.xhtml @@ -188,8 +188,8 @@
- - + @@ -247,37 +247,40 @@ - - + actionListener="#{organisationsBean.preparerBasculerStatut(org)}" + update=":formOrganisations:dlgConfirmStatut" + oncomplete="PF('dlgConfirmStatut').show();" /> - - + actionListener="#{organisationsBean.preparerSuppression(org)}" + update=":formOrganisations:dlgConfirmSuppr" + oncomplete="PF('dlgConfirmSuppr').show();" />
- - - - + +

Changer le statut de cette organisation ?

+ + + + +
+ +

Supprimer définitivement cette organisation ? Cette action est irréversible.

+ + + + +
diff --git a/src/main/resources/META-INF/resources/pages/super-admin/organisations.xhtml b/src/main/resources/META-INF/resources/pages/super-admin/organisations.xhtml index da4ea0b..9d9e958 100644 --- a/src/main/resources/META-INF/resources/pages/super-admin/organisations.xhtml +++ b/src/main/resources/META-INF/resources/pages/super-admin/organisations.xhtml @@ -124,31 +124,40 @@ - - + actionListener="#{organisationsBean.preparerBasculerStatut(org)}" + update=":formOrgs:dlgConfirmStatut" + oncomplete="PF('dlgConfirmStatut').show();" /> - - + actionListener="#{organisationsBean.preparerSuppression(org)}" + update=":formOrgs:dlgConfirmSuppr" + oncomplete="PF('dlgConfirmSuppr').show();" />
- - - - + +

Confirmez l'action sur le statut de l'organisation ?

+ + + + +
+ +

Supprimer cette organisation ? Cette action est irréversible.

+ + + + +
- - + rendered="#{role.supprimable}" + actionListener="#{rolesBean.preparerSuppression(role)}" + update=":tableauForm:dlgConfirmSupprRole" + oncomplete="PF('dlgConfirmSupprRole').show();" />
+ +

Êtes-vous sûr de vouloir supprimer ce rôle ?

+ + + + +
diff --git a/src/main/resources/META-INF/resources/pages/super-admin/types/organisations.xhtml b/src/main/resources/META-INF/resources/pages/super-admin/types/organisations.xhtml index 748668d..528cb0a 100644 --- a/src/main/resources/META-INF/resources/pages/super-admin/types/organisations.xhtml +++ b/src/main/resources/META-INF/resources/pages/super-admin/types/organisations.xhtml @@ -10,7 +10,7 @@ - +
@@ -67,7 +67,7 @@ severity="#{type.actif ? 'success' : 'danger'}" /> - + - - - +
- - - - + + +

Supprimer définitivement ce type d'organisation ? Cette action est irréversible.

+ + + + +
diff --git a/src/main/resources/META-INF/resources/templates/components/cards/kpi-card.xhtml b/src/main/resources/META-INF/resources/templates/components/cards/kpi-card.xhtml index 0a97208..2da3975 100644 --- a/src/main/resources/META-INF/resources/templates/components/cards/kpi-card.xhtml +++ b/src/main/resources/META-INF/resources/templates/components/cards/kpi-card.xhtml @@ -112,23 +112,23 @@ -
+
+#{growthValue} #{growthLabel}
-
+
#{noDataLabel}
-
+
+#{growthValue}% #{growthLabel}
-
+
#{noDataLabel}
diff --git a/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml b/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml index a276291..225cd01 100644 --- a/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml +++ b/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml @@ -31,7 +31,7 @@ - + @@ -184,7 +184,7 @@ - + diff --git a/src/main/resources/META-INF/resources/templates/components/security/page-access-control.xhtml b/src/main/resources/META-INF/resources/templates/components/security/page-access-control.xhtml new file mode 100644 index 0000000..e01fa52 --- /dev/null +++ b/src/main/resources/META-INF/resources/templates/components/security/page-access-control.xhtml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/resources/ui/includes/membre-form.xhtml b/src/main/resources/META-INF/resources/ui/includes/membre-form.xhtml index 77a14bc..c9c08fe 100644 --- a/src/main/resources/META-INF/resources/ui/includes/membre-form.xhtml +++ b/src/main/resources/META-INF/resources/ui/includes/membre-form.xhtml @@ -133,7 +133,7 @@
- + diff --git a/src/main/resources/META-INF/resources/ui/includes/organisation-form.xhtml b/src/main/resources/META-INF/resources/ui/includes/organisation-form.xhtml index 82ce4ec..5e3914f 100644 --- a/src/main/resources/META-INF/resources/ui/includes/organisation-form.xhtml +++ b/src/main/resources/META-INF/resources/ui/includes/organisation-form.xhtml @@ -487,6 +487,7 @@ + + Page de validation des inscriptions membres + /pages/secure/membre/validation + /pages/secure/membre/validation.xhtml + + + + + + Page des demandes de crédit + /pages/secure/credit/demandes + /pages/secure/credit/demandes.xhtml + + + + Page d'évaluation solvabilité crédit + /pages/secure/credit/evaluation + /pages/secure/credit/evaluation.xhtml + + + + Page de suivi des crédits + /pages/secure/credit/suivi + /pages/secure/credit/suivi.xhtml + + + + Page des remboursements crédit + /pages/secure/credit/remboursements + /pages/secure/credit/remboursements.xhtml + + + + Page des statistiques crédit + /pages/secure/credit/statistiques + /pages/secure/credit/statistiques.xhtml + + + Page de liste des organisations diff --git a/target/classes/META-INF/resources/pages/admin/documents/gestion.xhtml b/target/classes/META-INF/resources/pages/admin/documents/gestion.xhtml index a3b3f12..8cdf288 100644 --- a/target/classes/META-INF/resources/pages/admin/documents/gestion.xhtml +++ b/target/classes/META-INF/resources/pages/admin/documents/gestion.xhtml @@ -520,7 +520,7 @@
- + - -
+ +
@@ -63,8 +63,8 @@
- -
+ +
Répartition par Méthode de Paiement @@ -118,6 +118,7 @@ value="#{cotisationsBean.cotisationsFiltrees}" var="cotisation" filteredValue="#{cotisationsBean.cotisationsFiltrees}" + rowKey="#{cotisation.id}" paginator="true" rows="20" emptyMessage="Aucune cotisation en attente" @@ -137,7 +138,9 @@
- + +
#{cotisation.nomMembre}
#{cotisation.numeroMembre}
diff --git a/target/classes/META-INF/resources/pages/secure/dashboard.xhtml b/target/classes/META-INF/resources/pages/secure/dashboard.xhtml index 28b03b8..d595cb9 100644 --- a/target/classes/META-INF/resources/pages/secure/dashboard.xhtml +++ b/target/classes/META-INF/resources/pages/secure/dashboard.xhtml @@ -7,7 +7,14 @@ template="/templates/main-template.xhtml"> UnionFlow - Tableau de bord - + + + + + + + +
@@ -16,7 +23,7 @@

Tableau de bord UnionFlow

-

Bienvenue #{userSession.currentUser.nom}, voici un aperçu de votre union

+

Bienvenue #{userSession.currentUser != null ? userSession.currentUser.nomComplet : userSession.username}, voici un aperçu de votre union

#{dashboardBean.currentDate} @@ -130,54 +137,68 @@
- - - - - - - - - + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + -
-
+ + +
+
Évolution financière (3 derniers mois)
@@ -239,7 +260,11 @@
- + + + + +
État des cotisations
@@ -303,7 +328,8 @@
- + +
@@ -376,110 +402,142 @@
-
-
Actions rapides
-
-
- - - - - - - -
-
- - - - - - - -
-
- - - - - - - -
-
- - - - - - - + + +
+
Actions rapides
+
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + + +
+
+ + +
+ + + + + + + +
+
-
+ -
-
Tâches prioritaires
-
-
- -
-
Valider #{dashboardBean.adhesionsPendantes} adhésions
- Demandes en attente de validation -
- - - - - -
- -
- -
-
Relancer #{dashboardBean.cotisationsRetard} cotisations
- Paiements en retard -
- - - - - -
- -
- -
-
Traiter #{dashboardBean.aidesEnAttente} aides
- Demandes d'aide à examiner -
- - - - - -
- -
- -
-
Organiser prochains événements
- #{dashboardBean.evenementsAPlanifier} événements à planifier -
- - - - - + + +
+
Tâches prioritaires
+
+ + +
+ +
+
Valider #{dashboardBean.adhesionsPendantes} adhésions
+ Demandes en attente de validation +
+ + + + + +
+
+ + + +
+ +
+
Relancer #{dashboardBean.cotisationsRetard} cotisations
+ Paiements en retard +
+ + + + + +
+
+ + + +
+ +
+
Traiter #{dashboardBean.aidesEnAttente} aides
+ Demandes d'aide à examiner +
+ + + + + +
+
+ + + +
+ +
+
Organiser prochains événements
+ #{dashboardBean.evenementsAPlanifier} événements à planifier +
+ + + + + +
+
-
+
-
-
+ + +
+
Résumé financier mensuel
@@ -527,6 +585,7 @@
+
diff --git a/target/classes/META-INF/resources/pages/secure/membre/cotisations.xhtml b/target/classes/META-INF/resources/pages/secure/membre/cotisations.xhtml index 092270b..b224cd5 100644 --- a/target/classes/META-INF/resources/pages/secure/membre/cotisations.xhtml +++ b/target/classes/META-INF/resources/pages/secure/membre/cotisations.xhtml @@ -7,7 +7,7 @@ template="/templates/main-template.xhtml"> - + @@ -86,7 +86,7 @@
-
Historique des Cotisations
+
Mes Cotisations
diff --git a/target/classes/META-INF/resources/pages/secure/membre/export.xhtml b/target/classes/META-INF/resources/pages/secure/membre/export.xhtml index 1d29dc9..92eba64 100644 --- a/target/classes/META-INF/resources/pages/secure/membre/export.xhtml +++ b/target/classes/META-INF/resources/pages/secure/membre/export.xhtml @@ -143,7 +143,7 @@
- +
- + + + + + + Inscription Membre - UnionFlow @@ -268,7 +273,7 @@
- + + styleClass="ui-button-success mr-2" + rendered="#{menuBean.gestionMembresMenuVisible}" /> + onclick="PF('dlgImportExport').show();" type="button" styleClass="ui-button-info" + rendered="#{menuBean.gestionMembresMenuVisible}" /> - +
@@ -125,10 +129,11 @@ + rendered="#{not empty membreListeBean.selectedMembres and menuBean.gestionMembresMenuVisible}"> @@ -157,8 +162,9 @@ - - + + @@ -225,9 +231,10 @@ - + + styleClass="ui-button-warning ui-button-sm ui-button-rounded mr-1" + rendered="#{menuBean.gestionMembresMenuVisible}"> @@ -237,31 +244,34 @@ oncomplete="PF('dlgContact').show();" update=":dlgContact" styleClass="ui-button-help ui-button-sm ui-button-rounded mr-1" /> - + - - + actionListener="#{membreListeBean.preparerSuspendre(membre)}" + update=":formMembres:dlgConfirmSuspendre" + oncomplete="PF('dlgConfirmSuspendre').show();" + rendered="#{menuBean.gestionMembresMenuVisible and membre.statut == 'ACTIF'}" + styleClass="ui-button-danger ui-button-sm ui-button-rounded mr-1" /> - + - - - - - + +

Suspendre ce membre ?

+ + + + +
- - + @@ -247,37 +247,40 @@ - - + actionListener="#{organisationsBean.preparerBasculerStatut(org)}" + update=":formOrganisations:dlgConfirmStatut" + oncomplete="PF('dlgConfirmStatut').show();" /> - - + actionListener="#{organisationsBean.preparerSuppression(org)}" + update=":formOrganisations:dlgConfirmSuppr" + oncomplete="PF('dlgConfirmSuppr').show();" />
- - - - + +

Changer le statut de cette organisation ?

+ + + + +
+ +

Supprimer définitivement cette organisation ? Cette action est irréversible.

+ + + + +
diff --git a/target/classes/META-INF/resources/pages/super-admin/organisations.xhtml b/target/classes/META-INF/resources/pages/super-admin/organisations.xhtml index da4ea0b..9d9e958 100644 --- a/target/classes/META-INF/resources/pages/super-admin/organisations.xhtml +++ b/target/classes/META-INF/resources/pages/super-admin/organisations.xhtml @@ -124,31 +124,40 @@ - - + actionListener="#{organisationsBean.preparerBasculerStatut(org)}" + update=":formOrgs:dlgConfirmStatut" + oncomplete="PF('dlgConfirmStatut').show();" /> - - + actionListener="#{organisationsBean.preparerSuppression(org)}" + update=":formOrgs:dlgConfirmSuppr" + oncomplete="PF('dlgConfirmSuppr').show();" />
- - - - + +

Confirmez l'action sur le statut de l'organisation ?

+ + + + +
+ +

Supprimer cette organisation ? Cette action est irréversible.

+ + + + +
- - + rendered="#{role.supprimable}" + actionListener="#{rolesBean.preparerSuppression(role)}" + update=":tableauForm:dlgConfirmSupprRole" + oncomplete="PF('dlgConfirmSupprRole').show();" />
+ +

Êtes-vous sûr de vouloir supprimer ce rôle ?

+ + + + +
diff --git a/target/classes/META-INF/resources/pages/super-admin/types/organisations.xhtml b/target/classes/META-INF/resources/pages/super-admin/types/organisations.xhtml index 748668d..528cb0a 100644 --- a/target/classes/META-INF/resources/pages/super-admin/types/organisations.xhtml +++ b/target/classes/META-INF/resources/pages/super-admin/types/organisations.xhtml @@ -10,7 +10,7 @@ - +
@@ -67,7 +67,7 @@ severity="#{type.actif ? 'success' : 'danger'}" /> - + - - - +
- - - - + + +

Supprimer définitivement ce type d'organisation ? Cette action est irréversible.

+ + + + +
diff --git a/target/classes/META-INF/resources/templates/components/cards/kpi-card.xhtml b/target/classes/META-INF/resources/templates/components/cards/kpi-card.xhtml index 0a97208..2da3975 100644 --- a/target/classes/META-INF/resources/templates/components/cards/kpi-card.xhtml +++ b/target/classes/META-INF/resources/templates/components/cards/kpi-card.xhtml @@ -112,23 +112,23 @@ -
+
+#{growthValue} #{growthLabel}
-
+
#{noDataLabel}
-
+
+#{growthValue}% #{growthLabel}
-
+
#{noDataLabel}
diff --git a/target/classes/META-INF/resources/templates/components/layout/menu.xhtml b/target/classes/META-INF/resources/templates/components/layout/menu.xhtml index a276291..225cd01 100644 --- a/target/classes/META-INF/resources/templates/components/layout/menu.xhtml +++ b/target/classes/META-INF/resources/templates/components/layout/menu.xhtml @@ -31,7 +31,7 @@ - + @@ -184,7 +184,7 @@ - + diff --git a/target/classes/META-INF/resources/ui/includes/membre-form.xhtml b/target/classes/META-INF/resources/ui/includes/membre-form.xhtml index 77a14bc..c9c08fe 100644 --- a/target/classes/META-INF/resources/ui/includes/membre-form.xhtml +++ b/target/classes/META-INF/resources/ui/includes/membre-form.xhtml @@ -133,7 +133,7 @@
- + diff --git a/target/classes/META-INF/resources/ui/includes/organisation-form.xhtml b/target/classes/META-INF/resources/ui/includes/organisation-form.xhtml index 82ce4ec..5e3914f 100644 --- a/target/classes/META-INF/resources/ui/includes/organisation-form.xhtml +++ b/target/classes/META-INF/resources/ui/includes/organisation-form.xhtml @@ -487,6 +487,7 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file