Autosave: 20260215-204330

This commit is contained in:
Flatlogic Bot 2026-02-15 20:43:30 +00:00
parent ed25faf216
commit b710d6acf5
28 changed files with 1338 additions and 214 deletions

75
api/add_officer.php Normal file
View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
require_once __DIR__ . "/../auth_helper.php";
require_login();
require_role(["Admin", "Adviser"]);
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$election_id = $_POST["election_id"] ?? "";
$student_id = $_POST["student_id"] ?? "";
$name = $_POST["name"] ?? "";
$email = $_POST["email"] ?? "";
$role = $_POST["role"] ?? "Officer";
$password = $_POST["password"] ?? "iloilohns";
if (!$election_id || !$student_id || !$name || !$email) {
die("Missing fields");
}
try {
$pdo = db();
$pdo->beginTransaction();
// 1. Check if user already exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?");
$stmt->execute([$student_id, $email]);
$existing = $stmt->fetch();
if ($existing) {
$user_id = $existing["id"];
// Update role if changed
$upd = $pdo->prepare("UPDATE users SET role = ? WHERE id = ?");
$upd->execute([$role, $user_id]);
} else {
// 1a. Create user in Supabase
$supabaseUser = SupabaseAuth::createUser($email, $password);
$supabase_uid = null;
if ($supabaseUser['error']) {
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
$sbUser = SupabaseAuth::getUserByEmail($email);
$supabase_uid = $sbUser['id'] ?? null;
} else {
throw new Exception("Supabase Error: " . $supabaseUser['error']);
}
} else {
$supabase_uid = $supabaseUser['data']['id'] ?? null;
}
// Create new user locally
$user_id = uuid();
$stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, role) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $role]);
}
// 2. Assign to election
$chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?");
$chk->execute([$election_id, $user_id]);
if ($chk->fetchColumn() == 0) {
$role_in_election = $role; // Admin, Adviser, or Officer
$ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, ?, ?)");
$ea->execute([uuid(), $election_id, $user_id, $role_in_election, $_SESSION['user_id']]);
}
audit_log('assigned_officer', 'users', $user_id, null, null, "Assigned $role $name to election $election_id");
$pdo->commit();
header("Location: ../officers_management.php?success=officer_assigned");
exit;
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
die("Error: " . $e->getMessage());
}
} else {
header("Location: ../officers_management.php");
exit;
}

View File

@ -7,7 +7,7 @@ require_role(['Admin', 'Adviser', 'Officer']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$election_id = $_POST['election_id'] ?? '';
$name = $_POST['name'] ?? '';
$max_votes = (int)($_POST['max_votes'] ?? 1);
$type = $_POST['type'] ?? 'Uniform';
if (!$election_id || !$name) {
die("Missing fields");
@ -16,8 +16,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$pdo = db();
$id = uuid();
$stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, max_votes) VALUES (?, ?, ?, ?)");
$stmt->execute([$id, $election_id, $name, $max_votes]);
$stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, type, max_votes) VALUES (?, ?, ?, ?, 1)");
$stmt->execute([$id, $election_id, $name, $type]);
audit_log('Added position', 'positions', $id);

73
api/add_voter.php Normal file
View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
require_once __DIR__ . "/../auth_helper.php";
require_login();
require_role(["Admin", "Adviser", "Officer"]);
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$election_id = $_POST["election_id"] ?? "";
$student_id = $_POST["student_id"] ?? "";
$name = $_POST["name"] ?? "";
$email = $_POST["email"] ?? "";
$password = $_POST["password"] ?? "iloilohns";
$track = $_POST["track"] ?? "";
$grade_level = $_POST["grade_level"] ?? "";
if (!$election_id || !$student_id || !$name || !$email) {
die("Missing fields");
}
try {
$pdo = db();
$pdo->beginTransaction();
// 1. Check if user already exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?");
$stmt->execute([$student_id, $email]);
$existing = $stmt->fetch();
if ($existing) {
$user_id = $existing["id"];
// Update track/grade if needed
$upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ? WHERE id = ?");
$upd->execute([$track, $grade_level, $user_id]);
} else {
// 1a. Create user in Supabase
$supabaseUser = SupabaseAuth::createUser($email, $password);
$supabase_uid = null;
if ($supabaseUser['error']) {
// If user already exists in Supabase, try to get their UID
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
$sbUser = SupabaseAuth::getUserByEmail($email);
$supabase_uid = $sbUser['id'] ?? null;
} else {
throw new Exception("Supabase Error: " . $supabaseUser['error']);
}
} else {
$supabase_uid = $supabaseUser['data']['id'] ?? null;
}
// Create new user locally
$user_id = uuid();
$stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, track, grade_level, role) VALUES (?, ?, ?, ?, ?, ?, ?, 'Voter')");
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level]);
}
// 2. Assign to election
$chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?");
$chk->execute([$election_id, $user_id]);
if ($chk->fetchColumn() == 0) {
$ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, 'Voter', ?)");
$ea->execute([uuid(), $election_id, $user_id, $_SESSION['user_id']]);
}
audit_log("Registered voter", "users", $user_id);
$pdo->commit();
header("Location: ../voter_management.php?success=voter_added");
exit;
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
die("Error: " . $e->getMessage());
}
}

24
api/delete_candidate.php Normal file
View File

@ -0,0 +1,24 @@
<?php
require_once '../auth_helper.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id'])) {
$candId = $_GET['id'];
$pdo = db();
try {
$stmt = $pdo->prepare("DELETE FROM candidates WHERE id = ?");
$stmt->execute([$candId]);
$currentUser = get_user();
audit_log('candidate_deleted', 'candidates', $candId, null, null, "Deleted candidate ID $candId");
header("Location: ../candidate_management.php?success=candidate_deleted");
exit;
} catch (PDOException $e) {
die("Error deleting candidate: " . $e->getMessage());
}
} else {
header("Location: ../candidate_management.php");
exit;
}

