38458-vm/dashboard.php
Flatlogic Bot 3aa07f42ec Final
2026-02-15 22:06:09 +00:00

248 lines
9.5 KiB
PHP

<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
$electionId = get_active_election_id();
$election = get_active_election();
// Statistics (Filtered by Election)
$totalVoters = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND role_in_election = 'Voter'");
$totalVoters->execute([$electionId]);
$totalVoters = $totalVoters->fetchColumn();
$totalCandidates = $pdo->prepare("SELECT COUNT(*) FROM candidates WHERE election_id = ?");
$totalCandidates->execute([$electionId]);
$totalCandidates = $totalCandidates->fetchColumn();
$totalVotes = $pdo->prepare("SELECT COUNT(DISTINCT voter_id) FROM votes WHERE election_id = ?");
$totalVotes->execute([$electionId]);
$totalVotes = $totalVotes->fetchColumn();
// Chart Data: Participation per Grade Level
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$gradeCol = ($driver === 'pgsql') ? "u.grade_level::TEXT" : "CAST(u.grade_level AS CHAR)";
$gradeStats = $pdo->prepare("SELECT COALESCE($gradeCol, 'Unknown') as label, COUNT(DISTINCT v.voter_id) as count
FROM users u JOIN votes v ON u.id = v.voter_id
WHERE v.election_id = ?
GROUP BY u.grade_level ORDER BY u.grade_level");
$gradeStats->execute([$electionId]);
$gradeStats = $gradeStats->fetchAll(PDO::FETCH_ASSOC);
// Chart Data: Participation per Track
$trackStats = $pdo->prepare("SELECT COALESCE(u.track, 'Unknown') as label, COUNT(DISTINCT v.voter_id) as count
FROM users u JOIN votes v ON u.id = v.voter_id
WHERE v.election_id = ?
GROUP BY u.track");
$trackStats->execute([$electionId]);
$trackStats = $trackStats->fetchAll(PDO::FETCH_ASSOC);
// Chart Data: Participation per Section
$sectionStats = $pdo->prepare("SELECT u.track, u.section as label, COUNT(DISTINCT v.voter_id) as count
FROM users u JOIN votes v ON u.id = v.voter_id
WHERE v.election_id = ?
GROUP BY u.track, u.section");
$sectionStats->execute([$electionId]);
$sectionStats = $sectionStats->fetchAll(PDO::FETCH_ASSOC);
// Tracks for dropdown
$tracks = array_unique(array_column($sectionStats, 'track'));
sort($tracks);
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System for Senior High School';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Election Dashboard | <?= htmlspecialchars($projectDescription) ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="dashboard-body">
<?php require_once 'includes/sidebar.php'; ?>
<!-- Main Content -->
<div class="main-wrapper">
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header">
<div>
<h1 style="margin: 0 0 4px 0; font-size: 1.5rem; color: #1e293b;">Election Dashboard</h1>
<div class="welcome-msg">
Active Election: <strong><?= htmlspecialchars($election['title'] ?? 'None') ?></strong>
</div>
</div>
</div>
<!-- Stats Grid -->
<div class="stats-grid animate-stagger">
<div class="stat-card">
<div class="stat-label">Total Voters</div>
<div class="stat-value"><?= number_format($totalVoters) ?></div>
<div class="stat-footer voters">
<i data-lucide="users-2" style="width: 14px;"></i>
Assigned Students
</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Candidates</div>
<div class="stat-value"><?= number_format($totalCandidates) ?></div>
<div class="stat-footer candidates">
<i data-lucide="user-circle" style="width: 14px;"></i>
Validated Contestants
</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Votes Cast</div>
<div class="stat-value"><?= number_format($totalVotes) ?></div>
<div class="stat-footer votes">
<i data-lucide="check-circle-2" style="width: 14px;"></i>
Verified Ballots
</div>
</div>
</div>
<!-- Analytics Charts -->
<div class="analytics-row animate-stagger">
<div class="analytics-card">
<div class="card-header">
<div class="card-title">Votes Per Grade Level</div>
</div>
<div class="chart-container">
<canvas id="gradeChart"></canvas>
</div>
</div>
<div class="analytics-card">
<div class="card-header">
<div class="card-title">Votes Per Track</div>
</div>
<div class="chart-container">
<canvas id="trackChart"></canvas>
</div>
</div>
</div>
<div class="analytics-row animate-stagger">
<div class="analytics-card" style="grid-column: span 2;">
<div class="card-header">
<div class="card-title">Votes Per Section</div>
<select id="trackFilter" class="chart-filter">
<?php if (empty($tracks)): ?>
<option>No data</option>
<?php endif; ?>
<?php foreach ($tracks as $t): ?>
<option value="<?= htmlspecialchars($t) ?>"><?= htmlspecialchars($t) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="chart-container" style="height: 300px;">
<canvas id="sectionChart"></canvas>
</div>
</div>
</div>
</main>
</div>
<script>
lucide.createIcons();
// Chart Data from PHP
const gradeData = <?= json_encode($gradeStats) ?>;
const trackData = <?= json_encode($trackStats) ?>;
const sectionData = <?= json_encode($sectionStats) ?>;
// Common Chart Options
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true, grid: { display: false } },
x: { grid: { display: false } }
}
};
// Grade Level Bar Chart
if (gradeData.length) {
new Chart(document.getElementById('gradeChart'), {
type: 'bar',
data: {
labels: gradeData.map(d => 'Grade ' + d.label),
datasets: [{
label: 'Votes',
data: gradeData.map(d => d.count),
backgroundColor: '#4f46e5',
borderRadius: 6
}]
},
options: commonOptions
});
}
// Track Bar Chart
if (trackData.length) {
new Chart(document.getElementById('trackChart'), {
type: 'bar',
data: {
labels: trackData.map(d => d.label),
datasets: [{
label: 'Votes',
data: trackData.map(d => d.count),
backgroundColor: '#10b981',
borderRadius: 6
}]
},
options: commonOptions
});
}
// Section Chart
let sectionChart;
function updateSectionChart(track) {
const canvas = document.getElementById('sectionChart');
if (!canvas) return;
const filtered = sectionData.filter(d => d.track === track);
const data = {
labels: filtered.map(d => d.label),
datasets: [{
label: 'Votes',
data: filtered.map(d => d.count),
backgroundColor: '#4f46e5',
borderRadius: 6
}]
};
if (sectionChart) {
sectionChart.data = data;
sectionChart.update();
} else {
sectionChart = new Chart(canvas, {
type: 'bar',
data: data,
options: commonOptions
});
}
}
const trackFilter = document.getElementById('trackFilter');
if (trackFilter && trackData.length) {
trackFilter.addEventListener('change', (e) => {
updateSectionChart(e.target.value);
});
updateSectionChart(trackFilter.value);
}
</script>
</body>
</html>