HEX
Server: Apache
System: Linux info 3.0 #1337 SMP Tue Jan 01 00:00:00 CEST 2000 all GNU/Linux
User: u115237990 (7145895)
PHP: 8.3.32
Disabled: NONE
Upload Files
File: /homepages/35/d993672390/htdocs/dermiteseborrheique/admin/index.php
<?php
define('DS_APP', true);
require_once dirname(__DIR__) . '/includes/init.php';

$user = auth_require_admin();

// ── Stats globales ─────────────────────────────────────────────────────────────
$stats = [
    'users'       => db_count("SELECT COUNT(*) FROM users"),
    'users_today' => db_count("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()"),
    'entries'     => db_count("SELECT COUNT(*) FROM journal_entries"),
    'entries_today'=> db_count("SELECT COUNT(*) FROM journal_entries WHERE entry_date = CURDATE()"),
    'crises'      => db_count("SELECT COUNT(*) FROM journal_entries WHERE is_crisis = 1"),
    'items'       => db_count("SELECT COUNT(*) FROM items WHERE is_global = 1"),
    'correlations'=> db_count("SELECT COUNT(*) FROM correlations"),
    'free'        => db_count("SELECT COUNT(*) FROM users WHERE plan = 'free'"),
    'essential'   => db_count("SELECT COUNT(*) FROM users WHERE plan = 'essential'"),
    'premium'     => db_count("SELECT COUNT(*) FROM users WHERE plan = 'premium'"),
];

// Derniers inscrits
$recentUsers = db_fetch_all(
    "SELECT id, name, email, plan, google_id, created_at, last_login_at
     FROM users ORDER BY created_at DESC LIMIT 10"
);

// Activité 7 derniers jours
$activity = db_fetch_all(
    "SELECT DATE(created_at) as day, COUNT(*) as new_users FROM users
     WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
     GROUP BY DATE(created_at) ORDER BY day ASC"
);
$entryActivity = db_fetch_all(
    "SELECT entry_date as day, COUNT(*) as entries, SUM(is_crisis) as crises
     FROM journal_entries
     WHERE entry_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
     GROUP BY entry_date ORDER BY entry_date ASC"
);

// Cron status
$cronLog = db_fetch("SELECT * FROM cron_logs WHERE job = 'obf_sync'");

// Actions POST admin
if ($_SERVER['REQUEST_METHOD'] === 'POST' && is_ajax()) {
    csrf_verify();
    $action = $_POST['action'] ?? '';

    if ($action === 'change_plan') {
        $userId  = (int)($_POST['user_id'] ?? 0);
        $plan    = $_POST['plan'] ?? 'free';
        if (!in_array($plan, ['free','essential','premium'])) {
            json_response(['success'=>false,'error'=>'Plan invalide'], 400);
        }
        db_execute("UPDATE users SET plan = ? WHERE id = ?", [$plan, $userId]);
        json_response(['success' => true]);
    }

    if ($action === 'toggle_admin') {
        $userId = (int)($_POST['user_id'] ?? 0);
        if ($userId === $user['id']) json_response(['success'=>false,'error'=>'Vous ne pouvez pas modifier votre propre rôle admin.']);
        $current = db_fetch("SELECT is_admin FROM users WHERE id = ?", [$userId]);
        db_execute("UPDATE users SET is_admin = ? WHERE id = ?", [$current['is_admin'] ? 0 : 1, $userId]);
        json_response(['success' => true]);
    }

    if ($action === 'toggle_active') {
        $userId = (int)($_POST['user_id'] ?? 0);
        if ($userId === $user['id']) json_response(['success'=>false,'error'=>'Impossible de vous désactiver vous-même.']);
        $current = db_fetch("SELECT is_active FROM users WHERE id = ?", [$userId]);
        db_execute("UPDATE users SET is_active = ? WHERE id = ?", [$current['is_active'] ? 0 : 1, $userId]);
        json_response(['success' => true]);
    }

    json_response(['success'=>false,'error'=>'Action inconnue'], 400);
}


