Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 58 |
n/a |
0 / 0 |
CRAP | n/a |
0 / 0 |
||
| 1 | <!DOCTYPE html> |
| 2 | <html lang="fr"> |
| 3 | <head> |
| 4 | <meta charset="UTF-8"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 6 | |
| 7 | <title><?= $title ?? 'Tableau de bord CRM' ?></title> |
| 8 | |
| 9 | <meta name="description" content="Tableau de bord CRM"> |
| 10 | <meta name="robots" content="noindex, nofollow"> |
| 11 | |
| 12 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"> |
| 13 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css"> |
| 14 | <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> |
| 15 | |
| 16 | <style> |
| 17 | :root { |
| 18 | --s-primary: #1a56db; |
| 19 | } |
| 20 | |
| 21 | body { |
| 22 | background-color: #f4f6f9; |
| 23 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; |
| 24 | } |
| 25 | |
| 26 | /* ====================== SIDEBAR AVEC TOGGLE ====================== */ |
| 27 | .sidebar { |
| 28 | width: 260px; |
| 29 | height: 100vh; |
| 30 | position: fixed; |
| 31 | top: 0; |
| 32 | left: 0; |
| 33 | background: #111827; |
| 34 | color: #fff; |
| 35 | padding: 20px 15px; |
| 36 | overflow-x: hidden; |
| 37 | z-index: 100; |
| 38 | transition: width 0.25s ease; |
| 39 | } |
| 40 | |
| 41 | .sidebar.collapsed { |
| 42 | width: 70px; |
| 43 | } |
| 44 | |
| 45 | .sidebar a { |
| 46 | color: #cbd5e1; |
| 47 | text-decoration: none; |
| 48 | display: flex; |
| 49 | align-items: center; |
| 50 | gap: 10px; |
| 51 | padding: 12px 14px; |
| 52 | border-radius: 8px; |
| 53 | white-space: nowrap; |
| 54 | transition: background 0.2s, color 0.2s; |
| 55 | } |
| 56 | |
| 57 | .sidebar a:hover { |
| 58 | background: #1f2937; |
| 59 | color: #fff; |
| 60 | } |
| 61 | |
| 62 | .sidebar.collapsed a { |
| 63 | justify-content: center; |
| 64 | } |
| 65 | |
| 66 | /* Masquer le texte et les titres de section quand replié */ |
| 67 | .sidebar.collapsed a span, |
| 68 | .sidebar.collapsed .text-uppercase { |
| 69 | display: none; |
| 70 | } |
| 71 | |
| 72 | /* ====================== LOGO : conteneur fixe + image inline (plus de flicker) ====================== */ |
| 73 | .logo-container { |
| 74 | height: 48px; /* Ajustez selon la hauteur réelle de votre logo */ |
| 75 | display: flex; |
| 76 | align-items: center; |
| 77 | margin-bottom: 1.5rem; |
| 78 | transition: none; /* Pas de transition pour éviter tout décalage */ |
| 79 | } |
| 80 | .logo-full { |
| 81 | max-width: 215px; |
| 82 | max-height: 100%; |
| 83 | width: auto; |
| 84 | height: auto; |
| 85 | display: block; |
| 86 | } |
| 87 | .logo-icon { |
| 88 | display: none; |
| 89 | width: 32px; |
| 90 | height: 32px; |
| 91 | object-fit: contain; |
| 92 | } |
| 93 | |
| 94 | /* Quand sidebar repliée, on cache le logo large et on affiche l'icône */ |
| 95 | .sidebar.collapsed .logo-full { |
| 96 | display: none; |
| 97 | } |
| 98 | .sidebar.collapsed .logo-icon { |
| 99 | display: block; |
| 100 | margin: 0 auto; |
| 101 | } |
| 102 | /* Ajustement du conteneur pour centrer l'icône */ |
| 103 | .sidebar.collapsed .logo-container { |
| 104 | justify-content: center; |
| 105 | } |
| 106 | |
| 107 | /* ====================== MAIN CONTENT ====================== */ |
| 108 | .content { |
| 109 | margin-left: 260px; |
| 110 | padding: 25px 30px; |
| 111 | transition: margin-left 0.25s ease; |
| 112 | min-height: 100vh; |
| 113 | } |
| 114 | |
| 115 | .content.expanded { |
| 116 | margin-left: 70px; |
| 117 | } |
| 118 | |
| 119 | /* ====================== TOPBAR ====================== */ |
| 120 | .topbar { |
| 121 | background: #fff; |
| 122 | padding: 14px 24px; |
| 123 | border-radius: 14px; |
| 124 | margin-bottom: 24px; |
| 125 | box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04); |
| 126 | border: 1px solid #f0f2f7; |
| 127 | display: flex; |
| 128 | justify-content: space-between; |
| 129 | align-items: center; |
| 130 | } |
| 131 | |
| 132 | /* ====================== MONITOR WRAP ====================== */ |
| 133 | .monitor-wrap { |
| 134 | max-width: 1800px; |
| 135 | margin: 0 auto; |
| 136 | padding: 0; |
| 137 | } |
| 138 | |
| 139 | /* Scrollbar invisible */ |
| 140 | .sidebar::-webkit-scrollbar { width: 0; } |
| 141 | .sidebar { scrollbar-width: none; } |
| 142 | |
| 143 | /* Anti-flicker global pour les transitions (hors toggle) */ |
| 144 | body.preload-transitions-off * { |
| 145 | transition: none !important; |
| 146 | } |
| 147 | </style> |
| 148 | </head> |
| 149 | <body class="preload-transitions-off"> |
| 150 | |
| 151 | <!-- SIDEBAR AVEC TOGGLE --> |
| 152 | <div class="sidebar p-3" id="sidebar"> |
| 153 | <div class="logo-container"> |
| 154 | <?php |
| 155 | // Conversion du logo en base64 pour éliminer toute requête réseau et tout clignotement |
| 156 | $logoPath = FCPATH . 'assets/img/logo.png'; // Chemin absolu vers le logo |
| 157 | $logoData = ''; |
| 158 | if (file_exists($logoPath)) { |
| 159 | $mime = mime_content_type($logoPath); |
| 160 | $base64 = base64_encode(file_get_contents($logoPath)); |
| 161 | $logoData = 'data:' . $mime . ';base64,' . $base64; |
| 162 | } else { |
| 163 | // Fallback : URL classique |
| 164 | $logoData = base_url('assets/img/logo.png'); |
| 165 | } |
| 166 | ?> |
| 167 | <img src="<?= $logoData ?>" class="logo-full" alt="logo"> |
| 168 | <img src="<?= base_url('assets/img/logo-icon.png') ?>" class="logo-icon" alt="icon"> |
| 169 | </div> |
| 170 | |
| 171 | <!-- DASHBOARD --> |
| 172 | <div class="menu-group mb-3"> |
| 173 | <a href="<?= base_url('dashboard') ?>"> |
| 174 | <i class="bi bi-speedometer2"></i><span>Tableau de bord</span> |
| 175 | </a> |
| 176 | </div> |
| 177 | |
| 178 | <!-- GESTION DES ENVOIS --> |
| 179 | <div class="menu-group mb-3"> |
| 180 | <div class="text-uppercase text-secondary small mb-2 px-2">Gestion des envois</div> |
| 181 | |
| 182 | <a data-bs-toggle="collapse" href="#menu-envois"> |
| 183 | <i class="bi bi-send"></i><span>Envois</span> |
| 184 | </a> |
| 185 | <div class="collapse ps-4 mt-1" id="menu-envois"> |
| 186 | <a href="<?= base_url('dashboard/envoi/unique') ?>">Unique</a> |
| 187 | <a href="<?= base_url('dashboard/envoi/unique/brouillons') ?>">Brouillons</a> |
| 188 | <a href="<?= base_url('dashboard/envoi/unique/envoyes') ?>">Envoyés</a> |
| 189 | </div> |
| 190 | |
| 191 | <a data-bs-toggle="collapse" href="#menu-masse"> |
| 192 | <i class="bi bi-stack"></i><span>Masse</span> |
| 193 | </a> |
| 194 | <div class="collapse ps-4 mt-1" id="menu-masse"> |
| 195 | <a href="<?= base_url('dashboard/envoi/masse') ?>">Composer</a> |
| 196 | <a href="<?= base_url('dashboard/envoi/masse/brouillons') ?>">Brouillons</a> |
| 197 | <a href="<?= base_url('dashboard/envoi/masse/envoyes') ?>">Envoyés</a> |
| 198 | </div> |
| 199 | |
| 200 | <a data-bs-toggle="collapse" href="#menu-campagne"> |
| 201 | <i class="bi bi-megaphone"></i><span>Campagne</span> |
| 202 | </a> |
| 203 | <div class="collapse ps-4 mt-1" id="menu-campagne"> |
| 204 | <a href="<?= base_url('dashboard/campagnes/lancer') ?>">Lancer</a> |
| 205 | <a href="<?= base_url('dashboard/campagnes') ?>">Liste</a> |
| 206 | <a href="<?= base_url('dashboard/campagne/statistiques') ?>">Statistiques</a> |
| 207 | </div> |
| 208 | </div> |
| 209 | |
| 210 | <!-- GESTION DES CONTACTS --> |
| 211 | <div class="menu-group mb-3"> |
| 212 | <div class="text-uppercase text-secondary small mb-2 px-2">Gestion des contacts</div> |
| 213 | <a href="<?= base_url('dashboard/contacts') ?>"><i class="bi bi-person-lines-fill"></i><span>Contacts</span></a> |
| 214 | <a href="<?= base_url('dashboard/segments') ?>"><i class="bi bi-diagram-2"></i><span>Segments</span></a> |
| 215 | <a href="<?= base_url('dashboard/contact-listes') ?>"><i class="bi bi-list-ul"></i><span>Listes</span></a> |
| 216 | <a href="<?= base_url('dashboard/expediteurs') ?>"><i class="bi bi-hdd-network"></i><span>Expéditeurs</span></a> |
| 217 | </div> |
| 218 | |
| 219 | <!-- COMMUNICATION --> |
| 220 | <div class="menu-group mb-3"> |
| 221 | <div class="text-uppercase text-secondary small mb-2 px-2">Communication</div> |
| 222 | <a href="<?= base_url('dashboard/notification/regles/liste') ?>"><i class="bi bi-diagram-3"></i><span>Règles</span></a> |
| 223 | <a href="<?= base_url('dashboard/notification/regles') ?>"><i class="bi bi-bezier2"></i><span>Rule Builder</span></a> |
| 224 | <a href="<?= base_url('dashboard/templates') ?>"><i class="bi bi-file-earmark-text"></i><span>Templates</span></a> |
| 225 | </div> |
| 226 | |
| 227 | <!-- MONITORING --> |
| 228 | <div class="menu-group mb-3"> |
| 229 | <div class="text-uppercase text-secondary small mb-2 px-2">Monitoring</div> |
| 230 | <a href="<?= base_url('dashboard/monitoring/overview') ?>"><i class="bi bi-activity"></i><span>Vue globale</span></a> |
| 231 | <a href="<?= base_url('dashboard/monitoring/queue') ?>"><i class="bi bi-hourglass"></i><span>File</span></a> |
| 232 | <a href="<?= base_url('dashboard/monitoring/success') ?>"><i class="bi bi-check2-circle"></i><span>Succès</span></a> |
| 233 | <a href="<?= base_url('dashboard/monitoring/failed') ?>"><i class="bi bi-x-circle"></i><span>Échecs</span></a> |
| 234 | <a href="<?= base_url('dashboard/monitoring/stats') ?>"><i class="bi bi-bar-chart-line"></i><span>Statistiques</span></a> |
| 235 | <a href="<?= base_url('dashboard/monitoring/logs') ?>"><i class="bi bi-journal-text"></i><span>Logs</span></a> |
| 236 | </div> |
| 237 | |
| 238 | <!-- SYSTEM ACTIONS --> |
| 239 | <div class="mt-auto pt-3 border-top"> |
| 240 | <a href="<?= base_url('dashboard/system') ?>" class="btn btn-dark w-100 mb-2 d-flex align-items-center justify-content-center gap-2"> |
| 241 | <i class="bi bi-gear-fill"></i><span>Système</span> |
| 242 | </a> |
| 243 | <a href="<?= base_url('dashboard/updater') ?>" class="btn btn-primary w-100 mb-2 d-flex align-items-center justify-content-center gap-2"> |
| 244 | <i class="bi bi-arrow-repeat"></i><span>Mise à jour</span> |
| 245 | </a> |
| 246 | <a href="<?= base_url('dashboard/support') ?>" class="btn btn-success w-100 d-flex align-items-center justify-content-center gap-2"> |
| 247 | <i class="bi bi-life-preserver"></i><span>Support</span> |
| 248 | </a> |
| 249 | </div> |
| 250 | </div> |
| 251 | |
| 252 | <!-- CONTENT --> |
| 253 | <div class="content" id="content"> |
| 254 | |
| 255 | <!-- TOPBAR avec bouton toggle --> |
| 256 | <div class="topbar"> |
| 257 | <div class="d-flex align-items-center gap-3"> |
| 258 | <button class="btn btn-light border px-3 py-2" id="toggleSidebar" style="border-radius: 10px;"> |
| 259 | <i class="bi bi-list fs-5"></i> |
| 260 | </button> |
| 261 | <h5 class="mb-0 fw-semibold text-dark"><?= $title ?? 'Tableau de bord' ?></h5> |
| 262 | </div> |
| 263 | |
| 264 | <div class="dropdown"> |
| 265 | <button class="btn btn-light border dropdown-toggle d-flex align-items-center gap-2 px-3 py-2" |
| 266 | data-bs-toggle="dropdown" style="border-radius: 10px;"> |
| 267 | <i class="bi bi-person-circle fs-5"></i> |
| 268 | <span class="fw-medium">Administrateur</span> |
| 269 | </button> |
| 270 | |
| 271 | <ul class="dropdown-menu dropdown-menu-end shadow" style="border-radius: 12px; min-width: 280px;"> |
| 272 | <li class="dropdown-header">Paramètres globaux</li> |
| 273 | <li><a class="dropdown-item" href="<?= base_url('dashboard/profile') ?>"><i class="bi bi-people"></i> Profil</a></li> |
| 274 | <li><a class="dropdown-item" href="<?= base_url('dashboard/canaux') ?>"><i class="bi bi-gear-wide-connected"></i> Canaux</a></li> |
| 275 | |
| 276 | <li class="dropdown-divider"></li> |
| 277 | <li class="dropdown-header">Providers</li> |
| 278 | <li><a class="dropdown-item" href="<?= base_url('dashboard/fournisseurs/sms') ?>"><i class="bi bi-chat-text"></i> SMS / OTP</a></li> |
| 279 | <li><a class="dropdown-item" href="<?= base_url('dashboard/fournisseurs/whatsapp') ?>"><i class="bi bi-whatsapp"></i> WhatsApp</a></li> |
| 280 | <li><a class="dropdown-item" href="<?= base_url('dashboard/fournisseurs/email') ?>"><i class="bi bi-envelope-at"></i> Email</a></li> |
| 281 | |
| 282 | <li class="dropdown-divider"></li> |
| 283 | <li class="dropdown-header">Intégrations</li> |
| 284 | <li><a class="dropdown-item" href="<?= base_url('dashboard/integrations') ?>"><i class="bi bi-plug"></i> Liste des intégrations</a></li> |
| 285 | |
| 286 | <li class="dropdown-divider"></li> |
| 287 | <li class="dropdown-header fw-bold">Orchestration système</li> |
| 288 | <li class="px-3 py-2"> |
| 289 | <a href="<?= base_url('dashboard/lancer/cron') ?>" class="btn btn-dark btn-sm w-100 mb-2 d-flex align-items-center justify-content-center gap-2"> |
| 290 | <i class="bi bi-clock-history"></i> Exécuter le Cron |
| 291 | </a> |
| 292 | <a href="<?= base_url('dashboard/stream/file') ?>" class="btn btn-primary btn-sm w-100 mb-2 d-flex align-items-center justify-content-center gap-2"> |
| 293 | <i class="bi bi-cpu"></i> Supervision |
| 294 | </a> |
| 295 | <a href="<?= base_url('dashboard/system/queue') ?>" class="btn btn-warning btn-sm w-100 d-flex align-items-center justify-content-center gap-2"> |
| 296 | <i class="bi bi-list-check"></i> File de traitement |
| 297 | </a> |
| 298 | </li> |
| 299 | |
| 300 | <li class="dropdown-divider"></li> |
| 301 | <li class="dropdown-header fw-bold">Partager</li> |
| 302 | <li class="px-3 py-2"> |
| 303 | <button type="button" data-bs-toggle="modal" data-bs-target="#shareAppModal" |
| 304 | class="btn btn-warning btn-sm w-100 d-flex align-items-center justify-content-center gap-2"> |
| 305 | <i class="bi bi-share"></i> Partager l’application |
| 306 | </button> |
| 307 | </li> |
| 308 | |
| 309 | <li class="dropdown-divider"></li> |
| 310 | <li> |
| 311 | <form action="/logout" method="post"> |
| 312 | <?= csrf_field() ?> |
| 313 | <button class="dropdown-item text-danger" type="submit"> |
| 314 | <i class="bi bi-box-arrow-right"></i> Déconnexion |
| 315 | </button> |
| 316 | </form> |
| 317 | </li> |
| 318 | </ul> |
| 319 | </div> |
| 320 | </div> |
| 321 | |
| 322 | <!-- Modal Partager --> |
| 323 | <div class="modal fade" id="shareAppModal" tabindex="-1"> |
| 324 | <div class="modal-dialog modal-dialog-centered"> |
| 325 | <div class="modal-content"> |
| 326 | <div class="modal-header"> |
| 327 | <h5 class="modal-title">Partager l’application</h5> |
| 328 | <button type="button" class="btn-close" data-bs-dismiss="modal"></button> |
| 329 | </div> |
| 330 | <div class="modal-body text-center"> |
| 331 | <?php $url = base_url(); ?> |
| 332 | <p class="small text-muted"><?= $url ?></p> |
| 333 | <div class="d-grid gap-2"> |
| 334 | <a class="btn btn-success" href="https://wa.me/?text=<?= urlencode('Application : ' . $url) ?>" target="_blank">WhatsApp</a> |
| 335 | <button class="btn btn-secondary" onclick="copyLink()">Copier le lien</button> |
| 336 | </div> |
| 337 | </div> |
| 338 | </div> |
| 339 | </div> |
| 340 | </div> |
| 341 | |
| 342 | <!-- CONTENU DES VUES ENFANTS --> |
| 343 | <?= $this->renderSection('content') ?> |
| 344 | |
| 345 | </div> |
| 346 | |
| 347 | <!-- JS --> |
| 348 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> |
| 349 | |
| 350 | <script> |
| 351 | // Restauration de l'état de la sidebar (déployée ou repliée) depuis localStorage |
| 352 | (function() { |
| 353 | const sidebar = document.getElementById('sidebar'); |
| 354 | const content = document.getElementById('content'); |
| 355 | const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true'; |
| 356 | if (isCollapsed) { |
| 357 | sidebar.classList.add('collapsed'); |
| 358 | content.classList.add('expanded'); |
| 359 | } else { |
| 360 | sidebar.classList.remove('collapsed'); |
| 361 | content.classList.remove('expanded'); |
| 362 | } |
| 363 | })(); |
| 364 | |
| 365 | // Gestion du bouton toggle |
| 366 | const toggleBtn = document.getElementById('toggleSidebar'); |
| 367 | const sidebarEl = document.getElementById('sidebar'); |
| 368 | const contentEl = document.getElementById('content'); |
| 369 | |
| 370 | function toggleSidebar() { |
| 371 | sidebarEl.classList.toggle('collapsed'); |
| 372 | contentEl.classList.toggle('expanded'); |
| 373 | const nowCollapsed = sidebarEl.classList.contains('collapsed'); |
| 374 | localStorage.setItem('sidebarCollapsed', nowCollapsed); |
| 375 | } |
| 376 | |
| 377 | if (toggleBtn) { |
| 378 | toggleBtn.addEventListener('click', toggleSidebar); |
| 379 | } |
| 380 | |
| 381 | // Anti-flicker : réactivation des transitions après le chargement complet |
| 382 | window.addEventListener('DOMContentLoaded', function() { |
| 383 | document.body.classList.remove('preload-transitions-off'); |
| 384 | }); |
| 385 | |
| 386 | // Fonction utilitaire pour copier le lien |
| 387 | window.copyLink = function() { |
| 388 | const url = '<?= base_url() ?>'; |
| 389 | navigator.clipboard.writeText(url).then(() => { |
| 390 | alert('Lien copié dans le presse-papier'); |
| 391 | }).catch(() => { |
| 392 | prompt('Copiez manuellement :', url); |
| 393 | }); |
| 394 | }; |
| 395 | </script> |
| 396 | |
| 397 | </body> |
| 398 | </html> |