File: /home/onlyfibr/public_html/assinar/admin/relatorios.php
<?php
// HABILITAR ERROS PARA DEBUG - REMOVER/COMENTAR EM PRODUÇÃO!
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// ---------------------------------------------------------
// 1. Verifica se o admin está logado
require_once 'includes/auth_check.php';
// Define o título desta página ANTES de incluir o header
$pageTitle = 'Relatórios';
require_once '../includes/config/config.php'; // Necessário para $pdo e constantes
require_once '../includes/functions/functions.php'; // Pode ser necessário para alguma função auxiliar
// --- Processamento de Solicitações de Relatórios ---
$mensagem_erro = $_SESSION['erro_dashboard'] ?? null;
$mensagem_sucesso = $_SESSION['sucesso_dashboard'] ?? null;
unset($_SESSION['erro_dashboard'], $_SESSION['sucesso_dashboard']);
// Inicializa variáveis para estatísticas
$relatorio_data = [];
$graficos_data = [];
$filtro_data_inicio = $_GET['data_inicio'] ?? date('Y-m-d', strtotime('-30 days'));
$filtro_data_fim = $_GET['data_fim'] ?? date('Y-m-d');
$filtro_status = $_GET['status'] ?? '';
// --- Processamento do Formulário (Geração de Relatório) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['gerar_relatorio'])) {
// ADICIONAR VALIDAÇÃO CSRF TOKEN AQUI !!!
// Captura e valida os filtros
$filtro_data_inicio = filter_input(INPUT_POST, 'data_inicio', FILTER_SANITIZE_STRING) ?? date('Y-m-d', strtotime('-30 days'));
$filtro_data_fim = filter_input(INPUT_POST, 'data_fim', FILTER_SANITIZE_STRING) ?? date('Y-m-d');
$filtro_status = filter_input(INPUT_POST, 'status', FILTER_SANITIZE_STRING) ?? '';
// Validação simples de datas
if (strtotime($filtro_data_inicio) > strtotime($filtro_data_fim)) {
$mensagem_erro = "Data inicial não pode ser maior que a data final.";
}
}
// --- Geração dos Relatórios ---
if ($pdo) {
try {
// Condições básicas para a consulta
$where_conditions = ["1=1"]; // Sempre verdadeiro para facilitar concatenação de ANDs
$query_params = [];
// Adiciona condições com base nos filtros
if (!empty($filtro_data_inicio) && !empty($filtro_data_fim)) {
$where_conditions[] = "data_envio BETWEEN :data_inicio AND :data_fim";
$query_params[':data_inicio'] = $filtro_data_inicio . ' 00:00:00';
$query_params[':data_fim'] = $filtro_data_fim . ' 23:59:59';
}
if (!empty($filtro_status)) {
$where_conditions[] = "status_processamento = :status";
$query_params[':status'] = $filtro_status;
}
// Constrói a cláusula WHERE
$where_clause = implode(' AND ', $where_conditions);
// --------- Relatório 1: Cadastros por Status ---------
$sql_status = "
SELECT status_processamento, COUNT(*) as total
FROM precadastros
WHERE $where_clause
GROUP BY status_processamento
ORDER BY total DESC";
$stmt_status = $pdo->prepare($sql_status);
foreach ($query_params as $param => $value) {
$stmt_status->bindValue($param, $value);
}
$stmt_status->execute();
$relatorio_data['status'] = $stmt_status->fetchAll(PDO::FETCH_ASSOC);
// Prepara dados para o gráfico de pizza de status
$graficos_data['status_labels'] = [];
$graficos_data['status_values'] = [];
$graficos_data['status_colors'] = [];
$colors = [
'em_analise' => '#47076c',
'aguardando_contato' => '#e86d0f',
'aprovado' => '#046018',
'reprovado' => '#c90a0a',
'tentando_contato' => '#0b2bdf',
'contrato_realizado_no_sgp' => '#28f404',
'inviabilidade_tecnica' => '#fddf01'
];
foreach ($relatorio_data['status'] as $status_item) {
$status_key = $status_item['status_processamento'];
$status_label = ucwords(str_replace('_', ' ', $status_key));
$graficos_data['status_labels'][] = $status_label;
$graficos_data['status_values'][] = (int)$status_item['total'];
$graficos_data['status_colors'][] = $colors[$status_key] ?? '#999999';
}
// --------- Relatório 2: Cadastros por Dia ---------
$sql_diario = "
SELECT DATE(data_envio) as data, COUNT(*) as total
FROM precadastros
WHERE $where_clause
GROUP BY DATE(data_envio)
ORDER BY data ASC";
$stmt_diario = $pdo->prepare($sql_diario);
foreach ($query_params as $param => $value) {
$stmt_diario->bindValue($param, $value);
}
$stmt_diario->execute();
$relatorio_data['diario'] = $stmt_diario->fetchAll(PDO::FETCH_ASSOC);
// Prepara dados para o gráfico de linha diário
$graficos_data['diario_labels'] = [];
$graficos_data['diario_values'] = [];
foreach ($relatorio_data['diario'] as $dia_item) {
// Formata a data para exibição (DD/MM/YYYY)
$data_formatada = date('d/m/Y', strtotime($dia_item['data']));
$graficos_data['diario_labels'][] = $data_formatada;
$graficos_data['diario_values'][] = (int)$dia_item['total'];
}
// --------- Relatório 3: Top 5 Cidades ---------
$sql_cidades = "
SELECT cidade, estado, COUNT(*) as total
FROM precadastros
WHERE $where_clause
GROUP BY cidade, estado
ORDER BY total DESC
LIMIT 5";
$stmt_cidades = $pdo->prepare($sql_cidades);
foreach ($query_params as $param => $value) {
$stmt_cidades->bindValue($param, $value);
}
$stmt_cidades->execute();
$relatorio_data['cidades'] = $stmt_cidades->fetchAll(PDO::FETCH_ASSOC);
// Prepara dados para o gráfico de barras de cidades
$graficos_data['cidades_labels'] = [];
$graficos_data['cidades_values'] = [];
foreach ($relatorio_data['cidades'] as $cidade_item) {
$cidade_label = $cidade_item['cidade'] . '/' . $cidade_item['estado'];
$graficos_data['cidades_labels'][] = $cidade_label;
$graficos_data['cidades_values'][] = (int)$cidade_item['total'];
}
// --------- Relatório 4: Totais Gerais ---------
// Total de cadastros no período
$sql_total = "
SELECT COUNT(*) as total
FROM precadastros
WHERE $where_clause";
$stmt_total = $pdo->prepare($sql_total);
foreach ($query_params as $param => $value) {
$stmt_total->bindValue($param, $value);
}
$stmt_total->execute();
$relatorio_data['total_geral'] = $stmt_total->fetchColumn();
// Total pendente de processamento
$sql_pendentes = "
SELECT COUNT(*) as total
FROM precadastros
WHERE $where_clause AND p_processar = TRUE";
$stmt_pendentes = $pdo->prepare($sql_pendentes);
foreach ($query_params as $param => $value) {
$stmt_pendentes->bindValue($param, $value);
}
$stmt_pendentes->execute();
$relatorio_data['total_pendentes'] = $stmt_pendentes->fetchColumn();
// Total processado com sucesso
$sql_processados = "
SELECT COUNT(*) as total
FROM precadastros
WHERE $where_clause AND p_processar = FALSE AND status_processamento = 'aprovado'";
$stmt_processados = $pdo->prepare($sql_processados);
foreach ($query_params as $param => $value) {
$stmt_processados->bindValue($param, $value);
}
$stmt_processados->execute();
$relatorio_data['total_processados'] = $stmt_processados->fetchColumn();
} catch (\PDOException $e) {
$mensagem_erro = "Erro ao gerar relatórios: " . $e->getMessage();
error_log("Erro PDO ao gerar relatórios: " . $e->getMessage());
}
} else {
$mensagem_erro = "Erro crítico: Conexão com banco de dados falhou.";
}
// Lista de status para o filtro
$status_options = [
'em_analise' => 'Em Análise',
'aprovado' => 'Aprovado',
'reprovado' => 'Reprovado',
'aguardando_contato' => 'Aguardando Contato',
'tentando_contato' => 'Tentando Contato',
'inviabilidade_tecnica' => 'Inviabilidade Técnica',
'contrato_realizado_no_sgp' => 'Contrato Realizado no SGP'
];
// Obter os menus dinamicamente do banco de dados
$menus = carregarMenus($pdo, $usuario_permissoes ?? ['admin']);
// Incluir o header
include_once 'includes/header.php';
?>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1>Relatórios</h1>
<p class="text-muted">Análise de pré-cadastros e métricas do sistema</p>
</div>
<div>
<button type="button" class="btn btn-primary" id="btnImprimirRelatorio">
<i class="fas fa-print"></i> Imprimir Relatório
</button>
<button type="button" class="btn btn-outline-secondary ms-2" id="btnExportarPDF">
<i class="fas fa-file-pdf"></i> Exportar PDF
</button>
</div>
</div>
<?php if ($mensagem_erro): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($mensagem_erro); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if ($mensagem_sucesso): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?php echo htmlspecialchars($mensagem_sucesso); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<!-- Filtros do relatório -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-filter me-2"></i>Filtros</h5>
</div>
<div class="card-body">
<form method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" class="row g-3">
<input type="hidden" name="gerar_relatorio" value="1">
<!-- ADICIONE CSRF TOKEN AQUI !!! -->
<div class="col-md-4">
<label for="data_inicio" class="form-label">Data Inicial</label>
<input type="date" class="form-control" id="data_inicio" name="data_inicio"
value="<?php echo htmlspecialchars($filtro_data_inicio); ?>">
</div>
<div class="col-md-4">
<label for="data_fim" class="form-label">Data Final</label>
<input type="date" class="form-control" id="data_fim" name="data_fim"
value="<?php echo htmlspecialchars($filtro_data_fim); ?>">
</div>
<div class="col-md-4">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option value="">Todos os Status</option>
<?php foreach ($status_options as $value => $label): ?>
<option value="<?php echo htmlspecialchars($value); ?>"
<?php echo ($filtro_status === $value) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 mt-3">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-2"></i>Gerar Relatório
</button>
<a href="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" class="btn btn-outline-secondary ms-2">
<i class="fas fa-redo me-2"></i>Limpar Filtros
</a>
</div>
</form>
</div>
</div>
<!-- Resumo do relatório -->
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card bg-primary text-white h-100">
<div class="card-body text-center">
<h5 class="card-title">Total de Cadastros</h5>
<h2 class="display-4"><?php echo number_format($relatorio_data['total_geral'] ?? 0); ?></h2>
<p class="card-text">No período selecionado</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card bg-warning text-dark h-100">
<div class="card-body text-center">
<h5 class="card-title">Pendentes de Processamento</h5>
<h2 class="display-4"><?php echo number_format($relatorio_data['total_pendentes'] ?? 0); ?></h2>
<p class="card-text">Aguardando envio para API</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card bg-success text-white h-100">
<div class="card-body text-center">
<h5 class="card-title">Processados com Sucesso</h5>
<h2 class="display-4"><?php echo number_format($relatorio_data['total_processados'] ?? 0); ?></h2>
<p class="card-text">Enviados para API</p>
</div>
</div>
</div>
</div>
<!-- Gráficos -->
<div class="row mb-4">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-chart-pie me-2"></i>Cadastros por Status</h5>
</div>
<div class="card-body">
<canvas id="chartStatus" width="400" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-chart-line me-2"></i>Cadastros por Dia</h5>
</div>
<div class="card-body">
<canvas id="chartDiario" width="400" height="300"></canvas>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-chart-bar me-2"></i>Top 5 Cidades</h5>
</div>
<div class="card-body">
<canvas id="chartCidades" width="400" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-table me-2"></i>Dados por Status</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Status</th>
<th class="text-center">Quantidade</th>
<th class="text-center">Percentual</th>
</tr>
</thead>
<tbody>
<?php
$total_geral = $relatorio_data['total_geral'] ?? 0;
if (!empty($relatorio_data['status'])):
foreach ($relatorio_data['status'] as $status_item):
$percentual = ($total_geral > 0) ? ($status_item['total'] / $total_geral) * 100 : 0;
?>
<tr>
<td>
<span class="badge status-<?php echo htmlspecialchars($status_item['status_processamento']); ?>">
<?php echo ucwords(str_replace('_', ' ', htmlspecialchars($status_item['status_processamento']))); ?>
</span>
</td>
<td class="text-center"><?php echo number_format($status_item['total']); ?></td>
<td class="text-center"><?php echo number_format($percentual, 1); ?>%</td>
</tr>
<?php
endforeach;
else:
?>
<tr>
<td colspan="3" class="text-center">Nenhum dado encontrado.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Carregamento do Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
<script>
// Dados dos gráficos
const statusLabels = <?php echo json_encode($graficos_data['status_labels'] ?? []); ?>;
const statusValues = <?php echo json_encode($graficos_data['status_values'] ?? []); ?>;
const statusColors = <?php echo json_encode($graficos_data['status_colors'] ?? []); ?>;
const diarioLabels = <?php echo json_encode($graficos_data['diario_labels'] ?? []); ?>;
const diarioValues = <?php echo json_encode($graficos_data['diario_values'] ?? []); ?>;
const cidadesLabels = <?php echo json_encode($graficos_data['cidades_labels'] ?? []); ?>;
const cidadesValues = <?php echo json_encode($graficos_data['cidades_values'] ?? []); ?>;
// Configuração dos gráficos
document.addEventListener('DOMContentLoaded', function() {
// Gráfico de Status (Pie Chart)
if (document.getElementById('chartStatus')) {
const ctxStatus = document.getElementById('chartStatus').getContext('2d');
const chartStatus = new Chart(ctxStatus, {
type: 'pie',
data: {
labels: statusLabels,
datasets: [{
data: statusValues,
backgroundColor: statusColors,
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.raw || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
return `${label}: ${value} (${percentage}%)`;
}
}
}
}
}
});
}
// Gráfico Diário (Line Chart)
if (document.getElementById('chartDiario')) {
const ctxDiario = document.getElementById('chartDiario').getContext('2d');
const chartDiario = new Chart(ctxDiario, {
type: 'line',
data: {
labels: diarioLabels,
datasets: [{
label: 'Cadastros por Dia',
data: diarioValues,
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
// Gráfico de Cidades (Bar Chart)
if (document.getElementById('chartCidades')) {
const ctxCidades = document.getElementById('chartCidades').getContext('2d');
const chartCidades = new Chart(ctxCidades, {
type: 'bar',
data: {
labels: cidadesLabels,
datasets: [{
label: 'Cadastros por Cidade',
data: cidadesValues,
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(255, 206, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(153, 102, 255, 0.7)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
});
// Função para impressão de relatório
document.getElementById('btnImprimirRelatorio').addEventListener('click', function() {
window.print();
});
// Função para exportar PDF (simulada)
document.getElementById('btnExportarPDF').addEventListener('click', function() {
alert('Esta função exportaria o relatório para PDF.\nPara implementação real, você precisaria de uma biblioteca como jsPDF ou enviar o pedido para o servidor gerar o PDF.');
});
</script>
<?php
// Incluir o footer
include_once 'includes/footer.php';
?>