26
api/delete_officer.php Normal file
View File

@ -0,0 +1,26 @@
<?php
require_once '../auth_helper.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id']) && isset($_GET['election_id'])) {
$userId = $_GET['id'];
$electionId = $_GET['election_id'];
$pdo = db();
try {
// Remove the assignment for this election
$stmt = $pdo->prepare("DELETE FROM election_assignments WHERE user_id = ? AND election_id = ? AND role_in_election != 'Voter'");
$stmt->execute([$userId, $electionId]);
$currentUser = get_user();
audit_log('officer_removed', 'users', $userId, null, null, "Removed officer ID $userId from election $electionId");
header("Location: ../officers_management.php?success=officer_deleted");
exit;
} catch (PDOException $e) {
die("Error deleting officer: " . $e->getMessage());
}
} else {
header("Location: ../officers_management.php");
exit;
}

27
api/delete_voter.php Normal file
View File

@ -0,0 +1,27 @@
<?php
require_once '../auth_helper.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id']) && isset($_GET['election_id'])) {
$userId = $_GET['id'];
$electionId = $_GET['election_id'];
$pdo = db();
try {
// Remove the assignment for this election
$stmt = $pdo->prepare("DELETE FROM election_assignments WHERE user_id = ? AND election_id = ? AND role_in_election = 'Voter'");
$stmt->execute([$userId, $electionId]);
// Optional: Log the action
$currentUser = get_user();
audit_log('voter_removed', 'users', $userId, null, null, "Removed voter ID $userId from election $electionId");
header("Location: ../voter_management.php?success=voter_deleted");
exit;
} catch (PDOException $e) {
die("Error deleting voter: " . $e->getMessage());
}
} else {
header("Location: ../voter_management.php");
exit;
}

102
api/import_voters.php Normal file
View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
require_once __DIR__ . "/../auth_helper.php";
require_login();
require_role(["Admin", "Adviser", "Officer"]);
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$election_id = $_POST["election_id"] ?? "";
$file = $_FILES["csv_file"] ?? null;
if (!$election_id || !$file || $file["error"] !== UPLOAD_ERR_OK) {
die("Invalid submission or file upload error.");
}
$extension = pathinfo($file["name"], PATHINFO_EXTENSION);
if (strtolower($extension) !== "csv") {
die("Please upload a valid CSV file.");
}
try {
$pdo = db();
$pdo->beginTransaction();
$handle = fopen($file["tmp_name"], "r");
if ($handle === false) {
throw new Exception("Could not open the uploaded file.");
}
// Skip header if it exists
$header = fgetcsv($handle);
// Basic header validation (optional, but good)
// Expected: student_id, name, email, track, grade_level
$imported = 0;
$updated = 0;
while (($data = fgetcsv($handle)) !== false) {
if (count($data) < 5) continue; // Skip malformed rows
$student_id = trim($data[0]);
$name = trim($data[1]);
$email = trim($data[2]);
$track = trim($data[3]);
$grade_level = trim($data[4]);
if (!$student_id || !$name || !$email) continue;
// 1. Check if user already exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?");
$stmt->execute([$student_id, $email]);
$existing = $stmt->fetch();
if ($existing) {
$user_id = $existing["id"];
// Update track/grade if needed
$upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ? WHERE id = ?");
$upd->execute([$track, $grade_level, $user_id]);
$updated++;
} else {
// 1a. Create user in Supabase
$supabaseUser = SupabaseAuth::createUser($email, "iloilohns");
$supabase_uid = null;
if ($supabaseUser['error']) {
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
$sbUser = SupabaseAuth::getUserByEmail($email);
$supabase_uid = $sbUser['id'] ?? null;
} else {
// Log error but continue with other users? Or fail?
// Let's fail for now to be safe.
throw new Exception("Supabase Error for $email: " . $supabaseUser['error']);
}
} else {
$supabase_uid = $supabaseUser['data']['id'] ?? null;
}
// Create new user locally
$user_id = uuid();
$stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, track, grade_level, role) VALUES (?, ?, ?, ?, ?, ?, ?, 'Voter')");
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level]);
$imported++;
}
// 2. Assign to election
$chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?");
$chk->execute([$election_id, $user_id]);
if ($chk->fetchColumn() == 0) {
$ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, 'Voter', ?)");
$ea->execute([uuid(), $election_id, $user_id, $_SESSION['user_id']]);
}
}
fclose($handle);
audit_log("Imported voters via CSV", "users", "multiple");
$pdo->commit();
header("Location: ../voter_management.php?success=import_complete&imported=$imported&updated=$updated");
exit;
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
die("Error: " . $e->getMessage());
}
}

View File

