<?php
declare(strict_types=1);
require_once __DIR__ . '/bootstrap.php';
admin_require_login();

$recentPerPage = 20;
$sessionsPerPage = 10;

function activity_paginate(array $items, int $page, int $perPage): array
{
    $total = count($items);
    $pages = max(1, (int)ceil($total / max(1, $perPage)));
    $page = min(max(1, $page), $pages);
    return [
        'items' => array_slice($items, ($page - 1) * $perPage, $perPage),
        'pagination' => [
            'page' => $page,
            'per_page' => $perPage,
            'pages' => $pages,
            'total' => $total,
        ],
    ];
}

if (request_method() === 'POST') {
    header('Content-Type: application/json; charset=utf-8');
    if (!verify_csrf($_POST['_csrf'] ?? '')) {
        echo json_encode(['ok' => false, 'message' => 'csrf']);
        exit;
    }
    $action = (string)($_POST['action'] ?? '');
    if ($action === 'clear_logs') {
        $result = run_site_log_cleanup_now();
        echo json_encode(['ok' => true, 'result' => $result]);
        exit;
    }
    if ($action === 'save_activity_settings') {
        save_activity_settings([
            'auto_cleanup' => !empty($_POST['auto_cleanup']),
            'retention_days' => max(1, (int)($_POST['retention_days'] ?? 30)),
        ]);
        echo json_encode(['ok' => true, 'settings' => activity_settings()]);
        exit;
    }
    echo json_encode(['ok' => false, 'message' => 'unknown_action']);
    exit;
}

