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';