38458-vm/candidate_management.php
Flatlogic Bot 5ac812ef05 Final3
2026-02-16 03:29:05 +00:00

519 lines
24 KiB
PHP

<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
// Global Election Context
$electionId = get_active_election_id();
$election = get_active_election();
if (!$electionId) {
die("No active election selected. Please create an election first.");
}
// Statistics (Filtered by Election)
$totalCandidates = $pdo->prepare("SELECT COUNT(*) FROM candidates WHERE election_id = ?");
$totalCandidates->execute([$electionId]);
$totalCandidates = $totalCandidates->fetchColumn();
$uniquePositions = $pdo->prepare("SELECT COUNT(*) FROM positions WHERE election_id = ?");
$uniquePositions->execute([$electionId]);
$uniquePositions = $uniquePositions->fetchColumn();
$activeParties = $pdo->prepare("SELECT COUNT(*) FROM parties WHERE election_id = ?");
$activeParties->execute([$electionId]);
$activeParties = $activeParties->fetchColumn();
// Candidates by Position
$posStats = $pdo->prepare("SELECT p.name, COUNT(c.id) as count
FROM positions p LEFT JOIN candidates c ON p.id = c.position_id
WHERE p.election_id = ? GROUP BY p.id ORDER BY p.sort_order");
$posStats->execute([$electionId]);
$posStats = $posStats->fetchAll(PDO::FETCH_ASSOC);
// Candidates by Party
$partyStats = $pdo->prepare("SELECT p.name as party_name, COUNT(c.id) as count
FROM parties p LEFT JOIN candidates c ON p.name = c.party_name AND c.election_id = p.election_id
WHERE p.election_id = ? GROUP BY p.id ORDER BY count DESC");
$partyStats->execute([$electionId]);
$partyStats = $partyStats->fetchAll(PDO::FETCH_ASSOC);
// Filters
$search = $_GET['search'] ?? '';
$filterPosition = $_GET['position'] ?? 'All Positions';
$filterParty = $_GET['party'] ?? 'All Parties';
// Main Query
$query = "SELECT c.*, u.name as user_name, u.email as user_email, u.student_id, u.grade_level, u.track, p.name as position_name
FROM candidates c
JOIN users u ON c.user_id = u.id
JOIN positions p ON c.position_id = p.id
WHERE c.election_id = ?";
$params = [$electionId];
if ($search) {
$query .= " AND (u.name LIKE ? OR u.email LIKE ? OR c.party_name LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
if ($filterPosition !== 'All Positions') {
$query .= " AND p.name = ?";
$params[] = $filterPosition;
}
if ($filterParty !== 'All Parties') {
$query .= " AND c.party_name = ?";
$params[] = $filterParty;
}
$query .= " ORDER BY p.sort_order, u.name";
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$candidates = $stmt->fetchAll();
// Options for Modals/Filters
$allPositions = $pdo->prepare("SELECT * FROM positions WHERE election_id = ? ORDER BY sort_order");
$allPositions->execute([$electionId]);
$allPositions = $allPositions->fetchAll();
$allParties = $pdo->prepare("SELECT * FROM parties WHERE election_id = ? ORDER BY name");
$allParties->execute([$electionId]);
$allParties = $allParties->fetchAll();
$allVoters = $pdo->query("SELECT id, name, student_id FROM users WHERE role = 'Voter' ORDER BY name")->fetchAll();
$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>Candidate 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/candidate_management.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
.management-actions {
display: flex;
gap: 12px;
margin-bottom: 24px;
}
.btn-manage {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
border: 1px solid #e2e8f0;
background: white;
color: #1e293b;
}
.btn-manage:hover {
background: #f8fafc;
border-color: #cbd5e1;
}
.btn-manage.primary {
background: #4f46e5;
color: white;
border-color: #4f46e5;
}
.btn-manage.primary:hover {
background: #4338ca;
}
.modal {
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;
}
.modal-content {
background: white;
padding: 32px;
border-radius: 16px;
width: 100%;
max-width: 500px;
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.modal-header h2 { margin: 0; font-size: 1.25rem; color: #1e293b; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 12px; font-weight: 600; color: #64748b; margin-bottom: 6px; }
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 10px;
border-radius: 8px;
border: 1px solid #e2e8f0;
font-size: 14px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
</style>
</head>
<body class="dashboard-body <?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<?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" style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="display: flex; align-items: center; gap: 16px;">
<div class="header-icon-container">
<i data-lucide="user-square-2" style="width: 24px; color: #4f46e5;"></i>
</div>
<div>
<h1 style="margin: 0; font-size: 1.5rem; color: #1e293b;">Candidate Management</h1>
<p style="margin: 4px 0 0 0; color: #64748b; font-size: 0.875rem;">Managing <?= htmlspecialchars($election['title']) ?></p>
</div>
</div>
<div>
<span class="status-badge status-<?= strtolower($election['status'] ?? 'preparing') ?>">
<?= strtoupper($election['status'] ?? 'PREPARING') ?>
</span>
</div>
</div>
<div class="management-actions animate-stagger">
<button class="btn-manage primary" onclick="openModal('addCandidateModal')">
<i data-lucide="plus"></i> ADD CANDIDATE
</button>
<button class="btn-manage" onclick="openModal('addPositionModal')">
<i data-lucide="layout-list"></i> DEFINE POSITION
</button>
<button class="btn-manage" onclick="openModal('addPartyModal')">
<i data-lucide="flag"></i> DEFINE PARTY
</button>
</div>
<!-- Stats Grid -->
<div class="candidate-stats-grid animate-stagger">
<div class="candidate-stat-card">
<div class="candidate-stat-label">TOTAL CANDIDATES</div>
<div class="candidate-stat-value"><?= number_format($totalCandidates) ?></div>
</div>
<div class="candidate-stat-card">
<div class="candidate-stat-label">UNIQUE POSITIONS</div>
<div class="candidate-stat-value"><?= number_format($uniquePositions) ?></div>
</div>
<div class="candidate-stat-card">
<div class="candidate-stat-label" style="color: #10b981;">ACTIVE PARTIES</div>
<div class="candidate-stat-value" style="color: #10b981;"><?= number_format($activeParties) ?></div>
</div>
</div>
<!-- Distribution Row -->
<div class="distribution-row animate-stagger" style="margin-bottom: 32px;">
<div class="distribution-card">
<div class="distribution-header">Candidates by Position</div>
<div class="distribution-list">
<?php foreach ($posStats as $stat): ?>
<div class="distribution-item">
<span><?= htmlspecialchars($stat['name']) ?></span>
<span class="distribution-count"><?= $stat['count'] ?></span>
</div>
<?php endforeach; ?>
<?php if (empty($posStats)): ?>
<div style="padding: 12px; color: #94a3b8; font-size: 0.875rem; text-align: center;">No positions defined.</div>
<?php endif; ?>
</div>
</div>
<div class="distribution-card">
<div class="distribution-header">Candidates by Party</div>
<div class="distribution-list">
<?php foreach ($partyStats as $stat): ?>
<div class="distribution-item">
<span><?= htmlspecialchars($stat['party_name'] ?: 'Independent') ?></span>
<span class="distribution-count"><?= $stat['count'] ?></span>
</div>
<?php endforeach; ?>
<?php if (empty($partyStats)): ?>
<div style="padding: 12px; color: #94a3b8; font-size: 0.875rem; text-align: center;">No parties defined.</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Filters & Table Section -->
<div class="content-section animate-fade-in" style="background: white; border-radius: 12px; border: 1px solid #f3f4f6; overflow: hidden;">
<form 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 candidates...">
</div>
</div>
<div class="filter-group">
<label>POSITION</label>
<select name="position" onchange="this.form.submit()">
<option>All Positions</option>
<?php foreach ($allPositions as $p): ?>
<option value="<?= htmlspecialchars($p['name']) ?>" <?= $filterPosition === $p['name'] ? 'selected' : '' ?>><?= htmlspecialchars($p['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="filter-group">
<label>PARTY</label>
<select name="party" onchange="this.form.submit()">
<option>All Parties</option>
<?php foreach ($allParties as $pt): ?>
<option value="<?= htmlspecialchars($pt['name']) ?>" <?= $filterParty === $pt['name'] ? 'selected' : '' ?>><?= htmlspecialchars($pt['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</form>
<table class="candidates-table">
<thead>
<tr>
<th>CANDIDATE</th>
<th>POSITION</th>
<th>PARTY</th>
<th>GRADE/TRACK</th>
<th>ACTIONS</th>
</tr>
</thead>
<tbody>
<?php if (empty($candidates)): ?>
<tr>
<td colspan="5" style="text-align: center; color: #94a3b8; padding: 32px;">No candidates found in this election.</td>
</tr>
<?php else: ?>
<?php foreach ($candidates as $cand): ?>
<tr>
<td>
<div class="candidate-info">
<div class="candidate-avatar">
<?= strtoupper(substr($cand['user_name'], 0, 1)) ?>
</div>
<div class="candidate-details">
<span class="candidate-name"><?= htmlspecialchars($cand['user_name']) ?></span>
<span class="candidate-sub"><?= htmlspecialchars($cand['student_id']) ?> | <?= htmlspecialchars($cand['user_email']) ?></span>
</div>
</div>
</td>
<td>
<span class="position-badge"><?= htmlspecialchars($cand['position_name']) ?></span>
</td>
<td><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></td>
<td>
<div class="candidate-details">
<span class="candidate-name">Grade <?= htmlspecialchars($cand['grade_level'] ?: '12') ?></span>
<span class="candidate-sub"><?= htmlspecialchars($cand['track'] ?: 'N/A') ?></span>
</div>
</td>
<td class="actions-cell">
<button title="Edit" onclick='editCandidate(<?= json_encode($cand) ?>)'><i data-lucide="edit-2"></i></button>
<button title="Delete" style="color: #ef4444;" onclick="deleteCandidate('<?= $cand['id'] ?>', '<?= htmlspecialchars($cand['user_name']) ?>')"><i data-lucide="trash-2"></i></button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</main>
</div>
<!-- Modals -->
<div id="editCandidateModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Edit Candidate</h2>
<button onclick="closeModal('editCandidateModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/update_candidate.php" method="POST">
<input type="hidden" name="id" id="edit_cand_id">
<div class="form-group">
<label>Candidate Name</label>
<input type="text" id="edit_cand_name" disabled style="background: #f1f5f9;">
</div>
<div class="form-group">
<label>Position</label>
<select name="position_id" id="edit_cand_position_id" required>
<?php foreach ($allPositions as $p): ?>
<option value="<?= $p['id'] ?>"><?= htmlspecialchars($p['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Party</label>
<select name="party_name" id="edit_cand_party_name">
<option value="">Independent</option>
<?php foreach ($allParties as $pt): ?>
<option value="<?= htmlspecialchars($pt['name']) ?>"><?= htmlspecialchars($pt['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Manifesto / Vision</label>
<textarea name="manifesto" id="edit_cand_manifesto" rows="3" placeholder="Enter candidate vision..."></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('editCandidateModal')" class="btn-manage">Cancel</button>
<button type="submit" class="btn-manage primary">Update Candidate</button>
</div>
</form>
</div>
</div>
<div id="addCandidateModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Add New Candidate</h2>
<button onclick="closeModal('addCandidateModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/add_candidate.php" method="POST">
<input type="hidden" name="election_id" value="<?= $electionId ?>">
<div class="form-group">
<label>Select Student</label>
<select name="user_id" required>
<option value="">-- Choose Voter --</option>
<?php foreach ($allVoters as $v): ?>
<option value="<?= $v['id'] ?>"><?= htmlspecialchars($v['name']) ?> (<?= $v['student_id'] ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Position</label>
<select name="position_id" required>
<option value="">-- Choose Position --</option>
<?php foreach ($allPositions as $p): ?>
<option value="<?= $p['id'] ?>"><?= htmlspecialchars($p['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Party</label>
<select name="party_name">
<option value="">Independent</option>
<?php foreach ($allParties as $pt): ?>
<option value="<?= htmlspecialchars($pt['name']) ?>"><?= htmlspecialchars($pt['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Manifesto / Vision</label>
<textarea name="manifesto" rows="3" placeholder="Enter candidate vision..."></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('addCandidateModal')" class="btn-manage">Cancel</button>
<button type="submit" class="btn-manage primary">Save Candidate</button>
</div>
</form>
</div>
</div>
<div id="addPositionModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Define New Position</h2>
<button onclick="closeModal('addPositionModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/add_position.php" method="POST">
<input type="hidden" name="election_id" value="<?= $electionId ?>">
<div class="form-group">
<label>Position Name</label>
<input type="text" name="name" placeholder="e.g. President, Secretary" required>
</div>
<div class="form-group">
<label>Position Type</label>
<select name="type" required>
<option value="Uniform">Uniform (Global)</option>
<option value="Track Specific">Track Specific (Voter Track Only)</option>
</select>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('addPositionModal')" class="btn-manage">Cancel</button>
<button type="submit" class="btn-manage primary">Create Position</button>
</div>
</form>
</div>
</div>
<div id="addPartyModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Define New Party</h2>
<button onclick="closeModal('addPartyModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/add_party.php" method="POST">
<input type="hidden" name="election_id" value="<?= $electionId ?>">
<div class="form-group">
<label>Party Name</label>
<input type="text" name="name" placeholder="e.g. Unity Party" required>
</div>
<div class="form-group">
<label>Description</label>
<textarea name="description" rows="2" placeholder="Party slogan or vision..."></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('addPartyModal')" class="btn-manage">Cancel</button>
<button type="submit" class="btn-manage primary">Create Party</button>
</div>
</form>
</div>
</div>
<script>
lucide.createIcons();
function openModal(id) {
document.getElementById(id).style.display = 'flex';
}
function closeModal(id) {
document.getElementById(id).style.display = 'none';
}
function editCandidate(cand) {
document.getElementById('edit_cand_id').value = cand.id;
document.getElementById('edit_cand_name').value = cand.user_name;
document.getElementById('edit_cand_position_id').value = cand.position_id;
document.getElementById('edit_cand_party_name').value = cand.party_name || '';
document.getElementById('edit_cand_manifesto').value = cand.manifesto || '';
openModal('editCandidateModal');
}
function deleteCandidate(id, name) {
if (confirm(`Are you sure you want to remove ${name} from being a candidate?`)) {
window.location.href = `api/delete_candidate.php?id=${id}`;
}
}
window.onclick = function(event) {
if (event.target.className === 'modal') {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>