Compare commits

..

No commits in common. "ai-dev" and "master" have entirely different histories.

85 changed files with 132 additions and 8298 deletions

View File

@ -1,39 +0,0 @@
<?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'] ?? '';
$position_id = $_POST['position_id'] ?? '';
$user_id = $_POST['user_id'] ?? '';
$party_name = $_POST['party_name'] ?? '';
$manifesto = $_POST['manifesto'] ?? '';
if (!$election_id || !$position_id || !$user_id) {
die("Missing fields");
}
try {
$pdo = db();
$id = uuid();
// Check if user is already a candidate in this election
$check = $pdo->prepare("SELECT id FROM candidates WHERE election_id = ? AND user_id = ?");
$check->execute([$election_id, $user_id]);
if ($check->fetch()) {
die("User is already a candidate in this election.");
}
$stmt = $pdo->prepare("INSERT INTO candidates (id, election_id, position_id, user_id, party_name, manifesto, approved) VALUES (?, ?, ?, ?, ?, ?, TRUE)");
$stmt->execute([$id, $election_id, $position_id, $user_id, $party_name, $manifesto]);
audit_log('Added candidate', 'candidates', $id);
header("Location: ../candidate_management.php?success=1");
exit;
} catch (Exception $e) {
die($e->getMessage());
}
}

View File

@ -1,75 +0,0 @@
<?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

@ -1,29 +0,0 @@
<?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'] ?? '';
$name = $_POST['name'] ?? '';
$description = $_POST['description'] ?? '';
if (!$election_id || !$name) {
die("Missing fields");
}
try {
$pdo = db();
$id = uuid();
$stmt = $pdo->prepare("INSERT INTO parties (id, election_id, name, description) VALUES (?, ?, ?, ?)");
$stmt->execute([$id, $election_id, $name, $description]);
audit_log('Added party', 'parties', $id);
header("Location: ../candidate_management.php?success=1");
exit;
} catch (Exception $e) {
die($e->getMessage());
}
}

View File

@ -1,29 +0,0 @@
<?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'] ?? '';
$name = $_POST['name'] ?? '';
$type = $_POST['type'] ?? 'Uniform';
if (!$election_id || !$name) {
die("Missing fields");
}
try {
$pdo = db();
$id = uuid();
$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);
header("Location: ../candidate_management.php?success=1");
exit;
} catch (Exception $e) {
die($e->getMessage());
}
}

View File

@ -1,74 +0,0 @@
<?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"] ?? "";
$section = $_POST["section"] ?? "";
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/section if needed
$upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ?, section = ? WHERE id = ?");
$upd->execute([$track, $grade_level, $section, $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, section, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'Voter')");
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level, $section]);
}
// 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());
}
}

View File

@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../auth_helper.php';
require_login();
require_role(['Admin', 'Adviser', 'Officer']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$start_date = $_POST['start_date'] ?? '';
$end_date = $_POST['end_date'] ?? '';
$user = get_user();
if (!$title || !$start_date || !$end_date) {
die("Missing required fields.");
}
try {
$pdo = db();
$id = uuid();
$stmt = $pdo->prepare("INSERT INTO elections (id, title, description, status, start_date_and_time, end_date_and_time, created_by) VALUES (?, ?, ?, 'Preparing', ?, ?, ?)");
$stmt->execute([$id, $title, $description, $start_date, $end_date, $user['id']]);
audit_log('Created election', 'elections', $id);
$redirect = $_POST['redirect'] ?? "../view_election.php?id=$id&success=1";
header("Location: $redirect");
exit;
} catch (Exception $e) {
die("Error: " . $e->getMessage());
}
} else {
header("Location: ../index.php");
exit;
}

View File

@ -1,24 +0,0 @@
<?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;
}

View File

@ -1,26 +0,0 @@
<?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;
}

View File

@ -1,27 +0,0 @@
<?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;
}

View File

@ -1,103 +0,0 @@
<?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]);
$section = trim($data[5] ?? "");
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/section if needed
$upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ?, section = ? WHERE id = ?");
$upd->execute([$track, $grade_level, $section, $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, section, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'Voter')");
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level, $section]);
$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,46 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../auth_helper.php';
require_login();
require_role(['Admin', 'Adviser']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? '';
$status = $_POST['status'] ?? '';
$end_time = $_POST['end_time'] ?? '';
if (!$id) {
die("Missing election ID");
}
try {
$pdo = db();
$params = [];
$sql = "UPDATE elections SET ";
if ($status) {
$sql .= "status = ?, ";
$params[] = $status;
}
if ($end_time) {
$sql .= "end_date_and_time = ?, ";
$params[] = str_replace('T', ' ', $end_time);
}
// Remove trailing comma and space
$sql = rtrim($sql, ', ');
$sql .= " WHERE id = ?";
$params[] = $id;
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
audit_log("Election Managed: Status=$status, EndTime=$end_time", 'elections', $id);
header("Location: ../dashboard.php?success=Election updated successfully");
exit;
} catch (Exception $e) {
die($e->getMessage());
}
}

View File

@ -1,85 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . "/../auth_helper.php";
require_login();
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) {
die("Invalid submission.");
}
try {
$pdo = db();
$pdo->beginTransaction();
// 1. Verify election is ongoing
$eStmt = $pdo->prepare("SELECT status FROM elections WHERE id = ?");
$eStmt->execute([$election_id]);
$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"]]);
if ($vCheck->fetchColumn() > 0) {
throw new Exception("You have already cast your vote for this election.");
}
// 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 (?, ?, ?, ?, ?, ?, ?)");
// 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([
uuid(),
$election_id,
$position_id,
$candidate_id,
$user["id"],
$_SERVER["REMOTE_ADDR"] ?? "unknown",
$_SERVER["HTTP_USER_AGENT"] ?? "unknown"
]);
}
audit_log("Cast ballot", "elections", $election_id);
$pdo->commit();
header("Location: ../index.php?success=voted");
exit;
} catch (Exception $e) {
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
die("Error casting vote: " . $e->getMessage());
}
}

View File

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../auth_helper.php';
require_login();
require_role(['Admin', 'Adviser', 'Officer']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? '';
if (!$id) {
die("Missing ID");
}
try {
$pdo = db();
$stmt = $pdo->prepare("UPDATE candidates SET approved = NOT approved WHERE id = ?");
$stmt->execute([$id]);
$stmt = $pdo->prepare("SELECT position_id FROM candidates WHERE id = ?");
$stmt->execute([$id]);
$pos_id = $stmt->fetchColumn();
audit_log('Toggled candidate approval', 'candidates', $id);
header("Location: ../manage_candidates.php?position_id=$pos_id&success=1");
exit;
} catch (Exception $e) {
die($e->getMessage());
}
}

View File

@ -1,27 +0,0 @@
<?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;
}

View File

@ -1,29 +0,0 @@
<?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;
}

View File

@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../auth_helper.php';
require_login();
require_role(['Admin', 'Adviser', 'Officer']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? '';
$status = $_POST['status'] ?? '';
if (!$id || !$status) {
die("Missing fields");
}
try {
$pdo = db();
$stmt = $pdo->prepare("UPDATE elections SET status = ? WHERE id = ?");
$stmt->execute([$status, $id]);
audit_log("Updated election status to $status", 'elections', $id);
$redirect = $_POST['redirect'] ?? "../view_election.php?id=$id&success=1";
header("Location: $redirect");
exit;
} catch (Exception $e) {
die($e->getMessage());
}
}

View File

@ -1,42 +0,0 @@
<?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;
}

View File

