255 lines
12 KiB
PHP
255 lines
12 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 if possible, otherwise global)
|
|
// For now, let's assume we want to see voters assigned to the current election
|
|
$totalVoters = $pdo->prepare("SELECT COUNT(*) FROM users u JOIN election_assignments ea ON u.id = ea.user_id WHERE ea.election_id = ? AND ea.role_in_election = 'Voter'");
|
|
$totalVoters->execute([$electionId]);
|
|
$totalVoters = $totalVoters->fetchColumn();
|
|
|
|
$votedCount = $pdo->prepare("SELECT COUNT(DISTINCT voter_id) FROM votes WHERE election_id = ?");
|
|
$votedCount->execute([$electionId]);
|
|
$votedCount = $votedCount->fetchColumn();
|
|
|
|
$notVotedCount = $totalVoters - $votedCount;
|
|
|
|
// Distribution (Filtered by Election)
|
|
$trackStats = $pdo->prepare("SELECT u.track, COUNT(*) as count FROM users u JOIN election_assignments ea ON u.id = ea.user_id WHERE ea.election_id = ? AND ea.role_in_election = 'Voter' GROUP BY u.track ORDER BY u.track");
|
|
$trackStats->execute([$electionId]);
|
|
$trackStats = $trackStats->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$gradeStats = $pdo->prepare("SELECT u.grade_level, COUNT(*) as count FROM users u JOIN election_assignments ea ON u.id = ea.user_id WHERE ea.election_id = ? AND ea.role_in_election = 'Voter' GROUP BY u.grade_level ORDER BY u.grade_level");
|
|
$gradeStats->execute([$electionId]);
|
|
$gradeStats = $gradeStats->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Filters
|
|
$search = $_GET['search'] ?? '';
|
|
$filterTrack = $_GET['track'] ?? 'All Tracks';
|
|
$filterGrade = $_GET['grade'] ?? 'All Grades';
|
|
|
|
// Query Construction
|
|
$query = "SELECT u.*,
|
|
(SELECT COUNT(*) FROM votes v WHERE v.voter_id = u.id AND v.election_id = ?) as has_voted
|
|
FROM users u
|
|
JOIN election_assignments ea ON u.id = ea.user_id
|
|
WHERE ea.election_id = ? AND ea.role_in_election = 'Voter'";
|
|
|
|
$params = [$electionId, $electionId];
|
|
|
|
if ($search) {
|
|
$query .= " AND (u.email LIKE ? OR u.name LIKE ? OR u.student_id LIKE ?)";
|
|
$params[] = "%$search%";
|
|
$params[] = "%$search%";
|
|
$params[] = "%$search%";
|
|
}
|
|
|
|
if ($filterTrack !== 'All Tracks') {
|
|
$query .= " AND u.track = ?";
|
|
$params[] = $filterTrack;
|
|
}
|
|
|
|
if ($filterGrade !== 'All Grades') {
|
|
$query .= " AND u.grade_level = ?";
|
|
$params[] = $filterGrade;
|
|
}
|
|
|
|
$stmt = $pdo->prepare($query);
|
|
$stmt->execute($params);
|
|
$voters = $stmt->fetchAll();
|
|
|
|
// Get unique values for filters
|
|
$tracks = $pdo->query("SELECT DISTINCT track FROM users WHERE track IS NOT NULL ORDER BY track")->fetchAll(PDO::FETCH_COLUMN);
|
|
$grades = $pdo->query("SELECT DISTINCT grade_level FROM users WHERE grade_level IS NOT NULL ORDER BY grade_level")->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
$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>Voter Management | <?= 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() ?>">
|
|
<link rel="stylesheet" href="assets/css/voter_management.css?v=<?= time() ?>">
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
</head>
|
|
<body class="dashboard-body">
|
|
|
|
<?php require_once 'includes/sidebar.php'; ?>
|
|
|
|
<!-- Main Content -->
|
|
<div class="main-wrapper">
|
|
<header class="top-header">
|
|
<div class="search-bar">
|
|
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
|
<input type="text" placeholder="Quick search...">
|
|
</div>
|
|
|
|
<div class="user-profile">
|
|
<div class="user-info">
|
|
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
|
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
|
</div>
|
|
<div class="user-avatar">
|
|
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="dashboard-content animate-fade-in">
|
|
<div class="dashboard-header">
|
|
<div style="display: flex; align-items: center; gap: 16px;">
|
|
<div class="header-icon-container">
|
|
<i data-lucide="users" style="width: 24px; color: #4f46e5;"></i>
|
|
</div>
|
|
<div>
|
|
<h1 style="margin: 0; font-size: 1.5rem; color: #1e293b;">Voters List</h1>
|
|
<p style="margin: 4px 0 0 0; color: #64748b; font-size: 0.875rem;">Managing voters for <?= htmlspecialchars($election['title'] ?? 'Selected Election') ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Grid -->
|
|
<div class="voter-stats-grid animate-stagger">
|
|
<div class="voter-stat-card">
|
|
<div class="voter-stat-label">TOTAL VOTERS</div>
|
|
<div class="voter-stat-value" style="color: #2563eb;"><?= number_format($totalVoters) ?></div>
|
|
</div>
|
|
<div class="voter-stat-card">
|
|
<div class="voter-stat-label">VOTERS WHO VOTED</div>
|
|
<div class="voter-stat-value" style="color: #64748b;"><?= number_format($votedCount) ?></div>
|
|
</div>
|
|
<div class="voter-stat-card">
|
|
<div class="voter-stat-label" style="color: #ef4444;">VOTERS WHO HAVEN'T VOTED</div>
|
|
<div class="voter-stat-value" style="color: #ef4444;"><?= number_format($notVotedCount) ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 24px;" class="animate-stagger">
|
|
<button class="btn-action btn-add" onclick="openModal('addVoterModal')">
|
|
<i data-lucide="plus" style="width: 14px;"></i>
|
|
ADD VOTER
|
|
</button>
|
|
<button class="btn-action btn-import" onclick="openModal('importModal')">
|
|
<i data-lucide="upload" style="width: 14px;"></i>
|
|
Import CSV
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Filters & Table Section -->
|
|
<div class="content-section animate-fade-in">
|
|
<form id="filterForm" method="GET" class="filter-bar">
|
|
<div class="filter-group" style="flex: 2;">
|
|
<label>SEARCH</label>
|
|
<div class="search-input-wrapper">
|
|
<i data-lucide="search" style="width: 14px; color: #94a3b8;"></i>
|
|
<input type="text" name="search" value="<?= htmlspecialchars($search) ?>" placeholder="Search by email">
|
|
</div>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label>TRACK</label>
|
|
<select name="track" onchange="this.form.submit()">
|
|
<option>All Tracks</option>
|
|
<?php foreach ($tracks as $t): ?>
|
|
<option value="<?= htmlspecialchars($t) ?>" <?= $filterTrack === $t ? 'selected' : '' ?>><?= htmlspecialchars($t) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label>GRADE</label>
|
|
<select name="grade" onchange="this.form.submit()">
|
|
<option>All Grades</option>
|
|
<?php foreach ($grades as $g): ?>
|
|
<option value="<?= htmlspecialchars($g) ?>" <?= $filterGrade == $g ? 'selected' : '' ?>>Grade <?= htmlspecialchars($g) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
|
|
<table class="voters-table">
|
|
<thead>
|
|
<tr>
|
|
<th>USER ID</th>
|
|
<th>NAME</th>
|
|
<th>EMAIL</th>
|
|
<th>TRACK</th>
|
|
<th>GRADE</th>
|
|
<th>STATUS</th>
|
|
<th>ACTIONS</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($voters)): ?>
|
|
<tr>
|
|
<td colspan="7" style="text-align: center; color: #94a3b8; padding: 32px;">No voters assigned to this election.</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($voters as $voter): ?>
|
|
<tr>
|
|
<td><?= htmlspecialchars($voter['student_id']) ?></td>
|
|
<td><?= htmlspecialchars($voter['name']) ?></td>
|
|
<td><?= htmlspecialchars($voter['email']) ?></td>
|
|
<td><?= htmlspecialchars($voter['track']) ?></td>
|
|
<td>Grade <?= htmlspecialchars($voter['grade_level']) ?></td>
|
|
<td>
|
|
<span class="status-indicator <?= $voter['has_voted'] ? 'voted' : 'pending' ?>">
|
|
<?= $voter['has_voted'] ? 'Voted' : 'Pending' ?>
|
|
</span>
|
|
</td>
|
|
<td class="actions-cell">
|
|
<button title="Edit"><i data-lucide="edit-2"></i></button>
|
|
<button title="Delete" style="color: #ef4444;"><i data-lucide="trash-2"></i></button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modals (Simplified for this polishing phase) -->
|
|
<div id="addVoterModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2>Add New Voter</h2>
|
|
<button onclick="closeModal('addVoterModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
|
|
</div>
|
|
<p style="font-size: 0.875rem; color: #64748b;">This will assign an existing user or create a new one for this election.</p>
|
|
<!-- Add Voter form logic here -->
|
|
<div class="modal-footer">
|
|
<button onclick="closeModal('addVoterModal')" class="btn-cancel">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
lucide.createIcons();
|
|
|
|
function openModal(id) {
|
|
document.getElementById(id).style.display = 'flex';
|
|
}
|
|
|
|
function closeModal(id) {
|
|
document.getElementById(id).style.display = 'none';
|
|
}
|
|
|
|
window.onclick = function(event) {
|
|
if (event.target.classList.contains('modal')) {
|
|
event.target.style.display = 'none';
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|