16. Análise de Dados - Parte 3: Visualização e Privacidade
Esta seção aborda a visualização dos dados coletados e processados, bem como as práticas de privacidade e conformidade em soluções web desenvolvidas com Astro 5.
Visualização de Dados
Dashboard Interno
Componente de Dashboard
---import AdminLayout from '../../../layouts/AdminLayout.astro';import AnalyticsSummary from '../../../components/analytics/AnalyticsSummary.astro';import PageviewsChart from '../../../components/analytics/PageviewsChart.astro';import TopPagesTable from '../../../components/analytics/TopPagesTable.astro';import EventsChart from '../../../components/analytics/EventsChart.astro';import ConversionFunnel from '../../../components/analytics/ConversionFunnel.astro';
// Verificar autenticaçãoif (!Astro.locals.user || !Astro.locals.user.isAdmin) { return Astro.redirect('/login?redirect=/admin/analytics');}
// Parâmetros de dataconst searchParams = Astro.url.searchParams;const period = searchParams.get('period') || '7d';
let startDate, endDate;const now = new Date();endDate = new Date(now);
switch (period) { case '1d': startDate = new Date(now); startDate.setDate(startDate.getDate() - 1); break; case '7d': startDate = new Date(now); startDate.setDate(startDate.getDate() - 7); break; case '30d': startDate = new Date(now); startDate.setDate(startDate.getDate() - 30); break; case '90d': startDate = new Date(now); startDate.setDate(startDate.getDate() - 90); break; default: startDate = new Date(now); startDate.setDate(startDate.getDate() - 7);}---
<AdminLayout title="Analytics Dashboard"> <div class="analytics-dashboard"> <header class="dashboard-header"> <h1>Analytics Dashboard</h1>
<div class="period-selector"> <a href="?period=1d" class={period === '1d' ? 'active' : ''}>Hoje</a> <a href="?period=7d" class={period === '7d' ? 'active' : ''}>7 dias</a> <a href="?period=30d" class={period === '30d' ? 'active' : ''}>30 dias</a> <a href="?period=90d" class={period === '90d' ? 'active' : ''}>90 dias</a> </div> </header>
<AnalyticsSummary startDate={startDate} endDate={endDate} />
<div class="dashboard-grid"> <div class="dashboard-card full-width"> <h2>Pageviews</h2> <PageviewsChart startDate={startDate} endDate={endDate} /> </div>
<div class="dashboard-card"> <h2>Top Pages</h2> <TopPagesTable startDate={startDate} endDate={endDate} limit={10} /> </div>
<div class="dashboard-card"> <h2>Event Distribution</h2> <EventsChart startDate={startDate} endDate={endDate} /> </div>
<div class="dashboard-card full-width"> <h2>Conversion Funnel</h2> <ConversionFunnel startDate={startDate} endDate={endDate} steps={[ { name: 'pageview', label: 'Page View' }, { name: 'product_view', label: 'Product View' }, { name: 'add_to_cart', label: 'Add to Cart' }, { name: 'checkout_start', label: 'Start Checkout' }, { name: 'purchase', label: 'Purchase' } ]} /> </div> </div> </div></AdminLayout>
<style> .analytics-dashboard { padding: 1rem; }
.dashboard-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
.period-selector { display: flex; gap: 1rem; }
.period-selector a { padding: 0.5rem 1rem; border-radius: 4px; text-decoration: none; color: #333; background-color: #f0f0f0; }
.period-selector a.active { background-color: #4a6cf7; color: white; }
.dashboard-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; }
.dashboard-card { background-color: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); padding: 1.5rem; }
.full-width { grid-column: 1 / -1; }
h1 { margin: 0; font-size: 1.8rem; }
h2 { margin-top: 0; font-size: 1.2rem; color: #666; }</style>Componente de Resumo de Métricas
---import { getActiveUsers, getBounceRate } from '../../utils/analytics-metrics';import { getPageviewsByDay, getEventCountsByType } from '../../utils/analytics-aggregation';
const { startDate, endDate } = Astro.props;
// Buscar dadosconst [activeUsers, bounceRate, pageviewsByDay, eventCounts] = await Promise.all([ getActiveUsers('day'), getBounceRate(startDate, endDate), getPageviewsByDay(startDate, endDate), getEventCountsByType(startDate, endDate)]);
// Calcular total de pageviewsconst totalPageviews = pageviewsByDay.reduce((sum, day) => sum + day.pageview_count, 0);
// Calcular média diáriaconst dayCount = Math.max(1, Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)));const avgDailyPageviews = Math.round(totalPageviews / dayCount);
// Encontrar total de eventosconst totalEvents = eventCounts.reduce((sum, event) => sum + event.event_count, 0);---
<div class="metrics-summary"> <div class="metric-card"> <div class="metric-value">{activeUsers}</div> <div class="metric-label">Usuários Ativos Hoje</div> </div>
<div class="metric-card"> <div class="metric-value">{totalPageviews.toLocaleString()}</div> <div class="metric-label">Total de Pageviews</div> </div>
<div class="metric-card"> <div class="metric-value">{avgDailyPageviews.toLocaleString()}</div> <div class="metric-label">Média Diária</div> </div>
<div class="metric-card"> <div class="metric-value">{bounceRate}%</div> <div class="metric-label">Taxa de Rejeição</div> </div></div>
<style> .metrics-summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 2rem; }
.metric-card { background-color: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); padding: 1.5rem; text-align: center; }
.metric-value { font-size: 2rem; font-weight: bold; color: #4a6cf7; margin-bottom: 0.5rem; }
.metric-label { font-size: 0.9rem; color: #666; }
@media (max-width: 768px) { .metrics-summary { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .metrics-summary { grid-template-columns: 1fr; } }</style>Componente de Gráfico de Pageviews
---import { getPageviewsByDay } from '../../utils/analytics-aggregation';
const { startDate, endDate } = Astro.props;
// Buscar dadosconst pageviewsByDay = await getPageviewsByDay(startDate, endDate);
// Formatar dados para o gráficoconst chartData = { labels: pageviewsByDay.map(day => { const date = new Date(day.day); return date.toLocaleDateString('pt-BR', { day: 'numeric', month: 'short' }); }), datasets: [ { label: 'Pageviews', data: pageviewsByDay.map(day => day.pageview_count), backgroundColor: 'rgba(74, 108, 247, 0.2)', borderColor: 'rgba(74, 108, 247, 1)', borderWidth: 2, tension: 0.4, fill: true, } ]};
// ID único para o canvasconst chartId = `pageviews-chart-${Date.now()}`;---
<div class="chart-container"> <canvas id={chartId}></canvas></div>
<script define:vars={{ chartId, chartData }}> document.addEventListener('DOMContentLoaded', () => { const ctx = document.getElementById(chartId).getContext('2d');
new Chart(ctx, { type: 'line', data: chartData, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false, } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } } }); });</script>
<style> .chart-container { height: 300px; width: 100%; }</style>Integração com Chart.js
<html lang="pt-BR"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{Astro.props.title || 'Admin Dashboard'}</title>
<!-- Chart.js --> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.0.0/dist/chart.umd.min.js"></script>
<slot name="head" /></head><body> <div class="admin-layout"> <aside class="sidebar"> <nav> <ul> <li><a href="/admin">Dashboard</a></li> <li><a href="/admin/analytics">Analytics</a></li> <li><a href="/admin/content">Content</a></li> <li><a href="/admin/users">Users</a></li> <li><a href="/admin/settings">Settings</a></li> </ul> </nav> </aside>
<main class="content"> <slot /> </main> </div></body></html>
<style> .admin-layout { display: grid; grid-template-columns: 250px 1fr; min-height: 100vh; }
.sidebar { background-color: #1a1a2e; color: white; padding: 1.5rem; }
.sidebar ul { list-style: none; padding: 0; margin: 0; }
.sidebar li { margin-bottom: 0.5rem; }
.sidebar a { color: white; text-decoration: none; display: block; padding: 0.75rem 1rem; border-radius: 4px; }
.sidebar a:hover { background-color: rgba(255, 255, 255, 0.1); }
.content { background-color: #f5f5f5; padding: 1.5rem; }</style>Privacidade e Conformidade
Política de Privacidade
---import BaseLayout from '../layouts/BaseLayout.astro';---
<BaseLayout title="Política de Privacidade" description="Nossa política de privacidade e uso de dados"> <main class="privacy-policy"> <h1>Política de Privacidade</h1>
<section> <h2>1. Introdução</h2> <p> Esta Política de Privacidade descreve como coletamos, usamos e compartilhamos informações quando você utiliza nosso site. Respeitamos sua privacidade e estamos comprometidos com a proteção de seus dados pessoais. </p> </section>
<section> <h2>2. Dados que Coletamos</h2> <p>Podemos coletar os seguintes tipos de informações:</p>
<h3>2.1. Informações fornecidas por você</h3> <ul> <li>Informações de contato (como nome e e-mail) quando você preenche formulários</li> <li>Informações de conta quando você se registra</li> <li>Conteúdo que você publica ou compartilha em nosso site</li> </ul>
<h3>2.2. Informações coletadas automaticamente</h3> <ul> <li>Dados de uso e navegação (páginas visitadas, tempo gasto no site)</li> <li>Informações do dispositivo (tipo de navegador, sistema operacional)</li> <li>Endereço IP e localização aproximada</li> <li>Cookies e tecnologias similares</li> </ul> </section>
<section> <h2>3. Como Usamos seus Dados</h2> <p>Utilizamos suas informações para:</p> <ul> <li>Fornecer, manter e melhorar nossos serviços</li> <li>Processar suas solicitações e transações</li> <li>Entender como você utiliza nosso site para melhorar a experiência</li> <li>Personalizar conteúdo e recomendações</li> <li>Comunicar-nos com você sobre atualizações, ofertas e eventos</li> <li>Detectar e prevenir fraudes e abusos</li> </ul> </section>
<section> <h2>4. Cookies e Tecnologias Similares</h2> <p> Utilizamos cookies e tecnologias similares para coletar informações sobre sua atividade, navegador e dispositivo. Você pode gerenciar suas preferências de cookies através das configurações do seu navegador. </p> <p>Tipos de cookies que utilizamos:</p> <ul> <li><strong>Cookies essenciais:</strong> Necessários para o funcionamento do site</li> <li><strong>Cookies de preferências:</strong> Permitem lembrar suas escolhas e personalizar o site</li> <li><strong>Cookies analíticos:</strong> Nos ajudam a entender como os visitantes interagem com o site</li> <li><strong>Cookies de marketing:</strong> Utilizados para rastrear visitantes em sites</li> </ul> </section>
<section> <h2>5. Seus Direitos</h2> <p>Dependendo da sua localização, você pode ter os seguintes direitos:</p> <ul> <li>Acessar seus dados pessoais</li> <li>Corrigir dados imprecisos</li> <li>Excluir seus dados</li> <li>Restringir ou opor-se ao processamento de seus dados</li> <li>Portabilidade de dados</li> <li>Retirar consentimento</li> </ul> <p> Para exercer qualquer um desses direitos, entre em contato conosco através do e-mail: <a href="mailto:privacy@exemplo.com.br">privacy@exemplo.com.br</a> </p> </section>
<section> <h2>6. Segurança de Dados</h2> <p> Implementamos medidas de segurança técnicas e organizacionais para proteger seus dados contra acesso não autorizado, perda ou alteração. No entanto, nenhum método de transmissão pela Internet ou armazenamento eletrônico é 100% seguro. </p> </section>
<section> <h2>7. Alterações nesta Política</h2> <p> Podemos atualizar esta Política de Privacidade periodicamente. A versão mais recente estará sempre disponível nesta página, com a data da última atualização. </p> </section>
<section> <h2>8. Contato</h2> <p> Se você tiver dúvidas sobre esta Política de Privacidade ou sobre como tratamos seus dados, entre em contato conosco: </p> <address> <strong>E-mail:</strong> <a href="mailto:privacy@exemplo.com.br">privacy@exemplo.com.br</a><br> <strong>Endereço:</strong> Av. Exemplo, 123 - São Paulo, SP - Brasil </address> </section>
<footer> <p>Última atualização: 21 de março de 2025</p> </footer> </main></BaseLayout>
<style> .privacy-policy { max-width: 800px; margin: 0 auto; padding: 2rem 1rem; }
h1 { font-size: 2rem; margin-bottom: 2rem; color: #333; }
h2 { font-size: 1.5rem; margin: 2rem 0 1rem; color: #444; }
h3 { font-size: 1.2rem; margin: 1.5rem 0 0.75rem; color: #555; }
section { margin-bottom: 2rem; }
ul { margin-left: 1.5rem; }
li { margin-bottom: 0.5rem; }
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #eee; font-style: italic; color: #777; }</style>Componente de Consentimento de Cookies
<div id="cookie-consent" class="cookie-banner hidden"> <div class="cookie-content"> <h2>Utilizamos Cookies</h2> <p> Este site utiliza cookies para melhorar sua experiência. Ao continuar navegando, você concorda com nossa <a href="/privacy">Política de Privacidade</a>. </p>
<div class="cookie-settings"> <details> <summary>Configurações Avançadas</summary>
<div class="cookie-options"> <label> <input type="checkbox" name="essential" checked disabled> <span>Essenciais (obrigatórios)</span> </label>
<label> <input type="checkbox" name="preferences" checked> <span>Preferências</span> </label>
<label> <input type="checkbox" name="analytics" checked> <span>Análise</span> </label>
<label> <input type="checkbox" name="marketing"> <span>Marketing</span> </label> </div> </details> </div>
<div class="cookie-actions"> <button id="reject-cookies" class="btn-secondary">Rejeitar Não-Essenciais</button> <button id="accept-cookies" class="btn-primary">Aceitar Todos</button> </div> </div></div>
<script> document.addEventListener('DOMContentLoaded', () => { const cookieBanner = document.getElementById('cookie-consent'); const acceptButton = document.getElementById('accept-cookies'); const rejectButton = document.getElementById('reject-cookies'); const cookieOptions = document.querySelectorAll('.cookie-options input[type="checkbox"]');
// Verificar se já existe consentimento const hasConsent = localStorage.getItem('cookie-consent');
if (!hasConsent) { // Mostrar banner após um pequeno delay setTimeout(() => { cookieBanner.classList.remove('hidden'); }, 1000); } else { // Aplicar configurações salvas const savedSettings = JSON.parse(localStorage.getItem('cookie-settings') || '{}'); applyCookieSettings(savedSettings); }
// Aceitar todos os cookies acceptButton.addEventListener('click', () => { const settings = { essential: true, preferences: true, analytics: true, marketing: true, };
saveCookieSettings(settings); cookieBanner.classList.add('hidden'); });
// Rejeitar cookies não essenciais rejectButton.addEventListener('click', () => { const settings = { essential: true, preferences: false, analytics: false, marketing: false, };
saveCookieSettings(settings); cookieBanner.classList.add('hidden'); });
// Salvar configurações personalizadas function saveCookieSettings(settings) { localStorage.setItem('cookie-consent', 'true'); localStorage.setItem('cookie-settings', JSON.stringify(settings));
// Aplicar configurações applyCookieSettings(settings);
// Disparar evento para outros scripts window.dispatchEvent(new CustomEvent('cookieConsentUpdate', { detail: settings })); }
// Aplicar configurações de cookies function applyCookieSettings(settings) { // Desativar scripts de analytics se não permitido if (!settings.analytics) { disableAnalytics(); }
// Desativar scripts de marketing se não permitido if (!settings.marketing) { disableMarketing(); } }
function disableAnalytics() { // Desativar Google Analytics window['ga-disable-UA-XXXXX-Y'] = true;
// Remover scripts de analytics document.querySelectorAll('script[data-type="analytics"]').forEach(script => { script.remove(); }); }
function disableMarketing() { // Remover scripts de marketing document.querySelectorAll('script[data-type="marketing"]').forEach(script => { script.remove(); }); } });</script>
<style> .cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; background-color: white; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); z-index: 1000; padding: 1rem; transition: transform 0.3s ease-in-out; }
.cookie-banner.hidden { transform: translateY(100%); }
.cookie-content { max-width: 1200px; margin: 0 auto; padding: 1rem; }
.cookie-actions { display: flex; gap: 1rem; margin-top: 1rem; }
.cookie-settings { margin: 1rem 0; }
.cookie-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; margin-top: 1rem; }
.btn-primary { background-color: #4a6cf7; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; }
.btn-secondary { background-color: #f0f0f0; color: #333; border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; }
details summary { cursor: pointer; color: #4a6cf7; margin-bottom: 0.5rem; }
h2 { margin-top: 0; font-size: 1.2rem; }</style>Integração com o Componente de Analytics
---// src/components/Analytics.astro (versão atualizada)const GA_MEASUREMENT_ID = import.meta.env.PUBLIC_GA_MEASUREMENT_ID;---
{GA_MEASUREMENT_ID && ( <Fragment> <!-- Script carregado apenas se o usuário consentir --> <script type="text/partytown" data-type="analytics" async src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`} ></script> <script type="text/partytown" data-type="analytics" define:vars={{ GA_MEASUREMENT_ID }}> // Verificar consentimento const cookieSettings = JSON.parse(localStorage.getItem('cookie-settings') || '{}');
if (cookieSettings.analytics) { window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', GA_MEASUREMENT_ID, { send_page_view: false, anonymize_ip: true // Anonimizar IPs para GDPR });
// Enviar pageview inicial gtag('event', 'page_view', { page_title: document.title, page_location: window.location.href, page_path: window.location.pathname });
// Monitorar mudanças de rota em SPAs document.addEventListener('astro:page-load', () => { gtag('event', 'page_view', { page_title: document.title, page_location: window.location.href, page_path: window.location.pathname }); }); }
// Atualizar quando as configurações de cookies mudarem window.addEventListener('cookieConsentUpdate', (event) => { const settings = event.detail;
if (settings.analytics) { // Reativar analytics window['ga-disable-' + GA_MEASUREMENT_ID] = false;
// Inicializar se ainda não estiver inicializado if (!window.dataLayer) { window.dataLayer = []; gtag('js', new Date()); gtag('config', GA_MEASUREMENT_ID, { anonymize_ip: true }); } } else { // Desativar analytics window['ga-disable-' + GA_MEASUREMENT_ID] = true; } }); </script> </Fragment>)}Conclusão
A implementação de uma estratégia abrangente de análise de dados é essencial para entender o comportamento dos usuários e melhorar continuamente sua solução web desenvolvida com Astro 5. Ao seguir as práticas recomendadas neste documento, você estará coletando, processando e visualizando dados de forma ética e eficiente, respeitando a privacidade dos usuários e cumprindo com as regulamentações aplicáveis.
Lembre-se de que a análise de dados é um processo contínuo. À medida que sua solução evolui, você deve revisar e adaptar sua estratégia de análise para garantir que está coletando os dados mais relevantes para seus objetivos de negócio.
Os próximos documentos detalharão outros aspectos do desenvolvimento:
- Manutenção e Evolução: Práticas para manter e evoluir sua solução web
Última atualização: 21 de março de 2025