Espace réservé à l'administration MELAVAH PRO
voici le code dans l'espace pro, ajoute ceux de l'etape 4 et donne moi le code final. <!-- ========== MELAVAH PRO - TABLEAU DE BORD ADMIN COMPLET ========== -->
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700;800;900&family=Montserrat:wght@700;800;900&display=swap');
:root{
--ad-navy:#082B4D; --ad-blue:#0F3E68; --ad-blue2:#175A8A;
--ad-coral:#E89171; --ad-coral-light:#F5B89E;
--ad-bg:#F0F4F8; --ad-cream:#FAF7F4;
--ad-text:#14263A; --ad-muted:#6B7585;
--ad-green:#10B981; --ad-red:#E74C3C;
--ad-orange:#F59E0B; --ad-purple:#8B5CF6;
--ad-info:#3B82F6;
}
.ad-page,.ad-page *{box-sizing:border-box;}
.ad-page{
font-family:'DM Sans',Arial,sans-serif;
background:var(--ad-bg); color:var(--ad-text);
min-height:100vh; padding:30px 3%;
}
/* LOGIN */
.ad-login-wrap{
max-width:420px; margin:80px auto; background:#fff;
border-radius:24px; padding:40px; text-align:center;
box-shadow:0 25px 70px rgba(8,43,77,.15);
}
.ad-login-wrap h1{font-family:'Montserrat',sans-serif;font-size:24px;color:var(--ad-blue);margin:0 0 8px;}
.ad-login-wrap p{color:var(--ad-muted);font-size:14px;margin:0 0 24px;}
.ad-login-wrap input{width:100%;padding:14px 18px;border:1.5px solid rgba(15,62,104,.15);border-radius:12px;font-size:15px;font-family:'DM Sans',sans-serif;margin-bottom:14px;outline:none;}
.ad-login-wrap input:focus{border-color:var(--ad-coral);}
.ad-login-wrap button{width:100%;padding:14px;border:0;border-radius:50px;background:linear-gradient(135deg,var(--ad-blue),var(--ad-coral));color:#fff;font-weight:900;font-size:15px;cursor:pointer;font-family:'DM Sans',sans-serif;}
/* HEADER */
.ad-header{
max-width:1400px; margin:0 auto 24px; background:#fff;
border-radius:18px; padding:20px 28px; display:flex;
justify-content:space-between; align-items:center;
box-shadow:0 8px 25px rgba(8,43,77,.07); flex-wrap:wrap; gap:14px;
}
.ad-header h1{font-family:'Montserrat',sans-serif;font-size:22px;color:var(--ad-blue);margin:0;font-weight:900;}
.ad-header .badge-admin{display:inline-block;margin-left:10px;padding:4px 12px;background:var(--ad-coral);color:#fff;border-radius:50px;font-size:11px;font-weight:900;letter-spacing:1px;}
.ad-header-actions{display:flex;gap:10px;}
.ad-btn-reload{padding:10px 18px;border:0;border-radius:50px;background:var(--ad-blue);color:#fff;font-weight:800;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;}
.ad-btn-logout{padding:10px 18px;border:1.5px solid var(--ad-red);background:transparent;color:var(--ad-red);border-radius:50px;font-weight:800;font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;}
/* TABS PRINCIPAUX */
.ad-main-tabs{
max-width:1400px; margin:0 auto 20px;
display:flex; gap:8px; background:#fff;
padding:8px; border-radius:14px;
box-shadow:0 6px 20px rgba(8,43,77,.05);
}
.ad-main-tab{
flex:1; padding:14px 18px; border:0; background:transparent;
border-radius:10px; font-family:'DM Sans',sans-serif;
font-weight:900; font-size:13px; cursor:pointer;
color:var(--ad-muted); transition:.3s;
display:flex; align-items:center; justify-content:center; gap:8px;
}
.ad-main-tab.active{
background:linear-gradient(135deg,var(--ad-blue),var(--ad-coral));
color:#fff; box-shadow:0 6px 18px rgba(15,62,104,.2);
}
.ad-tab-badge{
background:var(--ad-red); color:#fff;
padding:2px 8px; border-radius:50px;
font-size:11px; font-weight:900;
min-width:22px; text-align:center;
}
/* STATS */
.ad-stats{
max-width:1400px; margin:0 auto 24px; display:grid;
grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:16px;
}
.ad-stat{
background:#fff; border-radius:16px; padding:22px;
box-shadow:0 8px 25px rgba(8,43,77,.07);
border-left:4px solid var(--ad-blue);
}
.ad-stat.green{border-color:var(--ad-green);}
.ad-stat.orange{border-color:var(--ad-orange);}
.ad-stat.purple{border-color:var(--ad-purple);}
.ad-stat.info{border-color:var(--ad-info);}
.ad-stat.red{border-color:var(--ad-red);}
.ad-stat small{display:block;font-size:11px;font-weight:900;color:var(--ad-muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;}
.ad-stat .value{font-family:'Montserrat',sans-serif;font-size:28px;font-weight:900;color:var(--ad-blue);line-height:1;}
.ad-stat .icon{font-size:24px;margin-bottom:8px;display:block;}
/* FILTRES */
.ad-filters{
max-width:1400px; margin:0 auto 20px; background:#fff;
border-radius:14px; padding:16px; display:flex; gap:10px;
flex-wrap:wrap; align-items:center;
box-shadow:0 6px 20px rgba(8,43,77,.05);
}
.ad-filters input,.ad-filters select{
padding:10px 14px; border:1.5px solid rgba(15,62,104,.12);
border-radius:10px; font-family:'DM Sans',sans-serif;
font-size:13px; outline:none;
}
.ad-filters input{flex:1;min-width:200px;}
/* TABLE PROS */
.ad-pros-wrap{
max-width:1400px; margin:0 auto; background:#fff;
border-radius:16px; overflow:hidden;
box-shadow:0 8px 25px rgba(8,43,77,.07);
}
.ad-pros-list{overflow-x:auto;}
.ad-pro-row{
display:grid; grid-template-columns:60px 1fr 140px 130px 90px 90px 130px 90px;
gap:14px; padding:14px 20px; align-items:center;
border-bottom:1px solid rgba(15,62,104,.06); font-size:13px;
}
.ad-pro-row:nth-child(odd){background:#FAFAFB;}
.ad-pro-row.header{background:var(--ad-blue) !important;color:#fff;font-weight:900;font-size:12px;text-transform:uppercase;letter-spacing:.8px;border-bottom:0;}
.ad-pro-avatar{
width:46px; height:46px; border-radius:50%; overflow:hidden;
background:linear-gradient(135deg,var(--ad-blue),var(--ad-coral));
display:flex; align-items:center; justify-content:center;
color:#fff; font-size:18px;
}
.ad-pro-avatar img{width:100%;height:100%;object-fit:cover;}
.ad-pro-info strong{display:block;font-size:14px;color:var(--ad-text);margin-bottom:3px;font-weight:800;}
.ad-pro-info small{display:block;font-size:11.5px;color:var(--ad-muted);}
.ad-pro-skills{display:flex;flex-wrap:wrap;gap:4px;}
.ad-skill-tag{font-size:10.5px;padding:2px 8px;border-radius:50px;background:rgba(15,62,104,.10);color:var(--ad-blue);font-weight:700;}
.ad-pro-amount{font-family:'Montserrat',sans-serif;font-weight:900;color:var(--ad-green);font-size:14px;}
.ad-pro-actions{display:flex;gap:6px;flex-wrap:wrap;}
.ad-action-btn{padding:6px 10px;border:0;border-radius:8px;font-size:11.5px;font-weight:800;cursor:pointer;font-family:'DM Sans',sans-serif;}
.ad-action-btn.view{background:var(--ad-blue);color:#fff;}
.ad-action-btn.del{background:var(--ad-red);color:#fff;}
.ad-badge-piece{display:inline-block;padding:3px 8px;border-radius:50px;font-size:10px;font-weight:800;}
.ad-badge-piece.yes{background:rgba(16,185,129,.15);color:#0F7B3E;}
.ad-badge-piece.no{background:rgba(231,76,60,.15);color:#C0392B;}
/* ===== SECTION PAIEMENTS - NOUVELLE ===== */
.ad-payments-wrap{
max-width:1400px; margin:0 auto;
}
.ad-payment-card{
background:#fff; border-radius:14px; padding:18px 22px;
margin-bottom:12px; box-shadow:0 6px 18px rgba(8,43,77,.06);
border-left:5px solid var(--ad-info);
display:grid; grid-template-columns:auto 1fr auto; gap:16px;
align-items:center;
}
/* COULEURS SELON STATUT */
.ad-payment-card.declare{
border-left-color:var(--ad-info);
background:linear-gradient(to right, rgba(59,130,246,.08), #fff 30%);
}
.ad-payment-card.confirme{
border-left-color:var(--ad-green);
background:linear-gradient(to right, rgba(16,185,129,.08), #fff 30%);
}
.ad-payment-card.refuse{
border-left-color:var(--ad-red);
background:linear-gradient(to right, rgba(231,76,60,.08), #fff 30%);
opacity:.85;
}
.ad-payment-pro-photo{
width:56px; height:56px; border-radius:50%; overflow:hidden;
background:linear-gradient(135deg,var(--ad-blue),var(--ad-coral));
display:flex; align-items:center; justify-content:center;
color:#fff; font-size:22px;
}
.ad-payment-pro-photo img{width:100%;height:100%;object-fit:cover;}
.ad-payment-info strong{
display:block; font-size:15px; color:var(--ad-text);
margin-bottom:4px; font-weight:800;
}
.ad-payment-info .meta{
display:flex; gap:14px; flex-wrap:wrap; font-size:12.5px;
color:var(--ad-muted); margin-bottom:6px;
}
.ad-payment-info .meta span{display:inline-flex;align-items:center;gap:4px;}
.ad-payment-info .meta strong{display:inline;color:var(--ad-text);font-weight:800;}
.ad-payment-wave{
display:inline-flex; align-items:center; gap:6px;
background:rgba(26,177,229,.12); padding:5px 12px;
border-radius:50px; font-size:12px; font-weight:800;
color:#0E7CA6;
}
.ad-payment-side{
display:flex; flex-direction:column; align-items:flex-end;
gap:10px;
}
.ad-payment-amount-big{
font-family:'Montserrat',sans-serif; font-size:22px;
font-weight:900; color:var(--ad-blue);
}
.ad-payment-status-pill{
display:inline-flex; align-items:center; gap:6px;
padding:6px 14px; border-radius:50px;
font-size:12px; font-weight:900;
}
.ad-payment-status-pill.declare{
background:var(--ad-info); color:#fff;
}
.ad-payment-status-pill.confirme{
background:var(--ad-green); color:#fff;
}
.ad-payment-status-pill.refuse{
background:var(--ad-red); color:#fff;
}
.ad-payment-actions{
display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end;
}
.ad-btn-approve{
padding:8px 16px; border:0; border-radius:50px;
background:var(--ad-green); color:#fff;
font-weight:900; font-size:12.5px; cursor:pointer;
font-family:'DM Sans',sans-serif;
display:inline-flex; align-items:center; gap:5px;
}
.ad-btn-reject{
padding:8px 16px; border:0; border-radius:50px;
background:var(--ad-red); color:#fff;
font-weight:900; font-size:12.5px; cursor:pointer;
font-family:'DM Sans',sans-serif;
display:inline-flex; align-items:center; gap:5px;
}
.ad-btn-contact{
padding:7px 14px; background:#25D366; color:#fff;
border-radius:50px; font-size:11.5px; font-weight:800;
text-decoration:none; display:inline-flex; align-items:center; gap:5px;
}
.ad-payment-extra-info{
width:100%; margin-top:10px; padding-top:10px;
border-top:1px dashed rgba(15,62,104,.10);
font-size:12.5px; color:var(--ad-muted);
}
.ad-motif-refus{
background:rgba(231,76,60,.08); padding:10px 14px;
border-radius:8px; margin-top:8px; font-size:12.5px;
color:#C0392B; border-left:3px solid var(--ad-red);
}
/* SOUS-FILTRES PAIEMENTS */
.ad-pay-subfilters{
display:flex; gap:8px; margin-bottom:16px; flex-wrap:wrap;
}
.ad-pay-subfilter{
padding:8px 16px; border:1.5px solid rgba(15,62,104,.15);
border-radius:50px; background:#fff; cursor:pointer;
font-family:'DM Sans',sans-serif; font-size:12.5px;
font-weight:800; color:var(--ad-muted); transition:.3s;
display:inline-flex; align-items:center; gap:6px;
}
.ad-pay-subfilter.active{
border-color:transparent; color:#fff;
}
.ad-pay-subfilter[data-filter="all"].active{background:var(--ad-blue);}
.ad-pay-subfilter[data-filter="declare"].active{background:var(--ad-info);}
.ad-pay-subfilter[data-filter="confirme"].active{background:var(--ad-green);}
.ad-pay-subfilter[data-filter="refuse"].active{background:var(--ad-red);}
.ad-pay-subfilter .count{
background:rgba(0,0,0,.15); padding:2px 8px;
border-radius:50px; font-size:11px;
}
.ad-pay-subfilter.active .count{background:rgba(255,255,255,.25);}
/* MODAL */
.ad-modal{
position:fixed; inset:0; background:rgba(8,43,77,.65);
z-index:9999; display:none; align-items:center; justify-content:center;
padding:20px; overflow-y:auto;
}
.ad-modal.show{display:flex;}
.ad-modal-content{
background:#fff; border-radius:20px; max-width:550px; width:100%;
max-height:90vh; overflow-y:auto;
}
.ad-modal-content.large{max-width:850px;}
.ad-modal-head{
position:sticky; top:0; background:linear-gradient(135deg,var(--ad-blue),var(--ad-coral));
color:#fff; padding:20px 28px; display:flex;
justify-content:space-between; align-items:center;
border-radius:20px 20px 0 0;
}
.ad-modal-head h2{font-family:'Montserrat',sans-serif;margin:0;font-size:20px;}
.ad-modal-close{background:rgba(255,255,255,.2);border:0;color:#fff;width:36px;height:36px;border-radius:50%;cursor:pointer;font-size:20px;font-weight:900;}
.ad-modal-body{padding:28px;}
.ad-reject-form label{
display:block; font-size:13px; font-weight:800;
color:var(--ad-blue); margin-bottom:8px;
}
.ad-reject-form textarea{
width:100%; padding:14px; border:1.5px solid rgba(15,62,104,.15);
border-radius:12px; font-family:'DM Sans',sans-serif;
font-size:14px; min-height:100px; resize:vertical;
outline:none; margin-bottom:14px;
}
.ad-reject-form .preset-motifs{
display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px;
}
.ad-reject-form .preset{
padding:7px 14px; border:1.5px solid var(--ad-red);
background:transparent; color:var(--ad-red);
border-radius:50px; cursor:pointer; font-size:12px;
font-weight:800; font-family:'DM Sans',sans-serif;
}
.ad-reject-form .preset:hover{background:var(--ad-red);color:#fff;}
.ad-modal-actions{
display:flex; gap:10px; justify-content:flex-end; margin-top:18px;
}
.ad-modal-actions button{
padding:12px 22px; border:0; border-radius:50px;
font-weight:900; cursor:pointer; font-family:'DM Sans',sans-serif;
font-size:13.5px;
}
.ad-modal-actions .cancel{
background:#E5E7EB; color:var(--ad-text);
}
.ad-modal-actions .confirm-reject{
background:var(--ad-red); color:#fff;
}
/* MODAL DÉTAILS */
.ad-detail-section{margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid rgba(15,62,104,.08);}
.ad-detail-section:last-child{border-bottom:0;}
.ad-detail-section h3{font-family:'Montserrat',sans-serif;color:var(--ad-blue);font-size:15px;margin:0 0 14px;padding:8px 14px;background:rgba(15,62,104,.08);border-radius:8px;font-weight:900;text-transform:uppercase;letter-spacing:.8px;}
.ad-detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;}
.ad-detail-item{background:#FAFAFB;padding:12px 16px;border-radius:10px;border-left:3px solid var(--ad-coral);}
.ad-detail-item small{display:block;font-size:11px;font-weight:900;color:var(--ad-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;}
.ad-detail-item strong{font-size:14px;color:var(--ad-text);font-weight:700;}
.ad-photo-zone{text-align:center;margin-bottom:14px;}
.ad-photo-zone img{max-width:160px;max-height:160px;border-radius:50%;border:4px solid var(--ad-coral);object-fit:cover;box-shadow:0 10px 30px rgba(8,43,77,.20);}
.ad-piece-images{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-top:10px;}
.ad-piece-img{border:2px solid rgba(15,62,104,.10);border-radius:10px;overflow:hidden;cursor:pointer;transition:.3s;}
.ad-piece-img:hover{border-color:var(--ad-coral);transform:translateY(-3px);}
.ad-piece-img img{width:100%;height:140px;object-fit:cover;display:block;}
.ad-piece-img small{display:block;text-align:center;padding:8px;font-size:12px;font-weight:800;color:var(--ad-muted);background:#FAFAFB;}
.ad-piece-empty{padding:20px;text-align:center;background:#FAFAFB;border-radius:10px;color:var(--ad-muted);font-size:13px;}
.ad-empty{padding:50px 20px;text-align:center;color:var(--ad-muted);font-size:14px;}
.ad-hidden{display:none !important;}
@media(max-width:900px){
.ad-payment-card{grid-template-columns:1fr;}
.ad-payment-side{align-items:flex-start;}
.ad-pro-row{grid-template-columns:50px 1fr;gap:10px;}
.ad-pro-row > div:not(:nth-child(-n+2)){display:none;}
.ad-pro-row.header{display:none;}
.ad-pro-row{background:#fff !important;border-radius:12px;margin:8px 12px;border:1px solid rgba(15,62,104,.10);}
}
</style>
<div class="ad-page">
<!-- LOGIN -->
<div id="adLogin" class="ad-login-wrap">
<h1>🔐 Tableau de bord</h1>
<p>Espace réservé à l'administration MELAVAH PRO</p>
<input type="password" id="adminPassword" placeholder="Mot de passe administrateur" autocomplete="off">
<button onclick="window.adLoginAdmin()">Accéder au tableau de bord</button>
<div id="adLoginMsg" style="margin-top:14px;color:var(--ad-red);font-size:13px;font-weight:700;"></div>
</div>
<!-- DASHBOARD -->
<div id="adDashboard" class="ad-hidden">
<div class="ad-header">
<div>
<h1>📊 MELAVAH PRO <span class="badge-admin">ADMIN</span></h1>
</div>
<div class="ad-header-actions">
<button class="ad-btn-reload" onclick="window.adReload()">🔄 Actualiser</button>
<button class="ad-btn-logout" onclick="window.adLogout()">🚪 Déconnexion</button>
</div>
</div>
<!-- ONGLETS PRINCIPAUX -->
<div class="ad-main-tabs">
<button class="ad-main-tab active" data-tab="pros" onclick="window.adSwitchTab('pros')">
👥 Professionnels
</button>
<button class="ad-main-tab" data-tab="payments" onclick="window.adSwitchTab('payments')">
💳 Paiements <span class="ad-tab-badge" id="pendingBadge" style="display:none;">0</span>
</button>
</div>
<!-- ===== ONGLET PROS ===== -->
<div id="tabPros">
<div class="ad-stats">
<div class="ad-stat">
<span class="icon">👥</span>
<small>Total Pros inscrits</small>
<div class="value" id="statPros">0</div>
</div>
<div class="ad-stat green">
<span class="icon">💰</span>
<small>Revenus confirmés</small>
<div class="value" id="statRevenus">0 FCFA</div>
</div>
<div class="ad-stat orange">
<span class="icon">📜</span>
<small>Total paiements</small>
<div class="value" id="statPaiements">0</div>
</div>
<div class="ad-stat purple">
<span class="icon">🪪</span>
<small>Pièces fournies</small>
<div class="value" id="statPieces">0</div>
</div>
</div>
<div class="ad-filters">
<input type="text" id="adSearch" placeholder="🔍 Rechercher (nom, email, ville, métier...)">
<select id="adFilterPiece">
<option value="all">Tous les pros</option>
<option value="with">Avec pièce d'identité</option>
<option value="without">Sans pièce d'identité</option>
</select>
<select id="adFilterPay">
<option value="all">Tous</option>
<option value="paid">Ont payé</option>
<option value="unpaid">N'ont pas payé</option>
</select>
<select id="adSort">
<option value="recent">📅 Plus récents</option>
<option value="amount">💰 Plus gros payeurs</option>
<option value="name">🔤 Ordre alphabétique</option>
</select>
</div>
<div class="ad-pros-wrap">
<div class="ad-pros-list">
<div class="ad-pro-row header">
<div></div>
<div>Professionnel</div>
<div>Téléphone</div>
<div>Ville</div>
<div>Total payé</div>
<div>Paiements</div>
<div>Pièce</div>
<div>Actions</div>
</div>
<div id="adProsList">
<div class="ad-empty">Chargement...</div>
</div>
</div>
</div>
</div>
<!-- ===== ONGLET PAIEMENTS ===== -->
<div id="tabPayments" class="ad-hidden">
<div class="ad-stats">
<div class="ad-stat info">
<span class="icon">⏳</span>
<small>En attente</small>
<div class="value" id="statPayDeclare" style="color:var(--ad-info);">0</div>
</div>
<div class="ad-stat green">
<span class="icon">✅</span>
<small>Confirmés</small>
<div class="value" id="statPayConfirme" style="color:var(--ad-green);">0</div>
</div>
<div class="ad-stat red">
<span class="icon">❌</span>
<small>Refusés</small>
<div class="value" id="statPayRefuse" style="color:var(--ad-red);">0</div>
</div>
<div class="ad-stat orange">
<span class="icon">💵</span>
<small>Total à valider</small>
<div class="value" id="statPayMontant" style="color:var(--ad-orange);">0 FCFA</div>
</div>
</div>
<!-- SOUS-FILTRES -->
<div class="ad-pay-subfilters">
<button class="ad-pay-subfilter active" data-filter="all" onclick="window.adFilterPayments('all')">
🔍 Tous <span class="count" id="cntAll">0</span>
</button>
<button class="ad-pay-subfilter" data-filter="declare" onclick="window.adFilterPayments('declare')">
⏳ En attente <span class="count" id="cntDeclare">0</span>
</button>
<button class="ad-pay-subfilter" data-filter="confirme" onclick="window.adFilterPayments('confirme')">
✅ Confirmés <span class="count" id="cntConfirme">0</span>
</button>
<button class="ad-pay-subfilter" data-filter="refuse" onclick="window.adFilterPayments('refuse')">
❌ Refusés <span class="count" id="cntRefuse">0</span>
</button>
</div>
<div class="ad-filters">
<input type="text" id="adSearchPay" placeholder="🔍 Rechercher (nom du pro, numéro Wave...)">
</div>
<div class="ad-payments-wrap" id="adPaymentsList">
<div class="ad-empty">Chargement...</div>
</div>
</div>
</div>
<!-- MODAL DÉTAILS PRO -->
<div class="ad-modal" id="adModal">
<div class="ad-modal-content large">
<div class="ad-modal-head">
<h2 id="modalTitle">Détails</h2>
<button class="ad-modal-close" onclick="window.adCloseModal()">×</button>
</div>
<div class="ad-modal-body" id="modalBody"></div>
</div>
</div>
<!-- MODAL REFUS PAIEMENT -->
<div class="ad-modal" id="adRejectModal">
<div class="ad-modal-content">
<div class="ad-modal-head" style="background:linear-gradient(135deg,var(--ad-red),#FF6B6B);">
<h2>❌ Refuser ce paiement</h2>
<button class="ad-modal-close" onclick="window.adCloseRejectModal()">×</button>
</div>
<div class="ad-modal-body">
<div class="ad-reject-form">
<p style="color:var(--ad-muted);font-size:14px;line-height:1.6;margin-bottom:18px;">
Indiquez la raison du refus. Le pro sera informé et le montant sera déduit de son total payé.
</p>
<label>Motifs courants (cliquez pour utiliser)</label>
<div class="preset-motifs">
<button type="button" class="preset" onclick="window.adSetMotif('Paiement non reçu sur Wave')">Paiement non reçu</button>
<button type="button" class="preset" onclick="window.adSetMotif('Montant insuffisant')">Montant insuffisant</button>
<button type="button" class="preset" onclick="window.adSetMotif('Numéro Wave incorrect')">N° Wave incorrect</button>
<button type="button" class="preset" onclick="window.adSetMotif('Doublon de paiement')">Doublon</button>
</div>
<label>Motif détaillé <span style="color:var(--ad-red);">*</span></label>
<textarea id="rejectMotif" placeholder="Expliquez la raison du refus..."></textarea>
<div class="ad-modal-actions">
<button type="button" class="cancel" onclick="window.adCloseRejectModal()">Annuler</button>
<button type="button" class="confirm-reject" onclick="window.adConfirmReject()">❌ Confirmer le refus</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
(function(){
const API_URL = 'https://script.google.com/macros/s/AKfycbweg2H5GzLWwYwKh3d7lp8qMFPmb2jeZ1O4rpNMs9rKNm8_zo7vIITUYQ7-qElNJkk/exec';
const ADMIN_STORAGE = 'mpro_admin_session';
let allPros = [];
let allPayments = [];
let adminKey = '';
let currentTab = 'pros';
let currentPayFilter = 'all';
let pendingReject = null;
// ===== LOGIN =====
window.adLoginAdmin = async function(){
const pwd = document.getElementById('adminPassword').value.trim();
if(!pwd){ document.getElementById('adLoginMsg').textContent = 'Mot de passe requis'; return; }
document.getElementById('adLoginMsg').textContent = '⏳ Vérification...';
try{
const formData = new FormData();
formData.append('action', 'adminGetAll');
formData.append('adminKey', pwd);
const response = await fetch(API_URL, {method:'POST', body:formData});
const result = await response.json();
if(result.success){
adminKey = pwd;
sessionStorage.setItem(ADMIN_STORAGE, pwd);
document.getElementById('adLogin').classList.add('ad-hidden');
document.getElementById('adDashboard').classList.remove('ad-hidden');
renderDashboard(result);
loadPayments();
} else {
document.getElementById('adLoginMsg').textContent = '❌ Mot de passe incorrect';
}
} catch(err){
document.getElementById('adLoginMsg').textContent = '❌ Erreur de connexion';
}
};
window.adLogout = function(){
sessionStorage.removeItem(ADMIN_STORAGE);
location.reload();
};
window.adReload = async function(){
if(!adminKey) return;
await loadPros();
await loadPayments();
};
async function loadPros(){
document.getElementById('adProsList').innerHTML = '<div class="ad-empty">⏳ Chargement...</div>';
try{
const formData = new FormData();
formData.append('action', 'adminGetAll');
formData.append('adminKey', adminKey);
const response = await fetch(API_URL, {method:'POST', body:formData});
const result = await response.json();
if(result.success) renderDashboard(result);
} catch(err){
document.getElementById('adProsList').innerHTML = '<div class="ad-empty">❌ Erreur</div>';
}
}
async function loadPayments(){
try{
const formData = new FormData();
formData.append('action', 'adminGetAllPayments');
formData.append('adminKey', adminKey);
const response = await fetch(API_URL, {method:'POST', body:formData});
const result = await response.json();
if(result.success){
allPayments = result.payments || [];
renderPayments();
updatePendingBadge();
}
} catch(err){
document.getElementById('adPaymentsList').innerHTML = '<div class="ad-empty">❌ Erreur</div>';
}
}
function renderDashboard(data){
allPros = data.pros || [];
const stats = data.stats || {};
document.getElementById('statPros').textContent = stats.totalPros || 0;
document.getElementById('statRevenus').textContent = (stats.totalRevenus || 0).toLocaleString('fr-FR') + ' FCFA';
document.getElementById('statPaiements').textContent = stats.totalPaiements || 0;
document.getElementById('statPieces').textContent = stats.prosAvecPiece || 0;
applyFilters();
}
function updatePendingBadge(){
const pending = allPayments.filter(p => (p.statut || 'Déclaré') === 'Déclaré').length;
const badge = document.getElementById('pendingBadge');
if(pending > 0){
badge.textContent = pending;
badge.style.display = 'inline-block';
} else {
badge.style.display = 'none';
}
}
// ===== ONGLETS PRINCIPAUX =====
window.adSwitchTab = function(tab){
currentTab = tab;
document.querySelectorAll('.ad-main-tab').forEach(t =>
t.classList.toggle('active', t.getAttribute('data-tab') === tab));
document.getElementById('tabPros').classList.toggle('ad-hidden', tab !== 'pros');
document.getElementById('tabPayments').classList.toggle('ad-hidden', tab !== 'payments');
};
// ===== FILTRES PROS =====
function applyFilters(){
const search = document.getElementById('adSearch').value.toLowerCase().trim();
const filterPiece = document.getElementById('adFilterPiece').value;
const filterPay = document.getElementById('adFilterPay').value;
const sortBy = document.getElementById('adSort').value;
let filtered = [...allPros];
if(search){
filtered = filtered.filter(p => {
const txt = (p.nom + ' ' + p.email + ' ' + p.ville + ' ' + p.competences + ' ' + p.zones).toLowerCase();
return txt.includes(search);
});
}
if(filterPiece === 'with') filtered = filtered.filter(p => p.pieceRecto || p.pieceVerso);
if(filterPiece === 'without') filtered = filtered.filter(p => !p.pieceRecto && !p.pieceVerso);
if(filterPay === 'paid') filtered = filtered.filter(p => p.totalPaye > 0);
if(filterPay === 'unpaid') filtered = filtered.filter(p => p.totalPaye === 0);
if(sortBy === 'amount') filtered.sort((a,b) => b.totalPaye - a.totalPaye);
else if(sortBy === 'name') filtered.sort((a,b) => a.nom.localeCompare(b.nom));
else filtered.sort((a,b) => parseInt(b.id.replace('MP','')) - parseInt(a.id.replace('MP','')));
renderProsList(filtered);
}
function renderProsList(pros){
const list = document.getElementById('adProsList');
if(pros.length === 0){
list.innerHTML = '<div class="ad-empty">Aucun pro trouvé.</div>';
return;
}
list.innerHTML = pros.map((p, idx) => {
const skills = (p.competences || '').split(',').slice(0, 3).map(s =>
`<span class="ad-skill-tag">${escapeHtml(s.trim())}</span>`).join('');
const photoHTML = p.photo ? `<img src="${p.photo}" alt="">` : '👤';
const hasPiece = (p.pieceRecto || p.pieceVerso);
const piecesCount = (p.pieceRecto ? 1 : 0) + (p.pieceVerso ? 1 : 0) + (p.piecePagesSup ? p.piecePagesSup.length : 0);
const paysFlag = getFlag(p.pays);
return `
<div class="ad-pro-row">
<div class="ad-pro-avatar">${photoHTML}</div>
<div class="ad-pro-info">
<strong>${escapeHtml(p.nom)}</strong>
<small>${escapeHtml(p.email)}</small>
<div class="ad-pro-skills" style="margin-top:6px;">${skills}</div>
</div>
<div><strong>${paysFlag} ${p.indicatif || ''} ${escapeHtml(p.numero)}</strong></div>
<div>${escapeHtml(p.ville || '—')}</div>
<div class="ad-pro-amount">${p.totalPaye.toLocaleString('fr-FR')} FCFA</div>
<div>${p.historique ? p.historique.length : 0} pmt(s)</div>
<div>
${hasPiece ? `<span class="ad-badge-piece yes">✓ ${piecesCount} doc</span>` : `<span class="ad-badge-piece no">✗ Aucune</span>`}
</div>
<div class="ad-pro-actions">
<button class="ad-action-btn view" onclick="window.adShowDetails(${idx})">👁️ Voir</button>
<button class="ad-action-btn del" onclick="window.adDelete(${p.rowNum}, '${escapeJs(p.nom)}')">🗑️</button>
</div>
</div>
`;
}).join('');
window._adFiltered = pros;
}
// ===== RENDER PAIEMENTS =====
function renderPayments(){
// Stats par statut
const declare = allPayments.filter(p => (p.statut || 'Déclaré') === 'Déclaré');
const confirme = allPayments.filter(p => p.statut === 'Confirmé');
const refuse = allPayments.filter(p => p.statut === 'Refusé');
document.getElementById('statPayDeclare').textContent = declare.length;
document.getElementById('statPayConfirme').textContent = confirme.length;
document.getElementById('statPayRefuse').textContent = refuse.length;
document.getElementById('statPayMontant').textContent =
declare.reduce((s,p) => s + p.montant, 0).toLocaleString('fr-FR') + ' FCFA';
document.getElementById('cntAll').textContent = allPayments.length;
document.getElementById('cntDeclare').textContent = declare.length;
document.getElementById('cntConfirme').textContent = confirme.length;
document.getElementById('cntRefuse').textContent = refuse.length;
applyPaymentFilters();
}
window.adFilterPayments = function(filter){
currentPayFilter = filter;
document.querySelectorAll('.ad-pay-subfilter').forEach(b =>
b.classList.toggle('active', b.getAttribute('data-filter') === filter));
applyPaymentFilters();
};
function applyPaymentFilters(){
const search = document.getElementById('adSearchPay').value.toLowerCase().trim();
let filtered = [...allPayments];
if(currentPayFilter === 'declare') filtered = filtered.filter(p => (p.statut || 'Déclaré') === 'Déclaré');
else if(currentPayFilter === 'confirme') filtered = filtered.filter(p => p.statut === 'Confirmé');
else if(currentPayFilter === 'refuse') filtered = filtered.filter(p => p.statut === 'Refusé');
if(search){
filtered = filtered.filter(p => {
const txt = (p.proNom + ' ' + p.proEmail + ' ' + p.numeroWave).toLowerCase();
return txt.includes(search);
});
}
renderPaymentsList(filtered);
}
function renderPaymentsList(payments){
const list = document.getElementById('adPaymentsList');
if(payments.length === 0){
list.innerHTML = '<div class="ad-empty">Aucun paiement trouvé.</div>';
return;
}
list.innerHTML = payments.map(p => {
const statut = (p.statut || 'Déclaré').toLowerCase();
const cardClass = statut === 'confirmé' ? 'confirme' : statut === 'refusé' ? 'refuse' : 'declare';
const pillClass = cardClass;
const photoHTML = p.proPhoto ? `<img src="${p.proPhoto}" alt="">` : '👤';
const paysFlag = getFlag(p.proPays);
const waPhone = (p.proIndicatif + p.proNumero).replace(/\D/g, '').replace(/^00/, '');
let statutLabel = '⏳ Déclaré (en attente)';
let statutIcon = '⏳';
if(statut === 'confirmé'){ statutLabel = '✅ Confirmé'; statutIcon = '✅'; }
else if(statut === 'refusé'){ statutLabel = '❌ Refusé'; statutIcon = '❌'; }
let actionsHTML = '';
if(statut === 'déclaré'){
actionsHTML = `
<button class="ad-btn-approve" onclick="window.adApprovePayment(${p.rowNum}, ${p.paymentIndex})">
✅ Approuver
</button>
<button class="ad-btn-reject" onclick="window.adOpenRejectModal(${p.rowNum}, ${p.paymentIndex}, '${escapeJs(p.proNom)}', ${p.montant})">
❌ Refuser
</button>
`;
}
let extraInfo = '';
if(statut === 'confirmé' && p.dateValidation){
extraInfo = `<div class="ad-payment-extra-info">✅ Validé le ${escapeHtml(p.dateValidation)}</div>`;
} else if(statut === 'refusé'){
extraInfo = `<div class="ad-payment-extra-info">❌ Refusé le ${escapeHtml(p.dateRefus || '—')}</div>`;
if(p.motifRefus){
extraInfo += `<div class="ad-motif-refus"><strong>Motif :</strong> ${escapeHtml(p.motifRefus)}</div>`;
}
}
return `
<div class="ad-payment-card ${cardClass}">
<div class="ad-payment-pro-photo">${photoHTML}</div>
<div class="ad-payment-info">
<strong>${escapeHtml(p.proNom)}</strong>
<div class="meta">
<span>📧 ${escapeHtml(p.proEmail)}</span>
<span>${paysFlag} ${p.proIndicatif} ${escapeHtml(p.proNumero)}</span>
<span>📅 ${escapeHtml(p.date)}</span>
</div>
<span class="ad-payment-wave">🌊 N° Wave : <strong>${escapeHtml(p.numeroWave || 'Non renseigné')}</strong></span>
${extraInfo}
</div>
<div class="ad-payment-side">
<div class="ad-payment-amount-big">${p.montant.toLocaleString('fr-FR')} FCFA</div>
<span class="ad-payment-status-pill ${pillClass}">${statutLabel}</span>
<div class="ad-payment-actions">
${actionsHTML}
<a href="https://wa.me/${waPhone}?text=Bonjour%20${encodeURIComponent(p.proNom)},%20concernant%20votre%20paiement%20du%20${encodeURIComponent(p.date)}" target="_blank" class="ad-btn-contact">💬 WhatsApp</a>
</div>
</div>
</div>
`;
}).join('');
}
// ===== ACTIONS PAIEMENTS =====
window.adApprovePayment = async function(rowNum, paymentIndex){
if(!confirm('✅ Confirmer ce paiement comme reçu ?')) return;
try{
const formData = new FormData();
formData.append('action', 'adminValidatePayment');
formData.append('adminKey', adminKey);
formData.append('rowNum', rowNum);
formData.append('paymentIndex', paymentIndex);
const response = await fetch(API_URL, {method:'POST', body:formData});
const result = await response.json();
if(result.success){
await loadPayments();
await loadPros();
} else {
alert('❌ ' + result.message);
}
} catch(err){
alert('❌ Erreur');
}
};
window.adOpenRejectModal = function(rowNum, paymentIndex, proNom, montant){
pendingReject = {rowNum, paymentIndex, proNom, montant};
document.getElementById('rejectMotif').value = '';
document.getElementById('adRejectModal').classList.add('show');
};
window.adCloseRejectModal = function(){
document.getElementById('adRejectModal').classList.remove('show');
pendingReject = null;
};
window.adSetMotif = function(motif){
document.getElementById('rejectMotif').value = motif;
};
window.adConfirmReject = async function(){
if(!pendingReject) return;
const motif = document.getElementById('rejectMotif').value.trim();
if(!motif){
alert('⚠️ Veuillez saisir un motif');
return;
}
if(!confirm(`Refuser le paiement de ${pendingReject.montant.toLocaleString('fr-FR')} FCFA de ${pendingReject.proNom} ?`)) return;
try{
const formData = new FormData();
formData.append('action', 'adminRejectPayment');
formData.append('adminKey', adminKey);
formData.append('rowNum', pendingReject.rowNum);
formData.append('paymentIndex', pendingReject.paymentIndex);
formData.append('motif', motif);
const response = await fetch(API_URL, {method:'POST', body:formData});
const result = await response.json();
if(result.success){
window.adCloseRejectModal();
await loadPayments();
await loadPros();
} else {
alert('❌ ' + result.message);
}
} catch(err){
alert('❌ Erreur');
}
};
// ===== DÉTAILS PRO =====
window.adShowDetails = function(idx){
const p = window._adFiltered[idx];
if(!p) return;
const skills = (p.competences || '').split(',').map(s =>
`<span class="ad-skill-tag">${escapeHtml(s.trim())}</span>`).join(' ');
const photoHTML = p.photo
? `<img src="${p.photo}" alt="profil">`
: '<div style="width:160px;height:160px;border-radius:50%;background:linear-gradient(135deg,var(--ad-blue),var(--ad-coral));margin:0 auto;display:flex;align-items:center;justify-content:center;color:#fff;font-size:60px;">👤</div>';
let piecesHTML = '';
const hasPiece = p.pieceRecto || p.pieceVerso || (p.piecePagesSup && p.piecePagesSup.length);
if(hasPiece){
piecesHTML = '<div class="ad-piece-images">';
if(p.pieceRecto) piecesHTML += `<div class="ad-piece-img" onclick="window.open('${p.pieceRecto}','_blank')"><img src="${p.pieceRecto}"><small>📄 Recto</small></div>`;
if(p.pieceVerso) piecesHTML += `<div class="ad-piece-img" onclick="window.open('${p.pieceVerso}','_blank')"><img src="${p.pieceVerso}"><small>📄 Verso</small></div>`;
if(p.piecePagesSup) p.piecePagesSup.forEach((url, i) => {
piecesHTML += `<div class="ad-piece-img" onclick="window.open('${url}','_blank')"><img src="${url}"><small>📑 Page ${i+1}</small></div>`;
});
piecesHTML += '</div>';
} else {
piecesHTML = '<div class="ad-piece-empty">Aucune pièce d\'identité fournie</div>';
}
document.getElementById('modalTitle').textContent = '👤 ' + p.nom;
document.getElementById('modalBody').innerHTML = `
<div class="ad-detail-section">
<div class="ad-photo-zone">${photoHTML}</div>
<h3>📋 Informations</h3>
<div class="ad-detail-grid">
<div class="ad-detail-item"><small>Email</small><strong>${escapeHtml(p.email)}</strong></div>
<div class="ad-detail-item"><small>Téléphone</small><strong>${getFlag(p.pays)} ${escapeHtml(p.indicatif || '')} ${escapeHtml(p.numero)}</strong></div>
<div class="ad-detail-item"><small>Pays</small><strong>${escapeHtml(p.pays || '—')}</strong></div>
<div class="ad-detail-item"><small>Ville</small><strong>${escapeHtml(p.ville)}</strong></div>
<div class="ad-detail-item"><small>Zones</small><strong>${escapeHtml(p.zones)}</strong></div>
<div class="ad-detail-item"><small>Code</small><strong style="color:var(--ad-coral);letter-spacing:3px;">${escapeHtml(p.code)}</strong></div>
</div>
</div>
<div class="ad-detail-section">
<h3>🛠️ Compétences</h3>
<div style="line-height:2;">${skills}</div>
</div>
<div class="ad-detail-section">
<h3>🪪 Pièces d'identité</h3>
${piecesHTML}
</div>
`;
document.getElementById('adModal').classList.add('show');
};
window.adCloseModal = function(){
document.getElementById('adModal').classList.remove('show');
};
window.adDelete = async function(rowNum, nom){
if(!confirm(`⚠️ Supprimer "${nom}" ?\n\nCette action est irréversible.`)) return;
try{
const formData = new FormData();
formData.append('action', 'adminDelete');
formData.append('adminKey', adminKey);
formData.append('rowNum', rowNum);
const response = await fetch(API_URL, {method:'POST', body:formData});
const result = await response.json();
if(result.success){
await loadPros();
await loadPayments();
} else {
alert('❌ ' + result.message);
}
} catch(err){
alert('❌ Erreur');
}
};
// ===== UTILS =====
function escapeHtml(s){
return String(s || '').replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[m]);
}
function escapeJs(s){
return String(s || '').replace(/'/g, "\\'");
}
function getFlag(country){
const flags = {
'Côte d\'Ivoire':'🇨🇮','Sénégal':'🇸🇳','Mali':'🇲🇱','Burkina Faso':'🇧🇫',
'Bénin':'🇧🇯','Togo':'🇹🇬','Niger':'🇳🇪','Guinée':'🇬🇳','Mauritanie':'🇲🇷',
'Ghana':'🇬🇭','Nigeria':'🇳🇬','Cameroun':'🇨🇲','Gabon':'🇬🇦','Congo':'🇨🇬',
'RD Congo':'🇨🇩','Tchad':'🇹🇩','Maroc':'🇲🇦','Algérie':'🇩🇿','Tunisie':'🇹🇳',
'Kenya':'🇰🇪','Rwanda':'🇷🇼','Afrique du Sud':'🇿🇦','Angola':'🇦🇴',
'Madagascar':'🇲🇬','France':'🇫🇷','Belgique':'🇧🇪','Canada':'🇨🇦','États-Unis':'🇺🇸'
};
return flags[country] || '🌍';
}
// ===== EVENTS =====
['adSearch','adFilterPiece','adFilterPay','adSort'].forEach(id => {
document.getElementById(id).addEventListener(id === 'adSearch' ? 'input' : 'change', applyFilters);
});
document.getElementById('adSearchPay').addEventListener('input', applyPaymentFilters);
document.getElementById('adminPassword').addEventListener('keypress', e => {
if(e.key === 'Enter') window.adLoginAdmin();
});
document.getElementById('adModal').addEventListener('click', e => {
if(e.target.id === 'adModal') window.adCloseModal();
});
document.getElementById('adRejectModal').addEventListener('click', e => {
if(e.target.id === 'adRejectModal') window.adCloseRejectModal();
});
// Auto-login
(async function(){
const savedPwd = sessionStorage.getItem(ADMIN_STORAGE);
if(savedPwd){
document.getElementById('adminPassword').value = savedPwd;
window.adLoginAdmin();
}
})();
})();
</script>