File: /homepages/35/d993672390/htdocs/dermiteseborrheique/app/history.php
<?php
define('DS_APP', true);
require_once dirname(__DIR__) . '/includes/init.php';
$user = auth_require();
// ── Paramètres ────────────────────────────────────────────────────────────────
$year = (int)($_GET['year'] ?? date('Y'));
$month = (int)($_GET['month'] ?? date('n'));
if ($month < 1) { $month = 12; $year--; }
if ($month > 12){ $month = 1; $year++; }
$monthStart = sprintf('%04d-%02d-01', $year, $month);
$monthEnd = date('Y-m-t', strtotime($monthStart));
$today = date('Y-m-d');
$jours = ['Lun','Mar','Mer','Jeu','Ven','Sam','Dim'];
$moisFR = ['','Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'];
// ── Données du mois ───────────────────────────────────────────────────────────
$monthEntries = db_fetch_all(
"SELECT je.*, COUNT(ei.id) as item_count
FROM journal_entries je
LEFT JOIN entry_items ei ON je.id = ei.entry_id
WHERE je.user_id = ? AND je.entry_date BETWEEN ? AND ?
GROUP BY je.id
ORDER BY je.entry_date DESC",
[$user['id'], $monthStart, $monthEnd]
);
// Index par date pour le calendrier
$entriesByDate = [];
foreach ($monthEntries as $e) {
$entriesByDate[$e['entry_date']] = $e;
}
// Stats du mois
$avgScore = $monthEntries ? round(array_sum(array_column($monthEntries, 'skin_score')) / count($monthEntries), 1) : null;
$crisisCount = count(array_filter($monthEntries, fn($e) => $e['is_crisis']));
$entryCount = count($monthEntries);
$daysInMonth = (int)date('t', strtotime($monthStart));
$fillRate = round(($entryCount / $daysInMonth) * 100);
// Entrée sélectionnée (pour le détail)
$selectedDate = $_GET['date'] ?? null;
$selectedEntry = null;
$selectedItems = [];
if ($selectedDate && preg_match('/^\d{4}-\d{2}-\d{2}$/', $selectedDate)) {
$selectedEntry = db_fetch(
"SELECT * FROM journal_entries WHERE user_id = ? AND entry_date = ?",
[$user['id'], $selectedDate]
);
if ($selectedEntry) {
$selectedItems = db_fetch_all(
"SELECT ei.*, i.name, i.category, i.brand, i.warning_ingredients
FROM entry_items ei
JOIN items i ON ei.item_id = i.id
WHERE ei.entry_id = ?
ORDER BY i.category, i.name",
[$selectedEntry['id']]
);
}
}
// Navigation prev/next mois
$prevMonth = $month - 1 < 1 ? 12 : $month - 1;
$prevYear = $month - 1 < 1 ? $year - 1 : $year;
$nextMonth = $month + 1 > 12 ? 1 : $month + 1;
$nextYear = $month + 1 > 12 ? $year + 1 : $year;
$canGoNext = "$year-" . sprintf('%02d', $month) . "-01" < date('Y-m-01');
// Helpers
function skinEmoji(int $s): string {
return match(true) { $s>=5=>'😊', $s>=4=>'🙂', $s>=3=>'😐', $s>=2=>'😕', default=>'😞' };
}
function skinBg(int $s): string {
return match(true) { $s>=4=>'rgba(0,232,122,0.12)', $s>=3=>'rgba(255,181,71,0.08)', default=>'rgba(255,107,138,0.1)' };
}
function skinBorder(int $s): string {
return match(true) { $s>=4=>'rgba(0,232,122,0.3)', $s>=3=>'rgba(255,181,71,0.2)', default=>'rgba(255,107,138,0.3)' };
}
function skinColor(int $s): string {
return match(true) { $s>=4=>'#00E87A', $s>=3=>'#FFB547', default=>'#FF6B8A' };
}
$catIcons = ['food'=>'🍽️','drink'=>'🥤','alcohol'=>'🍷','tobacco'=>'🚬','cosmetic'=>'🧴','haircare'=>'💇','medication'=>'💊','supplement'=>'🌿','other'=>'📦'];
$catLabels= ['food'=>'Alimentation','drink'=>'Boissons','alcohol'=>'Alcool','tobacco'=>'Tabac','cosmetic'=>'Cosmétiques','haircare'=>'Soins capillaires','medication'=>'Médicaments','supplement'=>'Compléments','other'=>'Autre'];
$pageTitle = 'Historique';
$pageSubtitle= $moisFR[$month] . ' ' . $year;
$activePage = 'history';
ob_start();
?>
<style>
.history-layout{display:grid;grid-template-columns:1fr 320px;gap:20px;align-items:start;}
/* Month nav */
.month-nav{display:flex;align-items:center;gap:12px;margin-bottom:20px;}
.mnav-btn{background:var(--surface);border:1px solid var(--border);color:var(--text);width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;text-decoration:none;font-size:1rem;font-weight:700;transition:all 0.2s;flex-shrink:0;}
.mnav-btn:hover{border-color:var(--cyan);color:var(--cyan);}
.mnav-btn.disabled{opacity:0.3;pointer-events:none;}
.mnav-title{flex:1;text-align:center;font-size:1.1rem;font-weight:800;letter-spacing:-0.02em;}
/* Stats bande */
.month-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:20px;}
.mstat{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px;text-align:center;}
.mstat-val{font-size:1.6rem;font-weight:900;letter-spacing:-0.03em;line-height:1;}
.mstat-label{font-size:0.65rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.06em;margin-top:4px;}
/* Calendrier */
.calendar-card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:20px;margin-bottom:16px;}
.cal-header{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin-bottom:8px;}
.cal-day-name{text-align:center;font-size:0.68rem;font-weight:700;color:var(--text-dim);text-transform:uppercase;padding:4px 0;}
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;}
.cal-cell{
aspect-ratio:1;border-radius:8px;display:flex;flex-direction:column;
align-items:center;justify-content:center;cursor:default;
border:1px solid transparent;transition:all 0.15s;
font-size:0.8rem;font-weight:600;position:relative;
}
.cal-cell.has-entry{cursor:pointer;}
.cal-cell.has-entry:hover{border-color:var(--border-bright);transform:scale(1.05);}
.cal-cell.selected{border-color:var(--cyan)!important;box-shadow:0 0 0 2px rgba(0,212,200,0.2);}
.cal-cell.today-cell{border-color:rgba(255,255,255,0.15);}
.cal-cell.empty{opacity:0;}
.cal-cell.future{opacity:0.25;cursor:not-allowed;}
.cal-score{font-size:0.65rem;color:rgba(255,255,255,0.5);margin-top:1px;}
.cal-crisis-dot{position:absolute;top:3px;right:3px;width:5px;height:5px;background:var(--rose);border-radius:50%;}
.cal-cell.no-entry{background:rgba(255,255,255,0.02);color:var(--text-dim);}
/* Liste entrées */
.entries-list{display:flex;flex-direction:column;gap:8px;}
.entry-item{
background:var(--surface);border:1px solid var(--border);
border-radius:12px;padding:14px 16px;
display:flex;align-items:center;gap:14px;
cursor:pointer;transition:all 0.2s;text-decoration:none;
}
.entry-item:hover{border-color:var(--border-bright);transform:translateX(2px);}
.entry-item.selected{border-color:rgba(0,212,200,0.3);background:var(--cyan-glow);}
.entry-item.is-crisis{border-color:rgba(255,107,138,0.25);}
.ei-date{font-size:0.78rem;color:var(--text-mid);width:80px;flex-shrink:0;line-height:1.3;}
.ei-score{width:36px;height:36px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0;}
.ei-info{flex:1;min-width:0;}
.ei-score-text{font-size:0.82rem;font-weight:700;}
.ei-items{font-size:0.75rem;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.ei-crisis{font-size:0.65rem;font-weight:700;color:var(--rose);background:rgba(255,107,138,0.1);padding:2px 7px;border-radius:100px;flex-shrink:0;}
.ei-arrow{color:var(--text-dim);font-size:0.85rem;flex-shrink:0;}
/* Détail entrée */
.detail-card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:20px;position:sticky;top:80px;}
.detail-header{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding-bottom:14px;border-bottom:1px solid var(--border);}
.detail-score-big{font-size:2.4rem;font-weight:900;letter-spacing:-0.04em;line-height:1;}
.detail-date{font-size:0.82rem;color:var(--text-mid);margin-top:2px;}
.detail-section{margin-bottom:16px;}
.detail-section-title{font-size:0.68rem;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:8px;}
.detail-item-chip{display:inline-flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:4px 9px;margin:2px;font-size:0.75rem;color:var(--text-mid);}
.detail-item-chip.warn{border-color:rgba(255,181,71,0.3);color:var(--amber);}
.detail-lifestyle{display:grid;grid-template-columns:1fr 1fr;gap:8px;}
.ls-item{background:var(--surface2);border-radius:8px;padding:8px 10px;}
.ls-item-label{font-size:0.65rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.06em;}
.ls-item-val{font-size:0.88rem;font-weight:700;color:var(--text);margin-top:2px;}
.detail-zones{display:flex;flex-wrap:wrap;gap:4px;}
.zone-chip{font-size:0.72rem;padding:3px 8px;border-radius:6px;font-weight:500;}
.zone-chip.s1{background:rgba(255,181,71,0.1);color:var(--amber);}
.zone-chip.s2{background:rgba(255,107,138,0.1);color:var(--rose);}
.zone-chip.s3{background:rgba(255,107,138,0.2);color:var(--rose);font-weight:700;}
.detail-notes{font-size:0.82rem;color:var(--text-mid);line-height:1.6;font-style:italic;}
.detail-edit-btn{display:flex;align-items:center;justify-content:center;gap:6px;background:var(--cyan);color:var(--bg);padding:9px;border-radius:8px;text-decoration:none;font-size:0.82rem;font-weight:800;transition:all 0.2s;margin-top:14px;}
.detail-edit-btn:hover{background:#fff;}
.no-selection{text-align:center;padding:30px 20px;color:var(--text-dim);font-size:0.85rem;}
@media(max-width:900px){
.history-layout{grid-template-columns:1fr;}
.detail-card{position:static;}
.month-stats{grid-template-columns:repeat(2,1fr);}
}
</style>
<!-- Navigation mois -->
<div class="month-nav">
<a href="?year=<?= $prevYear ?>&month=<?= $prevMonth ?>" class="mnav-btn">←</a>
<div class="mnav-title"><?= $moisFR[$month] ?> <?= $year ?></div>
<a href="?year=<?= $nextYear ?>&month=<?= $nextMonth ?>"
class="mnav-btn <?= !$canGoNext ? 'disabled' : '' ?>">→</a>
</div>
<!-- Stats du mois -->
<div class="month-stats">
<div class="mstat">
<div class="mstat-val" style="color:var(--cyan)"><?= $entryCount ?><span style="font-size:1rem;opacity:0.5">/<?= $daysInMonth ?></span></div>
<div class="mstat-label">Entrées</div>
</div>
<div class="mstat">
<div class="mstat-val" style="color:<?= $avgScore >= 4 ? 'var(--emerald)' : ($avgScore >= 3 ? 'var(--amber)' : 'var(--rose)') ?>"><?= $avgScore ?? '—' ?></div>
<div class="mstat-label">Score moyen</div>
</div>
<div class="mstat">
<div class="mstat-val" style="color:var(--rose)"><?= $crisisCount ?></div>
<div class="mstat-label">Crises</div>
</div>
<div class="mstat">
<div class="mstat-val" style="color:var(--amber)"><?= $fillRate ?>%</div>
<div class="mstat-label">Taux de remplissage</div>
</div>
</div>
<div class="history-layout">
<!-- ── Colonne principale ── -->
<div>
<!-- Calendrier -->
<div class="calendar-card">
<div class="cal-header">
<?php foreach ($jours as $j): ?>
<div class="cal-day-name"><?= $j ?></div>
<?php endforeach; ?>
</div>
<div class="cal-grid">
<?php
$firstDow = (int)date('N', strtotime($monthStart)); // 1=Lun, 7=Dim
// Cases vides avant le 1er
for ($i = 1; $i < $firstDow; $i++):
?>
<div class="cal-cell empty"></div>
<?php endfor;
for ($day = 1; $day <= $daysInMonth; $day++):
$dateStr = sprintf('%04d-%02d-%02d', $year, $month, $day);
$entry = $entriesByDate[$dateStr] ?? null;
$isToday = $dateStr === $today;
$isFuture= $dateStr > $today;
$isSelected = $dateStr === $selectedDate;
$cls = '';
$bg = $brd = $color = '';
if ($isFuture) {
$cls = 'future';
} elseif ($entry) {
$s = (int)$entry['skin_score'];
$bg = skinBg($s);
$brd = skinBorder($s);
$color= skinColor($s);
$cls = 'has-entry' . ($isSelected ? ' selected' : '') . ($isToday ? ' today-cell' : '');
} else {
$cls = 'no-entry' . ($isToday ? ' today-cell' : '');
}
?>
<div class="cal-cell <?= $cls ?>"
style="background:<?= $bg ?>;border-color:<?= $brd ?: 'transparent' ?>;color:<?= $color ?: 'inherit' ?>;"
<?= ($entry && !$isFuture) ? "onclick=\"selectDate('{$dateStr}')\"" : '' ?>>
<?= $day ?>
<?php if ($entry): ?>
<div class="cal-score"><?= $entry['skin_score'] ?>/5</div>
<?php if ($entry['is_crisis']): ?>
<div class="cal-crisis-dot"></div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php endfor; ?>
</div>
<div style="display:flex;gap:16px;margin-top:14px;padding-top:14px;border-top:1px solid var(--border);flex-wrap:wrap;">
<div style="display:flex;align-items:center;gap:6px;font-size:0.72rem;color:var(--text-dim);">
<div style="width:12px;height:12px;border-radius:3px;background:rgba(0,232,122,0.12);border:1px solid rgba(0,232,122,0.3);"></div> Bonne journée (4-5)
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:0.72rem;color:var(--text-dim);">
<div style="width:12px;height:12px;border-radius:3px;background:rgba(255,181,71,0.08);border:1px solid rgba(255,181,71,0.2);"></div> Journée moyenne (3)
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:0.72rem;color:var(--text-dim);">
<div style="width:12px;height:12px;border-radius:3px;background:rgba(255,107,138,0.1);border:1px solid rgba(255,107,138,0.3);"></div> Mauvaise journée (1-2)
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:0.72rem;color:var(--text-dim);">
<div style="width:5px;height:5px;border-radius:50%;background:var(--rose);"></div> Crise
</div>
</div>
</div>
<!-- Liste des entrées du mois -->
<?php if (!empty($monthEntries)): ?>
<div class="entries-list">
<?php
$jFR = ['','lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'];
$mFR = ['','jan','fév','mar','avr','mai','juin','juil','août','sep','oct','nov','déc'];
foreach ($monthEntries as $e):
$d = new DateTime($e['entry_date']);
$dow = (int)$d->format('N');
$dm = (int)$d->format('j');
$mn = (int)$d->format('n');
$dateLabel = $jFR[$dow] . ' ' . $dm . ' ' . $mFR[$mn];
$s = (int)$e['skin_score'];
$isSel = $e['entry_date'] === $selectedDate;
?>
<a href="?year=<?= $year ?>&month=<?= $month ?>&date=<?= $e['entry_date'] ?>"
class="entry-item <?= $e['is_crisis'] ? 'is-crisis' : '' ?> <?= $isSel ? 'selected' : '' ?>">
<div class="ei-date"><?= $dateLabel ?></div>
<div class="ei-score" style="background:<?= skinBg($s) ?>;border:1px solid <?= skinBorder($s) ?>">
<?= skinEmoji($s) ?>
</div>
<div class="ei-info">
<div class="ei-score-text" style="color:<?= skinColor($s) ?>">Score <?= $s ?>/5</div>
<div class="ei-items"><?= $e['item_count'] ?> item<?= $e['item_count'] > 1 ? 's' : '' ?> enregistré<?= $e['item_count'] > 1 ? 's' : '' ?></div>
</div>
<?php if ($e['is_crisis']): ?>
<div class="ei-crisis">⚠ Crise</div>
<?php endif; ?>
<div class="ei-arrow">→</div>
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<div style="text-align:center;padding:40px;color:var(--text-dim);font-size:0.85rem;">
Aucune entrée ce mois-ci.<br>
<a href="<?= url('app/journal.php') ?>" style="color:var(--cyan);text-decoration:none;font-weight:600;">Commencer le journal →</a>
</div>
<?php endif; ?>
</div><!-- /colonne principale -->
<!-- ── Détail (sidebar) ── -->
<div>
<div class="detail-card">
<?php if ($selectedEntry):
$s = (int)$selectedEntry['skin_score'];
$d = new DateTime($selectedEntry['entry_date']);
$dow = (int)$d->format('N');
$dm = (int)$d->format('j');
$mn = (int)$d->format('n');
$moisLong = ['','janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'];
$jLong = ['','lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'];
$dateLabelFull = $jLong[$dow] . ' ' . $dm . ' ' . $moisLong[$mn];
$zones = json_decode($selectedEntry['skin_zones'] ?? '{}', true) ?? [];
$zoneNames = ['scalp'=>'Cuir chevelu','face'=>'Visage','eyebrows'=>'Sourcils','nose'=>'Nez','ears'=>'Oreilles','chest'=>'Poitrine'];
$sevLabels = ['','Léger','Modéré','Sévère'];
// Grouper items par catégorie
$itemsByCat = [];
foreach ($selectedItems as $item) $itemsByCat[$item['category']][] = $item;
?>
<div class="detail-header">
<div>
<div class="detail-score-big" style="color:<?= skinColor($s) ?>"><?= skinEmoji($s) ?> <?= $s ?><span style="font-size:1rem;opacity:0.4">/5</span></div>
<div class="detail-date"><?= $dateLabelFull ?></div>
<?php if ($selectedEntry['is_crisis']): ?>
<div style="font-size:0.72rem;color:var(--rose);font-weight:700;margin-top:4px;">
⚠ Crise <?= $selectedEntry['crisis_severity'] ? ['','légère','modérée','sévère'][$selectedEntry['crisis_severity']] : '' ?>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!empty($zones)): ?>
<div class="detail-section">
<div class="detail-section-title">Zones touchées</div>
<div class="detail-zones">
<?php foreach ($zones as $zone => $sev):
if ($sev === 0) continue;
?>
<span class="zone-chip s<?= $sev ?>"><?= $zoneNames[$zone] ?? $zone ?> — <?= $sevLabels[$sev] ?></span>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if (!empty($selectedItems)): ?>
<div class="detail-section">
<div class="detail-section-title">Aliments & produits (<?= count($selectedItems) ?>)</div>
<?php foreach ($itemsByCat as $cat => $items): ?>
<div style="margin-bottom:6px;">
<div style="font-size:0.65rem;color:var(--text-dim);margin-bottom:3px;"><?= $catLabels[$cat] ?? $cat ?></div>
<?php foreach ($items as $item):
$hasWarn = !empty($item['warning_ingredients']);
?>
<span class="detail-item-chip <?= $hasWarn ? 'warn' : '' ?>">
<?= $catIcons[$cat] ?? '📦' ?>
<?= e($item['name']) ?>
<?php if ($hasWarn): ?> ⚠<?php endif; ?>
</span>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$hasLifestyle = $selectedEntry['sleep_hours'] || $selectedEntry['sleep_quality'] ||
$selectedEntry['stress_level'] || $selectedEntry['exercise_mins'] ||
$selectedEntry['water_liters'];
?>
<?php if ($hasLifestyle): ?>
<div class="detail-section">
<div class="detail-section-title">Facteurs du jour</div>
<div class="detail-lifestyle">
<?php if ($selectedEntry['sleep_hours']): ?>
<div class="ls-item">
<div class="ls-item-label">😴 Sommeil</div>
<div class="ls-item-val"><?= $selectedEntry['sleep_hours'] ?>h</div>
</div>
<?php endif; ?>
<?php if ($selectedEntry['sleep_quality']): ?>
<div class="ls-item">
<div class="ls-item-label">⭐ Qualité sommeil</div>
<div class="ls-item-val"><?= str_repeat('⭐', (int)$selectedEntry['sleep_quality']) ?></div>
</div>
<?php endif; ?>
<?php if ($selectedEntry['stress_level']): ?>
<div class="ls-item">
<div class="ls-item-label">😰 Stress</div>
<div class="ls-item-val"><?= str_repeat('🔥', (int)$selectedEntry['stress_level']) ?></div>
</div>
<?php endif; ?>
<?php if ($selectedEntry['water_liters']): ?>
<div class="ls-item">
<div class="ls-item-label">💧 Eau</div>
<div class="ls-item-val"><?= $selectedEntry['water_liters'] ?>L</div>
</div>
<?php endif; ?>
<?php if ($selectedEntry['exercise_mins']): ?>
<div class="ls-item">
<div class="ls-item-label">🏃 Exercice</div>
<div class="ls-item-val"><?= $selectedEntry['exercise_mins'] ?> min</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($selectedEntry['notes']): ?>
<div class="detail-section">
<div class="detail-section-title">Notes</div>
<div class="detail-notes">"<?= e($selectedEntry['notes']) ?>"</div>
</div>
<?php endif; ?>
<a href="<?= url('app/journal.php?date=' . $selectedEntry['entry_date']) ?>" class="detail-edit-btn">
✏️ Modifier cette entrée
</a>
<?php else: ?>
<div class="no-selection">
<div style="font-size:2rem;margin-bottom:10px;">📅</div>
Cliquez sur un jour du calendrier ou une entrée de la liste pour voir le détail.
</div>
<?php endif; ?>
</div>
</div>
</div><!-- /layout -->
<script>
function selectDate(date) {
const url = new URL(window.location);
url.searchParams.set('date', date);
window.location = url;
}
</script>
<?php
$content = ob_get_clean();
require DS_VIEWS . 'layouts/app.php';