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/app/suspects.php
<?php
define('DS_APP', true);
require_once dirname(__DIR__) . '/includes/init.php';
require_once DS_INCLUDES . 'triggers.php';

$user = auth_require();

// ── Moteur de corrélation ─────────────────────────────────────────────────────

function computeCorrelations(int $userId, int $windowHours = 48): void
{
    // Récupérer toutes les entrées avec crises
    $crisisEntries = db_fetch_all(
        "SELECT id, entry_date FROM journal_entries
         WHERE user_id = ? AND is_crisis = 1
         ORDER BY entry_date DESC",
        [$userId]
    );

    if (empty($crisisEntries)) return;

    // Récupérer tous les items consommés par l'user
    $allItems = db_fetch_all(
        "SELECT DISTINCT ei.item_id, i.name, i.category
         FROM entry_items ei
         JOIN journal_entries je ON ei.entry_id = je.id
         JOIN items i ON ei.item_id = i.id
         WHERE je.user_id = ?",
        [$userId]
    );

    foreach ($allItems as $item) {
        $itemId = $item['item_id'];

        // Total fois consommé
        $totalOccurrences = db_count(
            "SELECT COUNT(*) FROM entry_items ei
             JOIN journal_entries je ON ei.entry_id = je.id
             WHERE je.user_id = ? AND ei.item_id = ?",
            [$userId, $itemId]
        );

        if ($totalOccurrences < 2) continue; // Pas assez de données

        // Fois consommé dans la fenêtre précédant une crise
        $crisisOccurrences = 0;
        foreach ($crisisEntries as $crisis) {
            $windowStart = date('Y-m-d', strtotime($crisis['entry_date'] . " -{$windowHours} hours"));
            $found = db_count(
                "SELECT COUNT(*) FROM entry_items ei
                 JOIN journal_entries je ON ei.entry_id = je.id
                 WHERE je.user_id = ? AND ei.item_id = ?
                   AND je.entry_date BETWEEN ? AND ?",
                [$userId, $itemId, $windowStart, $crisis['entry_date']]
            );
            if ($found > 0) $crisisOccurrences++;
        }

        // Score = (crises précédées / total crises) * (occurrences avant crise / total occurrences) * 100
        $totalCrises  = count($crisisEntries);
        $crisisRatio  = $totalCrises > 0 ? $crisisOccurrences / $totalCrises : 0;
        $presenceRatio= $totalOccurrences > 0 ? $crisisOccurrences / $totalOccurrences : 0;
        $score        = round(($crisisRatio * 0.6 + $presenceRatio * 0.4) * 100, 2);

        // Upsert corrélation
        db_execute(
            "INSERT INTO correlations
                (user_id, item_id, score, occurrences_total, occurrences_crisis, time_window_hours)
             VALUES (?, ?, ?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE
                score              = VALUES(score),
                occurrences_total  = VALUES(occurrences_total),
                occurrences_crisis = VALUES(occurrences_crisis),
                last_computed_at   = NOW()",
            [$userId, $itemId, $score, $totalOccurrences, $crisisOccurrences, $windowHours]
        );
    }
}

// ── Moteur de corrélation par ingrédients ────────────────────────────────────

function computeIngredientCorrelations(int $userId, int $windowHours = 48): void
{
    // Récupérer toutes les entrées avec crises
    $crisisEntries = db_fetch_all(
        "SELECT id, entry_date FROM journal_entries
         WHERE user_id = ? AND is_crisis = 1
         ORDER BY entry_date DESC",
        [$userId]
    );

    if (empty($crisisEntries)) return;

    // Récupérer tous les ingrédients des items consommés par l'user
    $allIngredients = db_fetch_all(
        "SELECT DISTINCT ii.inci_name, ii.common_name, ii.item_id
         FROM entry_items ei
         JOIN journal_entries je ON ei.entry_id = je.id
         JOIN item_ingredients ii ON ei.item_id = ii.item_id
         WHERE je.user_id = ?",
        [$userId]
    );

    if (empty($allIngredients)) return;

    // Grouper par inci_name
    $byInci = [];
    foreach ($allIngredients as $ing) {
        $inci = $ing['inci_name'];
        if (!isset($byInci[$inci])) {
            $byInci[$inci] = [
                'inci_name'   => $inci,
                'common_name' => $ing['common_name'],
            ];
        }
    }

    $totalCrises = count($crisisEntries);

    foreach ($byInci as $inci => $ing) {

        // Total fois consommé (via items contenant cet ingrédient)
        $totalOccurrences = db_count(
            "SELECT COUNT(DISTINCT je.id)
             FROM journal_entries je
             JOIN entry_items ei ON ei.entry_id = je.id
             JOIN item_ingredients ii ON ii.item_id = ei.item_id
             WHERE je.user_id = ? AND ii.inci_name = ?",
            [$userId, $inci]
        );

        if ($totalOccurrences < 2) continue;

        // Fois consommé dans la fenêtre précédant une crise
        $crisisOccurrences = 0;
        foreach ($crisisEntries as $crisis) {
            $windowStart = date('Y-m-d', strtotime($crisis['entry_date'] . " -{$windowHours} hours"));
            $found = db_count(
                "SELECT COUNT(DISTINCT je.id)
                 FROM journal_entries je
                 JOIN entry_items ei ON ei.entry_id = je.id
                 JOIN item_ingredients ii ON ii.item_id = ei.item_id
                 WHERE je.user_id = ? AND ii.inci_name = ?
                   AND je.entry_date BETWEEN ? AND ?",
                [$userId, $inci, $windowStart, $crisis['entry_date']]
            );
            if ($found > 0) $crisisOccurrences++;
        }

        $crisisRatio   = $totalCrises > 0 ? $crisisOccurrences / $totalCrises : 0;
        $presenceRatio = $totalOccurrences > 0 ? $crisisOccurrences / $totalOccurrences : 0;
        $score         = round(($crisisRatio * 0.6 + $presenceRatio * 0.4) * 100, 2);

        if ($score < 10) continue; // Ignorer les scores trop faibles

        // Upsert dans ingredient_correlations
        db_execute(
            "INSERT INTO ingredient_correlations
                (user_id, inci_name, common_name, score, occurrence_count, total_count, last_computed)
             VALUES (?, ?, ?, ?, ?, ?, NOW())
             ON DUPLICATE KEY UPDATE
                score            = VALUES(score),
                occurrence_count = VALUES(occurrence_count),
                total_count      = VALUES(total_count),
                last_computed    = NOW()",
            [
                $userId,
                $inci,
                $ing['common_name'] ?: $inci,
                $score,
                $crisisOccurrences,
                $totalOccurrences,
            ]
        );
    }
}

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

    if ($action === 'update_status') {
        $itemId = (int)($_POST['item_id'] ?? 0);
        $status = $_POST['status'] ?? '';
        $allowed = ['suspect','confirmed','cleared','ignored'];
        if (!in_array($status, $allowed)) {
            json_response(['success' => false, 'error' => 'Statut invalide'], 400);
        }
        db_execute(
            "UPDATE correlations SET status = ? WHERE user_id = ? AND item_id = ?",
            [$status, $user['id'], $itemId]
        );

        // Si confirmé → créer une notification
        if ($status === 'confirmed') {
            $item = db_fetch("SELECT name FROM items WHERE id = ?", [$itemId]);
            db_insert(
                "INSERT INTO notifications (user_id, type, title, body)
                 VALUES (?, 'confirmed_trigger', ?, ?)",
                [
                    $user['id'],
                    '✅ Déclencheur confirmé !',
                    ($item['name'] ?? 'Un item') . ' a été marqué comme déclencheur confirmé.',
                ]
            );
            // Badge
            $badge = db_fetch("SELECT id FROM badges WHERE slug = 'confirmed_trigger'");
            if ($badge) {
                db_execute(
                    "INSERT IGNORE INTO user_badges (user_id, badge_id) VALUES (?,?)",
                    [$user['id'], $badge['id']]
                );
            }
        }
        json_response(['success' => true]);
    }

    if ($action === 'start_elimination') {
        $itemId   = (int)($_POST['item_id'] ?? 0);
        $duration = max(7, min(90, (int)($_POST['duration'] ?? 21)));
        $startDate= date('Y-m-d');
        $endDate  = date('Y-m-d', strtotime("+{$duration} days"));

        // Vérifier pas déjà actif
        $existing = db_count(
            "SELECT COUNT(*) FROM elimination_goals WHERE user_id = ? AND item_id = ? AND status = 'active'",
            [$user['id'], $itemId]
        );
        if ($existing) {
            json_response(['success' => false, 'error' => 'Objectif d\'élimination déjà actif pour cet item.']);
        }

        db_insert(
            "INSERT INTO elimination_goals (user_id, item_id, start_date, end_date) VALUES (?,?,?,?)",
            [$user['id'], $itemId, $startDate, $endDate]
        );

        // Badge
        $badge = db_fetch("SELECT id FROM badges WHERE slug = 'first_elim'");
        if ($badge) db_execute("INSERT IGNORE INTO user_badges (user_id, badge_id) VALUES (?,?)", [$user['id'], $badge['id']]);

        json_response(['success' => true, 'end_date' => $endDate]);
    }

    if ($action === 'recompute') {
        $window = (int)($_POST['window'] ?? 48);
        computeCorrelations($user['id'], $window);
        json_response(['success' => true]);
    }

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

// ── Recalcul auto si données obsolètes (> 24h) ───────────────────────────────
$lastComputed = db_fetch(
    "SELECT MIN(last_computed_at) as last FROM correlations WHERE user_id = ?",
    [$user['id']]
)['last'] ?? null;

$needsRecompute = !$lastComputed || (time() - strtotime($lastComputed)) > 86400;
if ($needsRecompute) {
    computeCorrelations($user['id']);
}

// ── Récupération des données ──────────────────────────────────────────────────
$windowHours = (int)($_GET['window'] ?? 48);
if (!in_array($windowHours, [12, 24, 48, 72])) $windowHours = 48;
$statusFilter = $_GET['status'] ?? 'all';

$whereStatus = match($statusFilter) {
    'suspect'   => "AND c.status = 'suspect'",
    'confirmed' => "AND c.status = 'confirmed'",
    'cleared'   => "AND c.status = 'cleared'",
    'ignored'   => "AND c.status = 'ignored'",
    default     => "AND c.status != 'ignored'",
};

$suspects = db_fetch_all(
    "SELECT c.*, i.name, i.category, i.brand, i.warning_ingredients,
            kt.name_fr as known_trigger, kt.trigger_type, kt.severity as kt_severity
     FROM correlations c
     JOIN items i ON c.item_id = i.id
     LEFT JOIN item_triggers it ON i.id = it.item_id
     LEFT JOIN known_triggers kt ON it.trigger_id = kt.id AND kt.ds_specific = 1
     WHERE c.user_id = ? {$whereStatus}
     GROUP BY c.id
     ORDER BY c.score DESC",
    [$user['id']]
);

// Graphique évolution peau 60j
$skinEvolution = db_fetch_all(
    "SELECT entry_date, skin_score, is_crisis FROM journal_entries
     WHERE user_id = ? AND entry_date >= DATE_SUB(CURDATE(), INTERVAL 60 DAY)
     ORDER BY entry_date ASC",
    [$user['id']]
);

// Stats globales
$totalEntries  = db_count("SELECT COUNT(*) FROM journal_entries WHERE user_id = ?", [$user['id']]);
$totalCrises   = db_count("SELECT COUNT(*) FROM journal_entries WHERE user_id = ? AND is_crisis = 1", [$user['id']]);
$confirmedCount= db_count("SELECT COUNT(*) FROM correlations WHERE user_id = ? AND status = 'confirmed'", [$user['id']]);

// Suspects ingrédients
$ingredientSuspects = db_fetch_all(
    "SELECT ic.*,
            COUNT(DISTINCT ii.item_id) as product_count,
            GROUP_CONCAT(DISTINCT i.name ORDER BY i.name SEPARATOR ', ') as found_in_products
     FROM ingredient_correlations ic
     LEFT JOIN item_ingredients ii ON ii.inci_name = ic.inci_name
     LEFT JOIN items i ON ii.item_id = i.id
     WHERE ic.user_id = ? AND ic.score >= 20
     GROUP BY ic.id
     ORDER BY ic.score DESC
     LIMIT 20",
    [$user['id']]
);

// Objectifs d'élimination actifs
$activeGoals = db_fetch_all(
    "SELECT eg.*, i.name as item_name,
            DATEDIFF(eg.end_date, CURDATE()) as days_left,
            DATEDIFF(CURDATE(), eg.start_date) as days_done,
            DATEDIFF(eg.end_date, eg.start_date) as total_days
     FROM elimination_goals eg
     JOIN items i ON eg.item_id = i.id
     WHERE eg.user_id = ? AND eg.status = 'active'
     ORDER BY eg.start_date DESC",
    [$user['id']]
);

// Helpers
$catIcons = ['food'=>'🍽️','drink'=>'🥤','alcohol'=>'🍷','tobacco'=>'🚬','cosmetic'=>'🧴','haircare'=>'💇','medication'=>'💊','supplement'=>'🌿','other'=>'📦'];

function scoreColor(float $score): string {
    return match(true) {
        $score >= 70 => '#FF6B8A',
        $score >= 50 => '#FFB547',
        $score >= 30 => '#00D4C8',
        default      => '#7A8FA0',
    };
}
function scoreLabel(float $score): string {
    return match(true) {
        $score >= 70 => 'Élevé',
        $score >= 50 => 'Modéré',
        $score >= 30 => 'Faible',
        default      => 'Très faible',
    };
}

$pageTitle  = 'Mes Suspects';
$activePage = 'suspects';
ob_start();
?>

<style>
/* ── Suspects styles ── */
.suspects-layout{display:grid;grid-template-columns:1fr 300px;gap:20px;align-items:start;}

/* Toolbar */
.toolbar{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin-bottom:20px;
}
.filter-btn{
  background:var(--surface);border:1px solid var(--border);
  color:var(--text-mid);padding:7px 14px;border-radius:8px;
  text-decoration:none;font-size:0.78rem;font-weight:600;
  transition:all 0.2s;
}
.filter-btn:hover,.filter-btn.active{
  background:var(--cyan-dim);border-color:rgba(0,212,200,0.3);color:var(--cyan);
}
.window-select{
  background:var(--surface);border:1px solid var(--border);
  color:var(--text-mid);padding:7px 12px;border-radius:8px;
  font-size:0.78rem;font-weight:600;font-family:inherit;
  cursor:pointer;margin-left:auto;
}
.window-select:focus{outline:none;border-color:var(--cyan);}
.recompute-btn{
  background:var(--surface2);border:1px solid var(--border);
  color:var(--text-mid);padding:7px 14px;border-radius:8px;
  font-size:0.78rem;font-weight:600;cursor:pointer;font-family:inherit;
  transition:all 0.2s;display:flex;align-items:center;gap:6px;
}
.recompute-btn:hover{border-color:var(--cyan);color:var(--cyan);}

/* No data */
.no-data{
  background:var(--surface);border:1px solid var(--border);
  border-radius:16px;padding:60px 40px;text-align:center;
}
.no-data-icon{font-size:3rem;margin-bottom:16px;}
.no-data-title{font-size:1.1rem;font-weight:700;margin-bottom:8px;}
.no-data-desc{font-size:0.85rem;color:var(--text-mid);line-height:1.6;max-width:400px;margin:0 auto 24px;}
.btn-go{display:inline-flex;align-items:center;gap:6px;background:var(--cyan);color:var(--bg);padding:10px 20px;border-radius:8px;text-decoration:none;font-size:0.85rem;font-weight:800;transition:all 0.2s;}
.btn-go:hover{background:#fff;}

/* Suspect card */
.suspect-card{
  background:var(--surface);border:1px solid var(--border);
  border-radius:14px;padding:20px;margin-bottom:12px;
  transition:border-color 0.2s,transform 0.2s;
  position:relative;overflow:hidden;
}
.suspect-card:hover{border-color:var(--border-bright);transform:translateY(-1px);}
.suspect-card.confirmed{border-color:rgba(0,232,122,0.25);background:rgba(0,232,122,0.03);}
.suspect-card.cleared{opacity:0.6;}

.sc-header{display:flex;align-items:flex-start;gap:12px;margin-bottom:14px;}
.sc-icon{
  width:40px;height:40px;border-radius:10px;
  background:var(--surface2);border:1px solid var(--border);
  display:flex;align-items:center;justify-content:center;
  font-size:1.2rem;flex-shrink:0;
}
.sc-info{flex:1;min-width:0;}
.sc-name{font-size:0.95rem;font-weight:800;color:var(--text);letter-spacing:-0.01em;}
.sc-brand{font-size:0.75rem;color:var(--text-dim);margin-top:2px;}
.sc-meta{display:flex;align-items:center;gap:8px;margin-top:6px;flex-wrap:wrap;}
.sc-badge{font-size:0.65rem;font-weight:700;padding:2px 8px;border-radius:100px;}
.badge-suspect{background:rgba(255,181,71,0.12);color:var(--amber);}
.badge-confirmed{background:var(--emerald-dim);color:var(--emerald);}
.badge-cleared{background:rgba(255,255,255,0.06);color:var(--text-dim);}
.badge-ignored{background:rgba(255,255,255,0.06);color:var(--text-dim);}
.badge-known{background:rgba(255,107,138,0.1);color:var(--rose);}
.badge-eu{background:rgba(0,212,200,0.1);color:var(--cyan);}

/* Score display */
.sc-score-wrap{display:flex;align-items:center;gap:12px;margin-bottom:12px;}
.sc-score-num{
  font-family:'Cabinet Grotesk',sans-serif;
  font-size:2.2rem;font-weight:900;letter-spacing:-0.04em;line-height:1;
}
.sc-score-bar-wrap{flex:1;}
.sc-score-label{font-size:0.7rem;color:var(--text-mid);margin-bottom:4px;}
.sc-score-track{height:6px;background:rgba(255,255,255,0.06);border-radius:10px;overflow:hidden;}
.sc-score-fill{height:100%;border-radius:10px;transition:width 0.8s ease;}
.sc-score-sub{font-size:0.7rem;color:var(--text-dim);margin-top:4px;}

/* Stats row */
.sc-stats{display:flex;gap:16px;margin-bottom:14px;}
.sc-stat{text-align:center;}
.sc-stat-val{font-size:1.1rem;font-weight:800;color:var(--text);}
.sc-stat-label{font-size:0.65rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.06em;}

/* Known trigger alert */
.sc-known{
  background:rgba(255,107,138,0.06);border:1px solid rgba(255,107,138,0.15);
  border-radius:8px;padding:8px 12px;margin-bottom:12px;
  font-size:0.78rem;color:var(--text-mid);
  display:flex;align-items:center;gap:8px;
}
.sc-known strong{color:var(--rose);}

/* Actions */
.sc-actions{display:flex;gap:6px;flex-wrap:wrap;}
.sc-action-btn{
  padding:6px 12px;border-radius:7px;border:1px solid var(--border);
  background:var(--surface2);color:var(--text-mid);
  font-size:0.75rem;font-weight:600;cursor:pointer;
  font-family:inherit;transition:all 0.2s;
}
.sc-action-btn:hover{border-color:var(--border-bright);color:var(--text);}
.sc-action-btn.confirm:hover{border-color:rgba(0,232,122,0.4);color:var(--emerald);background:var(--emerald-dim);}
.sc-action-btn.elim:hover{border-color:rgba(0,212,200,0.4);color:var(--cyan);background:var(--cyan-dim);}
.sc-action-btn.ignore:hover{border-color:rgba(255,107,138,0.3);color:var(--rose);}
.sc-action-btn.active-confirm{border-color:rgba(0,232,122,0.4);color:var(--emerald);background:var(--emerald-dim);}

/* Sidebar */
.side-card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:18px;margin-bottom:14px;position:sticky;top:80px;}
.side-title{font-size:0.68rem;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:14px;}

/* Stats globales */
.global-stat{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border);}
.global-stat:last-child{border:none;}
.global-stat-label{font-size:0.82rem;color:var(--text-mid);}
.global-stat-val{font-size:1rem;font-weight:800;color:var(--text);}

/* Objectifs élimination */
.goal-card{background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:12px;margin-bottom:8px;}
.goal-name{font-size:0.82rem;font-weight:700;color:var(--text);margin-bottom:6px;}
.goal-progress{height:4px;background:rgba(255,255,255,0.06);border-radius:10px;overflow:hidden;margin-bottom:4px;}
.goal-fill{height:100%;background:linear-gradient(90deg,var(--cyan),var(--emerald));border-radius:10px;}
.goal-days{font-size:0.7rem;color:var(--text-dim);}

/* Chart mini */
#evolutionChart{width:100%;height:120px;}

/* Elim modal */
.elim-modal{
  display:none;position:fixed;inset:0;z-index:200;
  align-items:center;justify-content:center;
}
.elim-modal.open{display:flex;}
.elim-backdrop{position:absolute;inset:0;background:rgba(0,0,0,0.7);backdrop-filter:blur(6px);}
.elim-dialog{
  position:relative;z-index:1;
  background:var(--surface);border:1px solid var(--border-bright);
  border-radius:16px;padding:28px;width:min(400px,90vw);
}
.elim-title{font-size:1rem;font-weight:800;margin-bottom:8px;}
.elim-desc{font-size:0.82rem;color:var(--text-mid);margin-bottom:20px;line-height:1.5;}
.elim-duration{display:flex;gap:8px;margin-bottom:20px;}
.dur-btn{flex:1;padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--surface2);color:var(--text-mid);font-size:0.8rem;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.2s;}
.dur-btn.active{border-color:var(--cyan);color:var(--cyan);background:var(--cyan-dim);}
.elim-actions{display:flex;gap:8px;}
.elim-confirm{flex:1;padding:10px;border-radius:8px;border:none;background:var(--cyan);color:var(--bg);font-weight:800;cursor:pointer;font-family:inherit;font-size:0.85rem;transition:all 0.2s;}
.elim-confirm:hover{background:#fff;}
.elim-cancel{padding:10px 16px;border-radius:8px;border:1px solid var(--border);background:transparent;color:var(--text-mid);cursor:pointer;font-family:inherit;font-size:0.85rem;}

@media(max-width:900px){
  .suspects-layout{grid-template-columns:1fr;}
  .side-card{position:static;}
}
</style>

<!-- Toolbar -->
<div class="toolbar">
  <?php
  $filters = ['all'=>'Tous','suspect'=>'Suspects','confirmed'=>'Confirmés','cleared'=>'Éliminés','ignored'=>'Ignorés'];
  foreach ($filters as $f => $label):
    $active = $statusFilter === $f ? 'active' : '';
  ?>
  <a href="?status=<?= $f ?>&window=<?= $windowHours ?>" class="filter-btn <?= $active ?>">
    <?= $label ?>
    <?php if ($f === 'confirmed' && $confirmedCount > 0): ?>
      <span style="background:var(--emerald);color:var(--bg);border-radius:100px;padding:1px 6px;font-size:0.6rem;margin-left:4px;"><?= $confirmedCount ?></span>
    <?php endif; ?>
  </a>
  <?php endforeach; ?>

  <select class="window-select" id="windowSelect" onchange="changeWindow(this.value)">
    <option value="12"  <?= $windowHours === 12  ? 'selected' : '' ?>>Fenêtre 12h</option>
    <option value="24"  <?= $windowHours === 24  ? 'selected' : '' ?>>Fenêtre 24h</option>
    <option value="48"  <?= $windowHours === 48  ? 'selected' : '' ?>>Fenêtre 48h</option>
    <option value="72"  <?= $windowHours === 72  ? 'selected' : '' ?>>Fenêtre 72h</option>
  </select>

  <button class="recompute-btn" onclick="recompute()">
    🔄 Recalculer
  </button>
</div>

<div class="suspects-layout">
<!-- ── Liste suspects ── -->
<div>

<?php if (empty($suspects)): ?>
  <div class="no-data">
    <div class="no-data-icon"><?= $totalCrises === 0 ? '📔' : '🔬' ?></div>
    <div class="no-data-title">
      <?= $totalCrises === 0 ? 'Pas encore de crises enregistrées' : 'Aucun suspect dans cette catégorie' ?>
    </div>
    <div class="no-data-desc">
      <?php if ($totalCrises === 0): ?>
        Déclarez vos crises dans le journal en cochant "Crise aujourd'hui". L'algorithme analysera ensuite ce que vous avez consommé dans les <?= $windowHours ?>h précédentes.
      <?php else: ?>
        Essayez une autre catégorie ou une fenêtre temporelle différente.
      <?php endif; ?>
    </div>
    <a href="<?= url('app/journal.php') ?>" class="btn-go">📔 Aller au journal</a>
  </div>

<?php else: ?>
  <?php foreach ($suspects as $s):
    $color   = scoreColor((float)$s['score']);
    $icon    = $catIcons[$s['category']] ?? '📦';
    $hasWarn = !empty($s['warning_ingredients']);
    $cardCls = $s['status'] === 'confirmed' ? 'confirmed' : ($s['status'] === 'cleared' ? 'cleared' : '');
  ?>
  <div class="suspect-card <?= $cardCls ?>" id="card-<?= $s['item_id'] ?>">
    <div class="sc-header">
      <div class="sc-icon"><?= $icon ?></div>
      <div class="sc-info">
        <div class="sc-name"><?= e($s['name']) ?></div>
        <?php if ($s['brand']): ?>
          <div class="sc-brand"><?= e($s['brand']) ?></div>
        <?php endif; ?>
        <div class="sc-meta">
          <span class="sc-badge badge-<?= $s['status'] ?>">
            <?= match($s['status']) {
              'confirmed' => '✅ Confirmé',
              'cleared'   => '✓ Éliminé',
              'ignored'   => '— Ignoré',
              default     => '? Suspect',
            } ?>
          </span>
          <?php if ($s['known_trigger']): ?>
            <span class="sc-badge <?= $s['trigger_type'] === 'eu_regulated' ? 'badge-eu' : 'badge-known' ?>">
              <?= $s['trigger_type'] === 'eu_regulated' ? '🇪🇺 EU' : '🔬 Scientifique' ?>
            </span>
          <?php endif; ?>
          <?php if ($hasWarn): ?>
            <span class="sc-badge badge-known">⚠ Ingrédient suspect</span>
          <?php endif; ?>
        </div>
      </div>

      <!-- Score -->
      <div style="text-align:right;flex-shrink:0;">
        <div class="sc-score-num" style="color:<?= $color ?>"><?= round($s['score']) ?><span style="font-size:1rem;opacity:0.5">%</span></div>
        <div style="font-size:0.68rem;color:<?= $color ?>;font-weight:700;"><?= scoreLabel((float)$s['score']) ?></div>
      </div>
    </div>

    <!-- Barre de score -->
    <div class="sc-score-wrap">
      <div class="sc-score-bar-wrap">
        <div class="sc-score-label">Score de corrélation (fenêtre <?= $windowHours ?>h)</div>
        <div class="sc-score-track">
          <div class="sc-score-fill" style="width:<?= min(100, $s['score']) ?>%;background:<?= $color ?>"></div>
        </div>
        <div class="sc-score-sub">
          <?= $s['occurrences_crisis'] ?> crise<?= $s['occurrences_crisis'] > 1 ? 's' : '' ?> précédées
          · <?= $s['occurrences_total'] ?> consommation<?= $s['occurrences_total'] > 1 ? 's' : '' ?> au total
        </div>
      </div>
    </div>

    <!-- Déclencheur connu -->
    <?php if ($s['known_trigger']): ?>
    <div class="sc-known">
      <?= $s['trigger_type'] === 'eu_regulated' ? '🇪🇺' : '🔬' ?>
      <span><strong><?= e($s['known_trigger']) ?></strong> est un déclencheur <?= $s['trigger_type'] === 'eu_regulated' ? 'réglementé par l\'UE' : 'scientifiquement confirmé' ?> pour la dermite séborrhéique.</span>
    </div>
    <?php endif; ?>

    <!-- Actions -->
    <div class="sc-actions">
      <?php if ($s['status'] !== 'confirmed'): ?>
      <button class="sc-action-btn confirm"
              onclick="updateStatus(<?= $s['item_id'] ?>, 'confirmed', this)">
        ✅ Confirmer
      </button>
      <?php else: ?>
      <button class="sc-action-btn active-confirm"
              onclick="updateStatus(<?= $s['item_id'] ?>, 'suspect', this)">
        ✅ Confirmé — Annuler
      </button>
      <?php endif; ?>

      <?php
      $hasGoal = db_count(
          "SELECT COUNT(*) FROM elimination_goals WHERE user_id = ? AND item_id = ? AND status = 'active'",
          [$user['id'], $s['item_id']]
      );
      ?>
      <?php if (!$hasGoal): ?>
      <button class="sc-action-btn elim"
              onclick="openElim(<?= $s['item_id'] ?>, '<?= e(addslashes($s['name'])) ?>')">
        🧪 Tester l'élimination
      </button>
      <?php else: ?>
      <span class="sc-action-btn" style="color:var(--cyan);border-color:rgba(0,212,200,0.3);">
        🧪 Test en cours
      </span>
      <?php endif; ?>

      <?php if ($s['status'] !== 'cleared'): ?>
      <button class="sc-action-btn"
              onclick="updateStatus(<?= $s['item_id'] ?>, 'cleared', this)">
        ✓ Marquer éliminé
      </button>
      <?php endif; ?>

      <button class="sc-action-btn ignore"
              onclick="updateStatus(<?= $s['item_id'] ?>, 'ignored', this)"
              style="margin-left:auto;">
        — Ignorer
      </button>
    </div>
  </div>
  <?php endforeach; ?>
<?php endif; ?>

</div><!-- /liste -->

<!-- ── Sidebar ── -->
<div>

  <!-- Stats globales -->
  <div class="side-card">
    <div class="side-title">📊 Mes statistiques</div>
    <div class="global-stat">
      <span class="global-stat-label">Entrées de journal</span>
      <span class="global-stat-val"><?= $totalEntries ?></span>
    </div>
    <div class="global-stat">
      <span class="global-stat-label">Crises enregistrées</span>
      <span class="global-stat-val" style="color:var(--rose)"><?= $totalCrises ?></span>
    </div>
    <div class="global-stat">
      <span class="global-stat-label">Suspects identifiés</span>
      <span class="global-stat-val" style="color:var(--amber)"><?= count($suspects) ?></span>
    </div>
    <div class="global-stat">
      <span class="global-stat-label">Déclencheurs confirmés</span>
      <span class="global-stat-val" style="color:var(--emerald)"><?= $confirmedCount ?></span>
    </div>
  </div>

  <!-- Évolution peau -->
  <?php if (!empty($skinEvolution)): ?>
  <div class="side-card">
    <div class="side-title">📈 Évolution (60j)</div>
    <div class="css-chart" style="height:100px;">
  <div class="css-bars">
    <?php foreach ($skinEvolution as $point):
      $h = max(4, round(($point['skin_score'] / 5) * 100));
      $col = $point['is_crisis'] ? 'var(--rose)' : 'var(--cyan)';
      $dt = new DateTime($point['entry_date']);
    ?>
      <div class="css-bar-wrap" title="<?= $dt->format('d/m') ?> — <?= $point['skin_score'] ?>/5">
        <div class="css-bar" style="height:<?= $h ?>%;background:<?= $col ?>;opacity:0.6;"></div>
      </div>
    <?php endforeach; ?>
  </div>
</div>
  </div>
  <?php endif; ?>

  <!-- Objectifs élimination -->
  <?php if (!empty($activeGoals)): ?>
  <div class="side-card">
    <div class="side-title">🧪 Tests d'élimination actifs</div>
    <?php foreach ($activeGoals as $goal):
      $pct = $goal['total_days'] > 0 ? min(100, round(($goal['days_done'] / $goal['total_days']) * 100)) : 0;
    ?>
    <div class="goal-card">
      <div class="goal-name">Sans <?= e($goal['item_name']) ?></div>
      <div class="goal-progress">
        <div class="goal-fill" style="width:<?= $pct ?>%"></div>
      </div>
      <div class="goal-days">
        Jour <?= $goal['days_done'] ?>/<?= $goal['total_days'] ?>
        · <?= max(0, $goal['days_left']) ?> jour<?= $goal['days_left'] > 1 ? 's' : '' ?> restant<?= $goal['days_left'] > 1 ? 's' : '' ?>
      </div>
    </div>
    <?php endforeach; ?>
  </div>
  <?php endif; ?>

  <!-- Conseils algo -->
  <div class="side-card">
    <div class="side-title">💡 Comment ça marche</div>
    <p style="font-size:0.78rem;color:var(--text-mid);line-height:1.6;">
      L'algorithme compare ce que vous avez consommé dans les <strong style="color:var(--text)"><?= $windowHours ?>h</strong> précédant chaque crise.
      Plus un item apparaît souvent avant vos crises, plus son score est élevé.
    </p>
    <p style="font-size:0.78rem;color:var(--text-mid);line-height:1.6;margin-top:8px;">
      Changez la <strong style="color:var(--text)">fenêtre temporelle</strong> pour affiner — certaines réactions sont retardées de 48 à 72h.
    </p>
  </div>

</div><!-- /sidebar -->
</div><!-- /layout -->

<!-- ── Modal élimination ── -->
<div class="elim-modal" id="elimModal">
  <div class="elim-backdrop" onclick="closeElim()"></div>
  <div class="elim-dialog">
    <div class="elim-title">🧪 Test d'élimination</div>
    <div class="elim-desc" id="elimDesc">...</div>
    <div class="elim-duration">
      <?php foreach ([7=>'1 sem.',14=>'2 sem.',21=>'3 sem.',30=>'1 mois'] as $days => $label): ?>
      <button class="dur-btn <?= $days === 21 ? 'active' : '' ?>"
              data-days="<?= $days ?>"
              onclick="selectDuration(<?= $days ?>, this)">
        <?= $label ?>
      </button>
      <?php endforeach; ?>
    </div>
    <div class="elim-actions">
      <button class="elim-cancel" onclick="closeElim()">Annuler</button>
      <button class="elim-confirm" onclick="confirmElim()">Démarrer le test</button>
    </div>
  </div>
</div>

<div id="toastMsg" style="position:fixed;bottom:24px;right:24px;background:var(--surface);border:1px solid var(--border-bright);border-radius:10px;padding:12px 18px;font-size:0.82rem;font-weight:600;z-index:300;opacity:0;transition:opacity 0.3s;pointer-events:none;"></div>

<script>
const csrf   = document.querySelector('meta[name="csrf-token"]').content;
const apiUrl = '<?= url('app/suspects.php') ?>';

// ── Fenêtre temporelle ────────────────────────────────────────────────────────
function changeWindow(val) {
  const url = new URL(window.location);
  url.searchParams.set('window', val);
  window.location = url;
}

// ── Recalculer ────────────────────────────────────────────────────────────────
async function recompute() {
  const btn = document.querySelector('.recompute-btn');
  btn.innerHTML = '⏳ Calcul en cours...';
  btn.disabled = true;

  const fd = new FormData();
  fd.append('action', 'recompute');
  fd.append('window', '<?= $windowHours ?>');
  fd.append('_csrf', csrf);

  await fetch(apiUrl, {method:'POST',body:fd,headers:{'X-Requested-With':'XMLHttpRequest'}});
  window.location.reload();
}

// ── Statut ────────────────────────────────────────────────────────────────────
async function updateStatus(itemId, status, btn) {
  const fd = new FormData();
  fd.append('action',  'update_status');
  fd.append('item_id', itemId);
  fd.append('status',  status);
  fd.append('_csrf',   csrf);

  const res  = await fetch(apiUrl, {method:'POST',body:fd,headers:{'X-Requested-With':'XMLHttpRequest'}});
  const data = await res.json();

  if (data.success) {
    const msgs = {confirmed:'✅ Marqué comme confirmé !',cleared:'✓ Marqué comme éliminé',suspect:'↩ Remis en suspect',ignored:'— Ignoré'};
    showToast(msgs[status] || 'Mis à jour');
    setTimeout(() => window.location.reload(), 800);
  }
}

// ── Élimination ───────────────────────────────────────────────────────────────
let elimItemId = null;
let elimDuration = 21;

function openElim(itemId, itemName) {
  elimItemId = itemId;
  document.getElementById('elimDesc').innerHTML =
    `Arrêtez de consommer <strong>${itemName}</strong> pendant la durée choisie et observez l'évolution de votre peau.`;
  document.getElementById('elimModal').classList.add('open');
}
function closeElim() {
  document.getElementById('elimModal').classList.remove('open');
}
function selectDuration(days, btn) {
  elimDuration = days;
  document.querySelectorAll('.dur-btn').forEach(b => b.classList.remove('active'));
  btn.classList.add('active');
}
async function confirmElim() {
  if (!elimItemId) return;
  const fd = new FormData();
  fd.append('action',   'start_elimination');
  fd.append('item_id',  elimItemId);
  fd.append('duration', elimDuration);
  fd.append('_csrf',    csrf);

  const res  = await fetch(apiUrl, {method:'POST',body:fd,headers:{'X-Requested-With':'XMLHttpRequest'}});
  const data = await res.json();

  closeElim();
  if (data.success) {
    showToast(`🧪 Test démarré ! Fin le ${data.end_date}`);
    setTimeout(() => window.location.reload(), 1000);
  } else {
    showToast(data.error || 'Erreur', 'error');
  }
}

// ── Toast ─────────────────────────────────────────────────────────────────────
function showToast(msg, type='success') {
  const el = document.getElementById('toastMsg');
  el.textContent = msg;
  el.style.color = type === 'error' ? 'var(--rose)' : 'var(--emerald)';
  el.style.opacity = '1';
  setTimeout(() => el.style.opacity = '0', 3000);
}
</script>

<?php
// Chart évolution
$chartJson = json_encode(array_map(fn($r) => [
  'date'   => $r['entry_date'],
  'score'  => (int)$r['skin_score'],
  'crisis' => (bool)$r['is_crisis'],
], $skinEvolution));

$content    = ob_get_clean();
$extraHead  = '';
$extraScript = "";
require DS_VIEWS . 'layouts/app.php';