@ -1,21 +0,0 @@
<?php
require_once '../auth_helper.php';
require_login();
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$theme = $input['theme'] ?? 'light';
if (!in_array($theme, ['light', 'dark'])) {
echo json_encode(['success' => false, 'error' => 'Invalid theme']);
exit;
}
try {
$stmt = db()->prepare("UPDATE users SET theme = ? WHERE id = ?");
$stmt->execute([$theme, $_SESSION['user_id']]);
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -1,46 +0,0 @@
<?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'];
$section = $_POST['section'] ?? '';
$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 = ?, section = ? WHERE id = ?");
$stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $section, $userId]);
} else {
$stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ?, section = ? WHERE id = ?");
$stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $section, $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,19 +0,0 @@
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
/* Delay for children */
.animate-stagger > * {
opacity: 0;
}
.animate-stagger > *:nth-child(1) { animation: fadeIn 0.5s ease-out 0.1s forwards; }
.animate-stagger > *:nth-child(2) { animation: fadeIn 0.5s ease-out 0.2s forwards; }
.animate-stagger > *:nth-child(3) { animation: fadeIn 0.5s ease-out 0.3s forwards; }
.animate-stagger > *:nth-child(4) { animation: fadeIn 0.5s ease-out 0.4s forwards; }
.animate-stagger > *:nth-child(5) { animation: fadeIn 0.5s ease-out 0.5s forwards; }

View File

@ -1,251 +0,0 @@
.header-icon-container {
background: #eef2ff;
padding: 12px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
/* Candidate Stats Grid */
.candidate-stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.candidate-stat-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
}
.candidate-stat-label {
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
margin-bottom: 16px;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.candidate-stat-value {
font-size: 2.5rem;
font-weight: 800;
color: #2563eb;
}
/* Distribution Grid */
.distribution-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.distribution-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
}
.distribution-header {
font-size: 0.875rem;
font-weight: 700;
color: #2563eb;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #f3f4f6;
}
.distribution-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.distribution-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.875rem;
color: #4b5563;
}
.distribution-count {
font-weight: 700;
color: var(--text-main);
}
/* Filter Bar */
.filter-bar {
padding: 24px;
display: flex;
gap: 16px;
align-items: flex-end;
background: var(--surface-color);
border-bottom: 1px solid var(--border-color);
}
.filter-group {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.filter-group label {
font-size: 0.7rem;
font-weight: 700;
color: #64748b;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.search-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.search-input-wrapper i {
position: absolute;
left: 12px;
}
.search-input-wrapper input {
width: 100%;
padding: 10px 12px 10px 36px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 0.875rem;
outline: none;
background: #f8fafc;
}
.filter-group select {
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 0.875rem;
outline: none;
background: var(--surface-color);
color: var(--text-main);
}
/* Candidates Table */
.candidates-table {
width: 100%;
border-collapse: collapse;
}
.candidates-table th {
padding: 12px 24px;
text-align: left;
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
background: var(--table-header-bg);
border-bottom: 1px solid var(--border-color);
text-transform: uppercase;
}
.candidates-table td {
padding: 16px 24px;
border-bottom: 1px solid var(--border-color);
font-size: 0.875rem;
color: var(--text-main);
}
.candidate-info {
display: flex;
align-items: center;
gap: 12px;
}
.candidate-avatar {
width: 32px;
height: 32px;
background: #eef2ff;
color: #4f46e5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.75rem;
}
.candidate-details {
display: flex;
flex-direction: column;
}
.candidate-name {
font-weight: 600;
color: var(--text-main);
}
.candidate-sub {
font-size: 0.75rem;
color: var(--text-muted);
}
.position-badge {
background: #eef2ff;
color: #4f46e5;
padding: 4px 12px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.actions-cell {
display: flex;
gap: 12px;
}
.actions-cell button {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #94a3b8;
transition: color 0.2s;
}
.actions-cell button:hover {
color: #4f46e5;
}
.actions-cell button i {
width: 16px;
height: 16px;
}
.status-badge {
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 6px;
}
.status-ongoing {
background: #dcfce7;
color: #166534;
}
.status-ongoing::before {
content: '';
width: 6px;
height: 6px;
background: #16a34a;
border-radius: 50%;
}

View File

@ -1,556 +0,0 @@
@import 'animations.css';
:root {
--sidebar-width: 260px;
--sidebar-bg: #ffffff;
--sidebar-active-bg: #eef2ff;
--sidebar-active-text: #4f46e5;
--sidebar-text: #4b5563;
--top-header-height: 64px;
--accent-blue: #4f46e5;
--bg-light: #f9fafb;
--border-color: #f3f4f6;
}
body.dashboard-body {
background-color: var(--bg-light);
display: flex;
min-height: 100vh;
font-family: 'Inter', sans-serif;
margin: 0;
}
/* Sidebar Styles */
.sidebar {
width: var(--sidebar-width);
background: var(--sidebar-bg);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
}
.sidebar-header {
padding: 24px;
display: flex;
flex-direction: column;
}
.sidebar-brand {
font-size: 1.1rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 4px;
}
.sidebar-subtitle {
font-size: 0.75rem;
color: #94a3b8;
}
.sidebar-nav {
flex: 1;
padding: 12px;
}
.nav-item {
display: flex;
align-items: center;
padding: 12px 16px;
color: var(--sidebar-text);
text-decoration: none;
border-radius: 8px;
margin-bottom: 4px;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.2s;
}
.nav-item:hover {
background: #f8fafc;
}
.nav-item.active {
background: var(--sidebar-active-bg);
color: var(--sidebar-active-text);
}
.nav-item i {
margin-right: 12px;
width: 18px;
height: 18px;
text-align: center;
}
[data-lucide] {
width: 18px;
height: 18px;
}
.sidebar-footer {
padding: 12px;
border-top: 1px solid var(--border-color);
}
/* Main Content Area */
.main-wrapper {
margin-left: var(--sidebar-width);
flex: 1;
display: flex;
flex-direction: column;
}
.top-header {
height: var(--top-header-height);
background: #ffffff;
border-bottom: 1px solid var(--border-color);
padding: 0 32px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 90;
}
.search-bar {
background: #f3f4f6;
border-radius: 8px;
padding: 8px 16px;
display: flex;
align-items: center;
width: 400px;
}
.search-bar input {
background: transparent;
border: none;
outline: none;
margin-left: 8px;
width: 100%;
font-size: 0.875rem;
}
.user-profile {
display: flex;
align-items: center;
gap: 12px;
}
.user-info {
text-align: right;
}
.user-name {
font-weight: 600;
font-size: 0.875rem;
color: #1e293b;
}
.user-role {
font-size: 0.75rem;
color: #94a3b8;
}
.user-avatar {
width: 36px;
height: 36px;
background: #e0e7ff;
color: #4f46e5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
}
/* Dashboard Content */
.dashboard-content {
padding: 32px;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 32px;
}
.welcome-msg {
color: #64748b;
font-size: 0.875rem;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 32px;
}
.stat-card {
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
}
.stat-label {
color: #94a3b8;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.025em;
margin-bottom: 12px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 12px;
}
.stat-footer {
display: flex;
align-items: center;
font-size: 0.75rem;
font-weight: 500;
}
.stat-footer.voters { color: #10b981; }
.stat-footer.candidates { color: #3b82f6; }
.stat-footer.votes { color: #10b981; }
.stat-footer i { margin-right: 6px; }
/* Analytics Charts */
.analytics-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.analytics-card {
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
display: flex;
flex-direction: column;
}
.analytics-card .card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.analytics-card .card-title {
font-weight: 600;
font-size: 0.875rem;
color: #1e293b;
}
.chart-filter {
padding: 4px 8px;
border-radius: 6px;
border: 1px solid var(--border-color);
font-size: 0.75rem;
color: #4b5563;
outline: none;
}
.chart-container {
position: relative;
height: 240px;
width: 100%;
}
/* Table Section */
.content-section {
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 0;
overflow: hidden;
}
.section-header {
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
font-weight: 600;
font-size: 1rem;
color: #1e293b;
}
.btn-new-election {
background: #2563eb;
color: white;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.btn-new-election:hover {
background: #1d4ed8;
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn-new-election:active {
transform: translateY(0) scale(0.98);
}
.election-table {
width: 100%;
border-collapse: collapse;
}
.election-table th {
background: #f9fafb;
padding: 12px 24px;
text-align: left;
font-size: 0.7rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
border-bottom: 1px solid var(--border-color);
}
.election-table td {
padding: 16px 24px;
border-bottom: 1px solid var(--border-color);
font-size: 0.875rem;
}
.status-badge {
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.status-ongoing {
background: #dcfce7;
color: #166534;
}
.status-preparing {
background: #fef3c7;
color: #92400e;
}
.status-finished {
background: #f1f5f9;
color: #475569;
}
.quick-actions {
display: flex;
align-items: center;
gap: 8px;
}
.select-status {
padding: 4px 8px;
border-radius: 4px;
border: 1px solid var(--border-color);
font-size: 0.75rem;
background: #f9fafb;
}
.btn-update {
background: #2563eb;
color: white;
padding: 4px 12px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
.btn-update:hover {
transform: translateY(-1px);
filter: brightness(110%);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-update:active {
transform: translateY(0) scale(0.95);
}
.flatlogic-badge {
position: fixed;
bottom: 16px;
right: 16px;
background: #ffffff;
border: 1px solid var(--border-color);
padding: 6px 12px;
border-radius: 6px;
font-size: 0.7rem;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* Dark Theme Variables */
body.dark-theme {
--bg-light: #0f172a;
--sidebar-bg: #1e293b;
--sidebar-text: #94a3b8;
--border-color: #334155;
--sidebar-active-bg: #334155;
--sidebar-active-text: #6366f1;
color: #f1f5f9;
}
body.dark-theme .top-header {
background: #1e293b;
border-color: #334155;
}
body.dark-theme .stat-card,
body.dark-theme .analytics-card,
body.dark-theme .content-section,
body.dark-theme .settings-card,
body.dark-theme .modal-content {
background: #1e293b;
border-color: #334155;
}
body.dark-theme .sidebar-brand,
body.dark-theme .user-name,
body.dark-theme .stat-value,
body.dark-theme .card-title,
body.dark-theme .section-title,
body.dark-theme .settings-info h3,
body.dark-theme .modal-title,
body.dark-theme h1 {
color: #f1f5f9 !important;
}
body.dark-theme .welcome-msg,
body.dark-theme .user-role,
body.dark-theme .stat-label,
body.dark-theme .settings-info p {
color: #94a3b8;
}
body.dark-theme .search-bar {
background: #334155;
}
body.dark-theme .search-bar input {
color: #f1f5f9;
}
body.dark-theme .election-table th {
background: #334155;
color: #94a3b8;
border-color: #475569;
}
body.dark-theme .election-table td {
border-color: #334155;
color: #cbd5e1;
}
body.dark-theme .nav-item:hover {
background: #334155;
}
body.dark-theme .flatlogic-badge {
background: #1e293b;
color: #f1f5f9;
}
/* Global Dark Theme Overrides */
body.dark-theme .audit-table,
body.dark-theme .audit-table th,
body.dark-theme .table-container,
body.dark-theme .content-section,
body.dark-theme .settings-card {
background: #1e293b !important;
border-color: #334155 !important;
}
body.dark-theme .audit-table td,
body.dark-theme .election-table td {
color: #cbd5e1 !important;
border-color: #334155 !important;
}
body.dark-theme .audit-table th,
body.dark-theme .election-table th {
background: #334155 !important;
color: #94a3b8 !important;
border-color: #475569 !important;
}
body.dark-theme .action-badge {
background: #334155;
color: #94a3b8;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
}
body.dark-theme input,
body.dark-theme select,
body.dark-theme textarea {
background-color: #334155 !important;
border-color: #475569 !important;
color: #f1f5f9 !important;
}
body.dark-theme .sidebar-brand,
body.dark-theme .user-name,
body.dark-theme .stat-value,
body.dark-theme .card-title,
body.dark-theme .section-title,
body.dark-theme .settings-info h3,
body.dark-theme .modal-title,
body.dark-theme h1,
body.dark-theme h2,
body.dark-theme h3 {
color: #f1f5f9 !important;
}
body.dark-theme .nav-item {
color: #94a3b8;
}
body.dark-theme .nav-item:hover {
background: #334155;
color: #f1f5f9;
}
body.dark-theme .nav-item.active {
background: #334155;
color: #6366f1;
}

View File

@ -1,364 +0,0 @@
:root {
--landing-primary: #5c7cfa;
--landing-dark: #212529;
--landing-gray: #f8f9fa;
--landing-text-muted: #6c757d;
}
body.landing-page {
margin: 0;
padding: 0;
font-family: 'Inter', sans-serif;
height: 100vh;
width: 100vw;
overflow: hidden;
position: relative;
background-repeat: no-repeat;
background-position: center center;
background-attachment: fixed;
background-size: cover;
}
body.landing-page::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6));
backdrop-filter: brightness(0.8);
z-index: 1;
}
.landing-container {
position: relative;
z-index: 2;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.school-header {
position: absolute;
top: 2rem;
left: 2rem;
display: flex;
align-items: center;
background: white;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
border-radius: 50px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.school-logo {
width: 40px;
height: 40px;
margin-right: 10px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.school-info h1 {
margin: 0;
font-size: 0.9rem;
font-weight: 800;
color: #1a3a8a;
text-transform: uppercase;
}
.school-info p {
margin: 0;
font-size: 0.7rem;
color: var(--landing-text-muted);
}
.info-card {
background: white;
width: 90%;
max-width: 500px;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.info-card-header {
background: var(--landing-primary);
color: white;
padding: 0.75rem 1.5rem;
display: flex;
align-items: center;
font-weight: 600;
font-size: 0.9rem;
letter-spacing: 0.05em;
}
.info-card-header i {
margin-right: 10px;
background: white;
color: var(--landing-primary);
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-style: normal;
font-weight: bold;
}
.info-card-body {
padding: 2.5rem;
text-align: center;
}
.info-card-body h2 {
margin: 0 0 1.5rem 0;
font-size: 2rem;
font-weight: 900;
color: var(--landing-dark);
text-transform: uppercase;
letter-spacing: -0.02em;
}
.info-card-body p {
color: var(--landing-text-muted);
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 2rem;
}
.btn-login {
background: var(--landing-primary);
color: white;
border: none;
padding: 0.8rem 2.5rem;
border-radius: 50px;
font-weight: 700;
font-size: 1rem;
cursor: pointer;
display: inline-flex;
align-items: center;
text-decoration: none;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 6px rgba(92, 124, 250, 0.3);
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(92, 124, 250, 0.4);
}
.btn-login i {
margin-right: 10px;
}
.landing-footer {
position: absolute;
bottom: 1.5rem;
width: 100%;
text-align: center;
color: #495057;
font-size: 0.75rem;
z-index: 2;
}
.flatlogic-badge {
position: absolute;
bottom: 1.5rem;
right: 1.5rem;
background: white;
padding: 0.4rem 1rem;
border-radius: 5px;
display: flex;
align-items: center;
font-size: 0.75rem;
font-weight: 600;
color: #1a3a8a;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 2;
}
.flatlogic-badge img {
margin-right: 8px;
height: 14px;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.8);
backdrop-filter: blur(4px);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-overlay.active {
display: flex;
}
.login-modal {
background: white;
width: 90%;
max-width: 450px;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
animation: modalIn 0.3s ease-out;
}
@keyframes modalIn {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.modal-header {
background: #5c7cfa;
color: white;
padding: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.modal-header-content {
display: flex;
align-items: center;
gap: 12px;
}
.modal-header h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 700;
}
.header-icon {
background: rgba(255, 255, 255, 0.2);
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.btn-close {
background: rgba(0, 0, 0, 0.2);
border: none;
color: white;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background 0.2s;
}
.btn-close:hover {
background: rgba(0, 0, 0, 0.4);
}
.modal-body {
padding: 2rem;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
font-size: 0.75rem;
font-weight: 800;
color: var(--landing-dark);
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.input-container {
position: relative;
display: flex;
align-items: center;
}
.input-container i {
position: absolute;
left: 1rem;
color: var(--landing-text-muted);
}
.input-container input,
.input-container select {
width: 100%;
padding: 0.8rem 1rem 0.8rem 2.8rem;
border: 1px solid #e2e8f0;
border-radius: 10px;
font-family: inherit;
font-size: 0.95rem;
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
}
.input-container input:focus,
.input-container select:focus {
border-color: var(--landing-primary);
box-shadow: 0 0 0 3px rgba(92, 124, 250, 0.1);
}
.password-toggle {
position: absolute;
right: 1rem;
color: var(--landing-text-muted);
cursor: pointer;
}
.modal-btn-login {
width: 100%;
background: var(--landing-primary);
color: white;
border: none;
padding: 1rem;
border-radius: 10px;
font-weight: 700;
font-size: 1rem;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 1rem;
transition: background 0.2s;
}
.modal-btn-login:hover {
background: #4c6ef5;
}
.forgot-password {
display: block;
text-align: center;
margin-top: 1.5rem;
color: var(--landing-primary);
text-decoration: none;
font-size: 0.9rem;
font-weight: 600;
}
.forgot-password:hover {
text-decoration: underline;
}

View File

@ -1,196 +0,0 @@
/* Officer Management Specific Styles */
.officer-management-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-top: 24px;
}
.officer-category-card {
background: var(--surface-color);
border-radius: 12px;
border: 1px solid var(--border-color);
display: flex;
flex-direction: column;
}
.category-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.category-title {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
color: var(--text-main);
font-size: 0.9375rem;
}
.active-count {
background: #f0fdf4;
color: #16a34a;
font-size: 0.75rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 99px;
}
.officer-list {
padding: 8px;
flex-grow: 1;
}
.officer-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 12px;
border-radius: 8px;
transition: background-color 0.2s;
}
.officer-item:hover {
background-color: var(--surface-hover);
}
.officer-main-info {
display: flex;
align-items: center;
gap: 12px;
}
.officer-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #e0e7ff;
color: #4f46e5;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
}
.officer-details {
display: flex;
flex-direction: column;
}
.officer-name {
font-size: 0.875rem;
font-weight: 500;
color: var(--text-main);
}
.officer-meta {
font-size: 0.75rem;
color: var(--text-muted);
}
.officer-actions {
display: flex;
gap: 8px;
}
.officer-actions button {
background: transparent;
border: none;
color: #94a3b8;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
}
.officer-actions button:hover {
color: #4f46e5;
background: #f1f5f9;
}
.empty-state {
padding: 40px 20px;
text-align: center;
color: #94a3b8;
font-size: 0.875rem;
}
/* Registration Form Section */
.registration-section {
background: var(--surface-color);
border-radius: 12px;
border: 1px solid var(--border-color);
padding: 24px;
margin-bottom: 24px;
}
.registration-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
color: var(--text-main);
font-weight: 600;
}
.form-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 16px;
align-items: flex-end;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-size: 0.75rem;
font-weight: 700;
color: var(--text-main);
text-transform: uppercase;
}
.form-group input, .form-group select {
padding: 10px 14px;
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 0.875rem;
color: var(--text-main);
outline: none;
transition: border-color 0.2s;
}
.form-group input:focus, .form-group select:focus {
border-color: #4f46e5;
}
.btn-save-officer {
background: #2563eb;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
font-size: 0.875rem;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
cursor: pointer;
transition: background 0.2s;
height: 42px;
}
.btn-save-officer:hover {
background: #1d4ed8;
}

View File

@ -1,119 +0,0 @@
/* Reports & Audit Styles */
.audit-table {
width: 100%;
border-collapse: collapse;
background: #ffffff;
border-radius: 12px;
overflow: hidden;
}
.audit-table th {
padding: 12px 24px;
text-align: left;
font-size: 0.7rem;
font-weight: 700;
color: #64748b;
background: #f9fafb;
border-bottom: 1px solid #f3f4f6;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.audit-table td {
padding: 16px 24px;
border-bottom: 1px solid #f3f4f6;
font-size: 0.875rem;
color: #1e293b;
vertical-align: middle;
}
.audit-timestamp {
color: #475569;
}
.audit-user-id {
font-weight: 500;
}
.role-badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 0.65rem;
font-weight: 700;
background: #dbeafe;
color: #2563eb;
text-transform: uppercase;
}
.audit-action {
font-weight: 700;
color: #0f172a;
}
.audit-details {
color: #64748b;
}
/* Header section */
.audit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
}
.audit-title h1 {
font-size: 1.75rem;
font-weight: 800;
color: #1e293b;
margin: 0;
}
.audit-subtitle {
font-size: 0.875rem;
color: #64748b;
font-weight: 500;
}
/* Container for the table to add shadow and border radius */
.table-container {
background: #ffffff;
border: 1px solid #f1f5f9;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}
.search-container {
margin-bottom: 24px;
}
.audit-search-wrapper {
position: relative;
display: flex;
align-items: center;
max-width: 400px;
}
.audit-search-wrapper i {
position: absolute;
left: 12px;
color: #94a3b8;
}
.audit-search-wrapper input {
width: 100%;
padding: 10px 12px 10px 40px;
border: 1px solid #e2e8f0;
border-radius: 10px;
font-size: 0.875rem;
outline: none;
background: #f8fafc;
transition: all 0.2s;
}
.audit-search-wrapper input:focus {
background: #ffffff;
border-color: #3b82f6;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}

View File

@ -1,204 +0,0 @@
:root {
--primary-color: #1e293b;
--accent-color: #2563eb;
--bg-color: #f8fafc;
--surface-color: #ffffff;
--surface-hover: #f8fafc;
--border-color: #e2e8f0;
--text-main: #1e293b;
--text-muted: #64748b;
--table-header-bg: #f9fafb;
--radius: 6px;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: 'Inter', -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
margin: 0;
}
.navbar {
background: var(--surface-color);
border-bottom: 1px solid var(--border-color);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.brand {
font-weight: 700;
font-size: 1.25rem;
color: var(--primary-color);
text-decoration: none;
}
.container {
max-width: 1000px;
margin: 2rem auto;
padding: 0 1rem;
}
.card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
border-radius: var(--radius);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary {
background: var(--accent-color);
color: white;
}
.btn-primary:hover {
background: #1d4ed8;
}
.btn-outline {
border-color: var(--border-color);
background: transparent;
color: var(--text-main);
}
.btn-outline:hover {
background: #f1f5f9;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th {
text-align: left;
padding: 0.75rem;
border-bottom: 2px solid var(--border-color);
color: var(--text-muted);
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
}
.table td {
padding: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.badge {
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}
.badge-preparing { background: #fef3c7; color: #92400e; }
.badge-ongoing { background: #dcfce7; color: #166534; }
.badge-finished { background: #f1f5f9; color: #475569; }
.form-group {
margin-bottom: 1rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.form-control {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: var(--radius);
font-family: inherit;
font-size: 0.875rem;
}
.form-control:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.header-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.text-muted { color: var(--text-muted); }
.text-center { text-align: center; }
.mb-4 { margin-bottom: 1rem; }
.mb-5 { margin-bottom: 2rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 1rem; }
.w-100 { width: 100%; }
.row { display: flex; flex-wrap: wrap; margin-right: -0.75rem; margin-left: -0.75rem; }
.col-12 { flex: 0 0 100%; max-width: 100%; padding: 0.75rem; }
.col-md-6 { flex: 0 0 50%; max-width: 50%; padding: 0.75rem; }
.col-lg-4 { flex: 0 0 33.333333%; max-width: 33.333333%; padding: 0.75rem; }
/* Dark Mode */
body.dark-theme {
--bg-color: #0f172a;
--surface-color: #1e293b;
--surface-hover: #334155;
--border-color: #334155;
--text-main: #f1f5f9;
--text-muted: #94a3b8;
--table-header-bg: #334155;
}
body.dark-theme .navbar {
background: #1e293b;
border-color: #334155;
}
body.dark-theme .brand {
color: #f1f5f9;
}
body.dark-theme .btn-outline {
border-color: #475569;
color: #f1f5f9;
}
body.dark-theme .btn-outline:hover {
background: #334155;
}
body.dark-theme .table th {
border-color: #475569;
}
body.dark-theme .table td {
border-color: #334155;
}
body.dark-theme .form-control {
background-color: #334155;
border-color: #475569;
color: #f1f5f9;
}

View File

@ -1,347 +0,0 @@
.header-icon-container {
background: #eef2ff;
padding: 12px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
/* Voter Stats Grid */
.voter-stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.voter-stat-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
}
.voter-stat-label {
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
margin-bottom: 16px;
letter-spacing: 0.05em;
}
.voter-stat-value {
font-size: 2.5rem;
font-weight: 800;
}
/* Distribution Grid */
.distribution-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.distribution-card {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
}
.distribution-header {
font-size: 0.875rem;
font-weight: 700;
color: #2563eb;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #f3f4f6;
}
.distribution-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.distribution-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.875rem;
color: #4b5563;
}
.distribution-count {
font-weight: 700;
color: var(--text-main);
}
/* Action Buttons */
.btn-action {
padding: 10px 20px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.btn-add {
background: #2563eb;
color: white;
}
.btn-add:hover { background: #1d4ed8; }
.btn-import {
background: #4f46e5;
color: white;
}
.btn-import:hover { background: #4338ca; }
/* Filter Bar */
.filter-bar {
padding: 24px;
display: flex;
gap: 16px;
align-items: flex-end;
background: var(--surface-color);
border-bottom: 1px solid var(--border-color);
}
.filter-group {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
}
.filter-group label {
font-size: 0.7rem;
font-weight: 700;
color: #64748b;
letter-spacing: 0.05em;
}
.search-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.search-input-wrapper i {
position: absolute;
left: 12px;
}
.search-input-wrapper input {
width: 100%;
padding: 10px 12px 10px 36px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 0.875rem;
outline: none;
background: #f8fafc;
}
.filter-group select {
padding: 10px 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 0.875rem;
outline: none;
background: var(--surface-color);
color: var(--text-main);
}
/* Voters Table */
.voters-table {
width: 100%;
border-collapse: collapse;
}
.voters-table th {
padding: 12px 24px;
text-align: left;
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
background: var(--table-header-bg);
border-bottom: 1px solid var(--border-color);
}
.voters-table td {
padding: 16px 24px;
border-bottom: 1px solid var(--border-color);
font-size: 0.875rem;
color: var(--text-main);
}
.status-indicator {
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
.status-indicator.voted {
background: #dcfce7;
color: #166534;
}
.status-indicator.pending {
background: #f1f5f9;
color: #475569;
}
.actions-cell {
display: flex;
gap: 12px;
}
.actions-cell button {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #94a3b8;
transition: color 0.2s;
}
.actions-cell button:hover {
color: #4f46e5;
}
.actions-cell button i {
width: 16px;
height: 16px;
}
/* Modals */
.modal {
display: none;
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.5);
backdrop-filter: blur(4px);
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--surface-color);
color: var(--text-main);
padding: 32px;
border-radius: 16px;
width: 100%;
max-width: 600px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
.modal-header h2 {
font-size: 1.25rem;
color: var(--text-main);
margin: 0;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.modal-header h2 {
font-size: 1.25rem;
color: #1e293b;
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
color: #94a3b8;
cursor: pointer;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
}
.form-group input, .form-group select {
padding: 10px 12px;
background: var(--bg-color);
color: var(--text-main);
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 0.875rem;
outline: none;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.btn-cancel {
padding: 10px 20px;
background: #f1f5f9;
color: #475569;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
}
.btn-submit {
padding: 10px 20px;
background: #2563eb;
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
}
.import-area {
border: 2px dashed #e2e8f0;
border-radius: 12px;
padding: 40px;
text-align: center;
background: #f8fafc;
}
.import-area p {
font-size: 0.875rem;
color: #64748b;
margin: 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1,81 +0,0 @@
<?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;
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
function require_login() {
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
}
function require_role($roles) {
$user = get_user();
if (!$user || !in_array($user['role'], (array)$roles)) {
header('Location: index.php?error=Unauthorized');
exit;
}
}
function uuid() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
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, 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,
$new ? json_encode($new) : null,
$electionId
]);
}
function get_active_election_id() {
if (isset($_GET['set_election_id'])) {
$_SESSION['active_election_id'] = $_GET['set_election_id'];
// Redirect to same page without the query param to keep URL clean
$url = strtok($_SERVER["REQUEST_URI"], '?');
header("Location: " . $url);
exit;
}
if (!isset($_SESSION['active_election_id'])) {
$election = db()->query("SELECT id FROM elections WHERE archived = FALSE ORDER BY created_at DESC LIMIT 1")->fetch();
$_SESSION['active_election_id'] = $election['id'] ?? null;
}
return $_SESSION['active_election_id'];
}
function get_active_election() {
$id = get_active_election_id();
if (!$id) return null;
$stmt = db()->prepare("SELECT * FROM elections WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
function get_all_elections() {
return db()->query("SELECT * FROM elections WHERE archived = FALSE ORDER BY created_at DESC")->fetchAll();
}

View File

@ -1,605 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/auth_helper.php';
require_login();
$user = get_user();
$id = $_GET['id'] ?? '';
if (!$id) {
header("Location: index.php");
exit;
}
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM elections WHERE id = ?");
$stmt->execute([$id]);
$election = $stmt->fetch();
if (!$election || $election['status'] !== 'Ongoing') {
die("Election is not currently ongoing.");
}
// Check if already voted
$check = $pdo->prepare("SELECT COUNT(*) FROM votes WHERE election_id = ? AND voter_id = ?");
$check->execute([$id, $user['id']]);
if ($check->fetchColumn() > 0) {
header("Location: index.php?error=already_voted");
exit;
}
$positions = $pdo->prepare("SELECT * FROM positions WHERE election_id = ? ORDER BY sort_order ASC");
$positions->execute([$id]);
$positions = $positions->fetchAll();
$endTime = strtotime($election['end_date_and_time']) * 1000;
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ballot: <?= htmlspecialchars($election['title']) ?></title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
:root {
--primary: #4f46e5;
--primary-hover: #4338ca;
--primary-light: #eef2ff;
--bg: #f8fafc;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
}
body {
background-color: var(--bg);
color: var(--text);
font-family: 'Plus Jakarta Sans', sans-serif;
margin: 0;
line-height: 1.5;
}
.navbar {
background: white;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 1000;
}
.brand {
font-weight: 800;
font-size: 1.25rem;
color: var(--primary);
text-decoration: none;
letter-spacing: -0.025em;
}
.ballot-container {
max-width: 900px;
margin: 0 auto;
padding: 40px 20px 120px;
}
.ballot-title-area {
margin-bottom: 40px;
}
.ballot-title-area h1 {
font-size: 1.875rem;
font-weight: 700;
margin: 0 0 8px 0;
color: #1e293b;
}
.ballot-title-area p {
color: #64748b;
margin: 0;
font-size: 1rem;
}
.voter-info-card {
background: white;
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
margin-bottom: 32px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.voter-info-title {
font-size: 1rem;
font-weight: 600;
color: #475569;
margin-bottom: 16px;
}
.voter-info-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.info-item label {
display: block;
font-size: 0.75rem;
font-weight: 500;
color: #94a3b8;
text-transform: uppercase;
margin-bottom: 4px;
}
.info-item span {
font-size: 0.875rem;
font-weight: 600;
color: #1e293b;
}
.timer-container {
display: flex;
align-items: center;
gap: 8px;
color: #ef4444;
font-weight: 700;
font-size: 0.875rem;
background: #fef2f2;
padding: 8px 16px;
border-radius: 8px;
border: 1px solid #fee2e2;
}
.position-group {
background: white;
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px;
margin-bottom: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.position-title {
font-size: 1.25rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 24px;
}
.candidates-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.candidate-label {
cursor: pointer;
display: block;
}
.candidate-card {
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px 20px;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 16px;
background: white;
}
.candidate-card:hover {
border-color: var(--primary);
background: #fcfdff;
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}
input[type="radio"]:checked + .candidate-card {
border-color: var(--primary);
background: white;
box-shadow: 0 0 0 1px var(--primary);
}
.radio-circle {
width: 20px;
height: 20px;
border: 2px solid #cbd5e1;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
input[type="radio"]:checked + .candidate-card .radio-circle {
border-color: var(--primary);
}
input[type="radio"]:checked + .candidate-card .radio-circle::after {
content: '';
width: 10px;
height: 10px;
background: var(--primary);
border-radius: 50%;
}
.candidate-avatar {
width: 48px;
height: 48px;
background: #f1f5f9;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #64748b;
}
.candidate-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.candidate-info h3 {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: #1e293b;
}
.candidate-info p {
margin: 2px 0 0 0;
font-size: 0.875rem;
color: #64748b;
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 16px 40px;
border-top: 1px solid var(--border);
display: flex;
justify-content: center;
z-index: 100;
box-shadow: 0 -10px 15px -3px rgba(0,0,0,0.05);
}
.submit-container {
max-width: 900px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.submit-text p {
margin: 0;
font-size: 0.75rem;
font-weight: 800;
color: var(--text-muted);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.submit-text h4 {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
color: var(--text);
}
.btn-submit {
background: var(--primary);
color: white;
border: none;
padding: 14px 40px;
border-radius: 12px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.2);
}
.btn-submit:hover {
background: var(--primary-hover);
transform: translateY(-1px);
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.3);
}
.btn-submit:active {
transform: translateY(0);
}
/* Modal Styles */
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(4px);
z-index: 2000;
align-items: center;
justify-content: center;
padding: 20px;
}
.modal-overlay.active {
display: flex;
}
.confirm-modal {
background: white;
border-radius: 20px;
max-width: 450px;
width: 100%;
padding: 32px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
animation: modalIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes modalIn {
from { transform: scale(0.9) translateY(20px); opacity: 0; }
to { transform: scale(1) translateY(0); opacity: 1; }
}
.modal-icon {
width: 64px;
height: 64px;
background: #eef2ff;
color: #4f46e5;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
}
.confirm-modal h2 {
margin: 0 0 12px 0;
font-size: 1.5rem;
font-weight: 800;
color: #1e293b;
}
.confirm-modal p {
margin: 0 0 32px 0;
color: #64748b;
font-size: 1rem;
line-height: 1.6;
}
.modal-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.btn-modal {
padding: 12px 24px;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
border: none;
text-align: center;
}
.btn-modal-cancel {
background: #f1f5f9;
color: #64748b;
}
.btn-modal-cancel:hover {
background: #e2e8f0;
color: #1e293b;
}
.btn-modal-confirm {
background: #4f46e5;
color: white;
box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.2);
}
.btn-modal-confirm:hover {
background: #4338ca;
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.3);
}
input[type="radio"] { display: none; }
@media (max-width: 768px) {
.voter-info-grid {
grid-template-columns: 1fr 1fr;
}
}
</style>
</head>
<body class="<?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<nav class="navbar">
<a href="index.php" class="brand">Click to Vote</a>
<div>
<span style="margin-right: 1rem; color: var(--text-muted); font-size: 0.875rem; font-weight: 600;"><?= htmlspecialchars($user['name']) ?></span>
<a href="logout.php" style="color: #ef4444; font-size: 0.875rem; font-weight: 700; text-decoration: none;">Logout</a>
</div>
</nav>
<div class="ballot-container">
<div class="ballot-title-area">
<h1><?= htmlspecialchars($election['title']) ?></h1>
<p>Review the candidates carefully and cast your secure vote below.</p>
</div>
<div class="voter-info-card">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px;">
<div class="voter-info-title">Voter Information</div>
<div class="timer-container">
<i data-lucide="clock" style="width: 16px;"></i>
<span id="countdown">00:00:00</span>
</div>
</div>
<div class="voter-info-grid">
<div class="info-item">
<label>Email</label>
<span><?= htmlspecialchars($user['email']) ?></span>
</div>
<div class="info-item">
<label>Grade Level</label>
<span>Grade <?= htmlspecialchars((string)($user['grade_level'] ?? 'N/A')) ?></span>
</div>
<div class="info-item">
<label>Track/Cluster</label>
<span><?= htmlspecialchars($user['track'] ?? 'N/A') ?></span>
</div>
<div class="info-item">
<label>Section</label>
<span><?= htmlspecialchars($user['section'] ?? 'N/A') ?></span>
</div>
</div>
</div>
<form id="ballotForm" action="api/submit_vote.php" method="POST">
<input type="hidden" name="election_id" value="<?= $id ?>">
<?php foreach ($positions as $index => $pos): ?>
<div class="position-group">
<div class="position-title">
<?= htmlspecialchars($pos['name']) ?>
</div>
<?php
$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)): ?>
<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 available for your track.</p>
</div>
<?php else: ?>
<div class="candidates-list">
<?php foreach ($candidates as $cand): ?>
<label class="candidate-label">
<input type="radio" name="votes[<?= $pos['id'] ?>]" value="<?= $cand['id'] ?>" required>
<div class="candidate-card">
<div class="radio-circle"></div>
<div class="candidate-avatar">
<?= substr($cand['name'], 0, 1) ?>
</div>
<div class="candidate-info">
<h3><?= htmlspecialchars($cand['name']) ?></h3>
<p><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></p>
</div>
</div>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<div class="submit-bar">
<div class="submit-container">
<div class="submit-text">
<p>Ready to submit?</p>
<h4>Review your selections</h4>
</div>
<button type="button" class="btn-submit" onclick="openConfirmModal()">
Cast My Vote
</button>
</div>
</div>
</form>
</div>
<!-- Confirmation Modal -->
<div id="confirmModal" class="modal-overlay">
<div class="confirm-modal">
<div class="modal-icon">
<i data-lucide="shield-check" style="width: 32px; height: 32px;"></i>
</div>
<h2>Cast your vote?</h2>
<p>You are about to submit your choices. This action is permanent and cannot be undone. Are you sure you want to proceed?</p>
<div class="modal-actions">
<button type="button" class="btn-modal btn-modal-cancel" onclick="closeConfirmModal()">Review Choices</button>
<button type="button" class="btn-modal btn-modal-confirm" onclick="submitBallot()">Yes, Cast My Vote</button>
</div>
</div>
</div>
<footer style="text-align: center; color: var(--text-muted); padding: 4rem 2rem; background: white; border-top: 1px solid var(--border);">
&copy; <?= date('Y') ?> Click to Vote | High School Online Election System
</footer>
<script>
lucide.createIcons();
function openConfirmModal() {
// Basic validation: check if all required radios are checked
const form = document.getElementById('ballotForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
document.getElementById('confirmModal').classList.add('active');
}
function closeConfirmModal() {
document.getElementById('confirmModal').classList.remove('active');
}
function submitBallot() {
document.getElementById('ballotForm').submit();
}
// Countdown Timer
const endTime = <?= $endTime ?>;
function updateCountdown() {
const now = new Date().getTime();
const distance = endTime - now;
if (distance < 0) {
document.getElementById("countdown").innerHTML = "EXPIRED";
document.getElementById("ballotForm").style.opacity = "0.5";
document.getElementById("ballotForm").style.pointerEvents = "none";
return;
}
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
document.getElementById("countdown").innerHTML =
(hours < 10 ? "0" : "") + hours + ":" +
(minutes < 10 ? "0" : "") + minutes + ":" +
(seconds < 10 ? "0" : "") + seconds;
}
setInterval(updateCountdown, 1000);
updateCountdown();
</script>
</body>
</html>

View File

@ -1,520 +0,0 @@
<?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 var(--border-color);
background: var(--surface-color);
color: var(--text-main);
}
.btn-manage:hover {
background: var(--surface-hover);
border-color: var(--border-color);
}
.btn-manage.primary {
background: var(--accent-color);
color: white;
border-color: var(--accent-color);
}
.btn-manage.primary:hover {
background: #4338ca;
}
.modal {
display: none;
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--surface-color);
color: var(--text-main);
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: var(--text-main); }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 6px; }
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 10px;
background: var(--bg-color);
color: var(--text-main);
border-radius: 8px;
border: 1px solid var(--border-color);
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: var(--text-main);">Candidate Management</h1>
<p style="margin: 4px 0 0 0; color: var(--text-muted); 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 style="color: var(--text-muted);"><?= htmlspecialchars($stat['name']) ?></span>
<span class="distribution-count"><?= $stat['count'] ?></span>
</div>
<?php endforeach; ?>
<?php if (empty($posStats)): ?>
<div style="padding: 12px; color: var(--text-muted); 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 style="color: var(--text-muted);"><?= 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: var(--text-muted); 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: var(--surface-color); border-radius: 12px; border: 1px solid var(--border-color); 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: var(--bg-color); color: var(--text-muted);">
</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>

View File

@ -1,41 +0,0 @@
<?php
require_once __DIR__ . '/db/config.php';
$supabasePass = 'gA82h8K80T5QUAwi';
$supabaseHost = "aws-1-ap-southeast-1.pooler.supabase.com";
$supabaseUser = "postgres.siqeqnizegizxemrfgkf";
$supabaseDb = "postgres";
$supabasePort = "6543";
try {
echo "Connecting to local MariaDB...\n";
$localPdo = db();
echo "Connecting to Supabase PostgreSQL...\n";
$dsn = "pgsql:host=$supabaseHost;port=$supabasePort;dbname=$supabaseDb";
$supabasePdo = new PDO($dsn, $supabaseUser, $supabasePass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$tables = ["users", "elections", "positions", "election_assignments", "parties", "candidates", "votes", "audit_logs"];
echo "\nComparison Table:\n";
echo str_pad("Table", 25) . " | " . str_pad("Local", 10) . " | " . str_pad("Supabase", 10) . "\n";
echo str_repeat("-", 50) . "\n";
foreach ($tables as $table) {
$localCount = $localPdo->query("SELECT COUNT(*) FROM $table")->fetchColumn();
// For Supabase, check if table exists first
try {
$supabaseCount = $supabasePdo->query("SELECT COUNT(*) FROM $table")->fetchColumn();
} catch (Exception $e) {
$supabaseCount = "N/A (Error)";
}
echo str_pad($table, 25) . " | " . str_pad($localCount, 10) . " | " . str_pad($supabaseCount, 10) . "\n";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}

View File

@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/auth_helper.php';
require_login();
require_role(['Admin', 'Adviser', 'Officer']);
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Create Election | <?= 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/style.css?v=<?= time() ?>">
</head>
<body>
<nav class="navbar">
<a href="index.php" class="brand">Click to Vote</a>
<div>
<a href="index.php" class="btn btn-outline">Back to Dashboard</a>
</div>
</nav>
<div class="container" style="max-width: 600px;">
<div class="card">
<h1 style="margin-top: 0; font-size: 1.25rem;">New Election</h1>
<p style="color: var(--text-muted); margin-bottom: 2rem;">Fill in the details to schedule a new election.</p>
<form action="api/create_election.php" method="POST">
<div class="form-group">
<label class="form-label">Election Title</label>
<input type="text" name="title" class="form-control" placeholder="e.g. SSG General Election 2026" required>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3" placeholder="Briefly describe the purpose of this election..."></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
<div class="form-group">
<label class="form-label">Start Date & Time</label>
<input type="datetime-local" name="start_date" class="form-control" required>
</div>
<div class="form-group">
<label class="form-label">End Date & Time</label>
<input type="datetime-local" name="end_date" class="form-control" required>
</div>
</div>
<div style="margin-top: 2rem; display: flex; gap: 1rem;">
<button type="submit" class="btn btn-primary" style="flex: 1;">Create Election</button>
<a href="index.php" class="btn btn-outline" style="flex: 1;">Cancel</a>
</div>
</form>
</div>
</div>
</body>
</html>

View File

@ -1,510 +0,0 @@
<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
$electionId = get_active_election_id();
$election = get_active_election();
// For Election Management Section
$allElections = [];
if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])) {
$allElections = $pdo->query("SELECT * FROM elections WHERE archived = FALSE ORDER BY created_at DESC")->fetchAll();
}
// Statistics (Filtered by Election)
$totalVoters = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND role_in_election = 'Voter'");
$totalVoters->execute([$electionId]);
$totalVoters = $totalVoters->fetchColumn();
$totalCandidates = $pdo->prepare("SELECT COUNT(*) FROM candidates WHERE election_id = ?");
$totalCandidates->execute([$electionId]);
$totalCandidates = $totalCandidates->fetchColumn();
$totalVotes = $pdo->prepare("SELECT COUNT(DISTINCT voter_id) FROM votes WHERE election_id = ?");
$totalVotes->execute([$electionId]);
$totalVotes = $totalVotes->fetchColumn();
// Chart Data: Participation per Grade Level
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$gradeCol = ($driver === 'pgsql') ? "u.grade_level::TEXT" : "CAST(u.grade_level AS CHAR)";
$gradeStats = $pdo->prepare("SELECT COALESCE($gradeCol, 'Unknown') as label, COUNT(DISTINCT v.voter_id) as count
FROM users u JOIN votes v ON u.id = v.voter_id
WHERE v.election_id = ?
GROUP BY u.grade_level ORDER BY u.grade_level");
$gradeStats->execute([$electionId]);
$gradeStats = $gradeStats->fetchAll(PDO::FETCH_ASSOC);
// Chart Data: Participation per Track
$trackStats = $pdo->prepare("SELECT COALESCE(u.track, 'Unknown') as label, COUNT(DISTINCT v.voter_id) as count
FROM users u JOIN votes v ON u.id = v.voter_id
WHERE v.election_id = ?
GROUP BY u.track");
$trackStats->execute([$electionId]);
$trackStats = $trackStats->fetchAll(PDO::FETCH_ASSOC);
// Chart Data: Participation per Section
$sectionStats = $pdo->prepare("SELECT u.track, u.section as label, COUNT(DISTINCT v.voter_id) as count
FROM users u JOIN votes v ON u.id = v.voter_id
WHERE v.election_id = ?
GROUP BY u.track, u.section");
$sectionStats->execute([$electionId]);
$sectionStats = $sectionStats->fetchAll(PDO::FETCH_ASSOC);
// Tracks for dropdown
$tracks = array_unique(array_column($sectionStats, 'track'));
sort($tracks);
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System for Senior High School';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Election Dashboard | <?= htmlspecialchars($projectDescription) ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.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: 24px;
border-radius: 12px;
width: 100%;
max-width: 400px;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.modal-header {
margin-bottom: 20px;
border-bottom: 1px solid #f1f5f9;
padding-bottom: 12px;
}
.modal-title {
font-weight: 700;
font-size: 1.1rem;
color: #1e293b;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 0.75rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
margin-bottom: 6px;
}
.form-control {
width: 100%;
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
font-size: 0.875rem;
outline: none;
}
.modal-footer {
margin-top: 24px;
display: flex;
gap: 12px;
}
.btn-submit {
flex: 1;
background: #4f46e5;
color: white;
border: none;
padding: 10px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
}
.btn-cancel {
flex: 1;
background: white;
border: 1px solid #e2e8f0;
padding: 10px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
}
</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">
<?php if (isset($_GET['success'])): ?>
<div style="background: #ecfdf5; color: #10b981; padding: 12px 16px; border-radius: 8px; margin-bottom: 24px; font-size: 0.875rem; border: 1px solid #10b981;">
<?= htmlspecialchars($_GET['success']) ?>
</div>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<div style="background: #fef2f2; color: #ef4444; padding: 12px 16px; border-radius: 8px; margin-bottom: 24px; font-size: 0.875rem; border: 1px solid #ef4444;">
<?= htmlspecialchars($_GET['error']) ?>
</div>
<?php endif; ?>
<div class="dashboard-header">
<div>
<h1 style="margin: 0 0 4px 0; font-size: 1.5rem; color: #1e293b;">Election Dashboard</h1>
<div class="welcome-msg">
Active Election: <strong><?= htmlspecialchars($election['title'] ?? 'None') ?></strong>
</div>
</div>
</div>
<?php if (!empty($allElections)): ?>
<!-- Election Control Center -->
<div class="content-section animate-stagger" style="margin-bottom: 32px;">
<div class="section-header">
<div class="section-title">Election Control Center</div>
<button class="btn-new-election" id="btnNewElection" style="border: none; cursor: pointer; display: flex; align-items: center; gap: 8px;">
<i data-lucide="plus"></i> New Election
</button>
</div>
<table class="election-table">
<thead>
<tr>
<th>Election Title</th>
<th>Status</th>
<th>Current End Time</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($allElections as $e): ?>
<tr>
<td style="font-weight: 500;"><a href="view_election.php?id=<?= $e['id'] ?>" style="color: #6366f1; text-decoration: none;"><?= htmlspecialchars($e['title']) ?></a></td>
<td>
<span class="status-badge status-<?= strtolower($e['status']) ?>">
<?= htmlspecialchars($e['status']) ?>
</span>
</td>
<td style="color: #64748b; font-size: 0.8rem;">
<?= date('M d, H:i', strtotime($e['end_date_and_time'])) ?>
</td>
<td>
<div class="quick-actions">
<?php if ($e['status'] === 'Preparing'): ?>
<form action="api/update_election_status.php" method="POST" style="display:inline;">
<input type="hidden" name="id" value="<?= $e['id'] ?>">
<input type="hidden" name="status" value="Ongoing">
<input type="hidden" name="redirect" value="../dashboard.php?success=Election started">
<button type="submit" class="btn-update" style="background: #10b981;">Start</button>
</form>
<?php elseif ($e['status'] === 'Ongoing'): ?>
<form action="api/update_election_status.php" method="POST" style="display:inline;">
<input type="hidden" name="id" value="<?= $e['id'] ?>">
<input type="hidden" name="status" value="Finished">
<input type="hidden" name="redirect" value="../dashboard.php?success=Election ended">
<button type="submit" class="btn-update" style="background: #ef4444;">End</button>
</form>
<?php endif; ?>
<?php if (in_array($user['role'], ['Admin', 'Adviser'])): ?>
<button
class="btn-update btn-manage-election"
style="background: #6366f1;"
data-id="<?= $e['id'] ?>"
data-title="<?= htmlspecialchars($e['title']) ?>"
data-status="<?= $e['status'] ?>"
data-end="<?= $e['end_date_and_time'] ?>"
>Manage</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Stats Grid -->
<div class="stats-grid animate-stagger">
<div class="stat-card">
<div class="stat-label">Total Voters</div>
<div class="stat-value"><?= number_format($totalVoters) ?></div>
<div class="stat-footer voters">
<i data-lucide="users-2" style="width: 14px;"></i>
Assigned Students
</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Candidates</div>
<div class="stat-value"><?= number_format($totalCandidates) ?></div>
<div class="stat-footer candidates">
<i data-lucide="user-circle" style="width: 14px;"></i>
Validated Contestants
</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Votes Cast</div>
<div class="stat-value"><?= number_format($totalVotes) ?></div>
<div class="stat-footer votes">
<i data-lucide="check-circle-2" style="width: 14px;"></i>
Verified Ballots
</div>
</div>
</div>
<!-- Analytics Charts -->
<div class="analytics-row animate-stagger">
<div class="analytics-card">
<div class="card-header">
<div class="card-title">Votes Per Grade Level</div>
</div>
<div class="chart-container">
<canvas id="gradeChart"></canvas>
</div>
</div>
<div class="analytics-card">
<div class="card-header">
<div class="card-title">Votes Per Track</div>
</div>
<div class="chart-container">
<canvas id="trackChart"></canvas>
</div>
</div>
</div>
<div class="analytics-row animate-stagger">
<div class="analytics-card" style="grid-column: span 2;">
<div class="card-header">
<div class="card-title">Votes Per Section</div>
<select id="trackFilter" class="chart-filter">
<?php if (empty($tracks)): ?>
<option>No data</option>
<?php endif; ?>
<?php foreach ($tracks as $t): ?>
<option value="<?= htmlspecialchars($t) ?>"><?= htmlspecialchars($t) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="chart-container" style="height: 300px;">
<canvas id="sectionChart"></canvas>
</div>
</div>
</div>
</main>
</div>
<script>
lucide.createIcons();
// Chart Data from PHP
const gradeData = <?= json_encode($gradeStats) ?>;
const trackData = <?= json_encode($trackStats) ?>;
const sectionData = <?= json_encode($sectionStats) ?>;
// Common Chart Options
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true, grid: { display: false } },
x: { grid: { display: false } }
}
};
// Grade Level Bar Chart
if (gradeData.length) {
new Chart(document.getElementById('gradeChart'), {
type: 'bar',
data: {
labels: gradeData.map(d => 'Grade ' + d.label),
datasets: [{
label: 'Votes',
data: gradeData.map(d => d.count),
backgroundColor: '#4f46e5',
borderRadius: 6
}]
},
options: commonOptions
});
}
// Track Bar Chart
if (trackData.length) {
new Chart(document.getElementById('trackChart'), {
type: 'bar',
data: {
labels: trackData.map(d => d.label),
datasets: [{
label: 'Votes',
data: trackData.map(d => d.count),
backgroundColor: '#10b981',
borderRadius: 6
}]
},
options: commonOptions
});
}
// Section Chart
let sectionChart;
function updateSectionChart(track) {
const canvas = document.getElementById('sectionChart');
if (!canvas) return;
const filtered = sectionData.filter(d => d.track === track);
const data = {
labels: filtered.map(d => d.label),
datasets: [{
label: 'Votes',
data: filtered.map(d => d.count),
backgroundColor: '#4f46e5',
borderRadius: 6
}]
};
if (sectionChart) {
sectionChart.data = data;
sectionChart.update();
} else {
sectionChart = new Chart(canvas, {
type: 'bar',
data: data,
options: commonOptions
});
}
}
const trackFilter = document.getElementById('trackFilter');
if (trackFilter && trackData.length) {
trackFilter.addEventListener('change', (e) => {
updateSectionChart(e.target.value);
});
updateSectionChart(trackFilter.value);
}
</script>
<!-- Override/Manage Modal -->
<div id="manageElectionModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title" id="modalElectionTitle">Manage Election</div>
</div>
<form action="api/manage_election_action.php" method="POST">
<input type="hidden" name="id" id="modalElectionId">
<div class="form-group">
<label>Override Status</label>
<select name="status" id="modalElectionStatus" class="form-control">
<option value="Preparing">Preparing</option>
<option value="Ongoing">Ongoing</option>
<option value="Finished">Finished</option>
</select>
</div>
<div class="form-group">
<label>Change End Time</label>
<input type="datetime-local" name="end_time" id="modalElectionEndTime" class="form-control">
</div>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeModal('manageElectionModal')">Cancel</button>
<button type="submit" class="btn-submit">Save Changes</button>
</div>
</form>
</div>
</div>
<!-- New Election Modal -->
<div id="createElectionModal" class="modal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<div class="modal-title">Create New Election</div>
</div>
<form action="api/create_election.php" method="POST">
<div class="form-group">
<label>Election Title</label>
<input type="text" name="title" class="form-control" placeholder="e.g. SSG General Election 2026" required>
</div>
<div class="form-group">
<label>Description</label>
<textarea name="description" class="form-control" rows="3" placeholder="Briefly describe the purpose..."></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
<div class="form-group">
<label>Start Date & Time</label>
<input type="datetime-local" name="start_date" class="form-control" required>
</div>
<div class="form-group">
<label>End Date & Time</label>
<input type="datetime-local" name="end_date" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeModal('createElectionModal')">Cancel</button>
<button type="submit" class="btn-submit">Create Election</button>
</div>
</form>
</div>
</div>
<script>
document.getElementById('btnNewElection').addEventListener('click', function() {
document.getElementById('createElectionModal').style.display = 'flex';
});
document.querySelectorAll('.btn-manage-election').forEach(button => {
button.addEventListener('click', function() {
const id = this.getAttribute('data-id');
const title = this.getAttribute('data-title');
const status = this.getAttribute('data-status');
const end = this.getAttribute('data-end');
document.getElementById('modalElectionId').value = id;
document.getElementById('modalElectionTitle').innerText = 'Manage: ' + title;
document.getElementById('modalElectionStatus').value = status;
if (end) {
const date = new Date(end);
const offset = date.getTimezoneOffset() * 60000;
const localISODate = new Date(date.getTime() - offset).toISOString().slice(0, 16);
document.getElementById('modalElectionEndTime').value = localISODate;
}
document.getElementById('manageElectionModal').style.display = 'flex';
});
});
function closeModal(modalId) {
if (modalId) {
document.getElementById(modalId).style.display = 'none';
} else {
document.getElementById('manageElectionModal').style.display = 'none';
document.getElementById('createElectionModal').style.display = 'none';
}
}
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>

View File

@ -8,15 +8,10 @@ define('DB_PASS', 'c217529c-a428-4a97-8f31-773c420377a7');
function db() { function db() {
static $pdo; static $pdo;
if (!$pdo) { if (!$pdo) {
// Local MariaDB/MySQL
try {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [ $pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]); ]);
} catch (PDOException $mysqlException) {
die("Connection failed: " . $mysqlException->getMessage());
}
} }
return $pdo; return $pdo;
} }

View File

@ -1,106 +0,0 @@
-- Clean slate for development
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS audit_logs;
DROP TABLE IF EXISTS votes;
DROP TABLE IF EXISTS candidates;
DROP TABLE IF EXISTS positions;
DROP TABLE IF EXISTS election_assignments;
DROP TABLE IF EXISTS elections;
DROP TABLE IF EXISTS users;
SET FOREIGN_KEY_CHECKS = 1;
-- Production-Ready Schema for Online Election System
CREATE TABLE users (
id CHAR(36) PRIMARY KEY,
student_id VARCHAR(10) UNIQUE NOT NULL, -- Format: XX-XXXX
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
grade_level INT NULL,
track VARCHAR(100) NULL,
section VARCHAR(100) NULL,
role ENUM('Admin', 'Adviser', 'Officer', 'Voter') DEFAULT 'Voter',
access_level INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL
);
CREATE TABLE elections (
id CHAR(36) PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
status ENUM('Preparing', 'Ongoing', 'Finished') DEFAULT 'Preparing',
start_date_and_time TIMESTAMP NOT NULL,
end_date_and_time TIMESTAMP NOT NULL,
created_by CHAR(36) NOT NULL,
archived BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id)
);
CREATE TABLE election_assignments (
id CHAR(36) PRIMARY KEY,
election_id CHAR(36) NOT NULL,
user_id CHAR(36) NOT NULL,
role_in_election ENUM('Adviser', 'Officer', 'Candidate', 'Voter') DEFAULT 'Voter',
assigned_by CHAR(36) NOT NULL,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (election_id) REFERENCES elections(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (assigned_by) REFERENCES users(id)
);
CREATE TABLE positions (
id CHAR(36) PRIMARY KEY,
election_id CHAR(36) NOT NULL,
name VARCHAR(255) NOT NULL,
max_votes INT DEFAULT 1,
sort_order INT DEFAULT 0,
FOREIGN KEY (election_id) REFERENCES elections(id) ON DELETE CASCADE
);
CREATE TABLE candidates (
id CHAR(36) PRIMARY KEY,
election_id CHAR(36) NOT NULL,
position_id CHAR(36) NOT NULL,
user_id CHAR(36) NOT NULL,
party_name VARCHAR(255) NULL,
manifesto TEXT NULL,
approved BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (election_id) REFERENCES elections(id) ON DELETE CASCADE,
FOREIGN KEY (position_id) REFERENCES positions(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE votes (
id CHAR(36) PRIMARY KEY,
election_id CHAR(36) NOT NULL,
position_id CHAR(36) NOT NULL,
candidate_id CHAR(36) NOT NULL,
voter_id CHAR(36) NOT NULL,
casted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent TEXT,
UNIQUE KEY unique_vote (election_id, position_id, voter_id),
FOREIGN KEY (election_id) REFERENCES elections(id),
FOREIGN KEY (position_id) REFERENCES positions(id),
FOREIGN KEY (candidate_id) REFERENCES candidates(id),
FOREIGN KEY (voter_id) REFERENCES users(id)
);
CREATE TABLE audit_logs (
id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NULL,
action VARCHAR(255) NOT NULL,
table_name VARCHAR(100) NULL,
record_id CHAR(36) NULL,
old_values TEXT NULL,
new_values TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Insert a default admin (password is 'Testing')
INSERT INTO users (id, student_id, name, email, password_hash, role, access_level)
VALUES ('admin-uuid-1', '00-0000', 'Admin User', 'Admin@iloilonhs.edu.ph', '$2y$10$W70K9blIfzVSYbr/sEQUte3eyUejciAHmpubscltUNZbmpkPrF71K', 'Admin', 4);

View File

@ -1,47 +0,0 @@
-- Sample Election Data
INSERT INTO elections (id, title, description, status, start_date_and_time, end_date_and_time, created_by)
VALUES (
'sample-election-uuid',
'School Year 2028 Election',
'General student council elections for the upcoming school year.',
'Ongoing',
'2026-02-11 08:00:00',
'2026-02-18 17:00:00',
'admin-uuid-1'
);
-- Sample Voters (to match the "8" in the screenshot)
INSERT INTO users (id, student_id, name, email, password_hash, role) VALUES
('voter-1', '21-0001', 'John Doe', 'john@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-2', '21-0002', 'Jane Smith', 'jane@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-3', '21-0003', 'Bob Johnson', 'bob@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-4', '21-0004', 'Alice Brown', 'alice@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-5', '21-0005', 'Charlie Davis', 'charlie@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-6', '21-0006', 'Eve Wilson', 'eve@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-7', '21-0007', 'Frank Miller', 'frank@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter'),
('voter-8', '21-0008', 'Grace Lee', 'grace@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter');
-- Sample Candidates (to match the "15" in the screenshot)
-- We'll need some positions first
INSERT INTO positions (id, election_id, name, max_votes, sort_order) VALUES
('pos-1', 'sample-election-uuid', 'President', 1, 1),
('pos-2', 'sample-election-uuid', 'Vice President', 1, 2);
-- Insert 15 candidates (reusing voters for simplicity in this mockup)
INSERT INTO candidates (id, election_id, position_id, user_id, party_name, approved) VALUES
('cand-1', 'sample-election-uuid', 'pos-1', 'voter-1', 'Unity Party', 1),
('cand-2', 'sample-election-uuid', 'pos-1', 'voter-2', 'Progress Party', 1),
('cand-3', 'sample-election-uuid', 'pos-2', 'voter-3', 'Unity Party', 1),
('cand-4', 'sample-election-uuid', 'pos-2', 'voter-4', 'Progress Party', 1),
('cand-5', 'sample-election-uuid', 'pos-1', 'voter-5', 'Independent', 1),
('cand-6', 'sample-election-uuid', 'pos-2', 'voter-6', 'Independent', 1),
('cand-7', 'sample-election-uuid', 'pos-1', 'voter-7', 'Students First', 1),
('cand-8', 'sample-election-uuid', 'pos-2', 'voter-8', 'Students First', 1),
('cand-9', 'sample-election-uuid', 'pos-1', 'admin-uuid-1', 'Faculty Choice', 1),
('cand-10', 'sample-election-uuid', 'pos-2', 'admin-uuid-1', 'Faculty Choice', 1),
-- Adding more to reach 15
('cand-11', 'sample-election-uuid', 'pos-1', 'voter-1', 'Extra 1', 1),
('cand-12', 'sample-election-uuid', 'pos-2', 'voter-2', 'Extra 2', 1),
('cand-13', 'sample-election-uuid', 'pos-1', 'voter-3', 'Extra 3', 1),
('cand-14', 'sample-election-uuid', 'pos-2', 'voter-4', 'Extra 4', 1),
('cand-15', 'sample-election-uuid', 'pos-1', 'voter-5', 'Extra 5', 1);

View File

@ -1,39 +0,0 @@
-- Update sample voters with grade level, track, and section
UPDATE users SET grade_level = 11, track = 'STEM', section = 'A' WHERE id = 'voter-1';
UPDATE users SET grade_level = 11, track = 'STEM', section = 'B' WHERE id = 'voter-2';
UPDATE users SET grade_level = 11, track = 'ABM', section = 'C' WHERE id = 'voter-3';
UPDATE users SET grade_level = 12, track = 'ABM', section = 'D' WHERE id = 'voter-4';
UPDATE users SET grade_level = 12, track = 'HUMSS', section = 'E' WHERE id = 'voter-5';
UPDATE users SET grade_level = 12, track = 'HUMSS', section = 'F' WHERE id = 'voter-6';
UPDATE users SET grade_level = 11, track = 'GAS', section = 'G' WHERE id = 'voter-7';
UPDATE users SET grade_level = 12, track = 'TVL', section = 'H' WHERE id = 'voter-8';
-- Insert some dummy votes so the charts aren't empty
-- We need to find the position and candidate IDs
-- President candidates: cand-1, cand-2, cand-5, cand-7, cand-9, cand-11, cand-13, cand-15
-- Vice President candidates: cand-3, cand-4, cand-6, cand-8, cand-10, cand-12, cand-14
-- voter-1 votes for cand-1 (President)
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-1', 'voter-1', '2026-02-11 10:00:00');
-- voter-2 votes for cand-2
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-2', 'voter-2', '2026-02-12 11:00:00');
-- voter-3 votes for cand-1
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-1', 'voter-3', '2026-02-13 09:00:00');
-- voter-4 votes for cand-5
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-5', 'voter-4', '2026-02-14 10:00:00');
-- voter-5 votes for cand-7
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-7', 'voter-5', '2026-02-15 11:00:00');
-- voter-6 votes for cand-1
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-1', 'voter-6', '2026-02-15 12:00:00');
-- voter-7 votes for cand-9
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-9', 'voter-7', '2026-02-15 13:00:00');
-- voter-8 votes for cand-11
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at)
VALUES (REPLACE(UUID(), '-', ''), 'sample-election-uuid', 'pos-1', 'cand-11', 'voter-8', '2026-02-15 14:00:00');

View File

@ -1,22 +0,0 @@
-- Past Elections for History
INSERT INTO elections (id, title, description, status, start_date_and_time, end_date_and_time, created_by) VALUES
('hist-uuid-1', 'School Year 2027-2028 Election', 'Previous year elections.', 'Finished', '2027-02-10 08:00:00', '2027-02-11 17:00:00', 'admin-uuid-1'),
('hist-uuid-2', 'School Year 2026-2027 Election', 'Elections from 2 years ago.', 'Finished', '2026-02-10 08:00:00', '2026-02-11 17:00:00', 'admin-uuid-1'),
('hist-uuid-3', 'School Year 2022-2023 Election', 'Older elections.', 'Finished', '2022-09-01 08:00:00', '2022-09-02 17:00:00', 'admin-uuid-1'),
('hist-uuid-4', 'School Year 2020-2021 Election', 'Archived elections.', 'Finished', '2020-09-01 08:00:00', '2020-09-02 17:00:00', 'admin-uuid-1');
-- Positions for 2027-2028
INSERT INTO positions (id, election_id, name, max_votes, sort_order) VALUES
('pos-hist-1', 'hist-uuid-1', 'President', 1, 1),
('pos-hist-2', 'hist-uuid-1', 'Vice President', 1, 2);
-- Candidates for 2027-2028
INSERT INTO candidates (id, election_id, position_id, user_id, party_name, approved) VALUES
('cand-hist-1', 'hist-uuid-1', 'pos-hist-1', 'voter-1', 'Unity Party', 1),
('cand-hist-2', 'hist-uuid-1', 'pos-hist-1', 'voter-2', 'Progress Party', 1);
-- Votes for 2027-2028
INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, casted_at) VALUES
(REPLACE(UUID(), '-', ''), 'hist-uuid-1', 'pos-hist-1', 'cand-hist-1', 'voter-1', '2027-02-10 09:00:00'),
(REPLACE(UUID(), '-', ''), 'hist-uuid-1', 'pos-hist-1', 'cand-hist-1', 'voter-2', '2027-02-10 10:00:00'),
(REPLACE(UUID(), '-', ''), 'hist-uuid-1', 'pos-hist-1', 'cand-hist-2', 'voter-3', '2027-02-10 11:00:00');

View File

@ -1,10 +0,0 @@
-- Migration 005: Additional Voter Data for Management View
INSERT INTO users (id, student_id, name, email, password_hash, grade_level, track, section, role) VALUES
(UUID(), '28-0001', 'John Doe', 'john.doe@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'STEM', 'A', 'Voter'),
(UUID(), '28-0002', 'Jane Smith', 'jane.smith@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'ABM', 'A', 'Voter'),
(UUID(), '28-0003', 'Bob Wilson', 'bob.wilson@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'GAS', 'A', 'Voter'),
(UUID(), '28-0004', 'Alice Brown', 'alice.brown@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'HE', 'F', 'Voter'),
(UUID(), '28-0005', 'Charlie Davis', 'charlie.davis@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'HUMSS', 'A', 'Voter'),
(UUID(), '28-0006', 'Diana Prince', 'diana.prince@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'ICT', 'A', 'Voter'),
(UUID(), '28-0007', 'Edward Norton', 'edward.norton@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'STEM', 'A', 'Voter'),
(UUID(), '28-0008', 'Fiona Gallagher', 'fiona.gallagher@shs.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 12, 'ICT', 'A', 'Voter');

View File

@ -1,49 +0,0 @@
-- Refined 006
SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM candidates;
DELETE FROM positions;
SET FOREIGN_KEY_CHECKS = 1;
INSERT INTO positions (id, election_id, name, max_votes, sort_order) VALUES
('pos-gov', 'sample-election-uuid', 'Governor', 1, 1),
('pos-vgov', 'sample-election-uuid', 'Vice Governor', 1, 2),
('pos-sec', 'sample-election-uuid', 'Secretary', 1, 3),
('pos-pio', 'sample-election-uuid', 'PIO', 1, 4),
('pos-bm', 'sample-election-uuid', 'Board Member', 4, 5);
-- Using different student IDs to avoid conflicts with 005
INSERT INTO users (id, student_id, name, email, password_hash, role, grade_level, track, section)
VALUES
('cand-1-uuid', '29-7832', 'Kurt Leovince Tse Wing', 'kurtleovince06@gmail.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ICT', 'A'),
('cand-2-uuid', '29-0002', 'Noah Padilla', 'noah.p@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ICT', 'A'),
('cand-3-uuid', '29-0003', 'Liam Garcia', 'liam.g@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'STEM', 'B'),
('cand-4-uuid', '29-0004', 'Emma Wilson', 'emma.w@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ABM', 'C'),
('cand-5-uuid', '29-0005', 'Olivia Martinez', 'olivia.m@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'HUMSS', 'D'),
('cand-6-uuid', '29-0006', 'James Brown', 'james.b@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ICT', 'B'),
('cand-7-uuid', '29-0007', 'Sophia Davis', 'sophia.d@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'STEM', 'A'),
('cand-8-uuid', '29-0008', 'Mason Rodriguez', 'mason.r@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ABM', 'A'),
('cand-9-uuid', '29-0009', 'Isabella Lopez', 'isabella.l@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'HUMSS', 'A'),
('cand-10-uuid', '29-0010', 'Ethan Wilson', 'ethan.w@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ICT', 'C'),
('cand-11-uuid', '29-0011', 'Ava Moore', 'ava.m@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'STEM', 'C'),
('cand-12-uuid', '29-0012', 'Lucas Taylor', 'lucas.t@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ABM', 'B'),
('cand-13-uuid', '29-0013', 'Mia Anderson', 'mia.a@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'HUMSS', 'B'),
('cand-14-uuid', '29-0014', 'Alexander Thomas', 'alex.t@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'ICT', 'D'),
('cand-15-uuid', '29-0015', 'Charlotte Jackson', 'charlotte.j@example.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Voter', 12, 'STEM', 'D')
ON DUPLICATE KEY UPDATE name=VALUES(name);
INSERT INTO candidates (id, election_id, position_id, user_id, party_name, approved) VALUES
(UUID(), 'sample-election-uuid', 'pos-bm', 'cand-1-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-bm', 'cand-2-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-bm', 'cand-3-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-gov', 'cand-4-uuid', 'Uswag', 1),
(UUID(), 'sample-election-uuid', 'pos-gov', 'cand-5-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-gov', 'cand-6-uuid', 'Uswag', 1),
(UUID(), 'sample-election-uuid', 'pos-vgov', 'cand-7-uuid', 'Uswag', 1),
(UUID(), 'sample-election-uuid', 'pos-vgov', 'cand-8-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-vgov', 'cand-9-uuid', 'Uswag', 1),
(UUID(), 'sample-election-uuid', 'pos-sec', 'cand-10-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-sec', 'cand-11-uuid', 'Uswag', 1),
(UUID(), 'sample-election-uuid', 'pos-pio', 'cand-12-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-pio', 'cand-13-uuid', 'Uswag', 1),
(UUID(), 'sample-election-uuid', 'pos-sec', 'cand-14-uuid', 'Maligaya', 1),
(UUID(), 'sample-election-uuid', 'pos-pio', 'cand-15-uuid', 'Uswag', 1);

View File

@ -1,6 +0,0 @@
-- Add Sample Officers for Management View
INSERT INTO users (id, student_id, name, email, password_hash, role, access_level)
VALUES
('officer-uuid-1', '23-5443', 'Jay Orly Mil Santiago', 'jay44296@gmail.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Adviser', 3),
('officer-uuid-2', '23-1111', 'Ma. Elena Santos', 'elena.santos@school.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Officer', 2),
('officer-uuid-3', '23-2222', 'Robert Chen', 'robert.chen@school.edu', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Officer', 2);

View File

@ -1,23 +0,0 @@
-- Add details column to audit_logs for human-readable descriptions
ALTER TABLE audit_logs ADD COLUMN details TEXT AFTER action;
-- Clear existing logs for a fresh start in development
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE audit_logs;
SET FOREIGN_KEY_CHECKS = 1;
-- Insert sample audit data matching the UI design
INSERT INTO audit_logs (id, user_id, action, details, created_at) VALUES
('a1', 'admin-uuid-1', 'Login', 'Successful login to the system', '2026-02-15 18:22:30'),
('a2', 'admin-uuid-1', 'Logout', 'User logged out of the system', '2026-02-11 04:09:53'),
('a3', 'admin-uuid-1', 'Update Election Status', 'Changed SY 2028 Election status from Preparing to Ongoing', '2026-02-11 04:09:43'),
('a4', 'admin-uuid-1', 'Remove Position', 'Removed position: Position_Name', '2026-02-11 04:08:34'),
('a5', 'admin-uuid-1', 'Add Position', 'Added new position: Position_Name (Uniform)', '2026-02-11 04:08:30'),
('a6', 'admin-uuid-1', 'Add Voter', 'Registered new voter: jay44296@gmail.com for Election ID: 6. User ID: 12-3456', '2026-02-11 04:07:57'),
('a7', 'admin-uuid-1', 'Login', 'Successful login to the system', '2026-02-11 04:03:00'),
('a8', 'admin-uuid-1', 'Logout', 'User logged out of the system', '2026-02-11 04:02:25'),
('a9', 'admin-uuid-1', 'Delete Voter', 'Deleted voter: jay44296@gmail.com (ID: 12-3456)', '2026-02-11 03:58:30'),
('a10', 'admin-uuid-1', 'Login', 'Successful login to the system', '2026-02-11 03:53:37'),
('a11', 'admin-uuid-1', 'Logout', 'User logged out of the system', '2026-02-11 03:49:56'),
('a12', 'admin-uuid-1', 'Add Candidate', 'Added candidate Vanessa Ortega for Board Member. ID: 25-7916', '2026-02-11 03:49:30'),
('a13', 'admin-uuid-1', 'Add Candidate', 'Added candidate Noah Padilla for Board Member. ID: 77-4683', '2026-02-11 03:49:10');

View File

@ -1,32 +0,0 @@
-- Migration to support multi-election and enhanced candidate management
SET FOREIGN_KEY_CHECKS = 0;
-- Create parties table for definition
CREATE TABLE IF NOT EXISTS parties (
id CHAR(36) PRIMARY KEY,
election_id CHAR(36) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
logo_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (election_id) REFERENCES elections(id) ON DELETE CASCADE
);
-- Add election_id to audit_logs if not exists
-- Check if column exists is not directly possible in standard SQL without procedural, but we can try to add it.
-- Since this is a fresh migration for polishing, we assume it's okay.
ALTER TABLE audit_logs ADD COLUMN election_id CHAR(36) NULL;
ALTER TABLE audit_logs ADD CONSTRAINT fk_audit_election FOREIGN KEY (election_id) REFERENCES elections(id) ON DELETE CASCADE;
-- Ensure election_assignments is used correctly
-- We don't need to change the schema here, but we will update the logic.
-- Add some sample parties for the existing elections
INSERT INTO parties (id, election_id, name, description)
SELECT UUID(), id, 'PROGRESSIVE PARTY', 'Committed to innovation and change.' FROM elections;
INSERT INTO parties (id, election_id, name, description)
SELECT UUID(), id, 'UNITY ALLIANCE', 'Together for a better future.' FROM elections;
INSERT INTO parties (id, election_id, name, description)
SELECT UUID(), id, 'YOUTH VOICE', 'Empowering the next generation.' FROM elections;
SET FOREIGN_KEY_CHECKS = 1;

View File

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

View File

@ -1,137 +0,0 @@
<?php
require_once __DIR__ . '/config.php';
function generate_uuid() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
$pdo = db();
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
echo "Connected using driver: $driver\n";
try {
$pdo->beginTransaction();
echo "Clearing existing data (preserving Admin)...\n";
// Order matters for foreign keys
$pdo->exec("DELETE FROM audit_logs");
$pdo->exec("DELETE FROM votes");
$pdo->exec("DELETE FROM candidates");
$pdo->exec("DELETE FROM positions");
$pdo->exec("DELETE FROM election_assignments");
$pdo->exec("DELETE FROM elections");
$pdo->exec("DELETE FROM users WHERE role != 'Admin'");
// Ensure admin exists
$admin = $pdo->query("SELECT id FROM users WHERE role = 'Admin' LIMIT 1")->fetch();
if (!$admin) {
$adminId = generate_uuid();
$stmt = $pdo->prepare("INSERT INTO users (id, student_id, name, email, password_hash, role, access_level) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$adminId, '00-0000', 'Admin User', 'Admin@iloilonhs.edu.ph', password_hash('Testing', PASSWORD_DEFAULT), 'Admin', 4]);
echo "Created default admin.\n";
} else {
$adminId = $admin['id'];
echo "Preserved existing admin: " . $adminId . "\n";
}
echo "Adding mock elections and positions...\n";
$electionId = generate_uuid();
$stmt = $pdo->prepare("INSERT INTO elections (id, title, description, status, start_date_and_time, end_date_and_time, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
$electionId,
'SSG General Elections 2026',
'Annual election for Supreme Student Government.',
'Ongoing',
date('Y-m-d H:i:s', strtotime('-1 day')),
date('Y-m-d H:i:s', strtotime('+7 days')),
$adminId
]);
$positions = [
['President', 1, 1],
['Vice President', 1, 2],
['Secretary', 1, 3],
['Treasurer', 1, 4]
];
$posIds = [];
$stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, max_votes, sort_order) VALUES (?, ?, ?, ?, ?)");
foreach ($positions as $p) {
$id = generate_uuid();
$stmt->execute([$id, $electionId, $p[0], $p[1], $p[2]]);
$posIds[$p[0]] = $id;
}
echo "Adding mock students (voters)...\n";
$tracks = ['STEM', 'ABM', 'HUMSS', 'GAS', 'TVL'];
$sections = ['A', 'B', 'C', 'D'];
$voters = [];
$stmt = $pdo->prepare("INSERT INTO users (id, student_id, name, email, password_hash, grade_level, track, section, role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$assignStmt = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, ?, ?)");
for ($i = 1; $i <= 40; $i++) {
$id = generate_uuid();
$studentId = sprintf('26-%04d', $i);
$track = $tracks[array_rand($tracks)];
$grade = rand(11, 12);
$section = $sections[array_rand($sections)];
$stmt->execute([
$id,
$studentId,
"Student $i",
"student$i@iloilonhs.edu.ph",
password_hash('password', PASSWORD_DEFAULT),
$grade,
$track,
$section,
'Voter'
]);
$assignStmt->execute([generate_uuid(), $electionId, $id, 'Voter', $adminId]);
$voters[] = ['id' => $id, 'grade' => $grade, 'track' => $track];
}
echo "Adding candidates...\n";
$candidateIds = [];
foreach ($posIds as $posName => $posId) {
for ($c = 1; $c <= 2; $c++) {
$voter = array_shift($voters);
$userId = $voter['id'];
$candId = generate_uuid();
$candStmt = $pdo->prepare("INSERT INTO candidates (id, election_id, position_id, user_id, party_name, approved) VALUES (?, ?, ?, ?, ?, ?)");
$candStmt->execute([$candId, $electionId, $posId, $userId, ($c == 1 ? 'Alpha Party' : 'Beta Party'), true]);
$candidateIds[$posId][] = $candId;
$updStmt = $pdo->prepare("UPDATE election_assignments SET role_in_election = 'Candidate' WHERE election_id = ? AND user_id = ?");
$updStmt->execute([$electionId, $userId]);
}
}
echo "Generating mock votes...\n";
$voteStmt = $pdo->prepare("INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id) VALUES (?, ?, ?, ?, ?)");
foreach ($voters as $v) {
// 85% turnout
if (rand(1, 100) <= 85) {
foreach ($posIds as $posId) {
$candId = $candidateIds[$posId][array_rand($candidateIds[$posId])];
$voteStmt->execute([generate_uuid(), $electionId, $posId, $candId, $v['id']]);
}
}
}
$pdo->commit();
echo "Done! Mock data successfully generated.\n";
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
echo "FATAL ERROR: " . $e->getMessage() . "\n";
exit(1);
}

View File

@ -1,326 +0,0 @@
<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
// Fetch all elections for the history
$elections = $pdo->query("SELECT * FROM elections WHERE archived = FALSE ORDER BY start_date_and_time DESC")->fetchAll();
// Extract years for the "Jump to School Year" dropdown
$years = [];
foreach ($elections as $e) {
$year = date('Y', strtotime($e['start_date_and_time']));
$years[$year] = $year;
}
krsort($years);
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System for Senior High School';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Election History | <?= htmlspecialchars($projectDescription) ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
.history-controls {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24px;
}
.history-title-area h1 {
margin: 0 0 4px 0;
font-size: 1.5rem;
color: #1e293b;
}
.history-title-area p {
margin: 0;
color: #64748b;
font-size: 0.875rem;
}
.year-selector {
padding: 8px 12px;
border-radius: 8px;
border: 1px solid var(--border-color);
background: white;
color: #4b5563;
font-size: 0.875rem;
outline: none;
}
.election-history-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.election-item {
background: white;
border: 1px solid var(--border-color);
border-radius: 12px;
overflow: hidden;
transition: all 0.2s;
}
.election-item-header {
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.election-item-header:hover {
background: #f8fafc;
}
.election-item-title {
font-weight: 600;
color: #2563eb;
font-size: 1rem;
}
.election-item-right {
display: flex;
align-items: center;
gap: 16px;
}
.status-badge {
padding: 4px 12px;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
}
.status-ongoing { background: #fffbeb; color: #d97706; }
.status-finished { background: #ecfdf5; color: #10b981; }
.election-item-body {
display: none;
padding: 24px;
border-top: 1px solid var(--border-color);
background: #fafafa;
}
.election-item.active .election-item-body {
display: block;
}
.election-item.active .chevron-icon {
transform: rotate(180deg);
}
.details-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.detail-card {
background: white;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
}
.detail-label {
font-size: 0.7rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
margin-bottom: 12px;
}
.detail-value {
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
}
.detail-sub {
font-size: 0.875rem;
color: #1e293b;
font-weight: 500;
}
.results-section {
background: white;
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
}
.results-title {
padding: 16px 20px;
font-weight: 700;
font-size: 0.9rem;
color: #1e293b;
border-bottom: 1px solid var(--border-color);
}
.results-table {
width: 100%;
border-collapse: collapse;
}
.results-table th {
background: #f9fafb;
padding: 10px 20px;
text-align: left;
font-size: 0.7rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
}
.results-table td {
padding: 12px 20px;
border-bottom: 1px solid var(--border-color);
font-size: 0.875rem;
color: #4b5563;
}
</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="history-controls">
<div class="history-title-area">
<h1>Election History</h1>
<p>Voter turnout and candidate results per election year</p>
</div>
<select class="year-selector" onchange="jumpToYear(this.value)">
<option value="">Jump to School Year</option>
<?php foreach ($years as $y): ?>
<option value="year-<?= $y ?>">SY <?= $y ?>-<?= $y+1 ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="election-history-list animate-stagger">
<?php foreach ($elections as $election):
$electionYear = date('Y', strtotime($election['start_date_and_time']));
$voterCount = $pdo->prepare("SELECT COUNT(DISTINCT voter_id) FROM votes WHERE election_id = ?");
$voterCount->execute([$election['id']]);
$totalVoters = $voterCount->fetchColumn();
$results = $pdo->prepare("
SELECT c.*, u.name as candidate_name, p.name as position_name,
(SELECT COUNT(*) FROM votes v WHERE v.candidate_id = c.id) as vote_count
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 = ?
ORDER BY p.sort_order, vote_count DESC
");
$results->execute([$election['id']]);
$candidates = $results->fetchAll();
?>
<div class="election-item" id="year-<?= $electionYear ?>">
<div class="election-item-header" onclick="toggleAccordion(this)">
<div class="election-item-title"><?= htmlspecialchars($election['title']) ?></div>
<div class="election-item-right">
<a href="view_results.php?id=<?= $election['id'] ?>" class="btn-generate" onclick="event.stopPropagation()" style="background: #4f46e5; color: white; padding: 6px 12px; border-radius: 6px; font-size: 0.75rem; font-weight: 600; text-decoration: none; display: flex; align-items: center; gap: 6px;">
<i data-lucide="file-text" style="width: 14px;"></i>
GENERATE RESULTS
</a>
<span class="status-badge status-<?= strtolower($election['status']) ?>">
<?= htmlspecialchars($election['status']) ?>
</span>
<i data-lucide="chevron-down" class="chevron-icon" style="width: 18px; color: #2563eb;"></i>
</div>
</div>
<div class="election-item-body">
<div class="details-grid">
<div class="detail-card">
<div class="detail-label">Total Voters</div>
<div class="detail-value"><?= number_format($totalVoters) ?></div>
</div>
<div class="detail-card">
<div class="detail-label">Election Period</div>
<div class="detail-sub">
<?= date('M d, Y', strtotime($election['start_date_and_time'])) ?> to
<?= date('M d, Y', strtotime($election['end_date_and_time'])) ?>
</div>
</div>
</div>
<div class="results-section">
<div class="results-title">Candidate Results</div>
<table class="results-table">
<thead>
<tr>
<th>Candidate Name</th>
<th>Position</th>
<th>Party</th>
<th>Votes</th>
</tr>
</thead>
<tbody>
<?php if (empty($candidates)): ?>
<tr>
<td colspan="4" style="text-align: center; padding: 20px; color: #94a3b8;">
No candidate results available for this election.
</td>
</tr>
<?php else: ?>
<?php foreach ($candidates as $cand): ?>
<tr>
<td style="font-weight: 500; color: #1e293b;"><?= htmlspecialchars($cand['candidate_name']) ?></td>
<td><?= htmlspecialchars($cand['position_name']) ?></td>
<td><?= htmlspecialchars($cand['party_name'] ?? 'Independent') ?></td>
<td style="font-weight: 600; color: #2563eb;"><?= number_format($cand['vote_count']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</main>
</div>
<script>
lucide.createIcons();
function toggleAccordion(header) {
const item = header.parentElement;
const isActive = item.classList.contains('active');
if (isActive) {
item.classList.remove('active');
} else {
item.classList.add('active');
}
}
function jumpToYear(id) {
if (!id) return;
const el = document.getElementById(id);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
el.classList.add('active');
}
}
lucide.createIcons();
</script>
</body>
</html>

View File

@ -1,29 +0,0 @@
<?php
require_once __DIR__ . '/db/config.php';
$regions = [
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2',
'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3',
'sa-east-1', 'ca-central-1', 'me-south-1', 'af-south-1'
];
$pass = 'gA82h8K80T5QUAwi';
$projectRef = 'siqeqnizegizxemrfgkf';
foreach ($regions as $region) {
$host = "aws-0-$region.pooler.supabase.com";
$user = "postgres.$projectRef";
echo "Testing $region ($host)... ";
try {
$dsn = "pgsql:host=$host;port=6543;dbname=postgres;connect_timeout=5";
$pdo = new PDO($dsn, $user, $pass);
echo "SUCCESS!\n";
exit(0);
} catch (Exception $e) {
if (strpos($e->getMessage(), 'Tenant or user not found') !== false) {
echo "Not here.\n";
} else {
echo "Error: " . $e->getMessage() . "\n";
}
}
}

View File

@ -1,69 +0,0 @@
<?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;
}
}

View File

@ -1,16 +0,0 @@
<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

@ -1,25 +0,0 @@
<?php
function pexels_key() {
$k = getenv('PEXELS_KEY');
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
}
function pexels_get($url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
CURLOPT_TIMEOUT => 15,
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
return null;
}
function download_to($srcUrl, $destPath) {
$data = @file_get_contents($srcUrl);
if ($data === false) return false;
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
return file_put_contents($destPath, $data) !== false;
}

View File

@ -1,64 +0,0 @@
<?php
$activeElectionId = get_active_election_id();
$allElections = get_all_elections();
$currentElection = get_active_election();
$currentPage = basename($_SERVER['PHP_SELF']);
?>
<aside class="sidebar">
<div class="sidebar-header">
<div class="sidebar-brand">CLICK TO VOTE</div>
<div class="sidebar-subtitle">Administrator Portal</div>
</div>
<div class="election-selector-container" style="padding: 0 20px; margin-bottom: 20px;">
<label style="font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; margin-bottom: 8px; display: block;">ACTIVE ELECTION</label>
<select onchange="window.location.href='?set_election_id=' + this.value" style="width: 100%; padding: 8px; border-radius: 6px; border: 1px solid #e2e8f0; background: #f8fafc; font-size: 12px; font-weight: 500; color: #1e293b; cursor: pointer;">
<?php foreach ($allElections as $e): ?>
<option value="<?= $e['id'] ?>" <?= $activeElectionId === $e['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($e['title']) ?>
</option>
<?php endforeach; ?>
<?php if (empty($allElections)): ?>
<option disabled>No elections found</option>
<?php endif; ?>
</select>
</div>
<nav class="sidebar-nav">
<a href="dashboard.php" class="nav-item <?= $currentPage === 'dashboard.php' ? 'active' : '' ?>">
<i data-lucide="layout-dashboard"></i>
Election Dashboard
</a>
<a href="election_history.php" class="nav-item <?= $currentPage === 'election_history.php' ? 'active' : '' ?>">
<i data-lucide="history"></i>
Election History
</a>
<a href="voter_management.php" class="nav-item <?= $currentPage === 'voter_management.php' ? 'active' : '' ?>">
<i data-lucide="users"></i>
Voter Management
</a>
<a href="candidate_management.php" class="nav-item <?= $currentPage === 'candidate_management.php' ? 'active' : '' ?>">
<i data-lucide="user-square-2"></i>
Candidate Management
</a>
<a href="officers_management.php" class="nav-item <?= $currentPage === 'officers_management.php' ? 'active' : '' ?>">
<i data-lucide="shield-check"></i>
Officers Management
</a>
<a href="reports_audit.php" class="nav-item <?= $currentPage === 'reports_audit.php' ? 'active' : '' ?>">
<i data-lucide="file-text"></i>
Reports & Audit
</a>
<a href="settings.php" class="nav-item <?= $currentPage === 'settings.php' ? 'active' : '' ?>">
<i data-lucide="settings"></i>
Settings
</a>
</nav>
<div class="sidebar-footer">
<a href="logout.php" class="nav-item" style="color: #ef4444;">
<i data-lucide="log-out"></i>
Logout
</a>
</div>
</aside>

335
index.php
View File

@ -1,233 +1,150 @@
<?php <?php
require_once 'auth_helper.php'; declare(strict_types=1);
$user = get_user(); @ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
if (!$user) { $phpVersion = PHP_VERSION;
include 'landing.php'; $now = date('Y-m-d H:i:s');
exit;
}
if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])) {
include 'dashboard.php';
exit;
}
$pdo = db();
// Voter redirection logic
if ($user['role'] === 'Voter') {
// Find ongoing elections that this voter is assigned to
$stmt = $pdo->prepare("
SELECT e.* FROM elections e
JOIN election_assignments ea ON e.id = ea.election_id
WHERE ea.user_id = ?
AND e.status = 'Ongoing'
AND e.archived = FALSE
AND e.end_date_and_time > CURRENT_TIMESTAMP
");
$stmt->execute([$user['id']]);
$activeElections = $stmt->fetchAll();
// Filter out elections where the user has already voted
$votedElectionsStmt = $pdo->prepare("SELECT election_id FROM votes WHERE voter_id = ?");
$votedElectionsStmt->execute([$user['id']]);
$votedIds = $votedElectionsStmt->fetchAll(PDO::FETCH_COLUMN);
$eligibleElections = array_filter($activeElections, function($e) use ($votedIds) {
return !in_array($e['id'], $votedIds);
});
if (count($eligibleElections) === 1) {
$singleElection = reset($eligibleElections);
header("Location: ballot.php?id=" . $singleElection['id']);
exit;
}
// If no eligible elections but they were assigned to some active ones, they've already voted
if (count($eligibleElections) === 0 && count($activeElections) > 0) {
if (!isset($_GET['success']) && !isset($_GET['error'])) {
header("Location: index.php?error=already_voted");
exit;
}
}
// For voters, only show their assigned elections in the list
$elections = $activeElections;
} else {
$elections = $pdo->query("SELECT * FROM elections WHERE archived = FALSE ORDER BY created_at DESC")->fetchAll();
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System for Senior High School';
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Election Dashboard | <?= htmlspecialchars($projectDescription) ?></title> <title>New Style</title>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" /> <?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/style.css?v=<?= time() ?>">
</head>
<?php $isVotedModalActive = (isset($_GET['success']) && $_GET['success'] === 'voted') || (isset($_GET['error']) && $_GET['error'] === 'already_voted'); ?>
<body class="<?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<nav class="navbar" <?= $isVotedModalActive ? 'style="display: none;"' : '' ?>>
<a href="index.php" class="brand">Click to Vote</a>
<div>
<span style="margin-right: 1rem; color: var(--text-muted);"><?= htmlspecialchars($user['name']) ?> (<?= $user['role'] ?>)</span>
<a href="logout.php" class="btn btn-outline">Logout</a>
</div>
</nav>
<div class="container" <?= $isVotedModalActive ? 'style="display: none;"' : '' ?>>
<?php if (isset($_GET['success']) && $_GET['success'] !== 'voted'): ?>
<div style="background: #dcfce7; color: #166534; padding: 1rem; border-radius: var(--radius); border: 1px solid #bbf7d0; margin-bottom: 1.5rem; font-size: 0.875rem;">
Action completed successfully.
</div>
<?php endif; ?>
<div class="header-actions">
<div>
<h1 style="margin: 0; font-size: 1.5rem;">Elections</h1>
<p style="margin: 0; color: var(--text-muted);">Manage your school elections and voting sessions.</p>
</div>
<?php if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])): ?>
<a href="create_election.php" class="btn btn-primary">+ New Election</a>
<?php endif; ?>
</div>
<div class="card">
<?php if (empty($elections)): ?>
<div style="text-align: center; padding: 2rem;">
<p style="color: var(--text-muted);">No elections found. Create your first election to get started.</p>
<?php if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])): ?>
<a href="create_election.php" class="btn btn-outline" style="margin-top: 1rem;">Setup Election</a>
<?php endif; ?>
</div>
<?php else: ?>
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Status</th>
<th>Period</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($elections as $election): ?>
<tr>
<td>
<div style="font-weight: 600;"><?= htmlspecialchars($election['title']) ?></div>
<div style="font-size: 0.75rem; color: var(--text-muted);"><?= htmlspecialchars($election['description']) ?></div>
</td>
<td>
<span class="badge badge-<?= strtolower($election['status']) ?>">
<?= htmlspecialchars($election['status']) ?>
</span>
</td>
<td>
<div style="font-size: 0.875rem;">
<?= date('M d, H:i', strtotime($election['start_date_and_time'])) ?> -
<?= date('M d, H:i', strtotime($election['end_date_and_time'])) ?>
</div>
</td>
<td>
<a href="view_election.php?id=<?= $election['id'] ?>" class="btn btn-outline" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;">View</a>
<?php if ($election['status'] === 'Ongoing'): ?>
<a href="ballot.php?id=<?= $election['id'] ?>" class="btn btn-primary" style="padding: 0.25rem 0.5rem; font-size: 0.75rem; background: #166534;">Vote</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<!-- Voted Modal -->
<div id="votedModal" class="modal-overlay <?= (isset($_GET['success']) && $_GET['success'] === 'voted') || (isset($_GET['error']) && $_GET['error'] === 'already_voted') ? 'active' : '' ?>">
<div class="confirm-modal" style="text-align: center;">
<div class="modal-icon" style="background: #f0fdf4; color: #10b981; margin: 0 auto 24px;">
<svg style="width: 32px; height: 32px;" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
</svg>
</div>
<h2>Already Voted</h2>
<p>You have already submitted your vote for this election. Thank you for participating!</p>
<div style="margin-top: 32px;">
<a href="logout.php" class="btn btn-primary" style="display: block; width: 100%; text-decoration: none; padding: 12px 0; background: #4f46e5; border-radius: 12px; font-weight: 700; color: white;">Back to Login</a>
</div>
</div>
</div>
<style> <style>
/* Modal Styles */ :root {
.modal-overlay { --bg-color-start: #6a11cb;
display: none; --bg-color-end: #2575fc;
position: fixed; --text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(4px);
z-index: 2000;
align-items: center;
justify-content: center;
padding: 20px;
}
.modal-overlay.active {
display: flex;
}
.confirm-modal {
background: white;
border-radius: 20px;
max-width: 450px;
width: 100%; width: 100%;
padding: 32px; height: 100%;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: modalIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); animation: bg-pan 20s linear infinite;
z-index: -1;
} }
@keyframes bg-pan {
@keyframes modalIn { 0% { background-position: 0% 0%; }
from { transform: scale(0.9) translateY(20px); opacity: 0; } 100% { background-position: 100% 100%; }
to { transform: scale(1) translateY(0); opacity: 1; }
} }
main {
.modal-icon { padding: 2rem;
width: 64px; }
height: 64px; .card {
background: #eef2ff; background: var(--card-bg-color);
color: #4f46e5; border: 1px solid var(--card-border-color);
border-radius: 16px; border-radius: 16px;
display: flex; padding: 2rem;
align-items: center; backdrop-filter: blur(20px);
justify-content: center; -webkit-backdrop-filter: blur(20px);
margin-bottom: 24px; box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
} }
.loader {
.confirm-modal h2 { margin: 1.25rem auto 1.25rem;
margin: 0 0 12px 0; width: 48px;
font-size: 1.5rem; height: 48px;
font-weight: 800; border: 3px solid rgba(255, 255, 255, 0.25);
color: #1e293b; border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
} }
@keyframes spin {
.confirm-modal p { from { transform: rotate(0deg); }
margin: 0 0 12px 0; to { transform: rotate(360deg); }
color: #64748b; }
font-size: 1rem; .hint {
line-height: 1.6; opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
} }
</style> </style>
</head>
<footer style="text-align: center; color: var(--text-muted); padding: 2rem; <?= $isVotedModalActive ? 'display: none;' : '' ?>"> <body>
&copy; <?= date('Y') ?> Click to Vote | High School Online Election System <main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer> </footer>
</body> </body>
</html> </html>

View File

@ -1,170 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Iloilo National High School | Election System</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;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/landing.css?v=<?= time() ?>">
</head>
<body class="landing-page" style="background-image: url('assets/images/background.jpg?v=<?= filemtime('assets/images/background.jpg') ?>');">
<div class="landing-container">
<div class="school-header">
<div class="school-logo">
<img src="assets/images/logo.png?v=<?= filemtime('assets/images/logo.png') ?>" alt="Logo" style="width: 100%; height: 100%; object-fit: contain;">
</div>
<div class="school-info">
<h1>Iloilo National High School</h1>
<p>Luna St., La Paz, Iloilo City</p>
</div>
</div>
<div class="info-card">
<div class="info-card-header">
<i>i</i> SYSTEM INFORMATION
</div>
<div class="info-card-body">
<h2>Click to Vote</h2>
<p>
This portal is currently a <strong>PROTOTYPE MODEL</strong> under active development to
demonstrate the digital election process. Please be aware that all features
are in a testing phase and interactions are for demonstration purposes only.
We are continuously refining the system to ensure a seamless experience
for the final implementation.
</p>
<button class="btn-login" onclick="toggleModal()">
<svg style="width: 18px; height: 18px; margin-right: 8px;" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path>
</svg>
LOGIN
</button>
</div>
</div>
<footer class="landing-footer">
&copy; <?= date('Y') ?> Click to Vote System [PROTOTYPE]. All Rights Reserved.
</footer>
<div class="flatlogic-badge">
<img src="https://flatlogic.com/favicon.ico" alt="Flatlogic">
Built with Flatlogic
</div>
</div>
<!-- Login Modal -->
<div id="loginModal" class="modal-overlay">
<div class="login-modal">
<div class="modal-header">
<div class="modal-header-content">
<div class="header-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
</div>
<h2>Login</h2>
</div>
<button class="btn-close" onclick="toggleModal()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="modal-body">
<?php if (isset($_GET['error'])): ?>
<div style="background: #fee2e2; color: #b91c1c; padding: 0.75rem; border-radius: 10px; margin-bottom: 1.5rem; font-size: 0.85rem; border: 1px solid #fecaca; text-align: center;">
<?= htmlspecialchars($_GET['error']) ?>
</div>
<?php endif; ?>
<form action="login.php" method="POST">
<div class="form-group">
<label>User Type</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</i>
<select name="role">
<option value="Voter">Voter</option>
<option value="Officer">Officer</option>
<option value="Adviser">Adviser</option>
<option value="Admin">Admin</option>
</select>
</div>
</div>
<div class="form-group">
<label>UID</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"></rect><line x1="7" y1="8" x2="17" y2="8"></line><line x1="7" y1="12" x2="17" y2="12"></line><line x1="7" y1="16" x2="12" y2="16"></line></svg>
</i>
<input type="text" name="student_id" placeholder="00-0000" required pattern="\d{2}-\d{4}">
</div>
</div>
<div class="form-group">
<label>Email Account</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
</i>
<input type="email" name="email" placeholder="firstname.lastname@iloilonhs.edu.ph" required>
</div>
</div>
<div class="form-group">
<label>Password</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
</i>
<input type="password" id="passwordInput" name="password" placeholder="Enter your password" required>
<i class="password-toggle" onclick="togglePassword()">
<svg id="eyeIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</i>
</div>
</div>
<button type="submit" class="modal-btn-login">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>
LOGIN
</button>
<a href="#" class="forgot-password">Forgot Password?</a>
</form>
</div>
</div>
</div>
<script>
function toggleModal() {
const modal = document.getElementById('loginModal');
modal.classList.toggle('active');
}
function togglePassword() {
const input = document.getElementById('passwordInput');
const icon = document.getElementById('eyeIcon');
if (input.type === 'password') {
input.type = 'text';
icon.innerHTML = '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>';
} else {
input.type = 'password';
icon.innerHTML = '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>';
}
}
// Close modal on click outside
window.onclick = function(event) {
const modal = document.getElementById('loginModal');
if (event.target == modal) {
toggleModal();
}
}
// Auto-open modal if error exists
<?php if (isset($_GET['error'])): ?>
window.onload = function() {
toggleModal();
}
<?php endif; ?>
</script>
</body>
</html>

170
login.php
View File

@ -1,170 +0,0 @@
<?php
require_once 'auth_helper.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$student_id = $_POST['student_id'] ?? '';
$email = $_POST['email'] ?? '';
$role = $_POST['role'] ?? '';
$password = $_POST['password'] ?? '';
// 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) {
// 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 ($error && isset($_POST['role']) && str_contains($_SERVER['HTTP_REFERER'] ?? '', 'index.php')) {
// Only redirect back if we actually came from landing page modal
header('Location: index.php?error=' . urlencode($error));
exit;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Iloilo National High School</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;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/landing.css?v=<?= time() ?>">
<style>
.login-page-container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
position: relative;
}
</style>
</head>
<body class="landing-page" style="background-image: url('assets/images/background.jpg?v=<?= filemtime('assets/images/background.jpg') ?>');">
<div class="login-page-container">
<div class="login-modal" style="display: block; position: static; animation: none;">
<div class="modal-header">
<div class="modal-header-content">
<div class="header-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
</div>
<h2>Election Login</h2>
</div>
</div>
<div class="modal-body">
<?php if ($error): ?>
<div style="background: #fee2e2; color: #b91c1c; padding: 0.75rem; border-radius: 10px; margin-bottom: 1.5rem; font-size: 0.85rem; border: 1px solid #fecaca; text-align: center;">
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>User Type</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</i>
<select name="role">
<option value="Voter">Voter</option>
<option value="Officer">Officer</option>
<option value="Adviser">Adviser</option>
<option value="Admin">Admin</option>
</select>
</div>
</div>
<div class="form-group">
<label>UID</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"></rect><line x1="7" y1="8" x2="17" y2="8"></line><line x1="7" y1="12" x2="17" y2="12"></line><line x1="7" y1="16" x2="12" y2="16"></line></svg>
</i>
<input type="text" name="student_id" placeholder="00-0000" required pattern="\d{2}-\d{4}">
</div>
</div>
<div class="form-group">
<label>Email Account</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
</i>
<input type="email" name="email" placeholder="firstname.lastname@iloilonhs.edu.ph" required>
</div>
</div>
<div class="form-group">
<label>Password</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
</i>
<input type="password" id="passwordInput" name="password" placeholder="Enter your password" required>
<i class="password-toggle" onclick="togglePassword()">
<svg id="eyeIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</i>
</div>
</div>
<button type="submit" class="modal-btn-login">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>
LOGIN
</button>
</form>
<div class="text-center mt-3">
<small style="color: var(--landing-text-muted); font-size: 0.8rem;">Don't have an account? <a href="signup.php" style="color: var(--landing-primary); font-weight: 600; text-decoration: none;">Register here</a></small>
</div>
</div>
</div>
</div>
<script>
function togglePassword() {
const input = document.getElementById('passwordInput');
const icon = document.getElementById('eyeIcon');
if (input.type === 'password') {
input.type = 'text';
icon.innerHTML = '<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>';
} else {
input.type = 'password';
icon.innerHTML = '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>';
}
}
</script>
</body>
</html>

View File

@ -1,5 +0,0 @@
<?php
session_start();
session_destroy();
header('Location: index.php');
exit;

View File

@ -1,162 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/auth_helper.php';
require_login();
require_role(['Admin', 'Adviser', 'Officer']);
$position_id = $_GET['position_id'] ?? '';
if (!$position_id) die("Position ID required");
$pdo = db();
$pStmt = $pdo->prepare("SELECT p.*, e.title as election_title, e.id as election_id FROM positions p JOIN elections e ON p.election_id = e.id WHERE p.id = ?");
$pStmt->execute([$position_id]);
$position = $pStmt->fetch();
if (!$position) die("Position not found");
$candidates = $pdo->prepare("SELECT c.*, u.name, u.student_id FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.position_id = ?");
$candidates->execute([$position_id]);
$candidates = $candidates->fetchAll();
// Get all users who could be candidates (could be improved with search)
$users = $pdo->query("SELECT id, name, student_id FROM users WHERE role = 'Voter' LIMIT 100")->fetchAll();
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Manage Candidates | <?= htmlspecialchars($position['name']) ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body class="dashboard-body <?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<?php require_once 'includes/sidebar.php'; ?>
<div class="main-wrapper">
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header">
<div>
<h1 style="margin: 0; font-size: 1.75rem; color: #1e293b;">Candidates for <?= htmlspecialchars($position['name']) ?></h1>
<p class="welcome-msg" style="margin-top: 4px;"><?= htmlspecialchars($position['election_title']) ?></p>
</div>
<div>
<a href="view_election.php?id=<?= $position['election_id'] ?>" style="display: flex; align-items: center; gap: 8px; color: #4f46e5; font-weight: 600; text-decoration: none; background: #ffffff; padding: 10px 20px; border-radius: 8px; border: 1px solid #e2e8f0;">
<i data-lucide="arrow-left" style="width: 16px;"></i>
Back to Election
</a>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 24px;">
<div style="background: #ffffff; border: 1px solid #f3f4f6; border-radius: 16px; padding: 24px; height: fit-content;">
<h3 style="margin-top: 0; font-size: 1.1rem; font-weight: 700; color: #1e293b; margin-bottom: 20px;">Add New Candidate</h3>
<form action="api/add_candidate.php" method="POST">
<input type="hidden" name="position_id" value="<?= $position_id ?>">
<input type="hidden" name="election_id" value="<?= $position['election_id'] ?>">
<div style="margin-bottom: 16px;">
<label style="display: block; font-size: 0.75rem; font-weight: 600; color: #64748b; margin-bottom: 6px;">Select Student</label>
<select name="user_id" style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 8px; outline: none; background: #ffffff; font-size: 0.875rem;" required>
<option value="">-- Choose Student --</option>
<?php foreach ($users as $u): ?>
<option value="<?= $u['id'] ?>"><?= htmlspecialchars($u['name']) ?> (<?= $u['student_id'] ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; font-size: 0.75rem; font-weight: 600; color: #64748b; margin-bottom: 6px;">Party Name</label>
<input type="text" name="party_name" style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 8px; outline: none; font-size: 0.875rem;" placeholder="e.g. Independent">
</div>
<div style="margin-bottom: 24px;">
<label style="display: block; font-size: 0.75rem; font-weight: 600; color: #64748b; margin-bottom: 6px;">Manifesto</label>
<textarea name="manifesto" rows="4" style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 8px; outline: none; font-size: 0.875rem; font-family: inherit;" placeholder="Briefly describe the candidate's goals..."></textarea>
</div>
<button type="submit" style="width: 100%; background: #4f46e5; color: white; border: none; padding: 12px; border-radius: 8px; font-weight: 700; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px;">
<i data-lucide="user-plus" style="width: 18px;"></i>
Register Candidate
</button>
</form>
</div>
<div style="background: #ffffff; border: 1px solid #f3f4f6; border-radius: 16px; overflow: hidden;">
<div style="padding: 24px; border-bottom: 1px solid #f3f4f6;">
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 700; color: #1e293b;">Registered Candidates</h3>
</div>
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f9fafb; border-bottom: 1px solid #f3f4f6;">
<th style="padding: 12px 24px; text-align: left; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Student Info</th>
<th style="padding: 12px 24px; text-align: left; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Party Affiliation</th>
<th style="padding: 12px 24px; text-align: left; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Status</th>
<th style="padding: 12px 24px; text-align: right; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($candidates)): ?>
<tr>
<td colspan="4" style="padding: 48px; text-align: center; color: #94a3b8;">
<div style="display: flex; flex-direction: column; align-items: center; gap: 12px;">
<i data-lucide="users" style="width: 48px; height: 48px; color: #e2e8f0;"></i>
<span>No candidates have been registered for this position yet.</span>
</div>
</td>
</tr>
<?php else: ?>
<?php foreach ($candidates as $c): ?>
<tr style="border-bottom: 1px solid #f3f4f6; transition: background 0.2s;" onmouseover="this.style.background='#f8fafc'" onmouseout="this.style.background='transparent'">
<td style="padding: 16px 24px;">
<div style="display: flex; align-items: center; gap: 12px;">
<div style="width: 36px; height: 36px; background: #e0e7ff; color: #4f46e5; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem;">
<?= strtoupper(substr($c['name'], 0, 1)) ?>
</div>
<div>
<div style="font-weight: 600; color: #1e293b;"><?= htmlspecialchars($c['name']) ?></div>
<div style="font-size: 0.75rem; color: #64748b;"><?= $c['student_id'] ?></div>
</div>
</div>
</td>
<td style="padding: 16px 24px;">
<span style="background: #f1f5f9; padding: 4px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 600; color: #475569;">
<?= htmlspecialchars($c['party_name'] ?: 'Independent') ?>
</span>
</td>
<td style="padding: 16px 24px;">
<span style="padding: 4px 12px; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; background: <?= $c['approved'] ? '#dcfce7' : '#f1f5f9' ?>; color: <?= $c['approved'] ? '#166534' : '#475569' ?>;">
<?= $c['approved'] ? 'Approved' : 'Pending' ?>
</span>
</td>
<td style="padding: 16px 24px; text-align: right;">
<form action="api/toggle_candidate_approval.php" method="POST" style="display:inline;">
<input type="hidden" name="id" value="<?= $c['id'] ?>">
<button type="submit" style="background: <?= $c['approved'] ? '#fef2f2' : '#f0f9ff' ?>; color: <?= $c['approved'] ? '#ef4444' : '#0ea5e9' ?>; border: 1px solid <?= $c['approved'] ? '#fee2e2' : '#e0f2fe' ?>; padding: 6px 12px; border-radius: 6px; font-size: 0.75rem; font-weight: 700; cursor: pointer; transition: all 0.2s;">
<?= $c['approved'] ? 'Revoke Approval' : 'Approve' ?>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
<script>
lucide.createIcons();
</script>
</body>
</html>

View File

@ -1,23 +0,0 @@
<?php
require_once __DIR__ . '/db/config.php';
try {
$pdo = db();
$migrationFiles = glob(__DIR__ . '/db/migrations/*.sql');
sort($migrationFiles);
foreach ($migrationFiles as $file) {
$sql = file_get_contents($file);
$statements = explode(';', $sql);
foreach ($statements as $statement) {
$statement = trim($statement);
if ($statement) {
$pdo->exec($statement);
}
}
echo "Executed: " . basename($file) . "\n";
}
echo "All migrations completed successfully!\n";
} catch (Exception $e) {
echo "Migration failed: " . $e->getMessage() . "\n";
}

View File

@ -1,273 +0,0 @@
<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
$electionId = get_active_election_id();
$election = get_active_election();
// Fetch officers assigned to this election grouped by role
$query = "SELECT u.* FROM users u
JOIN election_assignments ea ON u.id = ea.user_id
WHERE ea.election_id = ? AND u.deleted_at IS NULL";
$stmt = $pdo->prepare($query . " AND u.role = 'Admin' ORDER BY u.name");
$stmt->execute([$electionId]);
$admins = $stmt->fetchAll();
$stmt = $pdo->prepare($query . " AND u.role = 'Adviser' ORDER BY u.name");
$stmt->execute([$electionId]);
$advisers = $stmt->fetchAll();
$stmt = $pdo->prepare($query . " AND u.role = 'Officer' ORDER BY u.name");
$stmt->execute([$electionId]);
$officers = $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>Officer 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/officers_management.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
</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">
<div style="display: flex; align-items: center; gap: 16px;">
<div class="header-icon-container">
<i data-lucide="shield-check" style="width: 24px; color: #4f46e5;"></i>
</div>
<div>
<h1 style="margin: 0; font-size: 1.5rem; color: var(--text-main);">Officer Management</h1>
<p style="margin: 4px 0 0 0; color: var(--text-muted); font-size: 0.875rem;">Personnel for <?= htmlspecialchars($election['title'] ?? 'Selected Election') ?></p>
</div>
</div>
</div>
<!-- Register New Officer Form -->
<section class="registration-section animate-stagger">
<div class="registration-header">
<i data-lucide="plus-circle" style="width: 20px; color: #2563eb;"></i>
Assign New Officer to Election
</div>
<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" 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" name="email" placeholder="email@school.edu" required>
</div>
<div class="form-group">
<label>Role</label>
<select name="role" required>
<option value="Admin">Admin</option>
<option value="Adviser">Adviser</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="submit" class="btn-save-officer">
<i data-lucide="save" style="width: 18px;"></i>
ASSIGN TO ELECTION
</button>
</div>
</form>
</section>
<!-- Officer Categories Grid -->
<div class="officer-management-grid animate-fade-in">
<!-- Admins -->
<div class="officer-category-card">
<div class="category-header">
<div class="category-title">
<i data-lucide="user-cog" style="width: 18px; color: #4f46e5;"></i>
Admins
</div>
<span class="active-count"><?= count($admins) ?> ACTIVE</span>
</div>
<div class="officer-list">
<?php foreach ($admins as $o): ?>
<div class="officer-item">
<div class="officer-main-info">
<div class="officer-avatar"><?= strtoupper(substr($o['name'], 0, 1)) ?></div>
<div class="officer-details">
<span class="officer-name"><?= htmlspecialchars($o['name']) ?></span>
<span class="officer-meta"><?= htmlspecialchars($o['student_id']) ?> | <?= htmlspecialchars($o['email']) ?></span>
</div>
</div>
<div class="officer-actions">
<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; ?>
<?php if (empty($admins)): ?>
<div class="empty-state">No admins assigned.</div>
<?php endif; ?>
</div>
</div>
<!-- COMEA Advisers -->
<div class="officer-category-card">
<div class="category-header">
<div class="category-title">
<i data-lucide="user-check" style="width: 18px; color: #f97316;"></i>
Advisers
</div>
<span class="active-count" style="background: #fff7ed; color: #ea580c;"><?= count($advisers) ?> ACTIVE</span>
</div>
<div class="officer-list">
<?php foreach ($advisers as $o): ?>
<div class="officer-item">
<div class="officer-main-info">
<div class="officer-avatar" style="background: #eff6ff; color: #2563eb;"><?= strtoupper(substr($o['name'], 0, 1)) ?></div>
<div class="officer-details">
<span class="officer-name"><?= htmlspecialchars($o['name']) ?></span>
<span class="officer-meta"><?= htmlspecialchars($o['student_id']) ?> | <?= htmlspecialchars($o['email']) ?></span>
</div>
</div>
<div class="officer-actions">
<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; ?>
<?php if (empty($advisers)): ?>
<div class="empty-state">No advisers assigned.</div>
<?php endif; ?>
</div>
</div>
<!-- COMEA Officers -->
<div class="officer-category-card">
<div class="category-header">
<div class="category-title">
<i data-lucide="users" style="width: 18px; color: #10b981;"></i>
COMEA Officers
</div>
<span class="active-count" style="background: #f0fdf4; color: #16a34a;"><?= count($officers) ?> ACTIVE</span>
</div>
<div class="officer-list">
<?php foreach ($officers as $o): ?>
<div class="officer-item">
<div class="officer-main-info">
<div class="officer-avatar" style="background: #ecfdf5; color: #059669;"><?= strtoupper(substr($o['name'], 0, 1)) ?></div>
<div class="officer-details">
<span class="officer-name"><?= htmlspecialchars($o['name']) ?></span>
<span class="officer-meta"><?= htmlspecialchars($o['student_id']) ?> | <?= htmlspecialchars($o['email']) ?></span>
</div>
</div>
<div class="officer-actions">
<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; ?>
<?php if (empty($officers)): ?>
<div class="empty-state">No officers assigned.</div>
<?php endif; ?>
</div>
</div>
</div>
</main>
</div>
<!-- Edit Officer Modal -->
<div id="editOfficerModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content">
<div class="modal-header">
<h2 style="margin:0; font-size:1.25rem;">Edit Officer</h2>
<button onclick="closeModal('editOfficerModal')" style="border:none; background:none; cursor:pointer; color: var(--text-muted);"><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>FULL NAME</label>
<input type="text" name="name" id="edit_officer_name" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>EMAIL ADDRESS</label>
<input type="email" name="email" id="edit_officer_email" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>ROLE</label>
<select name="role" id="edit_officer_role" required>
<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>NEW PASSWORD (OPTIONAL)</label>
<input type="password" name="password" placeholder="Leave blank to keep current">
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('editOfficerModal')" class="btn-cancel">Cancel</button>
<button type="submit" class="btn-submit" style="background: var(--accent-color); color: white;">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

@ -1,123 +0,0 @@
<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$pdo = db();
$electionId = get_active_election_id();
$election = get_active_election();
// Filters
$search = $_GET['search'] ?? '';
// Query Construction
$query = "SELECT l.*, u.student_id, u.role, 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)";
$params = [$electionId];
if ($search) {
$query .= " AND (l.action LIKE ? OR l.details LIKE ? OR u.student_id LIKE ? OR u.name LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$query .= " ORDER BY l.created_at DESC LIMIT 100";
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$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>Reports & Audit | <?= 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/reports_audit.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
</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">
<div style="display: flex; align-items: center; gap: 16px;">
<div class="header-icon-container">
<i data-lucide="file-text" style="width: 24px; color: #4f46e5;"></i>
</div>
<div>
<h1 style="margin: 0; font-size: 1.5rem; color: #1e293b;">Reports & Audit Trail</h1>
<p style="margin: 4px 0 0 0; color: #64748b; font-size: 0.875rem;">Monitoring activity for <?= htmlspecialchars($election['title'] ?? 'Selected Election') ?></p>
</div>
</div>
</div>
<!-- Filters & Table Section -->
<div class="content-section animate-fade-in">
<table class="audit-table">
<thead>
<tr>
<th>TIMESTAMP</th>
<th>USER</th>
<th>ACTION</th>
<th>DETAILS</th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr>
<td colspan="4" style="text-align: center; color: #94a3b8; padding: 32px;">No activity logs found for this election.</td>
</tr>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<tr>
<td style="white-space: nowrap;"><?= date('M d, Y H:i:s', strtotime($log['created_at'])) ?></td>
<td>
<div style="display: flex; align-items: center; gap: 8px;">
<div class="user-avatar-small" style="width: 24px; height: 24px; font-size: 10px; background: #f1f5f9; color: #475569; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600;">
<?= strtoupper(substr($log['user_name'] ?? 'S', 0, 1)) ?>
</div>
<div>
<div style="font-weight: 600; font-size: 13px;"><?= htmlspecialchars($log['user_name'] ?? 'SYSTEM') ?></div>
<div style="font-size: 11px; color: #94a3b8;"><?= htmlspecialchars($log['role'] ?? 'SYSTEM') ?> (<?= htmlspecialchars($log['student_id'] ?? 'N/A') ?>)</div>
</div>
</div>
</td>
<td>
<span class="action-badge"><?= strtoupper(htmlspecialchars($log['action'])) ?></span>
</td>
<td style="color: #64748b; font-size: 13px;">
<?= htmlspecialchars($log['details'] ?? 'No additional details') ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</main>
</div>
<script>
lucide.createIcons();
</script>
</body>
</html>

View File

@ -1,148 +0,0 @@
<?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 <?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<?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

@ -1,164 +0,0 @@
<?php
require_once 'auth_helper.php';
require_login();
$user = get_user();
$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>Settings | <?= htmlspecialchars($projectDescription) ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
.settings-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
max-width: 600px;
}
.settings-group {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid #f1f5f9;
}
.settings-group:last-child {
border-bottom: none;
}
.settings-info h3 {
margin: 0 0 4px 0;
font-size: 1rem;
color: #1e293b;
}
.settings-info p {
margin: 0;
font-size: 0.875rem;
color: #64748b;
}
/* Toggle Switch */
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #e2e8f0;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #4f46e5;
}
input:checked + .slider:before {
transform: translateX(26px);
}
/* Dark mode preview styles (optional, can be integrated into global CSS later) */
.dark-mode-preview {
margin-top: 20px;
padding: 12px;
background: #1e293b;
color: white;
border-radius: 8px;
display: none;
}
</style>
</head>
<body class="dashboard-body <?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<?php require_once 'includes/sidebar.php'; ?>
<div class="main-wrapper">
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header">
<div>
<h1 style="margin: 0 0 4px 0; font-size: 1.5rem; color: #1e293b;">Settings</h1>
<div class="welcome-msg">Manage your account preferences</div>
</div>
</div>
<div class="settings-card">
<div class="settings-group">
<div class="settings-info">
<h3>Theme Toggle</h3>
<p>Switch between Light and Dark mode</p>
</div>
<div>
<label class="switch">
<input type="checkbox" id="themeToggle" <?= ($user['theme'] ?? 'light') === 'dark' ? 'checked' : '' ?>>
<span class="slider"></span>
</label>
</div>
</div>
</div>
</main>
</div>
<script>
lucide.createIcons();
document.getElementById('themeToggle').addEventListener('change', function() {
const theme = this.checked ? 'dark' : 'light';
// Apply theme immediately to body
if (theme === 'dark') {
document.body.classList.add('dark-theme');
} else {
document.body.classList.remove('dark-theme');
}
// Save to database
fetch('api/update_theme.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ theme: theme }),
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error('Failed to update theme:', data.error);
}
})
.catch((error) => {
console.error('Error:', error);
});
});
</script>
</body>
</html>

View File

@ -1,158 +0,0 @@
<?php
require_once 'auth_helper.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$student_id = $_POST['student_id'] ?? '';
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$role = $_POST['role'] ?? 'Voter';
// Simple validation
if (!preg_match('/^\d{2}-\d{4}$/', $student_id)) {
$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();
$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;
audit_log('User registered', 'users', $id);
header('Location: index.php');
exit;
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
$error = 'Student ID or Email already exists.';
} else {
$error = 'An error occurred: ' . $e->getMessage();
}
} catch (Exception $e) {
$error = $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registration - Iloilo National High School</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;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/landing.css?v=<?= time() ?>">
<style>
.signup-page-container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
position: relative;
padding: 2rem 0;
}
</style>
</head>
<body class="landing-page" style="background-image: url('assets/images/background.jpg?v=<?= filemtime('assets/images/background.jpg') ?>');">
<div class="signup-page-container">
<div class="login-modal" style="display: block; position: static; animation: none; max-width: 500px;">
<div class="modal-header">
<div class="modal-header-content">
<div class="header-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><polyline points="17 11 19 13 23 9"></polyline></svg>
</div>
<h2>Voter Registration</h2>
</div>
</div>
<div class="modal-body">
<?php if ($error): ?>
<div style="background: #fee2e2; color: #b91c1c; padding: 0.75rem; border-radius: 10px; margin-bottom: 1.5rem; font-size: 0.85rem; border: 1px solid #fecaca; text-align: center;">
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>Full Name</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</i>
<input type="text" name="name" placeholder="Juan Dela Cruz" required>
</div>
</div>
<div class="form-group">
<label>UID</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"></rect><line x1="7" y1="8" x2="17" y2="8"></line><line x1="7" y1="12" x2="17" y2="12"></line><line x1="7" y1="16" x2="12" y2="16"></line></svg>
</i>
<input type="text" name="student_id" placeholder="00-0000" required pattern="\d{2}-\d{4}">
</div>
</div>
<div class="form-group">
<label>Email Account</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
</i>
<input type="email" name="email" placeholder="firstname.lastname@iloilonhs.edu.ph" required>
</div>
</div>
<div class="form-group">
<label>Password</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
</i>
<input type="password" id="passwordInput" name="password" placeholder="Create a password" required>
</div>
</div>
<div class="form-group">
<label>User Type</label>
<div class="input-container">
<i>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</i>
<select name="role">
<option value="Voter">Voter</option>
<option value="Officer">Officer</option>
<option value="Adviser">Adviser</option>
<option value="Admin">Admin</option>
</select>
</div>
</div>
<button type="submit" class="modal-btn-login">
REGISTER
</button>
</form>
<div class="text-center mt-3">
<small style="color: var(--landing-text-muted); font-size: 0.8rem;">Already have an account? <a href="login.php" style="color: var(--landing-primary); font-weight: 600; text-decoration: none;">Login here</a></small>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,163 +0,0 @@
<?php
require_once __DIR__ . '/db/config.php';
// This script migrates the local MariaDB database to Supabase PostgreSQL.
// It requires the Supabase Database Password.
if (php_sapi_name() !== 'cli') {
die("This script must be run from the command line.");
}
$dbPassword = $argv[1] ?? '';
if (!$dbPassword) {
echo "Usage: php supabase_migration.php [SUPABASE_DB_PASSWORD]\n";
exit(1);
}
$supabaseHost = "aws-1-ap-southeast-1.pooler.supabase.com";
$supabaseUser = "postgres.siqeqnizegizxemrfgkf";
$supabaseDb = "postgres";
$supabasePort = "6543";
try {
echo "Connecting to local MariaDB...\n";
$localPdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
echo "Connecting to Supabase PostgreSQL...\n";
$dsn = "pgsql:host=$supabaseHost;port=$supabasePort;dbname=$supabaseDb";
$supabasePdo = new PDO($dsn, $supabaseUser, $dbPassword, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
echo "Converting and creating tables in Supabase...\n";
// Define tables and their PostgreSQL schemas
$schemas = [
"users" => "CREATE TABLE IF NOT EXISTS users (
id VARCHAR(255) PRIMARY KEY,
supabase_uid VARCHAR(255),
student_id VARCHAR(10) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255),
grade_level INTEGER,
track VARCHAR(100),
section VARCHAR(100),
role VARCHAR(50) DEFAULT 'Voter',
access_level INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
)",
"elections" => "CREATE TABLE IF NOT EXISTS elections (
id VARCHAR(255) PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(50) DEFAULT 'Preparing',
start_date_and_time TIMESTAMP NOT NULL,
end_date_and_time TIMESTAMP NOT NULL,
created_by VARCHAR(255) REFERENCES users(id),
archived BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)",
"positions" => "CREATE TABLE IF NOT EXISTS positions (
id VARCHAR(255) PRIMARY KEY,
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) DEFAULT 'Uniform',
max_votes INTEGER DEFAULT 1,
sort_order INTEGER DEFAULT 0
)",
"candidates" => "CREATE TABLE IF NOT EXISTS candidates (
id VARCHAR(255) PRIMARY KEY,
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
position_id VARCHAR(255) REFERENCES positions(id) ON DELETE CASCADE,
user_id VARCHAR(255) REFERENCES users(id) ON DELETE CASCADE,
party_name VARCHAR(255),
manifesto TEXT,
approved BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)",
"votes" => "CREATE TABLE IF NOT EXISTS votes (
id VARCHAR(255) PRIMARY KEY,
election_id VARCHAR(255) REFERENCES elections(id),
position_id VARCHAR(255) REFERENCES positions(id),
candidate_id VARCHAR(255) REFERENCES candidates(id),
voter_id VARCHAR(255) REFERENCES users(id),
casted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45),
user_agent TEXT,
UNIQUE (election_id, position_id, voter_id)
)",
"election_assignments" => "CREATE TABLE IF NOT EXISTS election_assignments (
id VARCHAR(255) PRIMARY KEY,
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
user_id VARCHAR(255) REFERENCES users(id) ON DELETE CASCADE,
role_in_election VARCHAR(50) DEFAULT 'Voter',
assigned_by VARCHAR(255) REFERENCES users(id),
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)",
"parties" => "CREATE TABLE IF NOT EXISTS parties (
id VARCHAR(255) PRIMARY KEY,
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
logo_url TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)",
"audit_logs" => "CREATE TABLE IF NOT EXISTS audit_logs (
id VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(255) REFERENCES users(id),
action VARCHAR(255) NOT NULL,
details TEXT,
table_name VARCHAR(100),
record_id VARCHAR(255),
old_values TEXT,
new_values TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE
)"
];
foreach ($schemas as $tableName => $sql) {
echo "Creating table: $tableName...\n";
$supabasePdo->exec($sql);
}
echo "Migrating data...\n";
$tables = array_keys($schemas);
// Order matters for foreign keys: users, elections, positions, candidates, assignments, votes, audit_logs
$orderedTables = ["users", "elections", "positions", "election_assignments", "parties", "candidates", "votes", "audit_logs"];
foreach ($orderedTables as $table) {
echo "Migrating data for $table...\n";
$stmt = $localPdo->query("SELECT * FROM $table");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)) {
echo "No data for $table.\n";
continue;
}
$columns = array_keys($rows[0]);
$placeholders = implode(',', array_fill(0, count($columns), '?'));
$insertSql = "INSERT INTO $table (" . implode(',', $columns) . ") VALUES ($placeholders) ON CONFLICT (id) DO NOTHING";
$insertStmt = $supabasePdo->prepare($insertSql);
$count = 0;
foreach ($rows as $row) {
$insertStmt->execute(array_values($row));
$count++;
}
echo "Migrated $count rows for $table.\n";
}
echo "Migration completed successfully!\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}

View File

@ -1,293 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/auth_helper.php';
require_login();
$user = get_user();
$id = $_GET['id'] ?? '';
if (!$id) {
header("Location: index.php");
exit;
}
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM elections WHERE id = ?");
$stmt->execute([$id]);
$election = $stmt->fetch();
if (!$election) {
die("Election not found.");
}
$positions = $pdo->prepare("SELECT * FROM positions WHERE election_id = ? ORDER BY sort_order ASC");
$positions->execute([$id]);
$positions = $positions->fetchAll();
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= htmlspecialchars($election['title']) ?> | Manage</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body class="dashboard-body <?= ($user['theme'] ?? 'light') === 'dark' ? 'dark-theme' : '' ?>">
<?php require_once 'includes/sidebar.php'; ?>
<div class="main-wrapper">
<?php require_once 'includes/header.php'; ?>
<main class="dashboard-content animate-fade-in">
<div class="dashboard-header" style="align-items: center;">
<div>
<h1 style="margin: 0; font-size: 1.75rem; color: #1e293b;"><?= htmlspecialchars($election['title']) ?></h1>
<p class="welcome-msg" style="margin-top: 4px;"><?= htmlspecialchars($election['description']) ?></p>
</div>
<div style="display: flex; gap: 12px;">
<?php if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])): ?>
<button class="btn-action" style="background: #ffffff; border: 1px solid #e2e8f0; color: #4b5563; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px;" onclick="document.getElementById('editElectionModal').style.display='flex'">
<i data-lucide="edit-3" style="width: 16px;"></i>
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'] ?>">
<input type="hidden" name="status" value="Ongoing">
<button type="submit" class="btn-action" style="background: #166534; color: white; padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px;">
<i data-lucide="play" style="width: 16px;"></i>
Launch Election
</button>
</form>
<?php elseif ($election['status'] === 'Ongoing'): ?>
<form action="api/update_election_status.php" method="POST">
<input type="hidden" name="id" value="<?= $election['id'] ?>">
<input type="hidden" name="status" value="Finished">
<button type="submit" class="btn-action" style="background: #ef4444; color: white; padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px;">
<i data-lucide="square" style="width: 16px;"></i>
End Election
</button>
</form>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<div class="stats-grid" style="grid-template-columns: repeat(3, 1fr); margin-bottom: 24px;">
<div class="stat-card">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="background: #eef2ff; padding: 10px; border-radius: 10px; color: #4f46e5;">
<i data-lucide="check-square"></i>
</div>
<span style="font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em;">Status</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span class="status-indicator <?= strtolower($election['status']) ?>" style="padding: 4px 12px; border-radius: 9999px; font-size: 0.875rem; font-weight: 600; background: <?= $election['status'] === 'Ongoing' ? '#dcfce7' : ($election['status'] === 'Preparing' ? '#fef9c3' : '#f1f5f9') ?>; color: <?= $election['status'] === 'Ongoing' ? '#166534' : ($election['status'] === 'Preparing' ? '#854d0e' : '#475569') ?>;">
<?= $election['status'] ?>
</span>
</div>
</div>
<div class="stat-card">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="background: #fff7ed; padding: 10px; border-radius: 10px; color: #f97316;">
<i data-lucide="calendar"></i>
</div>
<span style="font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em;">Election Period</span>
</div>
<div style="font-size: 0.875rem; font-weight: 600; color: #1e293b;">
<?= date('M d, H:i', strtotime($election['start_date_and_time'])) ?> — <?= date('M d, H:i', strtotime($election['end_date_and_time'])) ?>
</div>
</div>
<div class="stat-card">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="background: #ecfdf5; padding: 10px; border-radius: 10px; color: #10b981;">
<i data-lucide="users"></i>
</div>
<span style="font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em;">Total Participation</span>
</div>
<?php
$vStmt = $pdo->prepare("SELECT COUNT(DISTINCT voter_id) FROM votes WHERE election_id = ?");
$vStmt->execute([$id]);
$votesCount = $vStmt->fetchColumn();
?>
<div style="font-size: 1.5rem; font-weight: 800; color: #1e293b;"><?= $votesCount ?> <small style="font-size: 0.875rem; color: #64748b; font-weight: 500;">Votes Cast</small></div>
</div>
</div>
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 24px;">
<div style="background: #ffffff; border: 1px solid #f3f4f6; border-radius: 16px; overflow: hidden;">
<div style="padding: 24px; border-bottom: 1px solid #f3f4f6; display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin: 0; font-size: 1.1rem; color: #1e293b; font-weight: 700;">Positions & Structure</h2>
<?php if ($election['status'] === 'Preparing'): ?>
<button onclick="document.getElementById('addPositionForm').style.display='block'" style="background: #4f46e5; color: white; border: none; padding: 8px 16px; border-radius: 8px; font-size: 0.875rem; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 6px;">
<i data-lucide="plus" style="width: 14px;"></i>
Add Position
</button>
<?php endif; ?>
</div>
<div id="addPositionForm" style="display:none; margin: 24px; padding: 20px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px;">
<form action="api/add_position.php" method="POST">
<input type="hidden" name="election_id" value="<?= $election['id'] ?>">
<div style="display: grid; grid-template-columns: 2fr 1fr auto; gap: 16px; align-items: flex-end;">
<div>
<label style="display:block; font-size: 0.75rem; font-weight: 600; color: #64748b; margin-bottom: 6px;">Position Name</label>
<input type="text" name="name" style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 8px; outline: none;" placeholder="e.g. President" required>
</div>
<div>
<label style="display:block; font-size: 0.75rem; font-weight: 600; color: #64748b; margin-bottom: 6px;">Max Votes</label>
<input type="number" name="max_votes" style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 8px; outline: none;" value="1" min="1">
</div>
<div style="display: flex; gap: 8px;">
<button type="submit" style="background: #2563eb; color: white; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer;">Save</button>
<button type="button" onclick="document.getElementById('addPositionForm').style.display='none'" style="background: #f1f5f9; color: #475569; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer;">Cancel</button>
</div>
</div>
</form>
</div>
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f9fafb; border-bottom: 1px solid #f3f4f6;">
<th style="padding: 12px 24px; text-align: left; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Position Name</th>
<th style="padding: 12px 24px; text-align: left; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Max Votes</th>
<th style="padding: 12px 24px; text-align: left; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Candidates</th>
<th style="padding: 12px 24px; text-align: right; font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase;">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($positions)): ?>
<tr>
<td colspan="4" style="padding: 48px; text-align: center; color: #94a3b8;">
<div style="display: flex; flex-direction: column; align-items: center; gap: 12px;">
<i data-lucide="layers" style="width: 48px; height: 48px; color: #e2e8f0;"></i>
<span>No positions defined yet. Start by adding one.</span>
</div>
</td>
</tr>
<?php else: ?>
<?php foreach ($positions as $pos):
$cStmt = $pdo->prepare("SELECT COUNT(*) FROM candidates WHERE position_id = ?");
$cStmt->execute([$pos['id']]);
$cCount = $cStmt->fetchColumn();
?>
<tr style="border-bottom: 1px solid #f3f4f6; transition: background 0.2s;" onmouseover="this.style.background='#f8fafc'" onmouseout="this.style.background='transparent'">
<td style="padding: 16px 24px; font-weight: 600; color: #1e293b;"><?= htmlspecialchars($pos['name']) ?></td>
<td style="padding: 16px 24px; color: #4b5563;">
<span style="background: #f1f5f9; padding: 4px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 600;"><?= $pos['max_votes'] ?> Vote<?= $pos['max_votes'] > 1 ? 's' : '' ?></span>
</td>
<td style="padding: 16px 24px;">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 8px; height: 8px; border-radius: 50%; background: #10b981;"></div>
<span style="font-weight: 600; color: #1e293b;"><?= $cCount ?></span>
</div>
</td>
<td style="padding: 16px 24px; text-align: right;">
<a href="manage_candidates.php?position_id=<?= $pos['id'] ?>" style="color: #4f46e5; text-decoration: none; font-size: 0.875rem; font-weight: 600; display: inline-flex; align-items: center; gap: 6px;">
Manage Candidates
<i data-lucide="arrow-right" style="width: 14px;"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 24px;">
<div style="background: #ffffff; border: 1px solid #f3f4f6; border-radius: 16px; padding: 24px;">
<h2 style="margin-top: 0; font-size: 1.1rem; color: #1e293b; font-weight: 700; margin-bottom: 20px;">Quick Stats</h2>
<div style="display: flex; flex-direction: column; gap: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em;">Total Candidates</div>
<?php
$canStmt = $pdo->prepare("SELECT COUNT(*) FROM candidates WHERE election_id = ?");
$canStmt->execute([$id]);
$candidatesTotal = $canStmt->fetchColumn();
?>
<div style="font-size: 1.5rem; font-weight: 800; color: #1e293b;"><?= $candidatesTotal ?></div>
</div>
<div style="background: #fef2f2; padding: 12px; border-radius: 12px; color: #ef4444;">
<i data-lucide="user-check"></i>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 0.7rem; color: #64748b; text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em;">Voter Turnout</div>
<div style="font-size: 1.5rem; font-weight: 800; color: #1e293b;"><?= $votesCount ?></div>
</div>
<div style="background: #f0fdf4; padding: 12px; border-radius: 12px; color: #22c55e;">
<i data-lucide="bar-chart-3"></i>
</div>
</div>
</div>
</div>
<div style="background: linear-gradient(135deg, #4f46e5 0%, #3730a3 100%); border-radius: 16px; padding: 24px; color: white;">
<h3 style="margin: 0; font-size: 1rem; font-weight: 700; margin-bottom: 12px;">Need Help?</h3>
<p style="font-size: 0.875rem; opacity: 0.9; margin-bottom: 20px;">Manage your election structure by adding positions and assigning candidates to them. Once ready, you can launch the election.</p>
<a href="reports_audit.php" style="display: block; width: 100%; text-align: center; background: white; color: #4f46e5; padding: 12px; border-radius: 8px; font-weight: 700; text-decoration: none; font-size: 0.875rem;">View Reports</a>
</div>
</div>
</div>
</main>
</div>
<div id="editElectionModal" class="modal">
<div class="modal-content animate-scale-in" style="max-width: 550px;">
<div class="modal-header">
<h2 style="font-weight: 800;">Edit Election Details</h2>
<button type="button" onclick="document.getElementById('editElectionModal').style.display='none'" class="close-btn">&times;</button>
</div>
<form action="api/update_election.php" method="POST">
<input type="hidden" name="id" value="<?= $election['id'] ?>">
<div class="form-group" style="margin-bottom: 20px;">
<label>Election Title</label>
<input type="text" name="title" value="<?= htmlspecialchars($election['title']) ?>" style="width: 100%; padding: 12px; border: 1px solid #e2e8f0; border-radius: 10px; outline: none;" required>
</div>
<div class="form-group" style="margin-bottom: 20px;">
<label>Description</label>
<textarea name="description" rows="3" style="width: 100%; padding: 12px; border: 1px solid #e2e8f0; border-radius: 10px; outline: none; font-family: inherit;"><?= htmlspecialchars($election['description']) ?></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 32px;">
<div class="form-group">
<label>Start Date & Time</label>
<input type="datetime-local" name="start_date" value="<?= date('Y-m-d\TH:i', strtotime($election['start_date_and_time'])) ?>" style="width: 100%; padding: 12px; border: 1px solid #e2e8f0; border-radius: 10px; outline: none;" required>
</div>
<div class="form-group">
<label>End Date & Time</label>
<input type="datetime-local" name="end_date" value="<?= date('Y-m-d\TH:i', strtotime($election['end_date_and_time'])) ?>" style="width: 100%; padding: 12px; border: 1px solid #e2e8f0; border-radius: 10px; outline: none;" required>
</div>
</div>
<div class="modal-footer">
<button type="button" onclick="document.getElementById('editElectionModal').style.display='none'" class="btn-cancel">Cancel</button>
<button type="submit" class="btn-submit" style="background: #4f46e5;">Save Changes</button>
</div>
</form>
</div>
</div>
<script>
lucide.createIcons();
window.onclick = function(event) {
if (event.target.id === 'editElectionModal') {
event.target.style.display = 'none';
}
}
</script>
</body>
</html>

View File

@ -1,114 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/auth_helper.php';
require_login();
$user = get_user();
$id = $_GET['id'] ?? '';
if (!$id) {
header("Location: index.php");
exit;
}
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM elections WHERE id = ?");
$stmt->execute([$id]);
$election = $stmt->fetch();
if (!$election) die("Election not found");
// Summary stats
$vStmt = $pdo->prepare("SELECT COUNT(DISTINCT voter_id) FROM votes WHERE election_id = ?");
$vStmt->execute([$id]);
$totalVoters = $vStmt->fetchColumn();
// Positions and results
$positions = $pdo->prepare("SELECT * FROM positions WHERE election_id = ? ORDER BY sort_order ASC");
$positions->execute([$id]);
$positions = $positions->fetchAll();
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Results: <?= 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() ?>">
<style>
.result-bar-container { background: #e2e8f0; height: 12px; border-radius: 6px; overflow: hidden; margin-top: 0.5rem; }
.result-bar { background: #2563eb; height: 100%; transition: width 0.5s; }
.candidate-result { margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #e2e8f0; border-radius: 8px; }
</style>
</head>
<body>
<nav class="navbar">
<a href="index.php" class="brand">Click to Vote</a>
<div>
<a href="index.php" class="btn btn-outline">Dashboard</a>
</div>
</nav>
<div class="container">
<div class="card text-center mb-4">
<h1><?= htmlspecialchars($election['title']) ?> - Results</h1>
<p class="text-muted"><?= $election['status'] ?> Election</p>
<div style="font-size: 2rem; font-weight: 800;"><?= $totalVoters ?></div>
<div style="font-size: 0.75rem; color: #64748b; text-transform: uppercase;">Total Ballots Cast</div>
</div>
<?php if ($election['status'] === 'Preparing'): ?>
<div class="card text-center">
<p>Results will be available once the election starts.</p>
</div>
<?php else: ?>
<div class="row">
<?php foreach ($positions as $pos): ?>
<div class="col-12 mb-4">
<div class="card">
<h2 style="font-size: 1.25rem; border-bottom: 1px solid #e2e8f0; padding-bottom: 0.5rem; margin-bottom: 1rem;">
<?= htmlspecialchars($pos['name']) ?>
</h2>
<?php
$rStmt = $pdo->prepare("
SELECT c.*, u.name,
(SELECT COUNT(*) FROM votes v WHERE v.candidate_id = c.id) as vote_count
FROM candidates c
JOIN users u ON c.user_id = u.id
WHERE c.position_id = ?
ORDER BY vote_count DESC
");
$rStmt->execute([$pos['id']]);
$results = $rStmt->fetchAll();
$posTotal = array_sum(array_column($results, 'vote_count'));
?>
<?php foreach ($results as $index => $res):
$percent = $posTotal > 0 ? round(($res['vote_count'] / $posTotal) * 100, 1) : 0;
$isWinner = ($index === 0 && $res['vote_count'] > 0);
?>
<div class="candidate-result" style="<?= $isWinner ? 'border-color: #10b981; background: #f0fdf4;' : '' ?>">
<div style="display: flex; justify-content: space-between; font-weight: 600;">
<span style="display: flex; align-items: center; gap: 8px;">
<?= htmlspecialchars($res['name']) ?> (<?= htmlspecialchars($res['party_name'] ?: 'Ind.') ?>)
<?php if ($isWinner): ?>
<span style="background: #10b981; color: white; font-size: 0.65rem; padding: 2px 8px; border-radius: 4px; text-transform: uppercase;">Winner</span>
<?php endif; ?>
</span>
<span><?= $res['vote_count'] ?> votes (<?= $percent ?>%)</span>
</div>
<div class="result-bar-container">
<div class="result-bar" style="width: <?= $percent ?>%; <?= $isWinner ? 'background: #10b981;' : '' ?>"></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</body>
</html>

View File

@ -1,418 +0,0 @@
<?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 <?= ($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">
<?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: var(--text-main);">Voters List</h1>
<p style="margin: 4px 0 0 0; color: var(--text-muted); 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: var(--accent-color);"><?= 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: var(--text-muted);"><?= 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>SECTION</th>
<th>STATUS</th>
<th>ACTIONS</th>
</tr>
</thead>
<tbody>
<?php if (empty($voters)): ?>
<tr>
<td colspan="8" 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><?= htmlspecialchars($voter['section'] ?? 'N/A') ?></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%; z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content">
<div class="modal-header">
<h2>Register New Voter</h2>
<button onclick="closeModal('addVoterModal')" style="border:none; background:none; cursor:pointer; color: var(--text-muted);"><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>FULL NAME</label>
<input type="text" name="name" placeholder="Enter student's full name" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>STUDENT ID</label>
<input type="text" name="student_id" placeholder="XX-XXXX" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>EMAIL ADDRESS</label>
<input type="email" name="email" placeholder="student@school.edu" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>PASSWORD</label>
<input type="text" name="password" value="iloilohns" required>
<small style="color: var(--text-muted); 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>TRACK</label>
<select name="track" required>
<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>GRADE LEVEL</label>
<select name="grade_level" required>
<option value="11">Grade 11</option>
<option value="12">Grade 12</option>
</select>
</div>
</div>
<div class="form-group" style="margin-bottom:24px;">
<label>SECTION</label>
<input type="text" name="section" placeholder="Enter section (e.g. Einstein, Newton)" required>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('addVoterModal')" class="btn-cancel">Cancel</button>
<button type="submit" class="btn-action btn-add">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%; z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content">
<div class="modal-header">
<h2>Edit Voter</h2>
<button onclick="closeModal('editVoterModal')" style="border:none; background:none; cursor:pointer; color: var(--text-muted);"><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>FULL NAME</label>
<input type="text" name="name" id="edit_voter_name" placeholder="Enter student's full name" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>STUDENT ID</label>
<input type="text" name="student_id" id="edit_voter_student_id" placeholder="XX-XXXX" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>EMAIL ADDRESS</label>
<input type="email" name="email" id="edit_voter_email" placeholder="student@school.edu" required>
</div>
<div class="form-group" style="margin-bottom:16px;">
<label>NEW PASSWORD (OPTIONAL)</label>
<input type="password" name="password" placeholder="Leave blank to keep current">
</div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px; margin-bottom:24px;">
<div class="form-group">
<label>TRACK</label>
<select name="track" id="edit_voter_track" required>
<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>GRADE LEVEL</label>
<select name="grade_level" id="edit_voter_grade_level" required>
<option value="11">Grade 11</option>
<option value="12">Grade 12</option>
</select>
</div>
</div>
<div class="form-group" style="margin-bottom:24px;">
<label>SECTION</label>
<input type="text" name="section" id="edit_voter_section" placeholder="Enter section" required>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('editVoterModal')" class="btn-cancel">Cancel</button>
<button type="submit" class="btn-action btn-add">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%; z-index:1000; align-items:center; justify-content:center;">
<div class="modal-content">
<div class="modal-header">
<h2>Import Voters from CSV</h2>
<button onclick="closeModal('importModal')" style="border:none; background:none; cursor:pointer; color: var(--text-muted);"><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>SELECT CSV FILE</label>
<div style="border: 2px dashed var(--border-color); 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: var(--text-muted); margin-bottom: 12px;"></i>
<p style="margin: 0; font-size: 0.875rem; color: var(--text-muted);">Click to upload or drag and drop</p>
<p style="margin: 4px 0 0 0; font-size: 0.75rem; color: var(--text-muted);">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: var(--accent-color); font-size: 0.875rem;"></div>
</div>
</div>
<div style="background: var(--bg-color); padding: 16px; border-radius: 8px; margin-bottom: 24px;">
<p style="margin: 0 0 8px 0; font-size: 0.75rem; font-weight: 600; color: var(--text-muted);">CSV FORMAT REQUIREMENTS:</p>
<p style="margin: 0; font-size: 0.75rem; color: var(--text-muted); line-height: 1.5;">
Columns: <code>student_id, name, email, track, grade_level, section</code><br>
Example: <code>20-1234, John Doe, john@example.com, STEM, 12, Einstein</code>
</p>
</div>
<div class="modal-footer">
<button type="button" onclick="closeModal('importModal')" class="btn-cancel">Cancel</button>
<button type="submit" class="btn-action btn-add">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;
document.getElementById('edit_voter_section').value = voter.section || '';
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>