1340 lines
50 KiB
HTML
1340 lines
50 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dashboard Admin TPE - Wortis</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
:root {
|
|
--primary: #2563eb;
|
|
--primary-dark: #1e40af;
|
|
--success: #10b981;
|
|
--warning: #f59e0b;
|
|
--danger: #ef4444;
|
|
--dark: #1e293b;
|
|
--light: #f8fafc;
|
|
--border: #e2e8f0;
|
|
--text: #334155;
|
|
--text-light: #64748b;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: var(--light);
|
|
color: var(--text);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* Sidebar */
|
|
.sidebar {
|
|
width: 260px;
|
|
background: var(--dark);
|
|
color: white;
|
|
padding: 20px 0;
|
|
position: fixed;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.sidebar.hidden {
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
.logo {
|
|
padding: 0 20px 20px;
|
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.logo h2 {
|
|
font-size: 24px;
|
|
color: white;
|
|
}
|
|
|
|
.logo p {
|
|
font-size: 12px;
|
|
color: rgba(255,255,255,0.6);
|
|
}
|
|
|
|
.nav-menu {
|
|
list-style: none;
|
|
}
|
|
|
|
.nav-item {
|
|
padding: 12px 20px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: rgba(255,255,255,0.1);
|
|
border-left-color: var(--primary);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: rgba(37,99,235,0.2);
|
|
border-left-color: var(--primary);
|
|
}
|
|
|
|
.nav-item span {
|
|
font-size: 20px;
|
|
}
|
|
|
|
/* Main Content */
|
|
.main-content {
|
|
flex: 1;
|
|
margin-left: 260px;
|
|
padding: 20px;
|
|
transition: margin-left 0.3s;
|
|
}
|
|
|
|
.main-content.expanded {
|
|
margin-left: 0;
|
|
}
|
|
|
|
/* Header */
|
|
.header {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 28px;
|
|
color: var(--dark);
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
transition: all 0.3s;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--primary-dark);
|
|
}
|
|
|
|
.btn-success {
|
|
background: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: var(--danger);
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--border);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* Stats Cards */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.stat-info h3 {
|
|
font-size: 14px;
|
|
color: var(--text-light);
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.stat-info p {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: var(--dark);
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 30px;
|
|
}
|
|
|
|
.stat-icon.blue { background: rgba(37,99,235,0.1); color: var(--primary); }
|
|
.stat-icon.green { background: rgba(16,185,129,0.1); color: var(--success); }
|
|
.stat-icon.orange { background: rgba(245,158,11,0.1); color: var(--warning); }
|
|
.stat-icon.red { background: rgba(239,68,68,0.1); color: var(--danger); }
|
|
|
|
/* Content Sections */
|
|
.content-section {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
}
|
|
|
|
.content-section.active {
|
|
display: block;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 2px solid var(--border);
|
|
}
|
|
|
|
.section-header h2 {
|
|
font-size: 22px;
|
|
color: var(--dark);
|
|
}
|
|
|
|
/* Search and Filters */
|
|
.filters {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.search-box {
|
|
flex: 1;
|
|
min-width: 250px;
|
|
padding: 10px 15px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.filter-select {
|
|
padding: 10px 15px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Table */
|
|
.table-container {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th {
|
|
background: var(--light);
|
|
padding: 12px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
color: var(--dark);
|
|
border-bottom: 2px solid var(--border);
|
|
}
|
|
|
|
td {
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
tr:hover {
|
|
background: var(--light);
|
|
}
|
|
|
|
.badge {
|
|
padding: 4px 10px;
|
|
border-radius: 20px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.badge.active { background: rgba(16,185,129,0.1); color: var(--success); }
|
|
.badge.inactive { background: rgba(239,68,68,0.1); color: var(--danger); }
|
|
.badge.pending { background: rgba(245,158,11,0.1); color: var(--warning); }
|
|
.badge.completed { background: rgba(16,185,129,0.1); color: var(--success); }
|
|
.badge.agent { background: rgba(37,99,235,0.1); color: var(--primary); }
|
|
.badge.enterprise { background: rgba(139,92,246,0.1); color: #8b5cf6; }
|
|
|
|
/* Modal */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0,0,0,0.5);
|
|
z-index: 1000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal.active {
|
|
display: flex;
|
|
}
|
|
|
|
.modal-content {
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 10px;
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.modal-header {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.modal-header h3 {
|
|
font-size: 22px;
|
|
color: var(--dark);
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: 500;
|
|
color: var(--dark);
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group select,
|
|
.form-group textarea {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
/* Loading */
|
|
.loading {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: var(--text-light);
|
|
}
|
|
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid var(--border);
|
|
border-top-color: var(--primary);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 20px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.sidebar {
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
.sidebar.visible {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.main-content {
|
|
margin-left: 0;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.filters {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
|
|
/* Action buttons in table */
|
|
.action-btns {
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 5px 10px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* Pagination */
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 5px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.page-btn {
|
|
padding: 8px 12px;
|
|
border: 1px solid var(--border);
|
|
background: white;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.page-btn.active {
|
|
background: var(--primary);
|
|
color: white;
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
.toggle-sidebar {
|
|
display: none;
|
|
background: var(--primary);
|
|
color: white;
|
|
border: none;
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.toggle-sidebar {
|
|
display: block;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="logo">
|
|
<h2>🏦 TPE Admin</h2>
|
|
<p>Tableau de bord</p>
|
|
</div>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item active" data-section="dashboard">
|
|
<span>📊</span> Dashboard
|
|
</li>
|
|
<li class="nav-item" data-section="agents">
|
|
<span>👥</span> Agents
|
|
</li>
|
|
<li class="nav-item" data-section="enterprises">
|
|
<span>🏢</span> Entreprises
|
|
</li>
|
|
<li class="nav-item" data-section="transactions">
|
|
<span>💳</span> Transactions
|
|
</li>
|
|
<li class="nav-item" data-section="recharges">
|
|
<span>💰</span> Recharges
|
|
</li>
|
|
<li class="nav-item" data-section="services">
|
|
<span>⚙️</span> Services
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main-content" id="mainContent">
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<div>
|
|
<button class="toggle-sidebar" onclick="toggleSidebar()">☰</button>
|
|
<h1 id="pageTitle">Dashboard</h1>
|
|
</div>
|
|
<div class="header-actions">
|
|
<button class="btn btn-primary" onclick="refreshData()">🔄 Actualiser</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dashboard Section -->
|
|
<section class="content-section active" id="dashboard">
|
|
<div class="stats-grid" id="statsGrid">
|
|
<div class="loading">
|
|
<div class="spinner"></div>
|
|
<p>Chargement des statistiques...</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Agents Section -->
|
|
<section class="content-section" id="agents">
|
|
<div class="section-header">
|
|
<h2>Gestion des Agents</h2>
|
|
<button class="btn btn-primary" onclick="openModal('createAgent')">+ Nouvel Agent</button>
|
|
</div>
|
|
<div class="filters">
|
|
<input type="text" class="search-box" placeholder="🔍 Rechercher un agent..." onkeyup="searchAgents(this.value)">
|
|
<select class="filter-select" onchange="filterAgents(this.value)">
|
|
<option value="">Tous les statuts</option>
|
|
<option value="active">Actif</option>
|
|
<option value="inactive">Inactif</option>
|
|
<option value="blocked">Bloqué</option>
|
|
</select>
|
|
<select class="filter-select" onchange="filterAgentType(this.value)">
|
|
<option value="">Tous les types</option>
|
|
<option value="agent">Agent individuel</option>
|
|
<option value="enterprise_member">Membre entreprise</option>
|
|
</select>
|
|
</div>
|
|
<div class="table-container">
|
|
<table id="agentsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>ID Agent</th>
|
|
<th>Nom</th>
|
|
<th>Type</th>
|
|
<th>Solde</th>
|
|
<th>Statut</th>
|
|
<th>Date création</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="agentsTableBody">
|
|
<tr><td colspan="7" class="loading">Chargement...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="agentsPagination"></div>
|
|
</section>
|
|
|
|
<!-- Enterprises Section -->
|
|
<section class="content-section" id="enterprises">
|
|
<div class="section-header">
|
|
<h2>Gestion des Entreprises</h2>
|
|
<button class="btn btn-primary" onclick="openModal('createEnterprise')">+ Nouvelle Entreprise</button>
|
|
</div>
|
|
<div class="filters">
|
|
<input type="text" class="search-box" placeholder="🔍 Rechercher une entreprise...">
|
|
<select class="filter-select">
|
|
<option value="">Tous les statuts</option>
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nom</th>
|
|
<th>Email</th>
|
|
<th>Solde</th>
|
|
<th>Membres</th>
|
|
<th>Transactions</th>
|
|
<th>Statut</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="enterprisesTableBody">
|
|
<tr><td colspan="8" class="loading">Chargement...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Transactions Section -->
|
|
<section class="content-section" id="transactions">
|
|
<div class="section-header">
|
|
<h2>Historique des Transactions</h2>
|
|
<button class="btn btn-secondary" onclick="exportTransactions()">📥 Exporter</button>
|
|
</div>
|
|
<div class="filters">
|
|
<input type="text" class="search-box" placeholder="🔍 Rechercher...">
|
|
<input type="date" class="filter-select" onchange="filterByDate()">
|
|
<select class="filter-select">
|
|
<option value="">Tous les services</option>
|
|
<option value="Internet">Internet</option>
|
|
<option value="Pelisa">Pelisa</option>
|
|
<option value="E2C">E2C</option>
|
|
</select>
|
|
</div>
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID Transaction</th>
|
|
<th>Agent</th>
|
|
<th>Service</th>
|
|
<th>Montant</th>
|
|
<th>Commission</th>
|
|
<th>Statut</th>
|
|
<th>Date</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="transactionsTableBody">
|
|
<tr><td colspan="8" class="loading">Chargement...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Recharges Section -->
|
|
<section class="content-section" id="recharges">
|
|
<div class="section-header">
|
|
<h2>Demandes de Recharge</h2>
|
|
</div>
|
|
<div class="filters">
|
|
<select class="filter-select" onchange="filterRecharges(this.value)">
|
|
<option value="">Tous</option>
|
|
<option value="pending">En attente</option>
|
|
<option value="approved">Approuvée</option>
|
|
<option value="rejected">Rejetée</option>
|
|
</select>
|
|
</div>
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Agent/Entreprise</th>
|
|
<th>Type</th>
|
|
<th>Montant</th>
|
|
<th>Statut</th>
|
|
<th>Date demande</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="rechargesTableBody">
|
|
<tr><td colspan="7" class="loading">Chargement...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Services Section -->
|
|
<section class="content-section" id="services">
|
|
<div class="section-header">
|
|
<h2>Gestion des Services</h2>
|
|
<button class="btn btn-primary" onclick="openModal('createService')">+ Nouveau Service</button>
|
|
</div>
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Service</th>
|
|
<th>Description</th>
|
|
<th>Rang</th>
|
|
<th>Statut</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="servicesTableBody">
|
|
<tr><td colspan="5" class="loading">Chargement...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modal: Create Agent -->
|
|
<div class="modal" id="createAgentModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>Créer un Nouvel Agent</h3>
|
|
</div>
|
|
<form onsubmit="createAgent(event)">
|
|
<div class="form-group">
|
|
<label>Nom complet *</label>
|
|
<input type="text" name="nom" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Téléphone</label>
|
|
<input type="tel" name="telephone">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Email</label>
|
|
<input type="email" name="email">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>PIN (6 chiffres minimum) *</label>
|
|
<input type="password" name="pin" minlength="6" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Rôle</label>
|
|
<select name="role">
|
|
<option value="agent">Agent</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('createAgent')">Annuler</button>
|
|
<button type="submit" class="btn btn-primary">Créer l'agent</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Create Enterprise -->
|
|
<div class="modal" id="createEnterpriseModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>Créer une Nouvelle Entreprise</h3>
|
|
</div>
|
|
<form onsubmit="createEnterprise(event)">
|
|
<div class="form-group">
|
|
<label>Nom de l'entreprise *</label>
|
|
<input type="text" name="nom_entreprise" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Email *</label>
|
|
<input type="email" name="email_entreprise" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Téléphone *</label>
|
|
<input type="tel" name="telephone_entreprise" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Adresse</label>
|
|
<input type="text" name="adresse">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Secteur d'activité</label>
|
|
<input type="text" name="secteur_activite">
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('createEnterprise')">Annuler</button>
|
|
<button type="submit" class="btn btn-primary">Créer l'entreprise</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal: Recharge Agent -->
|
|
<div class="modal" id="rechargeModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3>Recharger le Solde</h3>
|
|
</div>
|
|
<form onsubmit="rechargeAgent(event)">
|
|
<input type="hidden" name="agent_id" id="rechargeAgentId">
|
|
<div class="form-group">
|
|
<label>Agent</label>
|
|
<input type="text" id="rechargeAgentName" readonly>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Montant (FCFA) *</label>
|
|
<input type="number" name="montant" min="100" required>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn btn-secondary" onclick="closeModal('recharge')">Annuler</button>
|
|
<button type="submit" class="btn btn-success">Recharger</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API_URL = 'https://api.live.wortis.cg/tpe';
|
|
let currentPage = 1;
|
|
let currentFilter = {};
|
|
|
|
// Toggle Sidebar
|
|
function toggleSidebar() {
|
|
document.getElementById('sidebar').classList.toggle('visible');
|
|
}
|
|
|
|
// Navigation
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
const section = item.dataset.section;
|
|
|
|
// Update active nav
|
|
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
|
item.classList.add('active');
|
|
|
|
// Update active section
|
|
document.querySelectorAll('.content-section').forEach(s => s.classList.remove('active'));
|
|
document.getElementById(section).classList.add('active');
|
|
|
|
// Update title
|
|
const titles = {
|
|
'dashboard': 'Dashboard',
|
|
'agents': 'Gestion des Agents',
|
|
'enterprises': 'Gestion des Entreprises',
|
|
'transactions': 'Historique des Transactions',
|
|
'recharges': 'Demandes de Recharge',
|
|
'services': 'Gestion des Services'
|
|
};
|
|
document.getElementById('pageTitle').textContent = titles[section];
|
|
|
|
// Load data
|
|
loadSectionData(section);
|
|
});
|
|
});
|
|
|
|
// Load Section Data
|
|
async function loadSectionData(section) {
|
|
switch(section) {
|
|
case 'dashboard':
|
|
await loadDashboard();
|
|
break;
|
|
case 'agents':
|
|
await loadAgents();
|
|
break;
|
|
case 'enterprises':
|
|
await loadEnterprises();
|
|
break;
|
|
case 'transactions':
|
|
await loadTransactions();
|
|
break;
|
|
case 'recharges':
|
|
await loadRecharges();
|
|
break;
|
|
case 'services':
|
|
await loadServices();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Load Dashboard
|
|
async function loadDashboard() {
|
|
try {
|
|
const response = await fetch(`${API_URL}/admin/system/stats`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const stats = data.stats;
|
|
document.getElementById('statsGrid').innerHTML = `
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Agents Actifs</h3>
|
|
<p>${stats.active_agents || 0}</p>
|
|
</div>
|
|
<div class="stat-icon blue">👥</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Entreprises</h3>
|
|
<p>${stats.total_enterprises || 0}</p>
|
|
</div>
|
|
<div class="stat-icon green">🏢</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Transactions (Aujourd'hui)</h3>
|
|
<p>${stats.daily_transactions || 0}</p>
|
|
</div>
|
|
<div class="stat-icon orange">💳</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Volume Total</h3>
|
|
<p>${formatMoney(stats.total_volume || 0)}</p>
|
|
</div>
|
|
<div class="stat-icon blue">💰</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Commissions Totales</h3>
|
|
<p>${formatMoney(stats.total_commissions || 0)}</p>
|
|
</div>
|
|
<div class="stat-icon green">📈</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Taux de Succès</h3>
|
|
<p>${stats.success_rate || 0}%</p>
|
|
</div>
|
|
<div class="stat-icon green">✅</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Recharges en Attente</h3>
|
|
<p>${stats.pending_recharges || 0}</p>
|
|
</div>
|
|
<div class="stat-icon orange">⏳</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<h3>Membres Entreprise</h3>
|
|
<p>${stats.total_enterprise_members || 0}</p>
|
|
</div>
|
|
<div class="stat-icon blue">👔</div>
|
|
</div>
|
|
`;
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur chargement dashboard:', error);
|
|
showNotification('Erreur de chargement des statistiques', 'error');
|
|
}
|
|
}
|
|
|
|
// Load Agents
|
|
async function loadAgents(page = 1) {
|
|
try {
|
|
const response = await fetch(`${API_URL}/admin/agents?page=${page}&limit=50`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const tbody = document.getElementById('agentsTableBody');
|
|
tbody.innerHTML = data.agents.map(agent => `
|
|
<tr>
|
|
<td><strong>${agent.agent_id}</strong></td>
|
|
<td>${agent.nom}</td>
|
|
<td><span class="badge ${agent.type === 'enterprise_member' ? 'enterprise' : 'agent'}">${agent.type === 'enterprise_member' ? 'Entreprise' : 'Individuel'}</span></td>
|
|
<td><strong>${formatMoney(agent.solde || 0)}</strong></td>
|
|
<td><span class="badge ${agent.status || 'active'}">${getStatusLabel(agent.status || 'active')}</span></td>
|
|
<td>${formatDate(agent.created_at)}</td>
|
|
<td>
|
|
<div class="action-btns">
|
|
<button class="btn btn-sm btn-primary" onclick="viewAgent('${agent.agent_id}')">👁️</button>
|
|
<button class="btn btn-sm btn-success" onclick="openRechargeModal('${agent.agent_id}', '${agent.nom}')">💰</button>
|
|
<button class="btn btn-sm btn-danger" onclick="toggleAgentStatus('${agent.agent_id}', '${agent.status}')">🔒</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
// Update pagination
|
|
updatePagination('agents', data.pagination);
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur chargement agents:', error);
|
|
showNotification('Erreur de chargement des agents', 'error');
|
|
}
|
|
}
|
|
|
|
// Load Enterprises
|
|
async function loadEnterprises() {
|
|
try {
|
|
const response = await fetch(`${API_URL}/enterprises`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const tbody = document.getElementById('enterprisesTableBody');
|
|
tbody.innerHTML = data.enterprises.map(ent => `
|
|
<tr>
|
|
<td><strong>${ent.enterprise_id}</strong></td>
|
|
<td>${ent.nom_entreprise}</td>
|
|
<td>${ent.email_entreprise}</td>
|
|
<td><strong>${formatMoney(ent.solde_entreprise || 0)}</strong></td>
|
|
<td>${ent.total_members || 0}</td>
|
|
<td>${ent.total_transactions || 0}</td>
|
|
<td><span class="badge ${ent.status}">${getStatusLabel(ent.status)}</span></td>
|
|
<td>
|
|
<div class="action-btns">
|
|
<button class="btn btn-sm btn-primary" onclick="viewEnterprise('${ent.enterprise_id}')">👁️</button>
|
|
<button class="btn btn-sm btn-success" onclick="rechargeEnterprise('${ent.enterprise_id}')">💰</button>
|
|
<button class="btn btn-sm btn-secondary" onclick="viewMembers('${ent.enterprise_id}')">👥</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur chargement entreprises:', error);
|
|
showNotification('Erreur de chargement des entreprises', 'error');
|
|
}
|
|
}
|
|
|
|
// Load Transactions
|
|
async function loadTransactions() {
|
|
try {
|
|
// Pour l'instant, on affiche un message
|
|
const tbody = document.getElementById('transactionsTableBody');
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="8" style="text-align: center; padding: 40px;">
|
|
<p style="font-size: 18px; color: var(--text-light);">
|
|
📊 Pour voir les transactions d'un agent spécifique,<br>
|
|
utilisez l'endpoint: /tpe/agent/{agent_id}/transactions
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
} catch (error) {
|
|
console.error('Erreur chargement transactions:', error);
|
|
}
|
|
}
|
|
|
|
// Load Recharges
|
|
async function loadRecharges() {
|
|
try {
|
|
const response = await fetch(`${API_URL}/recharge/get_recharges`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const tbody = document.getElementById('rechargesTableBody');
|
|
tbody.innerHTML = data.all_recharges.map(recharge => `
|
|
<tr>
|
|
<td>${recharge._id.$oid.substring(0, 8)}...</td>
|
|
<td><strong>${recharge.agent_id || recharge.enterprise_id}</strong></td>
|
|
<td><span class="badge ${recharge.type === 'enterprise_recharge' ? 'enterprise' : 'agent'}">${recharge.type === 'enterprise_recharge' ? 'Entreprise' : 'Agent'}</span></td>
|
|
<td><strong>${formatMoney(recharge.montant)}</strong></td>
|
|
<td><span class="badge ${recharge.status}">${getStatusLabel(recharge.status)}</span></td>
|
|
<td>${formatDate(recharge.created_at)}</td>
|
|
<td>
|
|
${recharge.status === 'pending' ? `
|
|
<div class="action-btns">
|
|
<button class="btn btn-sm btn-success" onclick="approveRecharge('${recharge._id.$oid}', '${recharge.agent_id}')">✅ Approuver</button>
|
|
<button class="btn btn-sm btn-danger" onclick="rejectRecharge('${recharge._id.$oid}')">❌ Rejeter</button>
|
|
</div>
|
|
` : `<span class="badge ${recharge.status}">${getStatusLabel(recharge.status)}</span>`}
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur chargement recharges:', error);
|
|
showNotification('Erreur de chargement des recharges', 'error');
|
|
}
|
|
}
|
|
|
|
// Load Services
|
|
async function loadServices() {
|
|
try {
|
|
const response = await fetch(`${API_URL}/get_services_tpe`);
|
|
const data = await response.json();
|
|
|
|
if (data.all_services) {
|
|
const tbody = document.getElementById('servicesTableBody');
|
|
tbody.innerHTML = data.all_services.map(service => `
|
|
<tr>
|
|
<td><strong>${service.service_name || service.name}</strong></td>
|
|
<td>${service.description || 'N/A'}</td>
|
|
<td>${service.rang || 'N/A'}</td>
|
|
<td><span class="badge active">Actif</span></td>
|
|
<td>
|
|
<div class="action-btns">
|
|
<button class="btn btn-sm btn-primary">✏️ Modifier</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur chargement services:', error);
|
|
showNotification('Erreur de chargement des services', 'error');
|
|
}
|
|
}
|
|
|
|
// Create Agent
|
|
async function createAgent(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.target);
|
|
const data = Object.fromEntries(formData);
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/users/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification(`Agent créé avec succès! ID: ${result.generated_agent_id}`, 'success');
|
|
closeModal('createAgent');
|
|
loadAgents();
|
|
} else {
|
|
showNotification(result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Erreur lors de la création de l\'agent', 'error');
|
|
}
|
|
}
|
|
|
|
// Create Enterprise
|
|
async function createEnterprise(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.target);
|
|
const data = Object.fromEntries(formData);
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/enterprise/create`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification(`Entreprise créée avec succès! ID: ${result.enterprise.enterprise_id}`, 'success');
|
|
closeModal('createEnterprise');
|
|
loadEnterprises();
|
|
} else {
|
|
showNotification(result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Erreur lors de la création de l\'entreprise', 'error');
|
|
}
|
|
}
|
|
|
|
// Recharge Agent
|
|
function openRechargeModal(agentId, agentName) {
|
|
document.getElementById('rechargeAgentId').value = agentId;
|
|
document.getElementById('rechargeAgentName').value = `${agentId} - ${agentName}`;
|
|
document.getElementById('rechargeModal').classList.add('active');
|
|
}
|
|
|
|
async function rechargeAgent(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.target);
|
|
const agentId = formData.get('agent_id');
|
|
const montant = formData.get('montant');
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/agent/${agentId}/recharge`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ montant: parseFloat(montant) })
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification(`Recharge de ${formatMoney(montant)} effectuée avec succès!`, 'success');
|
|
closeModal('recharge');
|
|
loadAgents();
|
|
} else {
|
|
showNotification(result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Erreur lors de la recharge', 'error');
|
|
}
|
|
}
|
|
|
|
// Approve Recharge
|
|
async function approveRecharge(rechargeId, agentId) {
|
|
if (!confirm('Confirmer l\'approbation de cette recharge?')) return;
|
|
|
|
const pin = prompt('Entrez votre PIN pour approuver:');
|
|
if (!pin) return;
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/recharge/approve/${rechargeId}/agent/${agentId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
pin: pin,
|
|
approved_by: 'admin'
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification('Recharge approuvée avec succès!', 'success');
|
|
loadRecharges();
|
|
} else {
|
|
showNotification(result.message, 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Erreur lors de l\'approbation', 'error');
|
|
}
|
|
}
|
|
|
|
// View Agent Details
|
|
async function viewAgent(agentId) {
|
|
try {
|
|
const response = await fetch(`${API_URL}/agent/${agentId}/balance`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert(`
|
|
Détails de l'Agent
|
|
━━━━━━━━━━━━━━━━━━━━
|
|
ID: ${data.agent_id}
|
|
Nom: ${data.nom}
|
|
Type: ${data.type === 'enterprise_member' ? 'Membre Entreprise' : 'Agent Individuel'}
|
|
Solde: ${formatMoney(data.solde)} FCFA
|
|
Transactions: ${data.total_transactions}
|
|
Commissions: ${formatMoney(data.total_commission)} FCFA
|
|
`);
|
|
}
|
|
} catch (error) {
|
|
showNotification('Erreur lors de la récupération des détails', 'error');
|
|
}
|
|
}
|
|
|
|
// View Enterprise Details
|
|
async function viewEnterprise(enterpriseId) {
|
|
try {
|
|
const response = await fetch(`${API_URL}/enterprise/${enterpriseId}/balance`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert(`
|
|
Détails de l'Entreprise
|
|
━━━━━━━━━━━━━━━━━━━━━━━
|
|
ID: ${data.enterprise_id}
|
|
Nom: ${data.nom_entreprise}
|
|
Solde: ${formatMoney(data.solde_entreprise)} FCFA
|
|
Membres: ${data.total_members}
|
|
Transactions: ${data.statistics.total_transactions}
|
|
Volume Total: ${formatMoney(data.statistics.total_volume)} FCFA
|
|
Commissions: ${formatMoney(data.statistics.total_commissions)} FCFA
|
|
`);
|
|
}
|
|
} catch (error) {
|
|
showNotification('Erreur lors de la récupération des détails', 'error');
|
|
}
|
|
}
|
|
|
|
// Modal Functions
|
|
function openModal(modalName) {
|
|
document.getElementById(`${modalName}Modal`).classList.add('active');
|
|
}
|
|
|
|
function closeModal(modalName) {
|
|
document.getElementById(`${modalName}Modal`).classList.remove('active');
|
|
}
|
|
|
|
// Utility Functions
|
|
function formatMoney(amount) {
|
|
return new Intl.NumberFormat('fr-FR').format(amount) + ' FCFA';
|
|
}
|
|
|
|
function formatDate(dateString) {
|
|
if (!dateString) return 'N/A';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('fr-FR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
function getStatusLabel(status) {
|
|
const labels = {
|
|
'active': 'Actif',
|
|
'inactive': 'Inactif',
|
|
'blocked': 'Bloqué',
|
|
'pending': 'En attente',
|
|
'approved': 'Approuvée',
|
|
'rejected': 'Rejetée',
|
|
'completed': 'Complétée'
|
|
};
|
|
return labels[status] || status;
|
|
}
|
|
|
|
function showNotification(message, type = 'info') {
|
|
alert(message); // Simplification - vous pouvez implémenter un meilleur système de notification
|
|
}
|
|
|
|
function updatePagination(section, paginationData) {
|
|
const container = document.getElementById(`${section}Pagination`);
|
|
if (!container || !paginationData) return;
|
|
|
|
let html = '';
|
|
for (let i = 1; i <= paginationData.pages; i++) {
|
|
html += `<button class="page-btn ${i === paginationData.page ? 'active' : ''}" onclick="loadAgents(${i})">${i}</button>`;
|
|
}
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function refreshData() {
|
|
const activeSection = document.querySelector('.content-section.active').id;
|
|
loadSectionData(activeSection);
|
|
showNotification('Données actualisées', 'success');
|
|
}
|
|
|
|
function searchAgents(query) {
|
|
// Implementation de la recherche
|
|
console.log('Recherche:', query);
|
|
}
|
|
|
|
function filterAgents(status) {
|
|
currentFilter.status = status;
|
|
loadAgents();
|
|
}
|
|
|
|
function filterAgentType(type) {
|
|
currentFilter.type = type;
|
|
loadAgents();
|
|
}
|
|
|
|
function filterRecharges(status) {
|
|
// Implémenter le filtre
|
|
loadRecharges();
|
|
}
|
|
|
|
function exportTransactions() {
|
|
showNotification('Fonctionnalité d\'export en cours de développement', 'info');
|
|
}
|
|
|
|
// Close modal on outside click
|
|
document.querySelectorAll('.modal').forEach(modal => {
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
modal.classList.remove('active');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadDashboard();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |