Autosave: 20260215-204330
This commit is contained in:
parent
ed25faf216
commit
b710d6acf5
75
api/add_officer.php
Normal file
75
api/add_officer.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . "/../auth_helper.php";
|
||||
require_login();
|
||||
require_role(["Admin", "Adviser"]);
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$election_id = $_POST["election_id"] ?? "";
|
||||
$student_id = $_POST["student_id"] ?? "";
|
||||
$name = $_POST["name"] ?? "";
|
||||
$email = $_POST["email"] ?? "";
|
||||
$role = $_POST["role"] ?? "Officer";
|
||||
$password = $_POST["password"] ?? "iloilohns";
|
||||
|
||||
if (!$election_id || !$student_id || !$name || !$email) {
|
||||
die("Missing fields");
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Check if user already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?");
|
||||
$stmt->execute([$student_id, $email]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
if ($existing) {
|
||||
$user_id = $existing["id"];
|
||||
// Update role if changed
|
||||
$upd = $pdo->prepare("UPDATE users SET role = ? WHERE id = ?");
|
||||
$upd->execute([$role, $user_id]);
|
||||
} else {
|
||||
// 1a. Create user in Supabase
|
||||
$supabaseUser = SupabaseAuth::createUser($email, $password);
|
||||
$supabase_uid = null;
|
||||
if ($supabaseUser['error']) {
|
||||
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
|
||||
$sbUser = SupabaseAuth::getUserByEmail($email);
|
||||
$supabase_uid = $sbUser['id'] ?? null;
|
||||
} else {
|
||||
throw new Exception("Supabase Error: " . $supabaseUser['error']);
|
||||
}
|
||||
} else {
|
||||
$supabase_uid = $supabaseUser['data']['id'] ?? null;
|
||||
}
|
||||
|
||||
// Create new user locally
|
||||
$user_id = uuid();
|
||||
$stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, role) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $role]);
|
||||
}
|
||||
|
||||
// 2. Assign to election
|
||||
$chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?");
|
||||
$chk->execute([$election_id, $user_id]);
|
||||
if ($chk->fetchColumn() == 0) {
|
||||
$role_in_election = $role; // Admin, Adviser, or Officer
|
||||
$ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, ?, ?)");
|
||||
$ea->execute([uuid(), $election_id, $user_id, $role_in_election, $_SESSION['user_id']]);
|
||||
}
|
||||
|
||||
audit_log('assigned_officer', 'users', $user_id, null, null, "Assigned $role $name to election $election_id");
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: ../officers_management.php?success=officer_assigned");
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
||||
die("Error: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../officers_management.php");
|
||||
exit;
|
||||
}
|
||||
@ -7,7 +7,7 @@ require_role(['Admin', 'Adviser', 'Officer']);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$election_id = $_POST['election_id'] ?? '';
|
||||
$name = $_POST['name'] ?? '';
|
||||
$max_votes = (int)($_POST['max_votes'] ?? 1);
|
||||
$type = $_POST['type'] ?? 'Uniform';
|
||||
|
||||
if (!$election_id || !$name) {
|
||||
die("Missing fields");
|
||||
@ -16,8 +16,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$pdo = db();
|
||||
$id = uuid();
|
||||
$stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, max_votes) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$id, $election_id, $name, $max_votes]);
|
||||
$stmt = $pdo->prepare("INSERT INTO positions (id, election_id, name, type, max_votes) VALUES (?, ?, ?, ?, 1)");
|
||||
$stmt->execute([$id, $election_id, $name, $type]);
|
||||
|
||||
audit_log('Added position', 'positions', $id);
|
||||
|
||||
|
||||
73
api/add_voter.php
Normal file
73
api/add_voter.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . "/../auth_helper.php";
|
||||
require_login();
|
||||
require_role(["Admin", "Adviser", "Officer"]);
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$election_id = $_POST["election_id"] ?? "";
|
||||
$student_id = $_POST["student_id"] ?? "";
|
||||
$name = $_POST["name"] ?? "";
|
||||
$email = $_POST["email"] ?? "";
|
||||
$password = $_POST["password"] ?? "iloilohns";
|
||||
$track = $_POST["track"] ?? "";
|
||||
$grade_level = $_POST["grade_level"] ?? "";
|
||||
|
||||
if (!$election_id || !$student_id || !$name || !$email) {
|
||||
die("Missing fields");
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Check if user already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?");
|
||||
$stmt->execute([$student_id, $email]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
if ($existing) {
|
||||
$user_id = $existing["id"];
|
||||
// Update track/grade if needed
|
||||
$upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ? WHERE id = ?");
|
||||
$upd->execute([$track, $grade_level, $user_id]);
|
||||
} else {
|
||||
// 1a. Create user in Supabase
|
||||
$supabaseUser = SupabaseAuth::createUser($email, $password);
|
||||
$supabase_uid = null;
|
||||
if ($supabaseUser['error']) {
|
||||
// If user already exists in Supabase, try to get their UID
|
||||
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
|
||||
$sbUser = SupabaseAuth::getUserByEmail($email);
|
||||
$supabase_uid = $sbUser['id'] ?? null;
|
||||
} else {
|
||||
throw new Exception("Supabase Error: " . $supabaseUser['error']);
|
||||
}
|
||||
} else {
|
||||
$supabase_uid = $supabaseUser['data']['id'] ?? null;
|
||||
}
|
||||
|
||||
// Create new user locally
|
||||
$user_id = uuid();
|
||||
$stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, track, grade_level, role) VALUES (?, ?, ?, ?, ?, ?, ?, 'Voter')");
|
||||
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level]);
|
||||
}
|
||||
|
||||
// 2. Assign to election
|
||||
$chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?");
|
||||
$chk->execute([$election_id, $user_id]);
|
||||
if ($chk->fetchColumn() == 0) {
|
||||
$ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, 'Voter', ?)");
|
||||
$ea->execute([uuid(), $election_id, $user_id, $_SESSION['user_id']]);
|
||||
}
|
||||
|
||||
audit_log("Registered voter", "users", $user_id);
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: ../voter_management.php?success=voter_added");
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
||||
die("Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
24
api/delete_candidate.php
Normal file
24
api/delete_candidate.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id'])) {
|
||||
$candId = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM candidates WHERE id = ?");
|
||||
$stmt->execute([$candId]);
|
||||
|
||||
$currentUser = get_user();
|
||||
audit_log('candidate_deleted', 'candidates', $candId, null, null, "Deleted candidate ID $candId");
|
||||
|
||||
header("Location: ../candidate_management.php?success=candidate_deleted");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error deleting candidate: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../candidate_management.php");
|
||||
exit;
|
||||
}
|
||||
26
api/delete_officer.php
Normal file
26
api/delete_officer.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id']) && isset($_GET['election_id'])) {
|
||||
$userId = $_GET['id'];
|
||||
$electionId = $_GET['election_id'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
// Remove the assignment for this election
|
||||
$stmt = $pdo->prepare("DELETE FROM election_assignments WHERE user_id = ? AND election_id = ? AND role_in_election != 'Voter'");
|
||||
$stmt->execute([$userId, $electionId]);
|
||||
|
||||
$currentUser = get_user();
|
||||
audit_log('officer_removed', 'users', $userId, null, null, "Removed officer ID $userId from election $electionId");
|
||||
|
||||
header("Location: ../officers_management.php?success=officer_deleted");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error deleting officer: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../officers_management.php");
|
||||
exit;
|
||||
}
|
||||
27
api/delete_voter.php
Normal file
27
api/delete_voter.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['id']) && isset($_GET['election_id'])) {
|
||||
$userId = $_GET['id'];
|
||||
$electionId = $_GET['election_id'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
// Remove the assignment for this election
|
||||
$stmt = $pdo->prepare("DELETE FROM election_assignments WHERE user_id = ? AND election_id = ? AND role_in_election = 'Voter'");
|
||||
$stmt->execute([$userId, $electionId]);
|
||||
|
||||
// Optional: Log the action
|
||||
$currentUser = get_user();
|
||||
audit_log('voter_removed', 'users', $userId, null, null, "Removed voter ID $userId from election $electionId");
|
||||
|
||||
header("Location: ../voter_management.php?success=voter_deleted");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error deleting voter: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../voter_management.php");
|
||||
exit;
|
||||
}
|
||||
102
api/import_voters.php
Normal file
102
api/import_voters.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . "/../auth_helper.php";
|
||||
require_login();
|
||||
require_role(["Admin", "Adviser", "Officer"]);
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$election_id = $_POST["election_id"] ?? "";
|
||||
$file = $_FILES["csv_file"] ?? null;
|
||||
|
||||
if (!$election_id || !$file || $file["error"] !== UPLOAD_ERR_OK) {
|
||||
die("Invalid submission or file upload error.");
|
||||
}
|
||||
|
||||
$extension = pathinfo($file["name"], PATHINFO_EXTENSION);
|
||||
if (strtolower($extension) !== "csv") {
|
||||
die("Please upload a valid CSV file.");
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$handle = fopen($file["tmp_name"], "r");
|
||||
if ($handle === false) {
|
||||
throw new Exception("Could not open the uploaded file.");
|
||||
}
|
||||
|
||||
// Skip header if it exists
|
||||
$header = fgetcsv($handle);
|
||||
// Basic header validation (optional, but good)
|
||||
// Expected: student_id, name, email, track, grade_level
|
||||
|
||||
$imported = 0;
|
||||
$updated = 0;
|
||||
|
||||
while (($data = fgetcsv($handle)) !== false) {
|
||||
if (count($data) < 5) continue; // Skip malformed rows
|
||||
|
||||
$student_id = trim($data[0]);
|
||||
$name = trim($data[1]);
|
||||
$email = trim($data[2]);
|
||||
$track = trim($data[3]);
|
||||
$grade_level = trim($data[4]);
|
||||
|
||||
if (!$student_id || !$name || !$email) continue;
|
||||
|
||||
// 1. Check if user already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE student_id = ? OR email = ?");
|
||||
$stmt->execute([$student_id, $email]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
if ($existing) {
|
||||
$user_id = $existing["id"];
|
||||
// Update track/grade if needed
|
||||
$upd = $pdo->prepare("UPDATE users SET track = ?, grade_level = ? WHERE id = ?");
|
||||
$upd->execute([$track, $grade_level, $user_id]);
|
||||
$updated++;
|
||||
} else {
|
||||
// 1a. Create user in Supabase
|
||||
$supabaseUser = SupabaseAuth::createUser($email, "iloilohns");
|
||||
$supabase_uid = null;
|
||||
if ($supabaseUser['error']) {
|
||||
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
|
||||
$sbUser = SupabaseAuth::getUserByEmail($email);
|
||||
$supabase_uid = $sbUser['id'] ?? null;
|
||||
} else {
|
||||
// Log error but continue with other users? Or fail?
|
||||
// Let's fail for now to be safe.
|
||||
throw new Exception("Supabase Error for $email: " . $supabaseUser['error']);
|
||||
}
|
||||
} else {
|
||||
$supabase_uid = $supabaseUser['data']['id'] ?? null;
|
||||
}
|
||||
|
||||
// Create new user locally
|
||||
$user_id = uuid();
|
||||
$stmt = $pdo->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, track, grade_level, role) VALUES (?, ?, ?, ?, ?, ?, ?, 'Voter')");
|
||||
$stmt->execute([$user_id, $supabase_uid, $student_id, $name, $email, $track, $grade_level]);
|
||||
$imported++;
|
||||
}
|
||||
|
||||
// 2. Assign to election
|
||||
$chk = $pdo->prepare("SELECT COUNT(*) FROM election_assignments WHERE election_id = ? AND user_id = ?");
|
||||
$chk->execute([$election_id, $user_id]);
|
||||
if ($chk->fetchColumn() == 0) {
|
||||
$ea = $pdo->prepare("INSERT INTO election_assignments (id, election_id, user_id, role_in_election, assigned_by) VALUES (?, ?, ?, 'Voter', ?)");
|
||||
$ea->execute([uuid(), $election_id, $user_id, $_SESSION['user_id']]);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
audit_log("Imported voters via CSV", "users", "multiple");
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: ../voter_management.php?success=import_complete&imported=$imported&updated=$updated");
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
||||
die("Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../auth_helper.php';
|
||||
require_once __DIR__ . "/../auth_helper.php";
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$election_id = $_POST['election_id'] ?? '';
|
||||
$votes = $_POST['votes'] ?? []; // Array of position_id => candidate_id
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$election_id = $_POST["election_id"] ?? "";
|
||||
$raw_votes = $_POST["votes"] ?? []; // Array of position_id => candidate_id (string or array)
|
||||
$user = get_user();
|
||||
|
||||
if (!$election_id || empty($votes)) {
|
||||
if (!$election_id) {
|
||||
die("Invalid submission.");
|
||||
}
|
||||
|
||||
@ -19,40 +19,67 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// 1. Verify election is ongoing
|
||||
$eStmt = $pdo->prepare("SELECT status FROM elections WHERE id = ?");
|
||||
$eStmt->execute([$election_id]);
|
||||
if ($eStmt->fetchColumn() !== 'Ongoing') {
|
||||
throw new Exception("Election is not ongoing.");
|
||||
$election = $eStmt->fetch();
|
||||
if (!$election || $election["status"] !== "Ongoing") {
|
||||
throw new Exception("Election is not currently ongoing.");
|
||||
}
|
||||
|
||||
// 2. Verify user hasn't voted yet
|
||||
$vCheck = $pdo->prepare("SELECT COUNT(*) FROM votes WHERE election_id = ? AND voter_id = ?");
|
||||
$vCheck->execute([$election_id, $user['id']]);
|
||||
$vCheck->execute([$election_id, $user["id"]]);
|
||||
if ($vCheck->fetchColumn() > 0) {
|
||||
throw new Exception("You have already cast your vote for this election.");
|
||||
}
|
||||
|
||||
// 3. Insert votes
|
||||
// 3. Prepare statement for inserting votes
|
||||
$stmt = $pdo->prepare("INSERT INTO votes (id, election_id, position_id, candidate_id, voter_id, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
foreach ($votes as $position_id => $candidate_id) {
|
||||
$vote_id = uuid();
|
||||
// 4. Validate each position's votes
|
||||
foreach ($raw_votes as $position_id => $candidate_id) {
|
||||
if (empty($candidate_id)) continue;
|
||||
|
||||
// Fetch position details
|
||||
$pStmt = $pdo->prepare("SELECT * FROM positions WHERE id = ? AND election_id = ?");
|
||||
$pStmt->execute([$position_id, $election_id]);
|
||||
$pos = $pStmt->fetch();
|
||||
|
||||
if (!$pos) {
|
||||
throw new Exception("Invalid position detected.");
|
||||
}
|
||||
|
||||
// Validate candidate
|
||||
$cStmt = $pdo->prepare("SELECT c.*, u.track FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.id = ? AND c.position_id = ? AND c.approved = TRUE");
|
||||
$cStmt->execute([$candidate_id, $position_id]);
|
||||
$cand = $cStmt->fetch();
|
||||
|
||||
if (!$cand) {
|
||||
throw new Exception("Invalid or unapproved candidate selected.");
|
||||
}
|
||||
|
||||
// Check Track Specific logic
|
||||
if ($pos["type"] === "Track Specific" && $cand["track"] !== $user["track"]) {
|
||||
throw new Exception("Candidate track mismatch for position: " . $pos["name"]);
|
||||
}
|
||||
|
||||
// Insert vote
|
||||
$stmt->execute([
|
||||
$vote_id,
|
||||
uuid(),
|
||||
$election_id,
|
||||
$position_id,
|
||||
$candidate_id,
|
||||
$user['id'],
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||
$_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
|
||||
$user["id"],
|
||||
$_SERVER["REMOTE_ADDR"] ?? "unknown",
|
||||
$_SERVER["HTTP_USER_AGENT"] ?? "unknown"
|
||||
]);
|
||||
}
|
||||
|
||||
audit_log('Cast ballot', 'elections', $election_id);
|
||||
audit_log("Cast ballot", "elections", $election_id);
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: ../view_results.php?id=$election_id&success=vote_cast");
|
||||
header("Location: ../index.php?success=voted");
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
||||
die("Error casting vote: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
27
api/update_candidate.php
Normal file
27
api/update_candidate.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
|
||||
$candId = $_POST['id'];
|
||||
$positionId = $_POST['position_id'];
|
||||
$partyName = $_POST['party_name'];
|
||||
$manifesto = $_POST['manifesto'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE candidates SET position_id = ?, party_name = ?, manifesto = ? WHERE id = ?");
|
||||
$stmt->execute([$positionId, $partyName, $manifesto, $candId]);
|
||||
|
||||
$currentUser = get_user();
|
||||
audit_log('candidate_updated', 'candidates', $candId, null, null, "Updated candidate ID $candId");
|
||||
|
||||
header("Location: ../candidate_management.php?success=candidate_updated");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error updating candidate: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../candidate_management.php");
|
||||
exit;
|
||||
}
|
||||
29
api/update_election.php
Normal file
29
api/update_election.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
require_role(['Admin', 'Adviser', 'Officer']);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
|
||||
$id = $_POST['id'];
|
||||
$title = $_POST['title'];
|
||||
$description = $_POST['description'];
|
||||
$startDate = $_POST['start_date'];
|
||||
$endDate = $_POST['end_date'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE elections SET title = ?, description = ?, start_date_and_time = ?, end_date_and_time = ? WHERE id = ?");
|
||||
$stmt->execute([$title, $description, $startDate, $endDate, $id]);
|
||||
|
||||
$currentUser = get_user();
|
||||
audit_log('election_updated', 'elections', $id, null, null, "Updated election $id");
|
||||
|
||||
header("Location: ../view_election.php?id=$id&success=1");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error updating election: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../index.php");
|
||||
exit;
|
||||
}
|
||||
42
api/update_officer.php
Normal file
42
api/update_officer.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
|
||||
$userId = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$email = $_POST['email'];
|
||||
$role = $_POST['role'];
|
||||
$password = $_POST['password'] ?? '';
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT email, supabase_uid FROM users WHERE id = ?");
|
||||
$stmt->execute([$userId]);
|
||||
$userRecord = $stmt->fetch();
|
||||
|
||||
if (!empty($password)) {
|
||||
// Update Supabase password
|
||||
if ($userRecord && $userRecord['supabase_uid']) {
|
||||
SupabaseAuth::updateUserPassword($userRecord['supabase_uid'], $password);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE users SET name = ?, email = ?, role = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $email, $role, $userId]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE users SET name = ?, email = ?, role = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $email, $role, $userId]);
|
||||
}
|
||||
|
||||
$currentUser = get_user();
|
||||
audit_log('officer_updated', 'users', $userId, null, null, "Updated officer ID $userId info");
|
||||
|
||||
header("Location: ../officers_management.php?success=officer_updated");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error updating officer: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../officers_management.php");
|
||||
exit;
|
||||
}
|
||||
45
api/update_voter.php
Normal file
45
api/update_voter.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
require_once '../auth_helper.php';
|
||||
require_login();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
|
||||
$userId = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$studentId = $_POST['student_id'];
|
||||
$email = $_POST['email'];
|
||||
$track = $_POST['track'];
|
||||
$gradeLevel = $_POST['grade_level'];
|
||||
$password = $_POST['password'] ?? '';
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT email, supabase_uid FROM users WHERE id = ?");
|
||||
$stmt->execute([$userId]);
|
||||
$userRecord = $stmt->fetch();
|
||||
|
||||
if (!empty($password)) {
|
||||
// Update Supabase password
|
||||
if ($userRecord && $userRecord['supabase_uid']) {
|
||||
SupabaseAuth::updateUserPassword($userRecord['supabase_uid'], $password);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $userId]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE users SET name = ?, student_id = ?, email = ?, track = ?, grade_level = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $studentId, $email, $track, $gradeLevel, $userId]);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
$currentUser = get_user();
|
||||
audit_log('voter_updated', 'users', $userId, null, null, "Updated voter ID $userId info");
|
||||
|
||||
header("Location: ../voter_management.php?success=voter_updated");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
die("Error updating voter: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
header("Location: ../voter_management.php");
|
||||
exit;
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/SupabaseAuth.php';
|
||||
|
||||
function get_user() {
|
||||
if (!isset($_SESSION['user_id'])) return null;
|
||||
@ -34,13 +35,14 @@ function uuid() {
|
||||
);
|
||||
}
|
||||
|
||||
function audit_log($action, $table = null, $record_id = null, $old = null, $new = null) {
|
||||
function audit_log($action, $table = null, $record_id = null, $old = null, $new = null, $details = null) {
|
||||
$electionId = get_active_election_id();
|
||||
$stmt = db()->prepare("INSERT INTO audit_logs (id, user_id, action, table_name, record_id, old_values, new_values, election_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt = db()->prepare("INSERT INTO audit_logs (id, user_id, action, details, table_name, record_id, old_values, new_values, election_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([
|
||||
uuid(),
|
||||
$_SESSION['user_id'] ?? null,
|
||||
$action,
|
||||
$details,
|
||||
$table,
|
||||
$record_id,
|
||||
$old ? json_encode($old) : null,
|
||||
|
||||
172
ballot.php
172
ballot.php
@ -37,74 +37,150 @@ $positions = $positions->fetchAll();
|
||||
<meta charset="utf-8" />
|
||||
<title>Vote: <?= htmlspecialchars($election['title']) ?></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/style.css?v=<?= time() ?>">
|
||||
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<style>
|
||||
body { background: #f8fafc; color: #1e293b; font-family: 'Inter', sans-serif; }
|
||||
.ballot-container { max-width: 800px; margin: 40px auto; padding: 0 20px; }
|
||||
.ballot-header { text-align: center; margin-bottom: 48px; }
|
||||
.ballot-header h1 { font-size: 2.5rem; font-weight: 800; color: #1e293b; margin-bottom: 12px; letter-spacing: -0.025em; }
|
||||
.ballot-header p { color: #64748b; font-size: 1.125rem; }
|
||||
|
||||
.position-group { margin-bottom: 40px; background: white; border-radius: 24px; border: 1px solid #e2e8f0; padding: 32px; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); }
|
||||
.position-title { font-size: 1.25rem; font-weight: 700; color: #1e293b; margin-bottom: 24px; display: flex; align-items: center; gap: 12px; }
|
||||
.position-title i { color: #4f46e5; }
|
||||
|
||||
.candidates-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
||||
.candidate-label { cursor: pointer; display: block; }
|
||||
.candidate-card {
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
gap: 16px;
|
||||
background: white;
|
||||
position: relative;
|
||||
}
|
||||
.candidate-card:hover { border-color: #2563eb; background: #f0f9ff; }
|
||||
input[type="radio"]:checked + .candidate-card {
|
||||
border-color: #2563eb;
|
||||
background: #eff6ff;
|
||||
box-shadow: 0 0 0 1px #2563eb;
|
||||
.candidate-card:hover { border-color: #cbd5e1; background: #f8fafc; }
|
||||
|
||||
input[type="radio"]:checked + .candidate-card,
|
||||
input[type="checkbox"]:checked + .candidate-card {
|
||||
border-color: #4f46e5;
|
||||
background: #f5f3ff;
|
||||
box-shadow: 0 0 0 1px #4f46e5;
|
||||
}
|
||||
input[type="radio"] { display: none; }
|
||||
.ballot-section { margin-bottom: 2rem; }
|
||||
.ballot-header { border-bottom: 2px solid #1e293b; padding-bottom: 0.5rem; margin-bottom: 1.5rem; }
|
||||
|
||||
input[type="radio"]:checked + .candidate-card .check-icon,
|
||||
input[type="checkbox"]:checked + .candidate-card .check-icon {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
border-color: #4f46e5;
|
||||
}
|
||||
|
||||
input[type="radio"], input[type="checkbox"] { display: none; }
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
color: #4f46e5;
|
||||
font-size: 1.25rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.candidate-info h3 { margin: 0; font-size: 1.125rem; font-weight: 700; color: #1e293b; }
|
||||
.candidate-info p { margin: 4px 0 0 0; font-size: 0.875rem; color: #64748b; font-weight: 500; }
|
||||
|
||||
.check-icon {
|
||||
margin-left: auto;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.submit-section { margin-top: 64px; text-align: center; padding: 48px; background: #1e293b; border-radius: 24px; color: white; }
|
||||
.btn-submit {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 16px 48px;
|
||||
border-radius: 12px;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.4);
|
||||
}
|
||||
.btn-submit:hover { transform: translateY(-2px); background: #4338ca; }
|
||||
.btn-submit:active { transform: translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<a href="index.php" class="brand">E-Vote Pro</a>
|
||||
<div>
|
||||
<span>Logged in as <?= htmlspecialchars($user['name']) ?></span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container" style="max-width: 800px;">
|
||||
<div class="text-center mb-5">
|
||||
<h1 style="font-size: 2rem; font-weight: 800;"><?= htmlspecialchars($election['title']) ?></h1>
|
||||
<p class="text-muted">Please select your candidates carefully. Your vote is immutable once cast.</p>
|
||||
<div class="ballot-container">
|
||||
<div class="ballot-header animate-fade-in">
|
||||
<div style="display: inline-flex; align-items: center; gap: 8px; background: #e0e7ff; color: #4338ca; padding: 6px 16px; border-radius: 100px; font-size: 0.875rem; font-weight: 700; margin-bottom: 16px;">
|
||||
<i data-lucide="vote" style="width: 16px;"></i> OFFICIAL BALLOT
|
||||
</div>
|
||||
<h1><?= htmlspecialchars($election['title']) ?></h1>
|
||||
<p>Your vote is secure and anonymous. Choose your representatives below.</p>
|
||||
</div>
|
||||
|
||||
<form action="api/submit_vote.php" method="POST">
|
||||
<form action="api/submit_vote.php" method="POST" onsubmit="return confirm('Are you sure you want to cast your vote? This action cannot be undone.')">
|
||||
<input type="hidden" name="election_id" value="<?= $id ?>">
|
||||
|
||||
<?php foreach ($positions as $pos): ?>
|
||||
<div class="ballot-section">
|
||||
<div class="ballot-header">
|
||||
<h2 style="margin: 0; font-size: 1.25rem;"><?= htmlspecialchars($pos['name']) ?></h2>
|
||||
<small class="text-muted">Select <?= $pos['max_votes'] ?> candidate(s)</small>
|
||||
<?php foreach ($positions as $index => $pos): ?>
|
||||
<div class="position-group animate-stagger" style="--order: <?= $index ?>">
|
||||
<div class="position-title">
|
||||
<i data-lucide="award"></i>
|
||||
<?= htmlspecialchars($pos['name']) ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$cStmt = $pdo->prepare("SELECT c.*, u.name FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.position_id = ? AND c.approved = TRUE");
|
||||
$cStmt->execute([$pos['id']]);
|
||||
$sql = "SELECT c.*, u.name, u.track FROM candidates c JOIN users u ON c.user_id = u.id WHERE c.position_id = ? AND c.approved = TRUE";
|
||||
$params = [$pos['id']];
|
||||
|
||||
if ($pos['type'] === 'Track Specific') {
|
||||
$sql .= " AND u.track = ?";
|
||||
$params[] = $user['track'];
|
||||
}
|
||||
|
||||
$cStmt = $pdo->prepare($sql);
|
||||
$cStmt->execute($params);
|
||||
$candidates = $cStmt->fetchAll();
|
||||
?>
|
||||
|
||||
<?php if (empty($candidates)): ?>
|
||||
<p class="text-muted">No candidates for this position.</p>
|
||||
<div style="padding: 24px; background: #f8fafc; border-radius: 12px; text-align: center; border: 1px dashed #cbd5e1;">
|
||||
<p style="margin: 0; color: #64748b; font-size: 0.875rem;">No candidates for this position.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="candidates-grid">
|
||||
<?php foreach ($candidates as $cand): ?>
|
||||
<label>
|
||||
<label class="candidate-label">
|
||||
<input type="radio" name="votes[<?= $pos['id'] ?>]" value="<?= $cand['id'] ?>" required>
|
||||
<div class="candidate-card">
|
||||
<div style="width: 40px; height: 40px; background: #e2e8f0; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #64748b;">
|
||||
<div class="avatar-placeholder">
|
||||
<?= substr($cand['name'], 0, 1) ?>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 700;"><?= htmlspecialchars($cand['name']) ?></div>
|
||||
<div style="font-size: 0.75rem; color: #64748b;"><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></div>
|
||||
<div class="candidate-info">
|
||||
<h3><?= htmlspecialchars($cand['name']) ?></h3>
|
||||
<p><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></p>
|
||||
</div>
|
||||
<div class="check-icon">
|
||||
<i data-lucide="check" style="width: 14px;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@ -114,11 +190,21 @@ $positions = $positions->fetchAll();
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div style="margin-top: 3rem; text-align: center; border-top: 1px solid #e2e8f0; padding-top: 2rem;">
|
||||
<p style="font-size: 0.875rem; color: #64748b; margin-bottom: 1.5rem;">By clicking "Cast My Vote", I acknowledge that my selection is final.</p>
|
||||
<button type="submit" class="btn btn-primary" style="padding: 1rem 3rem; font-size: 1.1rem; background: #1e293b;">Cast My Vote</button>
|
||||
<div class="submit-section animate-fade-in">
|
||||
<h2 style="margin: 0 0 12px 0; font-size: 1.5rem;">Ready to submit?</h2>
|
||||
<p style="margin: 0 0 32px 0; color: #94a3b8; font-size: 1rem;">Please review your selections before casting your vote.</p>
|
||||
<button type="submit" class="btn-submit">
|
||||
Cast My Vote
|
||||
</button>
|
||||
<div style="margin-top: 24px; display: flex; align-items: center; justify-content: center; gap: 8px; color: #64748b; font-size: 0.875rem;">
|
||||
<i data-lucide="shield-check" style="width: 16px; color: #10b981;"></i> Verified Secure Election
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -181,22 +181,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" placeholder="Quick search...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content animate-fade-in">
|
||||
<div class="dashboard-header" style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
@ -346,8 +331,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
</td>
|
||||
<td class="actions-cell">
|
||||
<button title="Edit"><i data-lucide="edit-2"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;"><i data-lucide="trash-2"></i></button>
|
||||
<button title="Edit" onclick='editCandidate(<?= json_encode($cand) ?>)'><i data-lucide="edit-2"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;" onclick="deleteCandidate('<?= $cand['id'] ?>', '<?= htmlspecialchars($cand['user_name']) ?>')"><i data-lucide="trash-2"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -359,6 +344,46 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<div id="editCandidateModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Edit Candidate</h2>
|
||||
<button onclick="closeModal('editCandidateModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<form action="api/update_candidate.php" method="POST">
|
||||
<input type="hidden" name="id" id="edit_cand_id">
|
||||
<div class="form-group">
|
||||
<label>Candidate Name</label>
|
||||
<input type="text" id="edit_cand_name" disabled style="background: #f1f5f9;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Position</label>
|
||||
<select name="position_id" id="edit_cand_position_id" required>
|
||||
<?php foreach ($allPositions as $p): ?>
|
||||
<option value="<?= $p['id'] ?>"><?= htmlspecialchars($p['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Party</label>
|
||||
<select name="party_name" id="edit_cand_party_name">
|
||||
<option value="">Independent</option>
|
||||
<?php foreach ($allParties as $pt): ?>
|
||||
<option value="<?= htmlspecialchars($pt['name']) ?>"><?= htmlspecialchars($pt['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Manifesto / Vision</label>
|
||||
<textarea name="manifesto" id="edit_cand_manifesto" rows="3" placeholder="Enter candidate vision..."></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="closeModal('editCandidateModal')" class="btn-manage">Cancel</button>
|
||||
<button type="submit" class="btn-manage primary">Update Candidate</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addCandidateModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -419,8 +444,11 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
<input type="text" name="name" placeholder="e.g. President, Secretary" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max Votes (Winners)</label>
|
||||
<input type="number" name="max_votes" value="1" min="1" required>
|
||||
<label>Position Type</label>
|
||||
<select name="type" required>
|
||||
<option value="Uniform">Uniform (Global)</option>
|
||||
<option value="Track Specific">Track Specific (Voter Track Only)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onclick="closeModal('addPositionModal')" class="btn-manage">Cancel</button>
|
||||
@ -465,6 +493,21 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
function editCandidate(cand) {
|
||||
document.getElementById('edit_cand_id').value = cand.id;
|
||||
document.getElementById('edit_cand_name').value = cand.user_name;
|
||||
document.getElementById('edit_cand_position_id').value = cand.position_id;
|
||||
document.getElementById('edit_cand_party_name').value = cand.party_name || '';
|
||||
document.getElementById('edit_cand_manifesto').value = cand.manifesto || '';
|
||||
openModal('editCandidateModal');
|
||||
}
|
||||
|
||||
function deleteCandidate(id, name) {
|
||||
if (confirm(`Are you sure you want to remove ${name} from being a candidate?`)) {
|
||||
window.location.href = `api/delete_candidate.php?id=${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.className === 'modal') {
|
||||
event.target.style.display = 'none';
|
||||
|
||||
@ -69,22 +69,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" placeholder="Search for voters, candidates, or records...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content animate-fade-in">
|
||||
<div class="dashboard-header">
|
||||
|
||||
@ -5,6 +5,10 @@ define('DB_NAME', 'app_38458');
|
||||
define('DB_USER', 'app_38458');
|
||||
define('DB_PASS', 'c217529c-a428-4a97-8f31-773c420377a7');
|
||||
|
||||
// Supabase Configuration - Provide your project URL and Service Role Key
|
||||
define('SUPABASE_URL', getenv('SUPABASE_URL') ?: 'https://your-project.supabase.co');
|
||||
define('SUPABASE_SERVICE_ROLE_KEY', getenv('SUPABASE_SERVICE_ROLE_KEY') ?: 'your-service-role-key');
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
|
||||
1
db/migrations/010_add_position_type.sql
Normal file
1
db/migrations/010_add_position_type.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE positions ADD COLUMN type ENUM('Uniform', 'Track Specific') DEFAULT 'Uniform' AFTER name;
|
||||
@ -196,22 +196,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" placeholder="Search for records...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content animate-fade-in">
|
||||
<div class="history-controls">
|
||||
@ -331,6 +316,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
el.classList.add('active');
|
||||
}
|
||||
}
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
69
includes/SupabaseAuth.php
Normal file
69
includes/SupabaseAuth.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
class SupabaseAuth {
|
||||
private static function request(string $method, string $endpoint, array $data = [], bool $useServiceKey = false): array {
|
||||
$url = rtrim(SUPABASE_URL, '/') . $endpoint;
|
||||
$key = $useServiceKey ? SUPABASE_SERVICE_ROLE_KEY : SUPABASE_SERVICE_ROLE_KEY; // Always use service key for admin actions
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'apikey: ' . $key,
|
||||
'Authorization: Bearer ' . $key
|
||||
];
|
||||
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
if (!empty($data)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$decoded = json_decode((string)$response, true);
|
||||
|
||||
return [
|
||||
'status' => $httpCode,
|
||||
'data' => $decoded,
|
||||
'error' => $httpCode >= 400 ? ($decoded['msg'] ?? $decoded['error_description'] ?? 'Unknown error') : null
|
||||
];
|
||||
}
|
||||
|
||||
public static function createUser(string $email, string $password): array {
|
||||
// Use Admin API to create user without email verification
|
||||
return self::request('POST', '/auth/v1/admin/users', [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'email_confirm' => true
|
||||
], true);
|
||||
}
|
||||
|
||||
public static function signIn(string $email, string $password): array {
|
||||
return self::request('POST', '/auth/v1/token?grant_type=password', [
|
||||
'email' => $email,
|
||||
'password' => $password
|
||||
]);
|
||||
}
|
||||
|
||||
public static function updateUserPassword(string $uid, string $password): array {
|
||||
return self::request('PUT', '/auth/v1/admin/users/' . $uid, [
|
||||
'password' => $password
|
||||
], true);
|
||||
}
|
||||
|
||||
public static function getUserByEmail(string $email): ?array {
|
||||
$res = self::request('GET', '/auth/v1/admin/users', [], true);
|
||||
if ($res['status'] === 200 && isset($res['data']['users'])) {
|
||||
foreach ($res['data']['users'] as $user) {
|
||||
if ($user['email'] === $email) return $user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
16
includes/header.php
Normal file
16
includes/header.php
Normal file
@ -0,0 +1,16 @@
|
||||
<header class="top-header">
|
||||
<form action="search_results.php" method="GET" class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" name="q" placeholder="Search for voters, candidates, or records..." value="<?= htmlspecialchars($_GET['q'] ?? '') ?>">
|
||||
</form>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
59
login.php
59
login.php
@ -8,22 +8,48 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$role = $_POST['role'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// We search by student_id and verify email and role match if provided
|
||||
// 1. Find user locally to verify student_id, email and role match
|
||||
$stmt = db()->prepare("SELECT * FROM users WHERE student_id = ? AND email = ? AND role = ?");
|
||||
$stmt->execute([$student_id, $email, $role]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password_hash'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_role'] = $user['role'];
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
if ($user) {
|
||||
// 2. Authenticate with Supabase
|
||||
$auth = SupabaseAuth::signIn($email, $password);
|
||||
|
||||
if ($auth['error']) {
|
||||
// Check if user exists locally with this password but not in Supabase yet
|
||||
if (!empty($user['password_hash']) && password_verify($password, $user['password_hash'])) {
|
||||
// Migrate to Supabase
|
||||
$supabaseUser = SupabaseAuth::createUser($email, $password);
|
||||
if (!$supabaseUser['error']) {
|
||||
$auth = SupabaseAuth::signIn($email, $password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$auth['error']) {
|
||||
// Update supabase_uid if missing
|
||||
if (empty($user['supabase_uid'])) {
|
||||
$supabase_uid = $auth['data']['user']['id'] ?? null;
|
||||
$upd = db()->prepare("UPDATE users SET supabase_uid = ? WHERE id = ?");
|
||||
$upd->execute([$supabase_uid, $user['id']]);
|
||||
}
|
||||
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_role'] = $user['role'];
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
$error = 'Authentication failed: ' . $auth['error'];
|
||||
}
|
||||
} else {
|
||||
$error = 'Invalid Credentials. Please check your UID, Email, and Role.';
|
||||
if (isset($_POST['role'])) { // Detect if coming from landing modal
|
||||
header('Location: index.php?error=' . urlencode($error));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($error && isset($_POST['role'])) { // Redirect back to landing if coming from modal
|
||||
header('Location: index.php?error=' . urlencode($error));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -49,6 +75,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<label class="form-label">Student ID (XX-XXXX)</label>
|
||||
<input type="text" name="student_id" class="form-control" placeholder="00-0000" required pattern="\d{2}-\d{4}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email Account</label>
|
||||
<input type="email" name="email" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Role</label>
|
||||
<select name="role" class="form-select">
|
||||
<option value="Voter">Voter</option>
|
||||
<option value="Officer">Officer</option>
|
||||
<option value="Adviser">Adviser</option>
|
||||
<option value="Admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
|
||||
@ -45,22 +45,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" placeholder="Search officers...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content animate-fade-in">
|
||||
<div class="dashboard-header">
|
||||
@ -81,27 +66,36 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
<i data-lucide="plus-circle" style="width: 20px; color: #2563eb;"></i>
|
||||
Assign New Officer to Election
|
||||
</div>
|
||||
<form class="registration-form">
|
||||
<form class="registration-form" action="api/add_officer.php" method="POST">
|
||||
<input type="hidden" name="election_id" value="<?= $electionId ?>">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Full Name</label>
|
||||
<input type="text" placeholder="Enter name">
|
||||
<input type="text" name="name" placeholder="Enter name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Student ID / Personnel ID</label>
|
||||
<input type="text" name="student_id" placeholder="XX-XXXX" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="email" placeholder="email@school.edu">
|
||||
<input type="email" name="email" placeholder="email@school.edu" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Role</label>
|
||||
<select>
|
||||
<select name="role" required>
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="Adviser">Adviser</option>
|
||||
<option value="Officer">Officer</option>
|
||||
<option value="Officer" selected>Officer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<input type="text" name="password" value="iloilohns" required>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
||||
<button type="button" class="btn-save-officer">
|
||||
<button type="submit" class="btn-save-officer">
|
||||
<i data-lucide="save" style="width: 18px;"></i>
|
||||
ASSIGN TO ELECTION
|
||||
</button>
|
||||
@ -131,7 +125,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
</div>
|
||||
<div class="officer-actions">
|
||||
<button title="Edit"><i data-lucide="edit-3" style="width: 16px;"></i></button>
|
||||
<button title="Edit" onclick='editOfficer(<?= json_encode($o) ?>)'><i data-lucide="edit-3" style="width: 16px;"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;" onclick="deleteOfficer('<?= $o['id'] ?>', '<?= htmlspecialchars($o['name']) ?>')"><i data-lucide="trash-2" style="width: 16px;"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -161,7 +156,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
</div>
|
||||
<div class="officer-actions">
|
||||
<button title="Edit"><i data-lucide="edit-3" style="width: 16px;"></i></button>
|
||||
<button title="Edit" onclick='editOfficer(<?= json_encode($o) ?>)'><i data-lucide="edit-3" style="width: 16px;"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;" onclick="deleteOfficer('<?= $o['id'] ?>', '<?= htmlspecialchars($o['name']) ?>')"><i data-lucide="trash-2" style="width: 16px;"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -191,7 +187,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
</div>
|
||||
<div class="officer-actions">
|
||||
<button title="Edit"><i data-lucide="edit-3" style="width: 16px;"></i></button>
|
||||
<button title="Edit" onclick='editOfficer(<?= json_encode($o) ?>)'><i data-lucide="edit-3" style="width: 16px;"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;" onclick="deleteOfficer('<?= $o['id'] ?>', '<?= htmlspecialchars($o['name']) ?>')"><i data-lucide="trash-2" style="width: 16px;"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -204,8 +201,73 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Edit Officer Modal -->
|
||||
<div id="editOfficerModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
|
||||
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);">
|
||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
|
||||
<h2 style="margin:0; font-size:1.25rem; color:#1e293b;">Edit Officer</h2>
|
||||
<button onclick="closeModal('editOfficerModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<form action="api/update_officer.php" method="POST">
|
||||
<input type="hidden" name="id" id="edit_officer_id">
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">FULL NAME</label>
|
||||
<input type="text" name="name" id="edit_officer_name" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">EMAIL ADDRESS</label>
|
||||
<input type="email" name="email" id="edit_officer_email" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">ROLE</label>
|
||||
<select name="role" id="edit_officer_role" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="Adviser">Adviser</option>
|
||||
<option value="Officer">Officer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:24px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">NEW PASSWORD (OPTIONAL)</label>
|
||||
<input type="password" name="password" placeholder="Leave blank to keep current" style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div style="display:flex; justify-content:flex-end; gap:12px;">
|
||||
<button type="button" onclick="closeModal('editOfficerModal')" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
|
||||
<button type="submit" style="padding:10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Update Officer</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
|
||||
function openModal(id) {
|
||||
document.getElementById(id).style.display = 'flex';
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
function editOfficer(officer) {
|
||||
document.getElementById('edit_officer_id').value = officer.id;
|
||||
document.getElementById('edit_officer_name').value = officer.name;
|
||||
document.getElementById('edit_officer_email').value = officer.email;
|
||||
document.getElementById('edit_officer_role').value = officer.role;
|
||||
openModal('editOfficerModal');
|
||||
}
|
||||
|
||||
function deleteOfficer(id, name) {
|
||||
if (confirm(`Are you sure you want to remove ${name} from this election?`)) {
|
||||
window.location.href = `api/delete_officer.php?id=${id}&election_id=<?= $electionId ?>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.classList.contains('modal')) {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -53,22 +53,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" placeholder="Quick search logs...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content animate-fade-in">
|
||||
<div class="dashboard-header">
|
||||
@ -85,19 +70,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Filters & Table Section -->
|
||||
<div class="content-section animate-fade-in">
|
||||
<form method="GET" class="filter-bar">
|
||||
<div class="filter-group" style="flex: 2;">
|
||||
<label>SEARCH LOGS</label>
|
||||
<div class="search-input-wrapper">
|
||||
<i data-lucide="search" style="width: 14px; color: #94a3b8;"></i>
|
||||
<input type="text" name="search" value="<?= htmlspecialchars($search) ?>" placeholder="Search by action, user, or details">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 12px; align-items: flex-end;">
|
||||
<button type="submit" class="btn-manage primary">Filter</button>
|
||||
<button type="button" class="btn-manage" onclick="window.print()"><i data-lucide="printer"></i> Print</button>
|
||||
</div>
|
||||
</form>
|
||||
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 24px;">
|
||||
<button type="button" class="btn-manage" onclick="window.print()"><i data-lucide="printer"></i> Print</button>
|
||||
</div>
|
||||
|
||||
<table class="audit-table">
|
||||
<thead>
|
||||
|
||||
148
search_results.php
Normal file
148
search_results.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
require_once 'auth_helper.php';
|
||||
require_login();
|
||||
$user = get_user();
|
||||
|
||||
$pdo = db();
|
||||
$electionId = get_active_election_id();
|
||||
$query = $_GET['q'] ?? '';
|
||||
|
||||
$voters = [];
|
||||
$candidates = [];
|
||||
$logs = [];
|
||||
|
||||
if ($query) {
|
||||
$search = "%$query%";
|
||||
|
||||
// Search Voters
|
||||
$stmt = $pdo->prepare("SELECT u.* FROM users u
|
||||
JOIN election_assignments ea ON u.id = ea.user_id
|
||||
WHERE ea.election_id = ? AND ea.role_in_election = 'Voter'
|
||||
AND (u.name LIKE ? OR u.email LIKE ? OR u.student_id LIKE ?)");
|
||||
$stmt->execute([$electionId, $search, $search, $search]);
|
||||
$voters = $stmt->fetchAll();
|
||||
|
||||
// Search Candidates
|
||||
$stmt = $pdo->prepare("SELECT c.*, u.name as user_name, u.student_id
|
||||
FROM candidates c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
WHERE c.election_id = ?
|
||||
AND (u.name LIKE ? OR c.position LIKE ? OR c.platform LIKE ?)");
|
||||
$stmt->execute([$electionId, $search, $search, $search]);
|
||||
$candidates = $stmt->fetchAll();
|
||||
|
||||
// Search Records (Audit Logs)
|
||||
$stmt = $pdo->prepare("SELECT l.*, u.name as user_name
|
||||
FROM audit_logs l
|
||||
LEFT JOIN users u ON l.user_id = u.id
|
||||
WHERE (l.election_id = ? OR l.election_id IS NULL)
|
||||
AND (l.action LIKE ? OR l.details LIKE ? OR u.name LIKE ?)");
|
||||
$stmt->execute([$electionId, $search, $search, $search]);
|
||||
$logs = $stmt->fetchAll();
|
||||
}
|
||||
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System for Senior High School';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Search Results | <?= htmlspecialchars($projectDescription) ?></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<style>
|
||||
.search-results-container { padding: 24px; }
|
||||
.result-section { margin-bottom: 40px; }
|
||||
.result-section h2 { font-size: 1.25rem; color: #1e293b; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
|
||||
.result-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
|
||||
.result-card { background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; transition: transform 0.2s; }
|
||||
.result-card:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); }
|
||||
.result-title { font-weight: 600; color: #1e293b; margin-bottom: 4px; }
|
||||
.result-meta { font-size: 0.875rem; color: #64748b; }
|
||||
.no-results { padding: 40px; text-align: center; color: #94a3b8; background: #f8fafc; border-radius: 12px; border: 2px dashed #e2e8f0; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<?php require_once 'includes/sidebar.php'; ?>
|
||||
<div class="main-wrapper">
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content">
|
||||
<div class="search-results-container">
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h1 style="font-size: 1.875rem; color: #1e293b;">Search Results</h1>
|
||||
<p style="color: #64748b;">Showing results for "<strong><?= htmlspecialchars($query) ?></strong>"</p>
|
||||
</div>
|
||||
|
||||
<?php if (!$query): ?>
|
||||
<div class="no-results">
|
||||
<i data-lucide="search" style="width: 48px; height: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
|
||||
<p>Enter a search term to find voters, candidates, or records.</p>
|
||||
</div>
|
||||
<?php elseif (empty($voters) && empty($candidates) && empty($logs)): ?>
|
||||
<div class="no-results">
|
||||
<i data-lucide="frown" style="width: 48px; height: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
|
||||
<p>No results found for "<?= htmlspecialchars($query) ?>".</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php if (!empty($voters)): ?>
|
||||
<div class="result-section">
|
||||
<h2><i data-lucide="users"></i> Voters (<?= count($voters) ?>)</h2>
|
||||
<div class="result-grid">
|
||||
<?php foreach ($voters as $v): ?>
|
||||
<div class="result-card">
|
||||
<div class="result-title"><?= htmlspecialchars($v['name']) ?></div>
|
||||
<div class="result-meta"><?= htmlspecialchars($v['student_id']) ?> • <?= htmlspecialchars($v['email']) ?></div>
|
||||
<div style="margin-top: 12px;">
|
||||
<a href="voter_management.php?search=<?= urlencode($v['student_id']) ?>" style="font-size: 12px; color: #4f46e5; font-weight: 500; text-decoration: none;">View in Voters List →</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($candidates)): ?>
|
||||
<div class="result-section">
|
||||
<h2><i data-lucide="user-square"></i> Candidates (<?= count($candidates) ?>)</h2>
|
||||
<div class="result-grid">
|
||||
<?php foreach ($candidates as $c): ?>
|
||||
<div class="result-card">
|
||||
<div class="result-title"><?= htmlspecialchars($c['user_name']) ?></div>
|
||||
<div class="result-meta"><?= htmlspecialchars($c['position']) ?> • <?= htmlspecialchars($c['student_id']) ?></div>
|
||||
<div style="margin-top: 12px;">
|
||||
<a href="candidate_management.php?search=<?= urlencode($c['user_name']) ?>" style="font-size: 12px; color: #4f46e5; font-weight: 500; text-decoration: none;">View in Candidates →</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($logs)): ?>
|
||||
<div class="result-section">
|
||||
<h2><i data-lucide="file-text"></i> Audit Records (<?= count($logs) ?>)</h2>
|
||||
<div class="result-grid">
|
||||
<?php foreach ($logs as $l): ?>
|
||||
<div class="result-card">
|
||||
<div class="result-title"><?= htmlspecialchars($l['action']) ?></div>
|
||||
<div class="result-meta"><?= date('M d, Y H:i', strtotime($l['created_at'])) ?> by <?= htmlspecialchars($l['user_name'] ?? 'SYSTEM') ?></div>
|
||||
<div style="margin-top: 8px; font-size: 13px; color: #64748b; line-height: 1.5;">
|
||||
<?= htmlspecialchars($l['details']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script>lucide.createIcons();</script>
|
||||
</body>
|
||||
</html>
|
||||
19
signup.php
19
signup.php
@ -14,10 +14,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$error = 'Invalid Student ID format. Use XX-XXXX.';
|
||||
} else {
|
||||
try {
|
||||
// 1. Create user in Supabase
|
||||
$supabaseUser = SupabaseAuth::createUser($email, $password);
|
||||
$supabase_uid = null;
|
||||
if ($supabaseUser['error']) {
|
||||
if (str_contains(strtolower($supabaseUser['error']), 'already registered')) {
|
||||
$sbUser = SupabaseAuth::getUserByEmail($email);
|
||||
$supabase_uid = $sbUser['id'] ?? null;
|
||||
} else {
|
||||
throw new Exception("Supabase Error: " . $supabaseUser['error']);
|
||||
}
|
||||
} else {
|
||||
$supabase_uid = $supabaseUser['data']['id'] ?? null;
|
||||
}
|
||||
|
||||
$id = uuid();
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = db()->prepare("INSERT INTO users (id, student_id, name, email, password_hash, role) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$id, $student_id, $name, $email, $hash, $role]);
|
||||
$stmt = db()->prepare("INSERT INTO users (id, supabase_uid, student_id, name, email, role) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$id, $supabase_uid, $student_id, $name, $email, $role]);
|
||||
|
||||
$_SESSION['user_id'] = $id;
|
||||
$_SESSION['user_role'] = $role;
|
||||
|
||||
@ -58,7 +58,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<?php if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])): ?>
|
||||
<button class="btn btn-outline">Edit Info</button>
|
||||
<button class="btn btn-outline" onclick="document.getElementById('editElectionModal').style.display='flex'">Edit Info</button>
|
||||
<?php if ($election['status'] === 'Preparing'): ?>
|
||||
<form action="api/update_election_status.php" method="POST">
|
||||
<input type="hidden" name="id" value="<?= $election['id'] ?>">
|
||||
@ -165,5 +165,50 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editElectionModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
|
||||
<div class="card" style="width: 100%; max-width: 500px; padding: 2rem;">
|
||||
<h2 style="margin-top: 0; font-size: 1.25rem;">Edit Election</h2>
|
||||
<form action="api/update_election.php" method="POST">
|
||||
<input type="hidden" name="id" value="<?= $election['id'] ?>">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Election Title</label>
|
||||
<input type="text" name="title" class="form-control" value="<?= htmlspecialchars($election['title']) ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($election['description']) ?></textarea>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Start Date</label>
|
||||
<input type="datetime-local" name="start_date" class="form-control" value="<?= date('Y-m-d\TH:i', strtotime($election['start_date_and_time'])) ?>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">End Date</label>
|
||||
<input type="datetime-local" name="end_date" class="form-control" value="<?= date('Y-m-d\TH:i', strtotime($election['end_date_and_time'])) ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<button type="submit" class="btn btn-primary" style="flex: 1;">Update</button>
|
||||
<button type="button" onclick="document.getElementById('editElectionModal').style.display='none'" class="btn btn-outline" style="flex: 1;">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.modal { display: flex; }
|
||||
.form-control { width: 100%; padding: 0.5rem; border: 1px solid var(--border-color); border-radius: var(--radius); margin-top: 0.25rem; }
|
||||
.form-group { margin-bottom: 1rem; }
|
||||
.form-label { font-size: 0.75rem; font-weight: 600; color: var(--text-muted); }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
window.onclick = function(event) {
|
||||
if (event.target.id === 'editElectionModal') {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -88,24 +88,28 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<div class="search-bar">
|
||||
<i data-lucide="search" style="width: 16px; color: #94a3b8;"></i>
|
||||
<input type="text" placeholder="Quick search...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="user-name"><?= htmlspecialchars($user['name'] ?? 'System Administrator') ?></div>
|
||||
<div class="user-role"><?= htmlspecialchars($user['role'] ?? 'Admin') ?></div>
|
||||
</div>
|
||||
<div class="user-avatar">
|
||||
<?= strtoupper(substr($user['name'] ?? 'S', 0, 1)) ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php require_once 'includes/header.php'; ?>
|
||||
|
||||
<main class="dashboard-content animate-fade-in">
|
||||
<?php if (isset($_GET['success'])): ?>
|
||||
<?php if ($_GET['success'] === 'voter_added'): ?>
|
||||
<div style="background: #ecfdf5; color: #065f46; padding: 16px; border-radius: 12px; border: 1px solid #a7f3d0; margin-bottom: 24px; display: flex; align-items: center; gap: 12px;">
|
||||
<i data-lucide="check-circle" style="width: 20px;"></i>
|
||||
<span style="font-weight: 500;">Voter successfully registered!</span>
|
||||
</div>
|
||||
<?php elseif ($_GET['success'] === 'import_complete'): ?>
|
||||
<div style="background: #ecfdf5; color: #065f46; padding: 16px; border-radius: 12px; border: 1px solid #a7f3d0; margin-bottom: 24px; display: flex; align-items: center; gap: 12px;">
|
||||
<i data-lucide="check-circle" style="width: 20px;"></i>
|
||||
<div>
|
||||
<span style="font-weight: 600; display: block;">Import completed successfully!</span>
|
||||
<span style="font-size: 0.875rem;">
|
||||
<?= (int)$_GET['imported'] ?> new voters added, <?= (int)$_GET['updated'] ?> existing records updated.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="dashboard-header">
|
||||
<div style="display: flex; align-items: center; gap: 16px;">
|
||||
<div class="header-icon-container">
|
||||
@ -206,8 +210,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</span>
|
||||
</td>
|
||||
<td class="actions-cell">
|
||||
<button title="Edit"><i data-lucide="edit-2"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;"><i data-lucide="trash-2"></i></button>
|
||||
<button title="Edit" onclick='editVoter(<?= json_encode($voter) ?>)'><i data-lucide="edit-2"></i></button>
|
||||
<button title="Delete" style="color: #ef4444;" onclick="deleteVoter('<?= $voter['id'] ?>', '<?= htmlspecialchars($voter['name']) ?>')"><i data-lucide="trash-2"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -218,24 +222,157 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Modals (Simplified for this polishing phase) -->
|
||||
<div id="addVoterModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Add New Voter</h2>
|
||||
<!-- Modals -->
|
||||
<div id="addVoterModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
|
||||
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px;">
|
||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
|
||||
<h2 style="margin:0; font-size:1.25rem;">Register New Voter</h2>
|
||||
<button onclick="closeModal('addVoterModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<p style="font-size: 0.875rem; color: #64748b;">This will assign an existing user or create a new one for this election.</p>
|
||||
<!-- Add Voter form logic here -->
|
||||
<div class="modal-footer">
|
||||
<button onclick="closeModal('addVoterModal')" class="btn-cancel">Close</button>
|
||||
</div>
|
||||
<form action="api/add_voter.php" method="POST">
|
||||
<input type="hidden" name="election_id" value="<?= $electionId ?>">
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">FULL NAME</label>
|
||||
<input type="text" name="name" placeholder="Enter student's full name" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">STUDENT ID</label>
|
||||
<input type="text" name="student_id" placeholder="XX-XXXX" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">EMAIL ADDRESS</label>
|
||||
<input type="email" name="email" placeholder="student@school.edu" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">PASSWORD</label>
|
||||
<input type="text" name="password" value="iloilohns" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<small style="color: #64748b; font-size: 11px;">Default is iloilohns</small>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px; margin-bottom:24px;">
|
||||
<div class="form-group">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">TRACK</label>
|
||||
<select name="track" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<option value="STEM">STEM</option>
|
||||
<option value="ABM">ABM</option>
|
||||
<option value="HUMSS">HUMSS</option>
|
||||
<option value="GAS">GAS</option>
|
||||
<option value="TVL">TVL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">GRADE LEVEL</label>
|
||||
<select name="grade_level" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<option value="11">Grade 11</option>
|
||||
<option value="12">Grade 12</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="display:flex; justify-content:flex-end; gap:12px;">
|
||||
<button type="button" onclick="closeModal('addVoterModal')" class="btn-cancel" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
|
||||
<button type="submit" class="btn-action btn-add" style="padding:10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Register Voter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="editVoterModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
|
||||
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px;">
|
||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
|
||||
<h2 style="margin:0; font-size:1.25rem;">Edit Voter</h2>
|
||||
<button onclick="closeModal('editVoterModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<form action="api/update_voter.php" method="POST">
|
||||
<input type="hidden" name="id" id="edit_voter_id">
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">FULL NAME</label>
|
||||
<input type="text" name="name" id="edit_voter_name" placeholder="Enter student's full name" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">STUDENT ID</label>
|
||||
<input type="text" name="student_id" id="edit_voter_student_id" placeholder="XX-XXXX" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">EMAIL ADDRESS</label>
|
||||
<input type="email" name="email" id="edit_voter_email" placeholder="student@school.edu" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:16px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">NEW PASSWORD (OPTIONAL)</label>
|
||||
<input type="password" name="password" placeholder="Leave blank to keep current" style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px; margin-bottom:24px;">
|
||||
<div class="form-group">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">TRACK</label>
|
||||
<select name="track" id="edit_voter_track" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<option value="STEM">STEM</option>
|
||||
<option value="ABM">ABM</option>
|
||||
<option value="HUMSS">HUMSS</option>
|
||||
<option value="GAS">GAS</option>
|
||||
<option value="TVL">TVL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:6px;">GRADE LEVEL</label>
|
||||
<select name="grade_level" id="edit_voter_grade_level" required style="width:100%; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
|
||||
<option value="11">Grade 11</option>
|
||||
<option value="12">Grade 12</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="display:flex; justify-content:flex-end; gap:12px;">
|
||||
<button type="button" onclick="closeModal('editVoterModal')" class="btn-cancel" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
|
||||
<button type="submit" class="btn-action btn-add" style="padding:10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Update Voter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="importModal" class="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center;">
|
||||
<div class="modal-content" style="background:white; padding:32px; border-radius:16px; width:100%; max-width:500px;">
|
||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px;">
|
||||
<h2 style="margin:0; font-size:1.25rem;">Import Voters from CSV</h2>
|
||||
<button onclick="closeModal('importModal')" style="border:none; background:none; cursor:pointer;"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<form action="api/import_voters.php" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="election_id" value="<?= $electionId ?>">
|
||||
<div class="form-group" style="margin-bottom:24px;">
|
||||
<label style="display:block; font-size:12px; font-weight:600; color:#64748b; margin-bottom:12px;">SELECT CSV FILE</label>
|
||||
<div style="border: 2px dashed #e2e8f0; padding: 32px; border-radius: 12px; text-align: center; cursor: pointer;" onclick="document.getElementById('csvFile').click()">
|
||||
<i data-lucide="upload-cloud" style="width: 32px; height: 32px; color: #94a3b8; margin-bottom: 12px;"></i>
|
||||
<p style="margin: 0; font-size: 0.875rem; color: #64748b;">Click to upload or drag and drop</p>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.75rem; color: #94a3b8;">CSV files only (Max 5MB)</p>
|
||||
<input type="file" id="csvFile" name="csv_file" accept=".csv" style="display: none;" onchange="updateFileName(this)">
|
||||
<div id="fileName" style="margin-top: 12px; font-weight: 500; color: #4f46e5; font-size: 0.875rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: #f8fafc; padding: 16px; border-radius: 8px; margin-bottom: 24px;">
|
||||
<p style="margin: 0 0 8px 0; font-size: 0.75rem; font-weight: 600; color: #64748b;">CSV FORMAT REQUIREMENTS:</p>
|
||||
<p style="margin: 0; font-size: 0.75rem; color: #64748b; line-height: 1.5;">
|
||||
Columns: <code>student_id, name, email, track, grade_level</code><br>
|
||||
Example: <code>20-1234, John Doe, john@example.com, STEM, 12</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="display:flex; justify-content:flex-end; gap:12px;">
|
||||
<button type="button" onclick="closeModal('importModal')" class="btn-cancel" style="padding:10px 20px; border-radius:8px; border:1px solid #e2e8f0; background:white; cursor:pointer;">Cancel</button>
|
||||
<button type="submit" class="btn-action btn-add" style="padding: 10px 24px; border-radius:8px; border:none; background:#4f46e5; color:white; cursor:pointer; font-weight:600;">Start Import</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
|
||||
function updateFileName(input) {
|
||||
const fileNameDisplay = document.getElementById('fileName');
|
||||
if (input.files && input.files.length > 0) {
|
||||
fileNameDisplay.textContent = input.files[0].name;
|
||||
} else {
|
||||
fileNameDisplay.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(id) {
|
||||
document.getElementById(id).style.display = 'flex';
|
||||
}
|
||||
@ -244,6 +381,22 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
function editVoter(voter) {
|
||||
document.getElementById('edit_voter_id').value = voter.id;
|
||||
document.getElementById('edit_voter_name').value = voter.name;
|
||||
document.getElementById('edit_voter_student_id').value = voter.student_id;
|
||||
document.getElementById('edit_voter_email').value = voter.email;
|
||||
document.getElementById('edit_voter_track').value = voter.track;
|
||||
document.getElementById('edit_voter_grade_level').value = voter.grade_level;
|
||||
openModal('editVoterModal');
|
||||
}
|
||||
|
||||
function deleteVoter(id, name) {
|
||||
if (confirm(`Are you sure you want to remove ${name} from this election?`)) {
|
||||
window.location.href = `api/delete_voter.php?id=${id}&election_id=<?= $electionId ?>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.classList.contains('modal')) {
|
||||
event.target.style.display = 'none';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user