$pageTitle  = '🛡 Administration';
$activePage = 'admin';
ob_start();
?>
<style>
.admin-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-bottom:24px;}
.astat{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;}
.astat-val{font-family:'Cabinet Grotesk',sans-serif;font-weight:900;font-size:2rem;letter-spacing:-0.03em;line-height:1;margin-bottom:4px;}
.astat-label{font-size:0.68rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;}
.astat-sub{font-size:0.72rem;color:var(--text-mid);margin-top:3px;}
.acard{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:20px;margin-bottom:16px;}
.acard-title{font-size:0.88rem;font-weight:800;letter-spacing:-0.01em;margin-bottom:16px;display:flex;align-items:center;gap:8px;}
.admin-table{width:100%;border-collapse:collapse;}
.admin-table th{font-size:0.66rem;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;padding:8px 12px;text-align:left;border-bottom:1px solid var(--border);}
.admin-table td{font-size:0.8rem;padding:10px 12px;border-bottom:1px solid var(--border);color:var(--text-mid);vertical-align:middle;}
.admin-table tr:last-child td{border:none;}
.admin-table td:first-child{color:var(--text);font-weight:600;}
.plan-sel{background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:4px 8px;border-radius:6px;font-size:0.75rem;font-family:inherit;cursor:pointer;}
.admin-btn{background:var(--surface2);border:1px solid var(--border);color:var(--text-mid);padding:4px 9px;border-radius:6px;font-size:0.72rem;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.2s;}
.admin-btn:hover{border-color:var(--cyan);color:var(--cyan);}
.admin-btn.danger:hover{border-color:var(--rose);color:var(--rose);}
.badge-plan{font-size:0.65rem;font-weight:700;padding:2px 7px;border-radius:100px;}
.bp-free{background:rgba(255,255,255,0.06);color:var(--text-dim);}
.bp-essential{background:var(--cyan-dim);color:var(--cyan);}
.bp-premium{background:rgba(255,181,71,0.12);color:var(--amber);}
.cron-status{display:flex;align-items:center;gap:8px;font-size:0.82rem;}
.cron-dot{width:8px;height:8px;border-radius:50%;}
.charts-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;}
@media(max-width:900px){
  .admin-grid{grid-template-columns:repeat(2,1fr);}
  .charts-row{grid-template-columns:1fr;}
  .acard{padding:16px;}
}
@media(max-width:480px){
  .admin-grid{grid-template-columns:repeat(2,1fr);gap:8px;}
  .astat{padding:12px;}
  .astat-val{font-size:1.6rem;}
  .charts-row{gap:8px;}
  .acard{padding:14px;}
  .admin-table th,.admin-table td{padding:7px 8px;font-size:0.72rem;}
}
</style>

<!-- Stats -->
<div class="admin-grid">
  <div class="astat">
    <div class="astat-val" style="color:var(--cyan)"><?= $stats['users'] ?></div>
    <div class="astat-label">Utilisateurs</div>
    <div class="astat-sub">+<?= $stats['users_today'] ?> aujourd'hui</div>
  </div>
  <div class="astat">
    <div class="astat-val" style="color:var(--emerald)"><?= $stats['entries'] ?></div>
    <div class="astat-label">Entrées journal</div>
    <div class="astat-sub">+<?= $stats['entries_today'] ?> aujourd'hui</div>
  </div>
  <div class="astat">
    <div class="astat-val" style="color:var(--rose)"><?= $stats['crises'] ?></div>
    <div class="astat-label">Crises déclarées</div>
  </div>
  <div class="astat">
    <div class="astat-val" style="color:var(--amber)"><?= $stats['items'] ?></div>
    <div class="astat-label">Items globaux</div>
  </div>
  <div class="astat">
    <div class="astat-val"><?= $stats['correlations'] ?></div>
    <div class="astat-label">Corrélations</div>
    <div class="astat-sub" style="display:flex;gap:6px;margin-top:6px;">
      <span style="font-size:0.65rem;color:var(--text-dim)">F:<?= $stats['free'] ?></span>
      <span style="font-size:0.65rem;color:var(--cyan)">E:<?= $stats['essential'] ?></span>
      <span style="font-size:0.65rem;color:var(--amber)">P:<?= $stats['premium'] ?></span>
    </div>
  </div>