@ -1,14 +1,14 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../auth_helper.php';
require_once __DIR__ . "/../auth_helper.php";
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$election_id = $_POST['election_id'] ?? '';
$votes = $_POST['votes'] ?? []; // Array of position_id => candidate_id
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$election_id = $_POST["election_id"] ?? "";
$raw_votes = $_POST["votes"] ?? []; // Array of position_id => candidate_id (string or array)
$user = get_user();
if (!$election_id || empty($votes)) {
if (!$election_id) {
die("Invalid submission.");
}
@ -19,40 +19,67 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. Verify election is ongoing
$eStmt = $pdo->prepare("SELECT status FROM elections WHERE id = ?");
$eStmt->execute([$election_id]);
if ($eStmt->fetchColumn() !== 'Ongoing') {
throw new Exception("Election is not ongoing.");
$election = $eStmt->fetch();
if (!$election || $election["status"] !== "Ongoing") {
throw new Exception("Election is not currently ongoing.");
}
// 2. Verify user hasn't voted yet
$vCheck = $pdo->prepare("SELECT COUNT(*) FROM votes WHERE election_id = ? AND voter_id = ?");
$vCheck->execute([$election_id, $user['id']]);
$vCheck->execute([$election_id, $user["id"]]);
if ($vCheck->fetchColumn() > 0) {
throw new Exception("You have already cast your vote for this election.");
}
// 3. Insert votes
// 3. Prepare statement for inserting votes
$stmt = $pdo->prepare("INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?)");
foreach ($votes as $position_id => $candidate_id) {
$vote_id = uuid();
// 4. Validate each position's votes
foreach ($raw_votes as $position_id => $candidate_id) {
if (empty($candidate_id)) continue;
// Fetch position details
$pStmt = $pdo->prepare("SELECT * FROM positions WHERE id = ? AND election_id = ?");
$pStmt->execute([$position_id, $election_id]);
$pos = $pStmt->fetch();
if (!$pos) {
throw new Exception("Invalid position detected.");
}
// Validate candidate
$cStmt = $pdo->prepare("SELECT c.*, u.track FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.id = ? AND c.position_id = ? AND c.approved = TRUE");
$cStmt->execute([$candidate_id, $position_id]);
$cand = $cStmt->fetch();
if (!$cand) {
throw new Exception("Invalid or unapproved candidate selected.");
}
// Check Track Specific logic
if ($pos["type"] === "Track Specific" && $cand["track"] !== $user["track"]) {
throw new Exception("Candidate track mismatch for position: " . $pos["name"]);
}
// Insert vote
$stmt->execute([
$vote_id,
uuid(),
$election_id,
$position_id,
$candidate_id,
$user['id'],
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
$_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
$user["id"],
$_SERVER["REMOTE_ADDR"] ?? "unknown",
$_SERVER["HTTP_USER_AGENT"] ?? "unknown"
]);
}
audit_log('Cast ballot', 'elections', $election_id);
audit_log("Cast ballot", "elections", $election_id);
$pdo->commit();
header("Location: ../view_results.php?id=$election_id&success=vote_cast");
header("Location: ../index.php?success=voted");
exit;
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
die("Error casting vote: " . $e->getMessage());
}
}

27
api/update_candidate.php Normal file
View File

@ -0,0 +1,27 @@
<?php
require_once '../auth_helper.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$candId = $_POST['id'];
$positionId = $_POST['position_id'];
$partyName = $_POST['party_name'];
$manifesto = $_POST['manifesto'];
$pdo = db();
try {
$stmt = $pdo->prepare("UPDATE candidates SET position_id = ?, party_name = ?, manifesto = ? WHERE id = ?");
$stmt->execute([$positionId, $partyName, $manifesto, $candId]);
$currentUser = get_user();
audit_log('candidate_updated', 'candidates', $candId, null, null, "Updated candidate ID $candId");
header("Location: ../candidate_management.php?success=candidate_updated");
exit;
} catch (PDOException $e) {
die("Error updating candidate: " . $e->getMessage());
}
} else {
header("Location: ../candidate_management.php");
exit;
}

29
api/update_election.php Normal file
View File

@ -0,0 +1,29 @@
<?php
require_once '../auth_helper.php';
require_login();
require_role(['Admin', 'Adviser', 'Officer']);
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$id = $_POST['id'];
$title = $_POST['title'];
$description = $_POST['description'];
$startDate = $_POST['start_date'];
$endDate = $_POST['end_date'];
$pdo = db();
try {
$stmt = $pdo->prepare("UPDATE elections SET title = ?, description = ?, start_date_and_time = ?, end_date_and_time = ? WHERE id = ?");
$stmt->execute([$title, $description, $startDate, $endDate, $id]);
$currentUser = get_user();
audit_log('election_updated', 'elections', $id, null, null, "Updated election $id");
header("Location: ../view_election.php?id=$id&success=1");
exit;
} catch (PDOException $e) {
die("Error updating election: " . $e->getMessage());
}
} else {
header("Location: ../index.php");
exit;
}

42
api/update_officer.php Normal file
View File

@ -0,0 +1,42 @@
<?php
require_once '../auth_helper.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$userId = $_POST['id'];
$name = $_POST['name'];
$email = $_POST['email'];
$role = $_POST['role'];
$password = $_POST['password'] ?? '';
$pdo = db();
try {
$stmt = $pdo->prepare("SELECT email, supabase_uid FROM users WHERE id = ?");
$stmt->execute([$userId]);
$userRecord = $stmt->fetch();
if (!empty($password)) {
// Update Supabase password
if ($userRecord && $userRecord['supabase_uid']) {
SupabaseAuth::updateUserPassword($userRecord['supabase_uid'], $password);
}
$stmt = $pdo->prepare("UPDATE users SET name = ?, email = ?, role = ? WHERE id = ?");
$stmt->execute([$name, $email, $role, $userId]);
} else {
$stmt = $pdo->prepare("UPDATE users SET name = ?, email = ?, role = ? WHERE id = ?");
$stmt->execute([$name, $email, $role, $userId]);
}
$currentUser = get_user();
audit_log('officer_updated', 'users', $userId, null, null, "Updated officer ID $userId info");
header("Location: ../officers_management.php?success=officer_updated");
exit;
} catch (PDOException $e) {
die("Error updating officer: " . $e->getMessage());
}
} else {
header("Location: ../officers_management.php");
exit;
}

45
api/update_voter.php Normal file
View File

@ -0,0 +1,45 @@
<?php
require_once '../auth_helper.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$userId = $_POST['id'];
$name = $_POST['name'];
$studentId = $_POST['student_id'];
$email = $_POST['email'];
$track = $_POST['track'];
$gradeLevel = $_POST['grade_level'];
$password = $_POST['password'] ?? '';
$pdo = db();
try {
$stmt = $pdo->prepare("SELECT email, supabase_uid FROM users WHERE id = ?");
$stmt->execute([$userId]);
$userRecord = $stmt->fetch();
if (!empty($password)) {
// Update Supabase password
if ($userRecord && $userRecord['supabase_uid']) {
SupabaseAuth::updateUserPassword($userRecord['supabase_uid'], $password);
}
$stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ? WHERE id = ?");
$stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $userId]);
} else {
$stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ? WHERE id = ?");
$stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $userId]);
}
// Log the action
$currentUser = get_user();
audit_log('voter_updated', 'users', $userId, null, null, "Updated voter ID $userId info");
header("Location: ../voter_management.php?success=voter_updated");
exit;
} catch (PDOException $e) {
die("Error updating voter: " . $e->getMessage());
}
} else {
header("Location: ../voter_management.php");
exit;
}