if (isset($_GET['ajax']) && $_GET['ajax'] === 'detail') {
    header('Content-Type: application/json; charset=utf-8');
    $sessionKey = (string)($_GET['session'] ?? '');
    echo json_encode([
        'ok' => true,
        'actions' => activity_actions_for_session($sessionKey, 120),
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

if (isset($_GET['ajax']) && $_GET['ajax'] === '1') {
    header('Content-Type: application/json; charset=utf-8');
    $recentPage = max(1, (int)($_GET['recent_page'] ?? 1));
    $sessionsPage = max(1, (int)($_GET['sessions_page'] ?? 1));
    $selectedDate = (string)($_GET['date'] ?? date('Y-m-d'));
    $selectedMonth = (string)($_GET['month'] ?? date('Y-m'));

    $online = online_visitors();
    $summary = activity_summary();
    $day = activity_day_breakdown($selectedDate);
    $month = activity_month_matrix($selectedMonth);
    $settings = activity_settings();

    $allRecent = recent_activity(5000, $selectedDate);
    $recentBundle = activity_paginate($allRecent, $recentPage, $recentPerPage);

    $allSessions = activity_sessions();
    $sessionsBundle = activity_paginate($allSessions, $sessionsPage, $sessionsPerPage);
    $sessionsPageItems = activity_enrich_sessions($sessionsBundle['items'], 8);

    echo json_encode([
        'ok' => true,
        'online' => $online,
        'online_count' => count($online),
        'summary' => $summary,
        'day' => $day,
        'month' => $month,
        'recent' => $recentBundle['items'],
        'recent_pagination' => $recentBundle['pagination'],
        'sessions' => $sessionsPageItems,
        'sessions_pagination' => $sessionsBundle['pagination'],
        'settings' => $settings,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

$activityCfg = activity_settings();
ob_start();
?>
<style>
.activity-page-v28{display:grid;gap:18px}
.activity-shell{background:linear-gradient(180deg,#fffdfa 0%,#f8f2e7 100%);border:1px solid #eadfce;border-radius:24px;padding:22px;box-shadow:0 18px 42px rgba(41,46,37,.06)}
.activity-shell__top{display:flex;justify-content:space-between;gap:18px;align-items:flex-start;flex-wrap:wrap}
.activity-shell__title h2{margin:0 0 8px;font-size:28px;color:#22352a}.activity-shell__title p{margin:0;color:#6d6a63;max-width:720px}
.activity-top-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.activity-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
.activity-kpi{background:linear-gradient(180deg,#fff 0%,#fbf7ef 100%);border:1px solid #ebdfcf;border-radius:22px;padding:18px;box-shadow:0 10px 26px rgba(38,47,40,.04)}
.activity-kpi__label{font-size:13px;color:#7d756a}.activity-kpi strong{display:block;font-size:34px;line-height:1.05;margin-top:10px;color:#1f3428}.activity-kpi small{display:block;margin-top:10px;color:#7b756d}
.activity-controls{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
.activity-controls label{display:grid;gap:6px;font-size:13px;color:#6f6a60}.activity-controls input,.activity-controls select{width:100%}
.activity-card{background:#fff;border:1px solid #eadfce;border-radius:22px;padding:18px;box-shadow:0 12px 28px rgba(34,42,35,.05)}
.activity-card__head{display:flex;justify-content:space-between;gap:14px;align-items:flex-start;flex-wrap:wrap}.activity-card__head h3{margin:0;font-size:20px}.activity-card__head p{margin:6px 0 0;color:#716c64}
.activity-card__actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.activity-card.is-collapsed .activity-card__body{display:none}
.activity-grid{display:grid;grid-template-columns:1.15fr .85fr;gap:18px}
.activity-grid--secondary{display:grid;grid-template-columns:.95fr 1.05fr;gap:18px}
.activity-online-table .lead-badge,.activity-sessions .lead-badge{white-space:nowrap}
.activity-table td,.activity-table th{vertical-align:top}
.activity-calendar-pro{margin-top:14px;background:linear-gradient(180deg,#fff 0%,#f8f5ef 100%);border:1px solid #ebdfcf;border-radius:22px;padding:16px}
.activity-calendar-pro__header,.activity-calendar-pro__grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:10px}
.activity-calendar-pro__header span{font-size:11px;letter-spacing:.06em;text-transform:uppercase;text-align:center;color:#767269;padding:4px 0}
.activity-calendar-pro__day,.activity-calendar-pro__empty{min-height:84px;border-radius:20px}
.activity-calendar-pro__empty{background:linear-gradient(180deg,rgba(134,126,111,.06),rgba(134,126,111,.02));border:1px dashed rgba(142,130,111,.18)}
.activity-calendar-pro__day{border:1px solid #e7d8c4;background:linear-gradient(180deg,#fff 0%,#fbfaf7 100%);padding:12px;display:flex;flex-direction:column;justify-content:space-between;align-items:flex-start;cursor:pointer;transition:.16s transform,.16s box-shadow,.16s border-color}
.activity-calendar-pro__day:hover{transform:translateY(-1px);box-shadow:0 12px 24px rgba(37,55,43,.08)}
.activity-calendar-pro__day strong{font-size:20px;color:#1c3125}.activity-calendar-pro__day small{font-size:12px;color:#607066}
.activity-calendar-pro__day .dot{display:inline-flex;align-items:center;gap:7px;font-size:12px;color:#5f6c63}.activity-calendar-pro__day .dot i{display:inline-block;width:9px;height:9px;border-radius:999px;background:#d5dfd8}
.activity-calendar-pro__day.lv0{background:linear-gradient(180deg,#fff 0%,#fbfaf7 100%)}
.activity-calendar-pro__day.lv1{background:linear-gradient(180deg,#f4fbf5 0%,#edf7ef 100%)}
.activity-calendar-pro__day.lv1 .dot i{background:#a8cfb2}
.activity-calendar-pro__day.lv2{background:linear-gradient(180deg,#e2f3e5 0%,#d6ebdb 100%);border-color:#bfd8c5}.activity-calendar-pro__day.lv2 .dot i{background:#70b386}
.activity-calendar-pro__day.lv3{background:linear-gradient(180deg,#cde8d3 0%,#bedec8 100%);border-color:#9fc5aa}.activity-calendar-pro__day.lv3 .dot i{background:#2f7c46}
.activity-calendar-pro__day.is-active{border-color:#23412f;box-shadow:0 0 0 2px rgba(35,65,47,.18),0 10px 24px rgba(35,65,47,.12)}
.activity-calendar-legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:14px;color:#6b6f69;font-size:12px}.activity-calendar-legend span{display:inline-flex;align-items:center;gap:7px}.activity-calendar-legend i{display:inline-block;width:18px;height:10px;border-radius:999px;background:#eef4ef}.activity-calendar-legend .lv1{background:#edf7ef}.activity-calendar-legend .lv2{background:#d6ebdb}.activity-calendar-legend .lv3{background:#bedec8}
.activity-bars{display:grid;gap:10px}.activity-bar{display:grid;grid-template-columns:100px minmax(0,1fr) 56px;gap:10px;align-items:center}.activity-bar div{height:12px;background:#efe7db;border-radius:999px;overflow:hidden}.activity-bar i{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,#1f5c36,#79b187)}
.activity-session-list{display:grid;gap:14px}.activity-session{border:1px solid #eadfce;border-radius:22px;background:linear-gradient(180deg,#fff 0%,#fbf7ef 100%);padding:16px;box-shadow:0 12px 24px rgba(41,47,40,.05)}
.activity-session.is-collapsed .activity-session__body{display:none}.activity-session__head{display:flex;justify-content:space-between;gap:14px;align-items:flex-start;flex-wrap:wrap}.activity-session__head h4{margin:0 0 6px;font-size:17px}.activity-session__head p{margin:0;color:#6f6a62}
.activity-session__meta{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:14px}.activity-session__meta span{display:block;padding:12px;border-radius:16px;background:#fff;border:1px solid #eee3d4;color:#4e544f;overflow-wrap:anywhere}.activity-session__meta strong{display:block;margin-bottom:4px;color:#26352b}
.activity-pagination{display:flex;justify-content:flex-end;margin-top:14px}.activity-pagination__inner{display:flex;gap:8px;flex-wrap:wrap}
.flag-badge--rich{display:inline-flex;align-items:center;gap:8px;padding:7px 10px;border-radius:999px;background:#fffdf9;border:1px solid #eadfce;max-width:100%}.flag-badge__flag{font-size:18px;line-height:1}.flag-badge__text{display:flex;flex-direction:column;min-width:0}.flag-badge__text small{color:#7d776d;line-height:1.2}
.activity-section-note{margin-top:8px;color:#6e6a64}
.activity-table td[data-short],.activity-table th[data-short]{max-width:220px}.activity-table td[data-short]{overflow-wrap:anywhere}
@media (max-width:1100px){.activity-kpis,.activity-controls,.activity-grid,.activity-grid--secondary{grid-template-columns:1fr 1fr}.activity-session__meta{grid-template-columns:1fr}}
@media (max-width:760px){.activity-kpis,.activity-controls,.activity-grid,.activity-grid--secondary{grid-template-columns:1fr}.activity-calendar-pro__day,.activity-calendar-pro__empty{min-height:68px}.activity-bar{grid-template-columns:80px minmax(0,1fr) 44px}}
</style>
<div class="activity-page-v28" data-native-collapse-root id="activity-root">
  <section class="activity-shell">
    <div class="activity-shell__top">
      <div class="activity-shell__title">
        <h2>Онлайн-аналитика и трафик</h2>
        <p>Кто сейчас на сайте, откуда пришёл, как двигался по страницам и насколько он тёплый как лид. Раздел оптимизирован: детали посетителя грузятся только при открытии.</p>
      </div>
      <div class="activity-top-actions">
        <button class="button" type="button" data-activity-refresh>Обновить сейчас</button>
        <label class="pill"><input type="checkbox" data-activity-autorefresh checked> автообновление 10с</label>
        <button class="button button--ghost" type="button" data-activity-clear>Очистить логи</button>
      </div>
    </div>
    <div class="activity-kpis" style="margin-top:18px">
      <div class="activity-kpi"><span class="activity-kpi__label">Сейчас онлайн</span><strong data-kpi-online>0</strong><small>активны за последние 90 секунд</small></div>
      <div class="activity-kpi"><span class="activity-kpi__label">Событий за день</span><strong data-kpi-actions>0</strong><small>в выбранную дату</small></div>
      <div class="activity-kpi"><span class="activity-kpi__label">Уникальных IP</span><strong data-kpi-ips>0</strong><small>по выбранной дате</small></div>
      <div class="activity-kpi"><span class="activity-kpi__label">Сохранённых сессий</span><strong data-kpi-sessions>0</strong><small>в журнале активности</small></div>
    </div>
    <div class="activity-controls" style="margin-top:18px">
      <label><span>Дата детального просмотра</span><input type="date" value="<?= e(date('Y-m-d')) ?>" data-activity-date></label>
      <label><span>Месяц календаря</span><input type="month" value="<?= e(date('Y-m')) ?>" data-activity-month></label>
      <form method="post" data-activity-settings-form>
        <input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>">
        <input type="hidden" name="action" value="save_activity_settings">
        <label><span>Хранить историю, дней</span><input type="number" min="1" name="retention_days" value="<?= e((string)$activityCfg['retention_days']) ?>"></label>
      </form>
      <form method="post" data-activity-settings-form>
        <input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>">
        <input type="hidden" name="action" value="save_activity_settings">
        <input type="hidden" name="retention_days" value="<?= e((string)$activityCfg['retention_days']) ?>">
        <label><span>Автоочистка логов</span><select name="auto_cleanup"><option value="1" <?= !empty($activityCfg['auto_cleanup']) ? 'selected' : '' ?>>включена</option><option value="0" <?= empty($activityCfg['auto_cleanup']) ? 'selected' : '' ?>>выключена</option></select></label>
      </form>
    </div>
    <div class="activity-section-note" data-activity-settings-note style="display:none">Настройки сохранены</div>
  </section>

  <div class="activity-grid">
    <section class="activity-card" data-activity-card>
      <div class="activity-card__head">
        <div><h3>Онлайн сейчас</h3><p>Живые посетители с их текущей страницей, источником и теплотой лида.</p></div>
        <div class="activity-card__actions"><button class="button button--ghost button--sm" type="button" data-activity-toggle>Свернуть</button></div>
      </div>
      <div class="activity-card__body">
        <div class="table-wrap">
          <table class="admin-table activity-table"><thead><tr><th>IP</th><th>Гео</th><th>Страница</th><th>Источник</th><th>Hits</th><th>Теплота</th><th>Последняя активность</th></tr></thead><tbody data-online-table><tr><td colspan="7">Загрузка…</td></tr></tbody></table>
        </div>
      </div>
    </section>

    <section class="activity-card" data-activity-card>
      <div class="activity-card__head">
        <div><h3>День в разрезе</h3><p>Почасовая активность и самые частые страны/источники за выбранную дату.</p></div>
        <div class="activity-card__actions"><button class="button button--ghost button--sm" type="button" data-activity-toggle>Свернуть</button></div>
      </div>
      <div class="activity-card__body">
        <div class="activity-bars" data-hours-bars></div>
        <div class="activity-grid--secondary" style="margin-top:16px">
          <div class="activity-card" style="padding:14px"><div class="activity-card__head"><div><h3 style="font-size:16px">Страны</h3></div></div><div class="activity-card__body"><div class="activity-bars" data-countries-bars></div></div></div>
          <div class="activity-card" style="padding:14px"><div class="activity-card__head"><div><h3 style="font-size:16px">Источники</h3></div></div><div class="activity-card__body"><div class="activity-bars" data-referrers-bars></div></div></div>
        </div>
      </div>
    </section>
  </div>

  <section class="activity-card" data-activity-card>
    <div class="activity-card__head">
      <div><h3>Календарь трафика</h3><p>Тепловая карта по дням месяца. Кликни по дню, чтобы обновить детализацию справа и журнал действий.</p></div>
      <div class="activity-card__actions"><button class="button button--ghost button--sm" type="button" data-activity-toggle>Свернуть</button></div>
    </div>
    <div class="activity-card__body">
      <div class="activity-calendar-pro" data-calendar-wrap></div>
    </div>
  </section>

  <div class="activity-grid--secondary">
    <section class="activity-card" data-activity-card>
      <div class="activity-card__head">
        <div><h3>Последние действия</h3><p>Компактный журнал действий с постраничной навигацией и сокращёнными длинными полями.</p></div>
        <div class="activity-card__actions"><button class="button button--ghost button--sm" type="button" data-activity-toggle>Свернуть</button></div>
      </div>
      <div class="activity-card__body">
        <div class="table-wrap"><table class="admin-table activity-table"><thead><tr><th>Время</th><th>IP</th><th>Страна</th><th>Страница</th><th>Источник</th><th>Действие</th><th data-short>Детали</th></tr></thead><tbody data-recent-table><tr><td colspan="7">Загрузка…</td></tr></tbody></table></div>
        <div class="activity-pagination" data-recent-pagination></div>
      </div>
    </section>

    <section class="activity-card" data-activity-card>
      <div class="activity-card__head">
        <div><h3>Сессии пользователей</h3><p>Карточки посетителей. История действий и подробности подгружаются только при раскрытии.</p></div>
        <div class="activity-card__actions"><button class="button button--ghost button--sm" type="button" data-activity-toggle>Свернуть</button></div>
      </div>
      <div class="activity-card__body">
        <div class="activity-session-list" data-sessions-list><div class="muted">Загрузка…</div></div>
        <div class="activity-pagination" data-sessions-pagination></div>
      </div>
    </section>
  </div>
</div>
<script>
(function(){
  const root = document.getElementById('activity-root');
  if(!root) return;
  const cfg = {
    dataUrl: '/admin/activity.php?ajax=1',
    detailUrl: '/admin/activity.php?ajax=detail&session=',
    postUrl: '/admin/activity.php',
    csrf: <?= json_encode(csrf_token(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
  };
  const q = (s,r=root)=>r.querySelector(s);
  const state = {date: q('[data-activity-date]').value, month: q('[data-activity-month]').value, recentPage:1, sessionsPage:1};
  let noteTimer = 0;

  const esc = s => String(s ?? '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));
  const fmt = ts => { if(!ts) return '—'; try{return new Date(Number(ts)*1000).toLocaleString('ru-RU');}catch(e){return '—';} };
  const short = (value, max=80) => { value = String(value ?? ''); return value.length > max ? value.slice(0, max - 1) + '…' : value; };
  const toneClass = tone => tone === 'hot' ? 'lead-badge lead-badge--hot' : tone === 'warm' ? 'lead-badge lead-badge--warm' : 'lead-badge lead-badge--cold';

  function setCollapsed(card, collapsed){
    card.classList.toggle('is-collapsed', collapsed);
    const btn = card.querySelector('[data-activity-toggle],[data-session-toggle]');
    if(btn) btn.textContent = collapsed ? 'Развернуть' : 'Свернуть';
  }

  function geoHtml(item){
    const title = item.geo_tooltip || 'Данные по IP не определены';
    const place = [item.country || '—', item.geo_city || '', item.geo_region || ''].filter(Boolean).join(' · ');
    return `<span class="flag-badge--rich" title="${esc(title)}"><span class="flag-badge__flag">${esc(item.country_flag || '🌐')}</span><span class="flag-badge__text"><span>${esc(item.country || '—')}</span><small>${esc(short(item.geo_city || item.geo_region || 'данные по IP', 32))}</small></span></span>`;
  }

  function renderBars(node, entries){
    if(!node) return;
    const rows = Array.isArray(entries) ? entries : Object.entries(entries || {});
    const max = Math.max(1, ...rows.map(row => Number(row[1] || 0)));
    node.innerHTML = rows.map(([label, value]) => `<div class="activity-bar"><span>${esc(label)}</span><div><i style="width:${Math.max(6, Math.round((Number(value||0)/max)*100))}%"></i></div><strong>${esc(value)}</strong></div>`).join('') || '<div class="muted">Нет данных</div>';
  }

  function calendarLevel(count, max){
    if(count <= 0) return 'lv0';
    if(count < max * 0.35) return 'lv1';
    if(count < max * 0.7) return 'lv2';
    return 'lv3';
  }

  function renderCalendar(month){
    const node = q('[data-calendar-wrap]');
    if(!node) return;
    const counts = Object.values(month.counts || {}).map(Number);
    const max = Math.max(1, ...counts, 1);
    let html = '<div class="activity-calendar-pro__header"><span>Пн</span><span>Вт</span><span>Ср</span><span>Чт</span><span>Пт</span><span>Сб</span><span>Вс</span></div><div class="activity-calendar-pro__grid">';
    (month.weeks || []).forEach(week => week.forEach(cell => {
      if(!cell){ html += '<span class="activity-calendar-pro__empty"></span>'; return; }
      const level = calendarLevel(Number(cell.count||0), max);
      const active = cell.date === state.date ? ' is-active' : '';
      html += `<button type="button" class="activity-calendar-pro__day ${level}${active}" data-calendar-date="${cell.date}" title="${esc(cell.date)} · событий: ${Number(cell.count||0)}"><strong>${cell.day}</strong><span class="dot"><i></i>${Number(cell.count||0)} событий</span></button>`;
    }));
    html += '</div><div class="activity-calendar-legend"><span><i></i>нет активности</span><span><i class="lv1"></i>низкая</span><span><i class="lv2"></i>средняя</span><span><i class="lv3"></i>высокая</span></div>';
    node.innerHTML = html;
  }

  function renderPager(node, meta, kind){
    if(!node || !meta) return;
    const pages = Number(meta.pages || 1);
    const current = Number(meta.page || 1);
    if(pages <= 1){ node.innerHTML = ''; return; }
    let html = '<div class="activity-pagination__inner">';
    for(let i=1;i<=pages;i++){
      html += `<button type="button" class="button ${i===current?'':'button--ghost'} button--sm" data-activity-page="${i}" data-activity-page-type="${kind}">${i}</button>`;
    }
    html += '</div>';
    node.innerHTML = html;
  }

  function sessionCard(item){
    const source = item.referrer_host || item.utm_source || '—';
    const place = [item.geo_city || '', item.geo_region || ''].filter(Boolean).join(', ') || '—';
    const provider = [item.geo_isp || '', item.geo_org || ''].filter(Boolean).join(' / ') || '—';
    return `<article class="activity-session is-collapsed" data-session-card data-session-key="${esc(item.key||'')}">
      <div class="activity-session__head">
        <div>
          <h4>${esc(item.ip||'—')} ${geoHtml(item)}</h4>
          <p>Источник: ${esc(source)} · Текущая страница: ${esc(short(item.current_page || '—', 64))}</p>
        </div>
        <div class="activity-card__actions">
          <span class="${toneClass(item.lead_tone)}">${esc(item.lead_temperature || 'Холодный')} · ${esc(item.lead_score || 0)}</span>
          <button class="button button--ghost button--sm" type="button" data-session-toggle>Развернуть</button>
        </div>
      </div>
      <div class="activity-session__body">
        <div class="activity-session__meta">
          <span><strong>Город / регион</strong>${esc(place)}</span>
          <span><strong>Провайдер / организация</strong>${esc(short(provider, 96))}</span>
          <span><strong>Landing URL</strong>${esc(short(item.landing_url || '—', 120))}</span>
          <span><strong>Текущий URL</strong>${esc(short(item.current_url || '—', 120))}</span>
          <span><strong>Последнее действие</strong>${esc(item.last_action || '—')}</span>
          <span><strong>Hits</strong>${esc(item.hits || 0)}</span>
          <span><strong>Последняя активность</strong>${esc(fmt(item.last_seen || 0))}</span>
          <span><strong>UTM</strong>${esc([item.utm_source||'—', item.utm_medium||'—', item.utm_campaign||'—'].join(' / '))}</span>
          <span><strong>Referrer</strong>${esc(short(item.referrer || '—', 120))}</span>
          <span><strong>ASN / TZ</strong>${esc((item.geo_asn || '—') + ' / ' + (item.geo_timezone || '—'))}</span>
          <span><strong>User-Agent</strong>${esc(short(item.user_agent || '—', 120))}</span>
        </div>
        <div class="table-wrap" style="margin-top:14px"><table class="admin-table activity-table"><thead><tr><th>Время</th><th>Действие</th><th>Страница</th><th>Детали</th></tr></thead><tbody data-session-actions><tr><td colspan="4">История подгрузится при раскрытии</td></tr></tbody></table></div>
      </div>
    </article>`;
  }

  async function loadDetail(card){
    const body = card.querySelector('[data-session-actions]');
    const key = card.getAttribute('data-session-key') || '';
    if(!body || !key || body.dataset.loaded === '1' || body.dataset.loading === '1') return;
    body.dataset.loading = '1';
    try{
      const res = await fetch(cfg.detailUrl + encodeURIComponent(key), {cache:'no-store'});
      const data = await res.json();
      body.innerHTML = (data.actions || []).map(item => `<tr><td>${esc(fmt(item.time||0))}</td><td>${esc(item.action||'—')}</td><td title="${esc(item.page||'—')}">${esc(short(item.page||'—', 56))}</td><td title="${esc(item.details||'—')}">${esc(short(item.details||'—', 88))}</td></tr>`).join('') || '<tr><td colspan="4">Нет действий</td></tr>';
      body.dataset.loaded = '1';
    } catch(e){
      body.innerHTML = '<tr><td colspan="4">Не удалось загрузить историю действий</td></tr>';
    } finally {
      body.dataset.loading = '0';
    }
  }

  async function loadActivity(){
    const url = `${cfg.dataUrl}&recent_page=${state.recentPage}&sessions_page=${state.sessionsPage}&date=${encodeURIComponent(state.date)}&month=${encodeURIComponent(state.month)}`;
    const res = await fetch(url, {cache:'no-store'});
    const data = await res.json();
    if(!data.ok) return;

    q('[data-kpi-online]').textContent = data.online_count || 0;
    q('[data-kpi-actions]').textContent = data.day?.actions_count || 0;
    q('[data-kpi-ips]').textContent = data.day?.unique_ips || 0;
    q('[data-kpi-sessions]').textContent = data.sessions_pagination?.total || 0;

    q('[data-online-table]').innerHTML = (data.online || []).map(item => `<tr>
      <td>${esc(item.ip||'—')}</td>
      <td>${geoHtml(item)}</td>
      <td title="${esc(item.current_page||'—')}">${esc(short(item.current_page||'—', 48))}</td>
      <td title="${esc(item.referrer_host || item.utm_source || '—')}">${esc(short(item.referrer_host || item.utm_source || '—', 26))}</td>
      <td>${esc(item.hits||0)}</td>
      <td><span class="${toneClass(item.lead_tone)}">${esc(item.lead_temperature||'Холодный')}</span></td>
      <td>${esc(fmt(item.last_seen||0))}</td>
    </tr>`).join('') || '<tr><td colspan="7">Сейчас никого нет онлайн</td></tr>';

    q('[data-recent-table]').innerHTML = (data.recent || []).map(item => `<tr>
      <td>${esc(fmt(item.time||0))}</td>
      <td>${esc(item.ip||'—')}</td>
      <td>${geoHtml(item)}</td>
      <td title="${esc(item.page||'—')}">${esc(short(item.page||'—', 24))}</td>
      <td title="${esc(item.referrer_host||item.utm_source||'—')}">${esc(short(item.referrer_host||item.utm_source||'—', 22))}</td>
      <td>${esc(item.action||'—')}</td>
      <td data-short title="${esc(item.details||'—')}">${esc(short(item.details||'—', 58))}</td>
    </tr>`).join('') || '<tr><td colspan="7">Нет данных</td></tr>';

    renderBars(q('[data-hours-bars]'), (data.day?.hourly || []).map((v,i)=>[String(i).padStart(2,'0') + ':00', v]));
    renderBars(q('[data-countries-bars]'), Object.entries(data.day?.countries || {}));
    renderBars(q('[data-referrers-bars]'), Object.entries(data.day?.referrers || {}));
    renderCalendar(data.month || {});
    renderPager(q('[data-recent-pagination]'), data.recent_pagination, 'recent');
    renderPager(q('[data-sessions-pagination]'), data.sessions_pagination, 'sessions');
    q('[data-sessions-list]').innerHTML = (data.sessions || []).map(sessionCard).join('') || '<div class="muted">Нет сохранённых сессий</div>';
  }

  root.addEventListener('click', async (e) => {
    const toggle = e.target.closest('[data-activity-toggle]');
    if(toggle){
      e.preventDefault();
      const card = toggle.closest('[data-activity-card]');
      if(card) setCollapsed(card, !card.classList.contains('is-collapsed'));
      return;
    }

    const sessionToggle = e.target.closest('[data-session-toggle]');
    if(sessionToggle){
      e.preventDefault();
      const card = sessionToggle.closest('[data-session-card]');
      if(card){
        const collapsed = !card.classList.contains('is-collapsed');
        setCollapsed(card, collapsed);
        if(!collapsed) await loadDetail(card);
      }
      return;
    }

    const pageBtn = e.target.closest('[data-activity-page]');
    if(pageBtn){
      e.preventDefault();
      const page = parseInt(pageBtn.getAttribute('data-activity-page') || '1', 10) || 1;
      const type = pageBtn.getAttribute('data-activity-page-type') || 'recent';
      if(type === 'sessions') state.sessionsPage = page; else state.recentPage = page;
      await loadActivity();
      return;
    }

    const day = e.target.closest('[data-calendar-date]');
    if(day){
      e.preventDefault();
      state.date = day.getAttribute('data-calendar-date') || state.date;
      q('[data-activity-date]').value = state.date;
      state.recentPage = 1;
      await loadActivity();
      return;
    }

    const refresh = e.target.closest('[data-activity-refresh]');
    if(refresh){
      e.preventDefault();
      await loadActivity();
      return;
    }

    const clearBtn = e.target.closest('[data-activity-clear]');
    if(clearBtn){
      e.preventDefault();
      if(!confirm('Очистить все локальные логи сайта, PHP-ошибки и историю действий?')) return;
      const fd = new FormData();
      fd.append('_csrf', cfg.csrf);
      fd.append('action', 'clear_logs');
      const res = await fetch(cfg.postUrl, {method:'POST', body:fd});
      const data = await res.json();
      if(data.ok) await loadActivity();
    }
  });

  q('[data-activity-date]').addEventListener('change', async (e) => { state.date = e.target.value; state.recentPage = 1; await loadActivity(); });
  q('[data-activity-month]').addEventListener('change', async (e) => { state.month = e.target.value; await loadActivity(); });
  root.querySelectorAll('[data-activity-settings-form]').forEach(form => form.addEventListener('change', () => form.requestSubmit()));
  root.querySelectorAll('[data-activity-settings-form]').forEach(form => form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const fd = new FormData(form);
    const res = await fetch(cfg.postUrl, {method:'POST', body:fd});
    const data = await res.json();
    if(data.ok){
      const note = q('[data-activity-settings-note]');
      note.style.display = 'block';
      clearTimeout(noteTimer);
      noteTimer = setTimeout(() => note.style.display = 'none', 2200);
    }
  }));

  setInterval(() => { const auto = q('[data-activity-autorefresh]'); if(auto && auto.checked) loadActivity(); }, 10000);
  loadActivity();
})();
</script>
<?php
admin_layout('Онлайн-активность', ob_get_clean(), 'activity');