</div>

<!-- Charts CSS purs -->
<div class="charts-row">
  <div class="acard" style="margin-bottom:0;">
    <div class="acard-title">📈 Nouveaux utilisateurs (7j)</div>
    <div style="display:flex;align-items:flex-end;gap:4px;height:120px;padding-top:8px;">
      <?php
      $colU = array_column($activity,'new_users');
      $maxU = empty($colU) ? 1 : max(1, max($colU));
      foreach ($activity as $day):
        $h = max(4, round(($day['new_users'] / $maxU) * 100)); ?>
        <div style="flex:1;display:flex;flex-direction:column;justify-content:flex-end;height:100%;"
             title="<?= date('d/m', strtotime($day['day'])) ?> — <?= $day['new_users'] ?> inscrit(s)">
          <div style="width:100%;background:var(--cyan);border-radius:2px 2px 0 0;height:<?= $h ?>%;opacity:0.7;min-height:3px;"></div>
        </div>
      <?php endforeach; ?>
    </div>
  </div>
  <div class="acard" style="margin-bottom:0;">
    <div class="acard-title">📔 Entrées journal (7j)</div>
    <div style="display:flex;align-items:flex-end;gap:3px;height:120px;padding-top:8px;">
      <?php
      $colE = array_column($entryActivity,'entries');
      $colC = array_column($entryActivity,'crises');
      $maxE = empty($colE) ? 1 : max(1, max($colE));
      $maxC = empty($colC) ? 1 : max(1, max($colC));
      foreach ($entryActivity as $day):
        $hE = max(4, round(($day['entries'] / $maxE) * 100));
        $hC = $day['crises'] > 0 ? max(3, round(($day['crises'] / $maxC) * 100)) : 0;
      ?>
        <div style="flex:1;display:flex;align-items:flex-end;gap:1px;height:100%;"
             title="<?= date('d/m', strtotime($day['day'])) ?> — <?= $day['entries'] ?> entrée(s), <?= $day['crises'] ?> crise(s)">
          <div style="flex:1;background:var(--cyan);border-radius:2px 2px 0 0;height:<?= $hE ?>%;opacity:0.6;min-height:3px;"></div>
          <?php if ($hC > 0): ?>
          <div style="flex:1;background:var(--rose);border-radius:2px 2px 0 0;height:<?= $hC ?>%;opacity:0.7;min-height:3px;"></div>
          <?php endif; ?>
        </div>
      <?php endforeach; ?>
    </div>
    <div style="display:flex;gap:12px;margin-top:6px;font-size:0.65rem;color:var(--text-dim);">
      <span><span style="display:inline-block;width:8px;height:8px;background:var(--cyan);border-radius:2px;margin-right:3px;"></span>Entrées</span>
      <span><span style="display:inline-block;width:8px;height:8px;background:var(--rose);border-radius:2px;margin-right:3px;"></span>Crises</span>
    </div>
  </div>
</div>