View File

@ -1,6 +1,7 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/SupabaseAuth.php';
function get_user() {
if (!isset($_SESSION['user_id'])) return null;
@ -34,13 +35,14 @@ function uuid() {
);
}
function audit_log($action, $table = null, $record_id = null, $old = null, $new = null) {
function audit_log($action, $table = null, $record_id = null, $old = null, $new = null, $details = null) {
$electionId = get_active_election_id();
$stmt = db()->prepare("INSERT INTO audit_logs (id, user_id, action, table_name, record_id, old_values, new_values, election_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt = db()->prepare("INSERT INTO audit_logs (id, user_id, action, details, table_name, record_id, old_values, new_values, election_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
uuid(),
$_SESSION['user_id'] ?? null,
$action,
$details,
$table,
$record_id,
$old ? json_encode($old) : null,

View File

@ -37,74 +37,150 @@ $positions = $positions->fetchAll();
<meta charset="utf-8" />
<title>Vote: <?= htmlspecialchars($election['title']) ?></title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/style.css?v=<?= time() ?>">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body { background: #f8fafc; color: #1e293b; font-family: 'Inter', sans-serif; }
.ballot-container { max-width: 800px; margin: 40px auto; padding: 0 20px; }
.ballot-header { text-align: center; margin-bottom: 48px; }
.ballot-header h1 { font-size: 2.5rem; font-weight: 800; color: #1e293b; margin-bottom: 12px; letter-spacing: -0.025em; }
.ballot-header p { color: #64748b; font-size: 1.125rem; }
.position-group { margin-bottom: 40px; background: white; border-radius: 24px; border: 1px solid #e2e8f0; padding: 32px; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); }
.position-title { font-size: 1.25rem; font-weight: 700; color: #1e293b; margin-bottom: 24px; display: flex; align-items: center; gap: 12px; }
.position-title i { color: #4f46e5; }
.candidates-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
.candidate-label { cursor: pointer; display: block; }
.candidate-card {
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
border-radius: 16px;
padding: 20px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 0.5rem;
gap: 16px;
background: white;
position: relative;
}
.candidate-card:hover { border-color: #2563eb; background: #f0f9ff; }
input[type="radio"]:checked + .candidate-card {
border-color: #2563eb;
background: #eff6ff;
box-shadow: 0 0 0 1px #2563eb;
.candidate-card:hover { border-color: #cbd5e1; background: #f8fafc; }
input[type="radio"]:checked + .candidate-card,
input[type="checkbox"]:checked + .candidate-card {
border-color: #4f46e5;
background: #f5f3ff;
box-shadow: 0 0 0 1px #4f46e5;
}
input[type="radio"] { display: none; }
.ballot-section { margin-bottom: 2rem; }
.ballot-header { border-bottom: 2px solid #1e293b; padding-bottom: 0.5rem; margin-bottom: 1.5rem; }
input[type="radio"]:checked + .candidate-card .check-icon,
input[type="checkbox"]:checked + .candidate-card .check-icon {
background: #4f46e5;
color: white;
border-color: #4f46e5;
}
input[type="radio"], input[type="checkbox"] { display: none; }
.avatar-placeholder {
width: 56px;
height: 56px;
background: #f1f5f9;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #4f46e5;
font-size: 1.25rem;
border: 1px solid #e2e8f0;
}
.candidate-info h3 { margin: 0; font-size: 1.125rem; font-weight: 700; color: #1e293b; }
.candidate-info p { margin: 4px 0 0 0; font-size: 0.875rem; color: #64748b; font-weight: 500; }
.check-icon {
margin-left: auto;
width: 24px;
height: 24px;
border: 2px solid #e2e8f0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: transparent;
transition: all 0.2s;
}
.submit-section { margin-top: 64px; text-align: center; padding: 48px; background: #1e293b; border-radius: 24px; color: white; }
.btn-submit {
background: #4f46e5;
color: white;
border: none;
padding: 16px 48px;
border-radius: 12px;
font-size: 1.125rem;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.4);
}
.btn-submit:hover { transform: translateY(-2px); background: #4338ca; }
.btn-submit:active { transform: translateY(0); }
</style>
</head>
<body>
<nav class="navbar">
<a href="index.php" class="brand">E-Vote Pro</a>
<div>
<span>Logged in as <?= htmlspecialchars($user['name']) ?></span>
</div>
</nav>
<div class="container" style="max-width: 800px;">
<div class="text-center mb-5">
<h1 style="font-size: 2rem; font-weight: 800;"><?= htmlspecialchars($election['title']) ?></h1>
<p class="text-muted">Please select your candidates carefully. Your vote is immutable once cast.</p>
<div class="ballot-container">
<div class="ballot-header animate-fade-in">
<div style="display: inline-flex; align-items: center; gap: 8px; background: #e0e7ff; color: #4338ca; padding: 6px 16px; border-radius: 100px; font-size: 0.875rem; font-weight: 700; margin-bottom: 16px;">
<i data-lucide="vote" style="width: 16px;"></i> OFFICIAL BALLOT
</div>
<h1><?= htmlspecialchars($election['title']) ?></h1>
<p>Your vote is secure and anonymous. Choose your representatives below.</p>
</div>
<form action="api/submit_vote.php" method="POST">
<form action="api/submit_vote.php" method="POST" onsubmit="return confirm('Are you sure you want to cast your vote? This action cannot be undone.')">
<input type="hidden" name="election_id" value="<?= $id ?>">
<?php foreach ($positions as $pos): ?>
<div class="ballot-section">
<div class="ballot-header">
<h2 style="margin: 0; font-size: 1.25rem;"><?= htmlspecialchars($pos['name']) ?></h2>
<small class="text-muted">Select <?= $pos['max_votes'] ?> candidate(s)</small>
<?php foreach ($positions as $index => $pos): ?>
<div class="position-group animate-stagger" style="--order: <?= $index ?>">
<div class="position-title">
<i data-lucide="award"></i>
<?= htmlspecialchars($pos['name']) ?>
</div>
<?php
$cStmt = $pdo->prepare("SELECT c.*, u.name FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.position_id = ? AND c.approved = TRUE");
$cStmt->execute([$pos['id']]);
$sql = "SELECT c.*, u.name, u.track FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.position_id = ? AND c.approved = TRUE";
$params = [$pos['id']];
if ($pos['type'] === 'Track Specific') {
$sql .= " AND u.track = ?";
$params[] = $user['track'];
}
$cStmt = $pdo->prepare($sql);
$cStmt->execute($params);
$candidates = $cStmt->fetchAll();
?>
<?php if (empty($candidates)): ?>
<p class="text-muted">No candidates for this position.</p>
<div style="padding: 24px; background: #f8fafc; border-radius: 12px; text-align: center; border: 1px dashed #cbd5e1;">
<p style="margin: 0; color: #64748b; font-size: 0.875rem;">No candidates for this position.</p>
</div>
<?php else: ?>
<div class="candidates-grid">
<?php foreach ($candidates as $cand): ?>
<label>
<label class="candidate-label">
<input type="radio" name="votes[<?= $pos['id'] ?>]" value="<?= $cand['id'] ?>" required>
<div class="candidate-card">
<div style="width: 40px; height: 40px; background: #e2e8f0; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #64748b;">
<div class="avatar-placeholder">
<?= substr($cand['name'], 0, 1) ?>
</div>
<div>
<div style="font-weight: 700;"><?= htmlspecialchars($cand['name']) ?></div>
<div style="font-size: 0.75rem; color: #64748b;"><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></div>
<div class="candidate-info">
<h3><?= htmlspecialchars($cand['name']) ?></h3>
<p><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></p>
</div>
<div class="check-icon">
<i data-lucide="check" style="width: 14px;"></i>
</div>
</div>
</label>
@ -114,11 +190,21 @@ $positions = $positions->fetchAll();
</div>
<?php endforeach; ?>
<div style="margin-top: 3rem; text-align: center; border-top: 1px solid #e2e8f0; padding-top: 2rem;">
<p style="font-size: 0.875rem; color: #64748b; margin-bottom: 1.5rem;">By clicking "Cast My Vote", I acknowledge that my selection is final.</p>
<button type="submit" class="btn btn-primary" style="padding: 1rem 3rem; font-size: 1.1rem; background: #1e293b;">Cast My Vote</button>
<div class="submit-section animate-fade-in">
<h2 style="margin: 0 0 12px 0; font-size: 1.5rem;">Ready to submit?</h2>
<p style="margin: 0 0 32px 0; color: #94a3b8; font-size: 1rem;">Please review your selections before casting your vote.</p>
<button type="submit" class="btn-submit">
Cast My Vote
</button>
<div style="margin-top: 24px; display: flex; align-items: center; justify-content: center; gap: 8px; color: #64748b; font-size: 0.875rem;">
<i data-lucide="shield-check" style="width: 16px; color: #10b981;"></i> Verified Secure Election
</div>
</div>
</form>
</div>
<script>
lucide.createIcons();
</script>
</body>
</html>

View File

@ -181,22 +181,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- 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>
<?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;">
@ -346,8 +331,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</div>
</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>
<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; ?>
@ -359,6 +344,46 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</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">
@ -419,8 +444,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<input type="text" name="name" placeholder="e.g. President, Secretary" required>
</div>
<div class="form-group">
<label>Max Votes (Winners)</label>
<input type="number" name="max_votes" value="1" min="1" required>
<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>
@ -465,6 +493,21 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
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';

View File

@ -69,22 +69,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- 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="Search for voters, candidates, or records...">
</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>
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header">

View File

@ -5,6 +5,10 @@ define('DB_NAME', 'app_38458');
define('DB_USER', 'app_38458');
define('DB_PASS', 'c217529c-a428-4a97-8f31-773c420377a7');
// Supabase Configuration - Provide your project URL and Service Role Key
define('SUPABASE_URL', getenv('SUPABASE_URL') ?: 'https://your-project.supabase.co');
define('SUPABASE_SERVICE_ROLE_KEY', getenv('SUPABASE_SERVICE_ROLE_KEY') ?: 'your-service-role-key');
function db() {
static $pdo;
if (!$pdo) {

View File

@ -0,0 +1 @@
ALTER TABLE positions ADD COLUMN type ENUM('Uniform', 'Track Specific') DEFAULT 'Uniform' AFTER name;

View File

@ -196,22 +196,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- 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="Search for records...">
</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>
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="history-controls">
@ -331,6 +316,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
el.classList.add('active');
}
}
lucide.createIcons();
</script>
</body>
</html>

69
includes/SupabaseAuth.php Normal file
View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
class SupabaseAuth {
private static function request(string $method, string $endpoint, array $data = [], bool $useServiceKey = false): array {
$url = rtrim(SUPABASE_URL, '/') . $endpoint;
$key = $useServiceKey ? SUPABASE_SERVICE_ROLE_KEY : SUPABASE_SERVICE_ROLE_KEY; // Always use service key for admin actions
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
$headers = [
'Content-Type: application/json',
'apikey: ' . $key,
'Authorization: Bearer ' . $key
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$decoded = json_decode((string)$response, true);
return [
'status' => $httpCode,
'data' => $decoded,
'error' => $httpCode >= 400 ? ($decoded['msg'] ?? $decoded['error_description'] ?? 'Unknown error') : null
];
}
public static function createUser(string $email, string $password): array {
// Use Admin API to create user without email verification
return self::request('POST', '/auth/v1/admin/users', [
'email' => $email,
'password' => $password,
'email_confirm' => true
], true);
}
public static function signIn(string $email, string $password): array {
return self::request('POST', '/auth/v1/token?grant_type=password', [
'email' => $email,
'password' => $password
]);
}
public static function updateUserPassword(string $uid, string $password): array {
return self::request('PUT', '/auth/v1/admin/users/' . $uid, [
'password' => $password
], true);
}
public static function getUserByEmail(string $email): ?array {
$res = self::request('GET', '/auth/v1/admin/users', [], true);
if ($res['status'] === 200 && isset($res['data']['users'])) {
foreach ($res['data']['users'] as $user) {
if ($user['email'] === $email) return $user;
}
}
return null;
}
}

16
includes/header.php Normal file
View File

@ -0,0 +1,16 @@
<header class="top-header">
<form action="search_results.php" method="GET" class="search-bar">
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
<input type="text" name="q" placeholder="Search for voters, candidates, or records..." value="<?= htmlspecialchars($_GET['q'] ?? '') ?>">
</form>
<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>

View File

@ -8,22 +8,48 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$role = $_POST['role'] ?? '';
$password = $_POST['password'] ?? '';
// We search by student_id and verify email and role match if provided
// 1. Find user locally to verify student_id, email and role match
$stmt = db()->prepare("SELECT * FROM users WHERE student_id = ? AND email = ? AND role = ?");
$stmt->execute([$student_id, $email, $role]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_role'] = $user['role'];
header('Location: index.php');
exit;
if ($user) {
// 2. Authenticate with Supabase
$auth = SupabaseAuth::signIn($email, $password);
if ($auth['error']) {
// Check if user exists locally with this password but not in Supabase yet
if (!empty($user['password_hash']) && password_verify($password, $user['password_hash'])) {
// Migrate to Supabase
$supabaseUser = SupabaseAuth::createUser($email, $password);
if (!$supabaseUser['error']) {
$auth = SupabaseAuth::signIn($email, $password);
}
}
}
if (!$auth['error']) {
// Update supabase_uid if missing
if (empty($user['supabase_uid'])) {
$supabase_uid = $auth['data']['user']['id'] ?? null;
$upd = db()->prepare("UPDATE users SET supabase_uid = ? WHERE id = ?");
$upd->execute([$supabase_uid, $user['id']]);
}
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_role'] = $user['role'];
header('Location: index.php');
exit;
} else {
$error = 'Authentication failed: ' . $auth['error'];
}
} else {
$error = 'Invalid Credentials. Please check your UID, Email, and Role.';
if (isset($_POST['role'])) { // Detect if coming from landing modal
header('Location: index.php?error=' . urlencode($error));
exit;
}
}
if ($error && isset($_POST['role'])) { // Redirect back to landing if coming from modal
header('Location: index.php?error=' . urlencode($error));
exit;
}
}
?>
@ -49,6 +75,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<label class="form-label">Student ID (XX-XXXX)</label>
<input type="text" name="student_id" class="form-control" placeholder="00-0000" required pattern="\d{2}-\d{4}">
</div>
<div class="mb-3">
<label class="form-label">Email Account</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Role</label>
<select name="role" class="form-select">
<option value="Voter">Voter</option>
<option value="Officer">Officer</option>
<option value="Adviser">Adviser</option>
<option value="Admin">Admin</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" name="password" class="form-control" required>

View File

@ -45,22 +45,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- 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="Search officers...">
</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>
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header">
@ -81,27 +66,36 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<i data-lucide="plus-circle" style="width: 20px; color: #2563eb;"></i>
Assign New Officer to Election
</div>
<form class="registration-form">
<form class="registration-form" action="api/add_officer.php" method="POST">
<input type="hidden" name="election_id" value="<?= $electionId ?>">
<div class="form-row">
<div class="form-group">
<label>Full Name</label>
<input type="text" placeholder="Enter name">
<input type="text" name="name" placeholder="Enter name" required>
</div>
<div class="form-group">
<label>Student ID / Personnel ID</label>
<input type="text" name="student_id" placeholder="XX-XXXX" required>
</div>
<div class="form-group">
<label>Email</label>
<input type="email" placeholder="email@school.edu">
<input type="email" name="email" placeholder="email@school.edu" required>
</div>
<div class="form-group">
<label>Role</label>
<select>
<select name="role" required>
<option value="Admin">Admin</option>
<option value="Adviser">Adviser</option>
<option value="Officer">Officer</option>
<option value="Officer" selected>Officer</option>
</select>
</div>
<div class="form-group">
<label>Password</label>
<input type="text" name="password" value="iloilohns" required>
</div>
</div>
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
<button type="button" class="btn-save-officer">
<button type="submit" class="btn-save-officer">
<i data-lucide="save" style="width: 18px;"></i>
ASSIGN TO ELECTION
</button>
@ -131,7 +125,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</div>
</div>
<div class="officer-actions">
<button title="Edit"><i data-lucide="edit-3" style="width: 16px;"></i></button>
<button title="Edit" onclick='editOfficer(<?= json_encode($o) ?>)'><i data-lucide="edit-3" style="width: 16px;"></i></button>
<button title="Delete" style="color: #ef4444;" onclick="deleteOfficer('<?= $o['id'] ?>', '<?= htmlspecialchars($o['name']) ?>')"><i data-lucide="trash-2" style="width: 16px;"></i></button>
</div>
</div>
<?php endforeach; ?>
@ -161,7 +156,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</div>
</div>
<div class="officer-actions">
<button title="Edit"><i data-lucide="edit-3" style="width: 16px;"></i></button>
<button title="Edit" onclick='editOfficer(<?= json_encode($o) ?>)'><i data-lucide="edit-3" style="width: 16px;"></i></button>
<button title="Delete" style="color: #ef4444;" onclick="deleteOfficer('<?= $o['id'] ?>', '<?= htmlspecialchars($o['name']) ?>')"><i data-lucide="trash-2" style="width: 16px;"></i></button>
</div>
</div>
<?php endforeach; ?>
@ -191,7 +187,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</div>
</div>
<div class="officer-actions">
<button title="Edit"><i data-lucide="edit-3" style="width: 16px;"></i></button>
<button title="Edit" onclick='editOfficer(<?= json_encode($o) ?>)'><i data-lucide="edit-3" style="width: 16px;"></i></button>
<button title="Delete" style="color: #ef4444;" onclick="deleteOfficer('<?= $o['id'] ?>', '<?= htmlspecialchars($o['name']) ?>')"><i data-lucide="trash-2" style="width: 16px;"></i></button>
</div>
</div>
<?php endforeach; ?>
@ -204,8 +201,73 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</main>
</div>
<!-- Edit Officer Modal -->
<div id="editOfficerModal" 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; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);">
<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; color:#1e293b;">Edit Officer</h2>
<button onclick="closeModal('editOfficerModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
</div>
<form action="api/update_officer.php" method="POST">
<input type="hidden" name="id" id="edit_officer_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_officer_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;">EMAIL ADDRESS</label>
<input type="email" name="email" id="edit_officer_email" 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;">ROLE</label>
<select name="role" id="edit_officer_role" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<option value="Admin">Admin</option>
<option value="Adviser">Adviser</option>
<option value="Officer">Officer</option>
</select>
</div>
<div class="form-group" style="margin-bottom:24px;">
<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:flex; justify-content:flex-end; gap:12px;">
<button type="button" onclick="closeModal('editOfficerModal')" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
<button type="submit" style="padding:10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Update Officer</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 editOfficer(officer) {
document.getElementById('edit_officer_id').value = officer.id;
document.getElementById('edit_officer_name').value = officer.name;
document.getElementById('edit_officer_email').value = officer.email;
document.getElementById('edit_officer_role').value = officer.role;
openModal('editOfficerModal');
}
function deleteOfficer(id, name) {
if (confirm(`Are you sure you want to remove ${name} from this election?`)) {
window.location.href = `api/delete_officer.php?id=${id}&election_id=<?= $electionId ?>`;
}
}
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>

View File

@ -53,22 +53,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- 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 logs...">
</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>
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header">
@ -85,19 +70,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- Filters & Table Section -->
<div class="content-section animate-fade-in">
<form method="GET" class="filter-bar">
<div class="filter-group" style="flex: 2;">
<label>SEARCH LOGS</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 action, user, or details">
</div>
</div>
<div style="display: flex; gap: 12px; align-items: flex-end;">
<button type="submit" class="btn-manage primary">Filter</button>
<button type="button" class="btn-manage" onclick="window.print()"><i data-lucide="printer"></i> Print</button>
</div>
</form>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 24px;">
<button type="button" class="btn-manage" onclick="window.print()"><i data-lucide="printer"></i> Print</button>
</div>
<table class="audit-table">
<thead>

148
search_results.php Normal file
View File

@ -0,0 +1,148 @@
<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
$electionId = get_active_election_id();
$query = $_GET['q'] ?? '';
$voters = [];
$candidates = [];
$logs = [];
if ($query) {
$search = "%$query%";
// Search Voters
$stmt = $pdo->prepare("SELECT u.* FROM users u
JOIN election_assignments ea ON u.id = ea.user_id
WHERE ea.election_id = ? AND ea.role_in_election = 'Voter'
AND (u.name LIKE ? OR u.email LIKE ? OR u.student_id LIKE ?)");
$stmt->execute([$electionId, $search, $search, $search]);
$voters = $stmt->fetchAll();
// Search Candidates
$stmt = $pdo->prepare("SELECT c.*, u.name as user_name, u.student_id
FROM candidates c
JOIN users u ON c.user_id = u.id
WHERE c.election_id = ?
AND (u.name LIKE ? OR c.position LIKE ? OR c.platform LIKE ?)");
$stmt->execute([$electionId, $search, $search, $search]);
$candidates = $stmt->fetchAll();
// Search Records (Audit Logs)
$stmt = $pdo->prepare("SELECT l.*, u.name as user_name
FROM audit_logs l
LEFT JOIN users u ON l.user_id = u.id
WHERE (l.election_id = ? OR l.election_id IS NULL)
AND (l.action LIKE ? OR l.details LIKE ? OR u.name LIKE ?)");
$stmt->execute([$electionId, $search, $search, $search]);
$logs = $stmt->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>Search Results | <?= htmlspecialchars($projectDescription) ?></title>
<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>
<style>
.search-results-container { padding: 24px; }
.result-section { margin-bottom: 40px; }
.result-section h2 { font-size: 1.25rem; color: #1e293b; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.result-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
.result-card { background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; transition: transform 0.2s; }
.result-card:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); }
.result-title { font-weight: 600; color: #1e293b; margin-bottom: 4px; }
.result-meta { font-size: 0.875rem; color: #64748b; }
.no-results { padding: 40px; text-align: center; color: #94a3b8; background: #f8fafc; border-radius: 12px; border: 2px dashed #e2e8f0; }
</style>
</head>
<body class="dashboard-body">
<?php require_once 'includes/sidebar.php'; ?>
<div class="main-wrapper">
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content">
<div class="search-results-container">
<div style="margin-bottom: 24px;">
<h1 style="font-size: 1.875rem; color: #1e293b;">Search Results</h1>
<p style="color: #64748b;">Showing results for "<strong><?= htmlspecialchars($query) ?></strong>"</p>
</div>
<?php if (!$query): ?>
<div class="no-results">
<i data-lucide="search" style="width: 48px; height: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
<p>Enter a search term to find voters, candidates, or records.</p>
</div>
<?php elseif (empty($voters) && empty($candidates) && empty($logs)): ?>
<div class="no-results">
<i data-lucide="frown" style="width: 48px; height: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
<p>No results found for "<?= htmlspecialchars($query) ?>".</p>
</div>
<?php else: ?>
<?php if (!empty($voters)): ?>
<div class="result-section">
<h2><i data-lucide="users"></i> Voters (<?= count($voters) ?>)</h2>
<div class="result-grid">
<?php foreach ($voters as $v): ?>
<div class="result-card">
<div class="result-title"><?= htmlspecialchars($v['name']) ?></div>
<div class="result-meta"><?= htmlspecialchars($v['student_id']) ?> • <?= htmlspecialchars($v['email']) ?></div>
<div style="margin-top: 12px;">
<a href="voter_management.php?search=<?= urlencode($v['student_id']) ?>" style="font-size: 12px; color: #4f46e5; font-weight: 500; text-decoration: none;">View in Voters List </a>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if (!empty($candidates)): ?>
<div class="result-section">
<h2><i data-lucide="user-square"></i> Candidates (<?= count($candidates) ?>)</h2>
<div class="result-grid">
<?php foreach ($candidates as $c): ?>
<div class="result-card">
<div class="result-title"><?= htmlspecialchars($c['user_name']) ?></div>
<div class="result-meta"><?= htmlspecialchars($c['position']) ?> • <?= htmlspecialchars($c['student_id']) ?></div>
<div style="margin-top: 12px;">
<a href="candidate_management.php?search=<?= urlencode($c['user_name']) ?>" style="font-size: 12px; color: #4f46e5; font-weight: 500; text-decoration: none;">View in Candidates </a>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php if (!empty($logs)): ?>
<div class="result-section">
<h2><i data-lucide="file-text"></i> Audit Records (<?= count($logs) ?>)</h2>
<div class="result-grid">
<?php foreach ($logs as $l): ?>
<div class="result-card">
<div class="result-title"><?= htmlspecialchars($l['action']) ?></div>
<div class="result-meta"><?= date('M d, Y H:i', strtotime($l['created_at'])) ?> by <?= htmlspecialchars($l['user_name'] ?? 'SYSTEM') ?></div>
<div style="margin-top: 8px; font-size: 13px; color: #64748b; line-height: 1.5;">
<?= htmlspecialchars($l['details']) ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</main>
</div>
<script>lucide.createIcons();</script>
</body>
</html>

View File

@ -14,10 +14,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'Invalid Student ID format. Use XX-XXXX.';
} else {
try {
// 1. Create user in Supabase
$supabaseUser = SupabaseAuth::createUser($email, $password);
$supabase_uid = null;
if ($supabaseUser['error']) {
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
$sbUser = SupabaseAuth::getUserByEmail($email);
$supabase_uid = $sbUser['id'] ?? null;
} else {
throw new Exception("Supabase Error: " . $supabaseUser['error']);
}
} else {
$supabase_uid = $supabaseUser['data']['id'] ?? null;
}
$id = uuid();
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("INSERT INTO users (id, student_id, name, email, password_hash, role) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$id, $student_id, $name, $email, $hash, $role]);
$stmt = db()->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, role) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$id, $supabase_uid, $student_id, $name, $email, $role]);
$_SESSION['user_id'] = $id;
$_SESSION['user_role'] = $role;

View File

@ -58,7 +58,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</div>
<div style="display: flex; gap: 0.5rem;">
<?php if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])): ?>
<button class="btn btn-outline">Edit Info</button>
<button class="btn btn-outline" onclick="document.getElementById('editElectionModal').style.display='flex'">Edit Info</button>
<?php if ($election['status'] === 'Preparing'): ?>
<form action="api/update_election_status.php" method="POST">
<input type="hidden" name="id" value="<?= $election['id'] ?>">
@ -165,5 +165,50 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</div>
</div>
</div>
<div id="editElectionModal" 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="card" style="width: 100%; max-width: 500px; padding: 2rem;">
<h2 style="margin-top: 0; font-size: 1.25rem;">Edit Election</h2>
<form action="api/update_election.php" method="POST">
<input type="hidden" name="id" value="<?= $election['id'] ?>">
<div class="form-group">
<label class="form-label">Election Title</label>
<input type="text" name="title" class="form-control" value="<?= htmlspecialchars($election['title']) ?>" required>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($election['description']) ?></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1.5rem;">
<div class="form-group">
<label class="form-label">Start Date</label>
<input type="datetime-local" name="start_date" class="form-control" value="<?= date('Y-m-d\TH:i', strtotime($election['start_date_and_time'])) ?>" required>
</div>
<div class="form-group">
<label class="form-label">End Date</label>
<input type="datetime-local" name="end_date" class="form-control" value="<?= date('Y-m-d\TH:i', strtotime($election['end_date_and_time'])) ?>" required>
</div>
</div>
<div style="display: flex; gap: 1rem;">
<button type="submit" class="btn btn-primary" style="flex: 1;">Update</button>
<button type="button" onclick="document.getElementById('editElectionModal').style.display='none'" class="btn btn-outline" style="flex: 1;">Cancel</button>
</div>
</form>
</div>
</div>
<style>
.modal { display: flex; }
.form-control { width: 100%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: var(--radius); margin-top: 0.25rem; }
.form-group { margin-bottom: 1rem; }
.form-label { font-size: 0.75rem; font-weight: 600; color: var(--text-muted); }
</style>
<script>
window.onclick = function(event) {
if (event.target.id === 'editElectionModal') {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>

View File

@ -88,24 +88,28 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
<!-- 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>
<?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">
@ -206,8 +210,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</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>
<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; ?>
@ -218,24 +222,157 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
</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>
<!-- 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>
<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>
<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';
}
@ -244,6 +381,22 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
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';