38458-vm/voter_management.php
2026-02-15 20:43:30 +00:00

408 lines
25 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">
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<?php if (isset($_GET['success'])): ?>
<?php if ($_GET['success'] === 'voter_added'): ?>
<div style="background: #ecfdf5; color: #065f46; padding: 16px; border-radius: 12px; border: 1px solid #a7f3d0; margin-bottom: 24px; display: flex; align-items: center; gap: 12px;">
<i data-lucide="check-circle" style="width: 20px;"></i>
<span style="font-weight: 500;">Voter successfully registered!</span>
</div>
<?php elseif ($_GET['success'] === 'import_complete'): ?>
<div style="background: #ecfdf5; color: #065f46; padding: 16px; border-radius: 12px; border: 1px solid #a7f3d0; margin-bottom: 24px; display: flex; align-items: center; gap: 12px;">
<i data-lucide="check-circle" style="width: 20px;"></i>
<div>
<span style="font-weight: 600; display: block;">Import completed successfully!</span>
<span style="font-size: 0.875rem;">
<?= (int)$_GET['imported'] ?> new voters added, <?= (int)$_GET['updated'] ?> existing records updated.
</span>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<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" onclick='editVoter(<?= json_encode($voter) ?>)'><i data-lucide="edit-2"></i></button>
<button title="Delete" style="color: #ef4444;" onclick="deleteVoter('<?= $voter['id'] ?>', '<?= htmlspecialchars($voter['name']) ?>')"><i data-lucide="trash-2"></i></button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</main>
</div>
<!-- Modals -->
<div id="addVoterModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px;">
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
<h2 style="margin:0; font-size:1.25rem;">Register New Voter</h2>
<button onclick="closeModal('addVoterModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/add_voter.php" method="POST">
<input type="hidden" name="election_id" value="<?= $electionId ?>">
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">FULL NAME</label>
<input type="text" name="name" placeholder="Enter student's full name" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">STUDENT ID</label>
<input type="text" name="student_id" placeholder="XX-XXXX" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">EMAIL ADDRESS</label>
<input type="email" name="email" placeholder="student@school.edu" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">PASSWORD</label>
<input type="text" name="password" value="iloilohns" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<small style="color: #64748b; font-size: 11px;">Default is iloilohns</small>
</div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px; margin-bottom:24px;">
<div class="form-group">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">TRACK</label>
<select name="track" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<option value="STEM">STEM</option>
<option value="ABM">ABM</option>
<option value="HUMSS">HUMSS</option>
<option value="GAS">GAS</option>
<option value="TVL">TVL</option>
</select>
</div>
<div class="form-group">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">GRADE LEVEL</label>
<select name="grade_level" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<option value="11">Grade 11</option>
<option value="12">Grade 12</option>
</select>
</div>
</div>
<div class="modal-footer" style="display:flex; justify-content:flex-end; gap:12px;">
<button type="button" onclick="closeModal('addVoterModal')" class="btn-cancel" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
<button type="submit" class="btn-action btn-add" style="padding:10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Register Voter</button>
</div>
</form>
</div>
</div>
<div id="editVoterModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px;">
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
<h2 style="margin:0; font-size:1.25rem;">Edit Voter</h2>
<button onclick="closeModal('editVoterModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/update_voter.php" method="POST">
<input type="hidden" name="id" id="edit_voter_id">
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">FULL NAME</label>
<input type="text" name="name" id="edit_voter_name" placeholder="Enter student's full name" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">STUDENT ID</label>
<input type="text" name="student_id" id="edit_voter_student_id" placeholder="XX-XXXX" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">EMAIL ADDRESS</label>
<input type="email" name="email" id="edit_voter_email" placeholder="student@school.edu" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div class="form-group" style="margin-bottom:16px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">NEW PASSWORD (OPTIONAL)</label>
<input type="password" name="password" placeholder="Leave blank to keep current" style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
</div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px; margin-bottom:24px;">
<div class="form-group">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">TRACK</label>
<select name="track" id="edit_voter_track" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<option value="STEM">STEM</option>
<option value="ABM">ABM</option>
<option value="HUMSS">HUMSS</option>
<option value="GAS">GAS</option>
<option value="TVL">TVL</option>
</select>
</div>
<div class="form-group">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">GRADE LEVEL</label>
<select name="grade_level" id="edit_voter_grade_level" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<option value="11">Grade 11</option>
<option value="12">Grade 12</option>
</select>
</div>
</div>
<div class="modal-footer" style="display:flex; justify-content:flex-end; gap:12px;">
<button type="button" onclick="closeModal('editVoterModal')" class="btn-cancel" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
<button type="submit" class="btn-action btn-add" style="padding:10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Update Voter</button>
</div>
</form>
</div>
</div>
<div id="importModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px;">
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
<h2 style="margin:0; font-size:1.25rem;">Import Voters from CSV</h2>
<button onclick="closeModal('importModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/import_voters.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="election_id" value="<?= $electionId ?>">
<div class="form-group" style="margin-bottom:24px;">
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:12px;">SELECT CSV FILE</label>
<div style="border: 2px dashed #e2e8f0; padding: 32px; border-radius: 12px; text-align: center; cursor: pointer;" onclick="document.getElementById('csvFile').click()">
<i data-lucide="upload-cloud" style="width: 32px; height: 32px; color: #94a3b8; margin-bottom: 12px;"></i>
<p style="margin: 0; font-size: 0.875rem; color: #64748b;">Click to upload or drag and drop</p>
<p style="margin: 4px 0 0 0; font-size: 0.75rem; color: #94a3b8;">CSV files only (Max 5MB)</p>
<input type="file" id="csvFile" name="csv_file" accept=".csv" style="display: none;" onchange="updateFileName(this)">
<div id="fileName" style="margin-top: 12px; font-weight: 500; color: #4f46e5; font-size: 0.875rem;"></div>
</div>
</div>
<div style="background: #f8fafc; padding: 16px; border-radius: 8px; margin-bottom: 24px;">
<p style="margin: 0 0 8px 0; font-size: 0.75rem; font-weight: 600; color: #64748b;">CSV FORMAT REQUIREMENTS:</p>
<p style="margin: 0; font-size: 0.75rem; color: #64748b; line-height: 1.5;">
Columns: <code>student_id, name, email, track, grade_level</code><br>
Example: <code>20-1234, John Doe, john@example.com, STEM, 12</code>
</p>
</div>
<div class="modal-footer" style="display:flex; justify-content:flex-end; gap:12px;">
<button type="button" onclick="closeModal('importModal')" class="btn-cancel" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
<button type="submit" class="btn-action btn-add" style="padding: 10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Start Import</button>
</div>
</form>
</div>
</div>
<script>
lucide.createIcons();
function updateFileName(input) {
const fileNameDisplay = document.getElementById('fileName');
if (input.files && input.files.length > 0) {
fileNameDisplay.textContent = input.files[0].name;
} else {
fileNameDisplay.textContent = '';
}
}
function openModal(id) {
document.getElementById(id).style.display = 'flex';
}
function closeModal(id) {
document.getElementById(id).style.display = 'none';
}
function editVoter(voter) {
document.getElementById('edit_voter_id').value = voter.id;
document.getElementById('edit_voter_name').value = voter.name;
document.getElementById('edit_voter_student_id').value = voter.student_id;
document.getElementById('edit_voter_email').value = voter.email;
document.getElementById('edit_voter_track').value = voter.track;
document.getElementById('edit_voter_grade_level').value = voter.grade_level;
openModal('editVoterModal');
}
function deleteVoter(id, name) {
if (confirm(`Are you sure you want to remove ${name} from this election?`)) {
window.location.href = `api/delete_voter.php?id=${id}&election_id=<?= $electionId ?>`;
}
}
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>