<!-- Cron status -->
<div class="acard">
  <div class="acard-title">⚙️ Statut du cron OBF</div>
  <?php if ($cronLog): ?>
    <div class="cron-status">
      <div class="cron-dot" style="background:<?= $cronLog['status']==='success' ? 'var(--emerald)' : ($cronLog['status']==='error' ? 'var(--rose)' : 'var(--amber)') ?>"></div>
      <span>Dernier run : <strong><?= $cronLog['status'] ?></strong></span>
      <span style="color:var(--text-dim);">· démarré <?= format_date($cronLog['started_at'], 'd/m/Y H:i') ?></span>
      <?php if ($cronLog['finished_at']): ?>
        <span style="color:var(--text-dim);">· terminé <?= format_date($cronLog['finished_at'], 'H:i') ?></span>
      <?php endif; ?>
    </div>
    <?php if ($cronLog['error_message']): ?>
      <div style="margin-top:8px;padding:8px 12px;background:rgba(255,107,138,0.08);border-radius:8px;font-size:0.78rem;color:var(--rose);">
        <?= e($cronLog['error_message']) ?>
      </div>
    <?php endif; ?>
    <?php if ($cronLog['meta']): ?>
      <?php $meta = json_decode($cronLog['meta'], true); ?>
      <div style="margin-top:8px;font-size:0.75rem;color:var(--text-dim);">
        Importés: <?= $meta['imported'] ?? 0 ?> · Mis à jour: <?= $meta['updated'] ?? 0 ?> · Erreurs: <?= $meta['errors'] ?? 0 ?>
      </div>
    <?php endif; ?>
  <?php else: ?>
    <div style="color:var(--text-dim);font-size:0.82rem;">Aucun run de cron enregistré pour le moment.</div>
  <?php endif; ?>
</div>

<!-- Users table -->
<div class="acard">
  <div class="acard-title">👤 Utilisateurs récents</div>
  <div style="overflow-x:auto;">
    <table class="admin-table">
      <thead>
        <tr>
          <th>Nom</th>
          <th>Email</th>
          <th>Plan</th>
          <th>Auth</th>
          <th>Inscrit le</th>
          <th>Dernier login</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        <?php foreach ($recentUsers as $u): ?>
        <tr id="user-row-<?= $u['id'] ?>">
          <td><?= e($u['name']) ?></td>
          <td style="font-size:0.75rem;"><?= e($u['email']) ?></td>
          <td>
            <select class="plan-sel" onchange="changePlan(<?= $u['id'] ?>, this.value)">
              <option value="free"      <?= $u['plan']==='free'?'selected':'' ?>>Free</option>
              <option value="essential" <?= $u['plan']==='essential'?'selected':'' ?>>Essential</option>
              <option value="premium"   <?= $u['plan']==='premium'?'selected':'' ?>>Premium</option>
            </select>
          </td>
          <td><?= $u['google_id'] ? '🔗 Google' : '📧 Email' ?></td>
          <td style="font-size:0.72rem;"><?= format_date($u['created_at'], 'd/m/Y') ?></td>
          <td style="font-size:0.72rem;"><?= $u['last_login_at'] ? time_ago($u['last_login_at']) : '—' ?></td>
          <td style="display:flex;gap:4px;">
            <?php if ($u['id'] !== $user['id']): ?>
              <button class="admin-btn" onclick="toggleActive(<?= $u['id'] ?>)">
                <?= $u['is_active'] ?? 1 ? 'Désactiver' : 'Activer' ?>
              </button>
            <?php endif; ?>
          </td>
        </tr>
        <?php endforeach; ?>
      </tbody>
    </table>
  </div>
</div>

<script>
const csrf = document.querySelector('meta[name="csrf-token"]').content;
const adminUrl = window.location.href;

async function adminAction(action, data) {
  const fd = new FormData();
  fd.append('action', action);
  fd.append('_csrf', csrf);
  Object.entries(data).forEach(([k,v]) => fd.append(k, v));
  const res  = await fetch(adminUrl, {method:'POST',body:fd,headers:{'X-Requested-With':'XMLHttpRequest'}});
  return res.json();
}

async function changePlan(userId, plan) {
  const data = await adminAction('change_plan', {user_id: userId, plan});
  if (!data.success) alert(data.error || 'Erreur');
}

async function toggleActive(userId) {
  const data = await adminAction('toggle_active', {user_id: userId});
  if (data.success) window.location.reload();
  else alert(data.error || 'Erreur');
}

</script>
<?php
$content = ob_get_clean();
require DS_VIEWS . 'layouts/app.php';