File: /home/u353189757/domains/adaptia.com.br/public_html/admin/index.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../api/config.php';
adapt_send_noindex_headers();
adapt_start_admin_session();
function e(mixed $value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function admin_is_authenticated(): bool
{
return ($_SESSION['adapt_admin_authenticated'] ?? false) === true;
}
function admin_redirect(string $path = '/admin/'): never
{
header('Location: ' . $path);
exit;
}
function admin_format_date(mixed $value): string
{
$text = (string) $value;
if ($text === '') {
return '-';
}
try {
$date = new DateTimeImmutable($text);
return $date->setTimezone(new DateTimeZone('America/Sao_Paulo'))->format('d/m/Y H:i');
} catch (Throwable) {
return $text;
}
}
function admin_format_duration(int|float $milliseconds): string
{
$seconds = max(0, (int) round($milliseconds / 1000));
$minutes = intdiv($seconds, 60);
$remainingSeconds = $seconds % 60;
if ($minutes >= 60) {
$hours = intdiv($minutes, 60);
$remainingMinutes = $minutes % 60;
return sprintf('%dh %02dmin', $hours, $remainingMinutes);
}
if ($minutes > 0) {
return sprintf('%dmin %02ds', $minutes, $remainingSeconds);
}
return sprintf('%ds', $remainingSeconds);
}
function admin_percent(float|int $value, float|int $max): float
{
if ($max <= 0) {
return 0;
}
return round(max(0, min(100, ($value / $max) * 100)), 2);
}
function admin_lower(string $value): string
{
return function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
}
function admin_submission_matches(array $submission, string $query): bool
{
if ($query === '') {
return true;
}
$fields = $submission['fields'] ?? [];
$haystack = admin_lower(json_encode($fields, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '');
return str_contains($haystack, admin_lower($query));
}
function admin_target_label(array $event): string
{
$target = $event['target'] ?? [];
if (!is_array($target)) {
return '-';
}
foreach (['label', 'href', 'link_host', 'id', 'name', 'tag'] as $key) {
if (!empty($target[$key])) {
return (string) $target[$key];
}
}
return '-';
}
function admin_event_summary(array $event): string
{
$type = (string) ($event['type'] ?? '');
$section = (string) ($event['section_title'] ?? $event['section_id'] ?? '');
return match ($type) {
'session_start' => 'Início da sessão',
'section_enter' => 'Entrou em ' . ($section !== '' ? $section : 'seção'),
'section_leave' => 'Saiu de ' . ($section !== '' ? $section : 'seção'),
'click' => 'Clique em ' . admin_target_label($event) . ($section !== '' ? ' / ' . $section : ''),
'scroll_depth' => 'Scroll até ' . (int) ($event['max_scroll_depth'] ?? 0) . '%',
'session_ping' => 'Atualização da sessão',
'session_end' => 'Fim da sessão',
default => $type,
};
}
function admin_build_tracking_report(array $records): array
{
$visits = [];
$sectionNames = [];
$sectionClicks = [];
$targetClicks = [];
$totalClickEvents = 0;
foreach ($records as $record) {
$visitId = (string) ($record['visit_id'] ?? '');
$event = $record['event'] ?? [];
if ($visitId === '' || !is_array($event)) {
continue;
}
if (!isset($visits[$visitId])) {
$visits[$visitId] = [
'id' => $visitId,
'first_at' => (string) ($record['server_time'] ?? ''),
'last_at' => (string) ($record['server_time'] ?? ''),
'first_ts' => strtotime((string) ($record['server_time'] ?? '')) ?: 0,
'last_ts' => strtotime((string) ($record['server_time'] ?? '')) ?: 0,
'page_url' => (string) ($record['page_url'] ?? ''),
'referrer' => (string) ($record['referrer'] ?? ''),
'duration_ms' => 0,
'max_scroll_depth' => 0,
'clicks' => 0,
'sections' => [],
'timeline' => [],
];
}
$serverTime = (string) ($record['server_time'] ?? '');
$serverTs = strtotime($serverTime) ?: 0;
$visit = &$visits[$visitId];
if ($serverTs > 0 && ($visit['first_ts'] === 0 || $serverTs < $visit['first_ts'])) {
$visit['first_ts'] = $serverTs;
$visit['first_at'] = $serverTime;
}
if ($serverTs >= $visit['last_ts']) {
$visit['last_ts'] = $serverTs;
$visit['last_at'] = $serverTime;
}
if (!empty($record['page_url'])) {
$visit['page_url'] = (string) $record['page_url'];
}
if (!empty($record['referrer'])) {
$visit['referrer'] = (string) $record['referrer'];
}
$type = (string) ($event['type'] ?? '');
if (!empty($event['section_id'])) {
$sectionId = (string) $event['section_id'];
$sectionNames[$sectionId] = (string) ($event['section_title'] ?? $sectionId);
}
if ($type === 'click') {
$totalClickEvents++;
$visit['clicks']++;
$sectionId = (string) ($event['section_id'] ?? 'sem-secao');
$sectionTitle = (string) ($event['section_title'] ?? $sectionId);
$sectionNames[$sectionId] = $sectionTitle;
$sectionClicks[$sectionId] = ($sectionClicks[$sectionId] ?? 0) + 1;
$targetLabel = admin_target_label($event);
$targetKey = $sectionId . '|' . $targetLabel;
$targetClicks[$targetKey] = [
'section_id' => $sectionId,
'section_title' => $sectionTitle,
'target' => $targetLabel,
'count' => ($targetClicks[$targetKey]['count'] ?? 0) + 1,
];
}
if (isset($event['duration_ms'])) {
$visit['duration_ms'] = max($visit['duration_ms'], (int) $event['duration_ms']);
}
if (isset($event['max_scroll_depth'])) {
$visit['max_scroll_depth'] = max($visit['max_scroll_depth'], (int) $event['max_scroll_depth']);
}
if (isset($event['click_count'])) {
$visit['clicks'] = max($visit['clicks'], (int) $event['click_count']);
}
if (isset($event['sections']) && is_array($event['sections'])) {
foreach ($event['sections'] as $section) {
if (!is_array($section) || empty($section['id'])) {
continue;
}
$sectionId = (string) $section['id'];
$duration = (int) ($section['duration_ms'] ?? 0);
$sectionNames[$sectionId] = (string) ($section['title'] ?? $sectionId);
$visit['sections'][$sectionId] = max((int) ($visit['sections'][$sectionId] ?? 0), $duration);
}
}
$visit['timeline'][] = [
'time' => $serverTime,
'summary' => admin_event_summary($event),
];
if (count($visit['timeline']) > 120) {
array_shift($visit['timeline']);
}
unset($visit);
}
$sectionTotals = [];
$sectionVisitCounts = [];
$totalDuration = 0;
foreach ($visits as $visit) {
$totalDuration += (int) $visit['duration_ms'];
foreach ($visit['sections'] as $sectionId => $duration) {
$sectionTotals[$sectionId] = ($sectionTotals[$sectionId] ?? 0) + (int) $duration;
$sectionVisitCounts[$sectionId] = ($sectionVisitCounts[$sectionId] ?? 0) + 1;
}
}
arsort($sectionTotals);
arsort($sectionClicks);
usort($targetClicks, static fn (array $a, array $b): int => $b['count'] <=> $a['count']);
uasort($visits, static fn (array $a, array $b): int => $b['last_ts'] <=> $a['last_ts']);
return [
'visits' => $visits,
'visit_count' => count($visits),
'event_count' => count($records),
'click_count' => $totalClickEvents,
'average_duration_ms' => count($visits) > 0 ? $totalDuration / count($visits) : 0,
'section_names' => $sectionNames,
'section_totals' => $sectionTotals,
'section_visit_counts' => $sectionVisitCounts,
'section_clicks' => $sectionClicks,
'target_clicks' => $targetClicks,
];
}
function admin_render_login(?string $error, array $storageStatus): void
{
?>
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>Admin | Adapt IA</title>
<style>
:root { color-scheme: dark; --bg: #0d1117; --panel: #161b22; --panel-2: #1f2937; --border: #30363d; --text: #f0f6fc; --muted: #8b949e; --accent: #7ee7d6; --green: #3fb950; --danger: #ff7b72; --radius: 8px; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
* { box-sizing: border-box; }
body { min-height: 100vh; margin: 0; display: grid; place-items: center; color: var(--text); background: linear-gradient(135deg, #0d1117, #111820); }
main { width: min(100% - 32px, 420px); }
.login-panel { padding: 28px; border: 1px solid var(--border); border-radius: var(--radius); background: rgba(22, 27, 34, 0.94); box-shadow: 0 24px 70px rgba(0, 0, 0, 0.36); }
h1 { margin: 0 0 8px; font-size: 1.6rem; }
p { margin: 0 0 20px; color: var(--muted); }
label { display: block; margin: 16px 0 8px; color: #c9d1d9; font-weight: 700; }
input { width: 100%; min-height: 44px; padding: 0 12px; color: var(--text); background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); }
input:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
button { width: 100%; min-height: 46px; margin-top: 18px; color: #07130d; font-weight: 800; border: 0; border-radius: var(--radius); background: linear-gradient(135deg, var(--accent), var(--green)); cursor: pointer; }
.error, .storage-error { padding: 12px; margin-bottom: 14px; color: #ffd7d3; border: 1px solid rgba(255, 123, 114, 0.35); border-radius: var(--radius); background: rgba(255, 123, 114, 0.1); }
.storage-error small { display: block; margin-top: 6px; color: var(--muted); overflow-wrap: anywhere; }
</style>
</head>
<body>
<main>
<form class="login-panel" method="post" action="/admin/">
<h1>Adapt IA Admin</h1>
<p>Acesso restrito ao painel de dados.</p>
<?php if (!$storageStatus['ok']): ?>
<div class="storage-error">
Armazenamento privado indisponível.
<small><?= e($storageStatus['path']) ?> / <?= e($storageStatus['error']) ?></small>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?= e($error) ?></div>
<?php endif; ?>
<input type="hidden" name="action" value="login">
<label for="username">Usuário</label>
<input id="username" name="username" type="text" autocomplete="username" required>
<label for="password">Senha</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
<button type="submit">Entrar</button>
</form>
</main>
</body>
</html>
<?php
}
$loginError = null;
$storageStatus = adapt_storage_status();
if (isset($_GET['logout'])) {
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], (bool) $params['secure'], (bool) $params['httponly']);
}
session_destroy();
admin_redirect('/admin/');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'login') {
try {
$rateKey = adapt_client_rate_key();
$lockedUntil = adapt_auth_locked_until($rateKey);
if ($lockedUntil > time()) {
$loginError = 'Muitas tentativas. Tente novamente em alguns minutos.';
} else {
$username = adapt_clean_text($_POST['username'] ?? '', 80);
$password = (string) ($_POST['password'] ?? '');
$valid = hash_equals(ADAPT_ADMIN_USERNAME, $username) && password_verify($password, ADAPT_ADMIN_PASSWORD_HASH);
if ($valid) {
adapt_auth_clear_rate($rateKey);
session_regenerate_id(true);
$_SESSION['adapt_admin_authenticated'] = true;
$_SESSION['adapt_admin_login_at'] = time();
admin_redirect('/admin/');
}
adapt_auth_record_failure($rateKey);
$loginError = 'Usuário ou senha inválidos.';
}
} catch (Throwable $exception) {
$loginError = 'Não foi possível acessar o armazenamento privado.';
$storageStatus = adapt_storage_status();
}
}
if (!admin_is_authenticated()) {
admin_render_login($loginError, $storageStatus);
exit;
}
if (($_GET['export'] ?? '') === 'submissions') {
$submissionsData = adapt_load_submissions();
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: attachment; filename="adaptia-submissions.json"');
echo json_encode($submissionsData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
exit;
}
$view = in_array(($_GET['view'] ?? 'dashboard'), ['dashboard', 'submissions', 'heatmap'], true) ? (string) ($_GET['view'] ?? 'dashboard') : 'dashboard';
$query = trim((string) ($_GET['q'] ?? ''));
$submissionsData = ['version' => 1, 'items' => []];
$trackingRecords = [];
$loadError = '';
try {
$submissionsData = adapt_load_submissions();
$trackingRecords = adapt_read_tracking_events();
} catch (Throwable $exception) {
$loadError = $exception->getMessage();
}
$submissions = isset($submissionsData['items']) && is_array($submissionsData['items']) ? array_reverse($submissionsData['items']) : [];
$filteredSubmissions = array_values(array_filter($submissions, static fn (array $submission): bool => admin_submission_matches($submission, $query)));
$report = admin_build_tracking_report($trackingRecords);
$maxSectionTime = $report['section_totals'] ? max($report['section_totals']) : 0;
$maxSectionClicks = $report['section_clicks'] ? max($report['section_clicks']) : 0;
?>
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>Painel Admin | Adapt IA</title>
<style>
:root { color-scheme: dark; --bg: #0d1117; --panel: #161b22; --panel-2: #1f2937; --border: #30363d; --text: #f0f6fc; --soft: #c9d1d9; --muted: #8b949e; --accent: #7ee7d6; --green: #3fb950; --blue: #58a6ff; --danger: #ff7b72; --radius: 8px; --container: 1180px; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
* { box-sizing: border-box; }
body { margin: 0; min-width: 320px; color: var(--text); background: #0d1117; line-height: 1.55; }
a { color: inherit; text-decoration: none; }
button, input { font: inherit; }
.admin-header { position: sticky; top: 0; z-index: 5; border-bottom: 1px solid var(--border); background: rgba(13, 17, 23, 0.9); backdrop-filter: blur(12px); }
.header-inner, main { width: min(100% - 32px, var(--container)); margin: 0 auto; }
.header-inner { display: grid; grid-template-columns: 1fr auto; gap: 18px; align-items: center; padding: 16px 0; }
.brand-label { display: block; color: var(--accent); font-size: 0.75rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
h1 { margin: 2px 0 0; font-size: clamp(1.4rem, 3vw, 2.2rem); line-height: 1.1; }
.logout { padding: 10px 12px; color: var(--soft); border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); }
main { padding: 24px 0 48px; }
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.tabs a { padding: 9px 12px; color: var(--soft); border: 1px solid var(--border); border-radius: var(--radius); background: rgba(22, 27, 34, 0.72); }
.tabs a.is-active { color: #07130d; border-color: transparent; background: linear-gradient(135deg, var(--accent), var(--green)); font-weight: 800; }
.notice { padding: 12px 14px; margin-bottom: 18px; border: 1px solid rgba(255, 123, 114, 0.35); border-radius: var(--radius); background: rgba(255, 123, 114, 0.1); color: #ffd7d3; }
.stats-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 22px; }
.stat, .panel, .row-card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); }
.stat { padding: 16px; }
.stat span { display: block; color: var(--muted); font-size: 0.86rem; }
.stat strong { display: block; margin-top: 6px; font-size: 1.75rem; line-height: 1.1; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.panel { padding: 18px; overflow: hidden; }
.panel + .panel { margin-top: 14px; }
.panel h2 { margin: 0 0 14px; font-size: 1.05rem; }
.muted { color: var(--muted); }
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; min-width: 680px; }
th, td { padding: 11px 10px; border-bottom: 1px solid rgba(139, 148, 158, 0.18); text-align: left; vertical-align: top; }
th { color: var(--muted); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; }
td { color: var(--soft); }
.bar-list { display: grid; gap: 12px; }
.bar-row { display: grid; gap: 7px; }
.bar-meta { display: flex; gap: 12px; justify-content: space-between; color: var(--soft); }
.bar-track { height: 9px; overflow: hidden; border-radius: 999px; background: #0d1117; }
.bar-track span { display: block; width: var(--bar-width); height: 100%; background: linear-gradient(90deg, var(--accent), var(--blue)); }
.toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.search { display: flex; gap: 8px; min-width: min(100%, 420px); }
.search input { flex: 1; min-height: 42px; padding: 0 12px; color: var(--text); background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); }
.button { display: inline-flex; align-items: center; justify-content: center; min-height: 42px; padding: 0 12px; font-weight: 800; border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel-2); color: var(--text); cursor: pointer; }
.button.primary { color: #07130d; border-color: transparent; background: linear-gradient(135deg, var(--accent), var(--green)); }
details { border-radius: var(--radius); }
summary { cursor: pointer; color: var(--accent); font-weight: 750; }
pre { max-height: 420px; overflow: auto; padding: 14px; border: 1px solid var(--border); border-radius: var(--radius); background: #0d1117; color: var(--soft); white-space: pre-wrap; }
.row-card { padding: 14px; }
.row-card + .row-card { margin-top: 10px; }
.visit-summary { display: grid; grid-template-columns: 1.2fr repeat(4, auto); gap: 12px; align-items: center; color: var(--soft); }
.timeline { margin: 14px 0 0; padding-left: 20px; color: var(--soft); }
.timeline li + li { margin-top: 6px; }
.empty { padding: 18px; color: var(--muted); border: 1px dashed var(--border); border-radius: var(--radius); }
@media (max-width: 900px) { .header-inner, .grid-2, .stats-grid, .visit-summary { grid-template-columns: 1fr; } .logout { justify-self: start; } }
</style>
</head>
<body>
<header class="admin-header">
<div class="header-inner">
<div>
<span class="brand-label">Adapt IA</span>
<h1>Painel admin</h1>
</div>
<a class="logout" href="/admin/?logout=1">Sair</a>
</div>
</header>
<main>
<nav class="tabs" aria-label="Abas do painel">
<a class="<?= $view === 'dashboard' ? 'is-active' : '' ?>" href="/admin/">Dashboard</a>
<a class="<?= $view === 'submissions' ? 'is-active' : '' ?>" href="/admin/?view=submissions">Submissões</a>
<a class="<?= $view === 'heatmap' ? 'is-active' : '' ?>" href="/admin/?view=heatmap">Heatmap</a>
</nav>
<?php if ($loadError !== ''): ?>
<div class="notice">Falha ao carregar dados privados: <?= e($loadError) ?></div>
<?php endif; ?>
<?php if ($view === 'dashboard'): ?>
<section class="stats-grid" aria-label="Resumo">
<div class="stat"><span>Submissões</span><strong><?= count($submissions) ?></strong></div>
<div class="stat"><span>Sessões rastreadas</span><strong><?= (int) $report['visit_count'] ?></strong></div>
<div class="stat"><span>Tempo médio</span><strong><?= e(admin_format_duration($report['average_duration_ms'])) ?></strong></div>
<div class="stat"><span>Cliques</span><strong><?= (int) $report['click_count'] ?></strong></div>
</section>
<div class="grid-2">
<section class="panel">
<h2>Tempo por seção</h2>
<?php if (!$report['section_totals']): ?>
<div class="empty">Nenhum tempo por seção registrado ainda.</div>
<?php else: ?>
<div class="bar-list">
<?php foreach (array_slice($report['section_totals'], 0, 8, true) as $sectionId => $duration): ?>
<?php $barWidth = admin_percent((int) $duration, (int) $maxSectionTime); ?>
<div class="bar-row">
<div class="bar-meta"><strong><?= e($report['section_names'][$sectionId] ?? $sectionId) ?></strong><span><?= e(admin_format_duration((int) $duration)) ?></span></div>
<div class="bar-track"><span style="--bar-width: <?= e((string) $barWidth) ?>%"></span></div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<section class="panel">
<h2>Cliques mais frequentes</h2>
<?php if (!$report['target_clicks']): ?>
<div class="empty">Nenhum clique registrado ainda.</div>
<?php else: ?>
<div class="table-wrap">
<table>
<thead><tr><th>Alvo</th><th>Seção</th><th>Cliques</th></tr></thead>
<tbody>
<?php foreach (array_slice($report['target_clicks'], 0, 8) as $click): ?>
<tr>
<td><?= e($click['target']) ?></td>
<td><?= e($click['section_title']) ?></td>
<td><?= (int) $click['count'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
</div>
<section class="panel">
<h2>Submissões recentes</h2>
<?php if (!$submissions): ?>
<div class="empty">Nenhum formulário enviado ainda.</div>
<?php else: ?>
<div class="table-wrap">
<table>
<thead><tr><th>Data</th><th>Nome</th><th>WhatsApp</th><th>Serviço</th><th>Mensagem</th></tr></thead>
<tbody>
<?php foreach (array_slice($submissions, 0, 6) as $submission): ?>
<?php $fields = is_array($submission['fields'] ?? null) ? $submission['fields'] : []; ?>
<tr>
<td><?= e(admin_format_date($submission['received_at'] ?? '')) ?></td>
<td><?= e($fields['nome'] ?? '') ?></td>
<td><?= e($fields['whatsapp'] ?? '') ?></td>
<td><?= e($fields['servico'] ?? '') ?></td>
<td><?= e($fields['mensagem'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<?php endif; ?>
<?php if ($view === 'submissions'): ?>
<section class="panel">
<div class="toolbar">
<form class="search" method="get" action="/admin/">
<input type="hidden" name="view" value="submissions">
<input name="q" type="search" value="<?= e($query) ?>" placeholder="Buscar por nome, serviço, telefone ou mensagem">
<button class="button primary" type="submit">Buscar</button>
</form>
<a class="button" href="/admin/?export=submissions">Exportar JSON</a>
</div>
<details>
<summary>Ver JSON bruto</summary>
<pre><?= e(json_encode($submissionsData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)) ?></pre>
</details>
</section>
<section class="panel">
<h2>Submissões de formulário</h2>
<?php if (!$filteredSubmissions): ?>
<div class="empty">Nenhuma submissão encontrada.</div>
<?php else: ?>
<div class="table-wrap">
<table>
<thead><tr><th>Data</th><th>Nome</th><th>Empresa</th><th>WhatsApp</th><th>Serviço</th><th>Detalhes</th></tr></thead>
<tbody>
<?php foreach ($filteredSubmissions as $submission): ?>
<?php $fields = is_array($submission['fields'] ?? null) ? $submission['fields'] : []; ?>
<?php $meta = is_array($submission['meta'] ?? null) ? $submission['meta'] : []; ?>
<tr>
<td><?= e(admin_format_date($submission['received_at'] ?? '')) ?></td>
<td><?= e($fields['nome'] ?? '') ?></td>
<td><?= e($fields['empresa'] ?? '') ?></td>
<td><?= e($fields['whatsapp'] ?? '') ?></td>
<td><?= e($fields['servico'] ?? '') ?></td>
<td>
<details>
<summary>Abrir</summary>
<p><strong>Investimento:</strong> <?= e($fields['investimento'] ?? '') ?></p>
<p><strong>Prazo:</strong> <?= e($fields['prazo'] ?? '') ?></p>
<p><strong>Mensagem:</strong><br><?= nl2br(e($fields['mensagem'] ?? '')) ?></p>
<p class="muted">Página: <?= e($meta['page_url'] ?? '') ?><br>Visita: <?= e($meta['visit_id'] ?? '') ?></p>
</details>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<?php endif; ?>
<?php if ($view === 'heatmap'): ?>
<div class="grid-2">
<section class="panel">
<h2>Tempo total por seção</h2>
<?php if (!$report['section_totals']): ?>
<div class="empty">Nenhum dado de permanência registrado ainda.</div>
<?php else: ?>
<div class="bar-list">
<?php foreach ($report['section_totals'] as $sectionId => $duration): ?>
<?php $barWidth = admin_percent((int) $duration, (int) $maxSectionTime); ?>
<div class="bar-row">
<div class="bar-meta">
<strong><?= e($report['section_names'][$sectionId] ?? $sectionId) ?></strong>
<span><?= e(admin_format_duration((int) $duration)) ?> em <?= (int) ($report['section_visit_counts'][$sectionId] ?? 0) ?> visitas</span>
</div>
<div class="bar-track"><span style="--bar-width: <?= e((string) $barWidth) ?>%"></span></div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
<section class="panel">
<h2>Cliques por seção</h2>
<?php if (!$report['section_clicks']): ?>
<div class="empty">Nenhum clique registrado ainda.</div>
<?php else: ?>
<div class="bar-list">
<?php foreach ($report['section_clicks'] as $sectionId => $count): ?>
<?php $barWidth = admin_percent((int) $count, (int) $maxSectionClicks); ?>
<div class="bar-row">
<div class="bar-meta"><strong><?= e($report['section_names'][$sectionId] ?? $sectionId) ?></strong><span><?= (int) $count ?> cliques</span></div>
<div class="bar-track"><span style="--bar-width: <?= e((string) $barWidth) ?>%"></span></div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
</div>
<section class="panel">
<h2>Sessões recentes</h2>
<?php if (!$report['visits']): ?>
<div class="empty">Nenhuma sessão registrada ainda.</div>
<?php else: ?>
<?php foreach (array_slice($report['visits'], 0, 20) as $visit): ?>
<details class="row-card">
<summary>
<span class="visit-summary">
<span><?= e($visit['id']) ?></span>
<span><?= e(admin_format_date($visit['first_at'])) ?></span>
<span><?= e(admin_format_duration((int) $visit['duration_ms'])) ?></span>
<span><?= (int) $visit['clicks'] ?> cliques</span>
<span><?= (int) $visit['max_scroll_depth'] ?>% scroll</span>
</span>
</summary>
<p class="muted">Página: <?= e($visit['page_url']) ?><br>Origem: <?= e($visit['referrer'] ?: '-') ?></p>
<?php if ($visit['sections']): ?>
<div class="table-wrap">
<table>
<thead><tr><th>Seção</th><th>Tempo</th></tr></thead>
<tbody>
<?php foreach ($visit['sections'] as $sectionId => $duration): ?>
<tr><td><?= e($report['section_names'][$sectionId] ?? $sectionId) ?></td><td><?= e(admin_format_duration((int) $duration)) ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<ol class="timeline">
<?php foreach ($visit['timeline'] as $item): ?>
<li><span class="muted"><?= e(admin_format_date($item['time'])) ?></span> <?= e($item['summary']) ?></li>
<?php endforeach; ?>
</ol>
</details>
<?php endforeach; ?>
<?php endif; ?>
</section>
<?php endif; ?>
</main>
</body>
</html>