diff --git a/README.md b/README.md index 14df7392..dcab1c4f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ #### A real-time map-based reporting system for campus infrastructure issues, built to improve visibility, accountability, and resolution efficiency. [![status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/xodivorce/infra-xodivorce-in/) -[![version](https://img.shields.io/badge/version-v1.3.3-yellow.svg?style=flat)](https://github.com/xodivorce/infra-xodivorce-in/) +[![version](https://img.shields.io/badge/version-v1.4.0-yellow.svg?style=flat)](https://github.com/xodivorce/infra-xodivorce-in/) [![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/xodivorce/infra-xodivorce-in/) > **🥰 Like this project? Please consider giving it a Star (🌟) on GitHub to show us your appreciation. Thank you!** diff --git a/src.zip b/src.zip deleted file mode 100644 index 968b49a9..00000000 Binary files a/src.zip and /dev/null differ diff --git a/src/assets/_sidebar.php b/src/assets/_sidebar.php index cbfe548a..d11690f4 100755 --- a/src/assets/_sidebar.php +++ b/src/assets/_sidebar.php @@ -43,26 +43,26 @@ $inactiveClass = 'text-neutral-400 hover:text-white hover:bg-neutral-800/60 bord Admin Overview - - - - - Activity Logs - - - - + - System Admin + Admin Panel + + + + + + + System Logs diff --git a/src/assets/admin/_admin.php b/src/assets/admin/_admin.php deleted file mode 100755 index bc4934e3..00000000 --- a/src/assets/admin/_admin.php +++ /dev/null @@ -1,438 +0,0 @@ -
-
-

Admin Panel

-

System management and administrative controls

-
- -
-
-
-
-

Total Users

-

1,247

-
-
- - - - -
-
-

Active: 1,189 | Suspended: 58

-
- -
-
-
-

Pending Reports

-

34

-
-
- - - - -
-
-

High: 8 | Medium: 15 | Low: 11

-
- -
-
-
-

Active Incidents

-

7

-
-
- - - - -
-
-

Critical: 2 | Warning: 5

-
- -
-
-
-

System Health

-

98.5%

-
-
- - - -
-
-

All services operational

-
-
- -
-
-

User Management

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- User ID - Name - Email - Role - Status - Actions
#1001John Andersonjohn.anderson@company.com - Admin - - Active - - - -
#1002Sarah Mitchellsarah.mitchell@company.com - Moderator - - Active - - - -
#1003Michael Chenmichael.chen@company.com - User - - Active - - - -
#1004Emily Rodriguezemily.rodriguez@company.com - - User - - Suspended - - - -
#1005David Thompsondavid.thompson@company.com - Moderator - - Active - - - -
-
-
- -
-
-

Report Moderation

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Report ID - Title - Submitted By - Priority - Status - Actions
#2045Database Connection Issuesalex.kim@company.com - High - - Pending - - - - -
#2044API Response Time Degradationmaria.santos@company.com - Medium - - Pending - - - - -
#2043UI Rendering Bugjames.wilson@company.com - Low - - Pending - - - - -
#2042Memory Leak in Productionlisa.park@company.com - High - - Pending - - - - -
-
-
- -
-
-

System Controls

-
-
-
-
-

Maintenance Mode

-

Enable maintenance mode to perform system updates

-
- -
- -
- - - - - -
-
-
- - - - -
diff --git a/src/assets/admin/_admin_panel.php b/src/assets/admin/_admin_panel.php new file mode 100755 index 00000000..5d95e30d --- /dev/null +++ b/src/assets/admin/_admin_panel.php @@ -0,0 +1,516 @@ +Access Denied"; + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update_status') { + $report_id = intval($_POST['report_id']); + $new_status = $_POST['new_status']; + $allowed_statuses = ['Opened', 'In Progress', 'Resolved']; + + if (in_array($new_status, $allowed_statuses)) { + $stmt = $conn->prepare("UPDATE reports SET status = ? WHERE id = ?"); + $stmt->bind_param("si", $new_status, $report_id); + if ($stmt->execute()) { + echo ""; + exit; + } + $stmt->close(); + } +} + +$sql_users = "SELECT COUNT(*) as total FROM users"; +$total_users = $conn->query($sql_users)->fetch_assoc()['total'] ?? 0; + +$sql_active = "SELECT COUNT(DISTINCT user_id) as active FROM reports"; +$active_users = $conn->query($sql_active)->fetch_assoc()['active'] ?? 0; + +$sql_pending = "SELECT COUNT(*) as pending FROM reports WHERE status = 'Opened'"; +$pending_reports = $conn->query($sql_pending)->fetch_assoc()['pending'] ?? 0; + +$sql_resolved = "SELECT COUNT(*) as resolved FROM reports WHERE status = 'Resolved'"; +$resolved_reports = $conn->query($sql_resolved)->fetch_assoc()['resolved'] ?? 0; + +$sql_reports = "SELECT r.id, r.title, r.category, r.priority, r.status, r.created_at, r.location, r.image_path, u.username as user + FROM reports r + JOIN users u ON r.user_id = u.id + ORDER BY r.created_at DESC"; +$result = $conn->query($sql_reports); + +$reports = []; +if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $reports[] = [ + 'id' => $row['id'], + 'title' => htmlspecialchars($row['title']), + 'user' => htmlspecialchars($row['user']), + 'category' => htmlspecialchars($row['category']), + 'priority' => htmlspecialchars($row['priority']), + 'status' => htmlspecialchars($row['status']), + 'date' => $row['created_at'], + 'image' => htmlspecialchars($row['image_path']), + 'location' => htmlspecialchars($row['location']) + ]; + } +} +?> + +
+
+

Admin Panel

+

System management and administrative controls

+
+ +
+
+
+
+

Total Users

+

+
+
+ + + +
+
+

Registered accounts

+
+ +
+
+
+

Active Reporters

+

+
+
+ + + +
+
+

Users who submitted reports

+
+ +
+
+
+

Pending Issues

+

+
+
+ + + +
+
+

Requires attention

+
+ +
+
+
+

Issues Resolved

+

+
+
+ + + +
+
+

Successfully closed reports

+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+

Report Moderation

+

Manage facility and infrastructure issues

+
+
+ +
+
+
+ + + + +
+ +
+
+ + + + +
+ +
+ + + + +
+
+
+
+ + +
+ + +
+
+ + \ No newline at end of file diff --git a/src/assets/admin/_activity_logs.php b/src/assets/admin/_system_logs.php similarity index 100% rename from src/assets/admin/_activity_logs.php rename to src/assets/admin/_system_logs.php diff --git a/src/assets/js/dashboard_config.js b/src/assets/js/dashboard_config.js index 2680c9a5..8da1f229 100755 --- a/src/assets/js/dashboard_config.js +++ b/src/assets/js/dashboard_config.js @@ -152,6 +152,24 @@ function askAboutReport(id, title, category) { } } +function askAboutReportAdmin(id, title, category, priority, status) { + switchTab("gemini"); + const inputField = document.getElementById("gemini-prompt"); + + if (inputField) { + inputField.value = `Regarding Report #${id} ("${title}") in category "${category}": + Provide a quick internal steps to resolve or debug this issue. + Structure your answer with these exact headers (keep bullet points short and technical): + + 1. Immediate Actions to take on the issue + 2. Debug / Troubleshooting Steps`; + + inputField.style.height = "auto"; + inputField.style.height = inputField.scrollHeight + "px"; + inputField.focus(); + } +} + function validateFile(input) { const file = input.files[0]; const labelMain = document.getElementById("file-label-main"); diff --git a/src/core/actions/admin_gemini.php b/src/core/actions/admin_gemini.php new file mode 100644 index 00000000..0cc26f3f --- /dev/null +++ b/src/core/actions/admin_gemini.php @@ -0,0 +1,205 @@ + 'Admin access only']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + http_response_code(405); + echo json_encode(['error' => 'Method not allowed']); + exit; +} + +$raw = file_get_contents('php://input'); +$data = json_decode($raw, true); +$prompt = trim($data['prompt'] ?? ''); + +if ($prompt === '') { + http_response_code(400); + echo json_encode(['error' => 'Prompt required']); + exit; +} + +$apiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY'); +if (!$apiKey) { + http_response_code(500); + echo json_encode(['error' => 'Gemini API key missing']); + exit; +} + +/* + System prompt: strict admin role. Instruct model to output: + 1) Plain readable analysis + 2) A JSON block STRICTLY wrapped between ###JSON_START### and ###JSON_END### + with a defined schema so server can parse reliably. + 3) A YouTube search query + suggested video title. + 4) The mandatory footer contact lines. +*/ +$domain = $_ENV['DOMAIN'] ?? 'YourCampus'; +$app_name = $_ENV['APP_NAME'] ?? 'Campus Dashboard'; +$helpEmail = $_ENV['IT_HELPDESK_EMAIL'] ?? 'it@example.com'; +$helpPhone = $_ENV['IT_HELPDESK_PHONE'] ?? '+000'; +$secEmail = $_ENV['SECURITY_EMAIL'] ?? 'security@example.com'; +$secPhone = $_ENV['SECURITY_PHONE'] ?? '+000'; +$healthEmail = $_ENV['HEALTH_EMAIL'] ?? 'health@example.com'; +$healthPhone = $_ENV['HEALTH_PHONE'] ?? '+000'; +$mgmtEmail = $_ENV['MANAGEMENT_EMAIL'] ?? 'mgmt@example.com'; +$mgmtPhone = $_ENV['MANAGEMENT_PHONE'] ?? '+000'; +$libEmail = $_ENV['LIBRARY_EMAIL'] ?? 'library@example.com'; +$libPhone = $_ENV['LIBRARY_PHONE'] ?? '+000'; + + +$systemPrompt = << "I cannot assist with that. I am designed to help with campus facility and infrastructure issues only." + +--- +### RESPONSE GUIDELINES: +- Keep answers concise and professional +- Use bullet points +- Do NOT reference external websites, except youtube.com for video suggestions. +- Always provide a YouTube search query and suggested video title for further learning. +- Do NOT invent policies, departments, or contacts + +### MANDATORY FOOTER (STRICT): +At the very end of EVERY response, you MUST do the following IN ORDER: + +1. Identify the issue category +2. Provide a Suggested YouTube Search Query as . Do NOT fabricate direct links. +PROMPT; + +$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={$apiKey}"; + +$payload = [ + 'systemInstruction' => [ + 'parts' => [['text' => $systemPrompt]] + ], + 'contents' => [ + [ + 'role' => 'user', + 'parts' => [['text' => $prompt]] + ] + ], + 'generationConfig' => [ + 'temperature' => 0.25, + 'maxOutputTokens' => 700 + ] +]; + +$ch = curl_init($url); +curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_TIMEOUT => 30 +]); + +$response = curl_exec($ch); +$curlError = curl_error($ch); +curl_close($ch); + +if ($curlError) { + http_response_code(500); + echo json_encode(['error' => 'Connection failed', 'details' => $curlError]); + exit; +} + +$result = json_decode($response, true); +if ($result === null) { + // invalid JSON from API + http_response_code(500); + echo json_encode(['error' => 'Invalid API response', 'raw' => $response]); + exit; +} + +if (isset($result['error'])) { + http_response_code($result['error']['code'] ?? 500); + echo json_encode([ + 'error' => $result['error'], + 'raw_response' => $result + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} + + +/* Merge all text parts reliably */ +$fullText = ''; +if (!empty($result['candidates'])) { + foreach ($result['candidates'] as $cand) { + if (!empty($cand['content']['parts'])) { + foreach ($cand['content']['parts'] as $part) { + if (!empty($part['text'])) $fullText .= $part['text'] . "\n"; + } + } + } +} +$fullText = trim($fullText); + +if ($fullText === '') { + http_response_code(500); + echo json_encode(['error' => 'Gemini returned empty response', 'debug' => $result]); + exit; +} + +/* Try to extract the structured JSON between markers ###JSON_START### ... ###JSON_END### */ +$structured = null; +$jsonStart = strpos($fullText, '###JSON_START###'); +$jsonEnd = strpos($fullText, '###JSON_END###'); + +if ($jsonStart !== false && $jsonEnd !== false && $jsonEnd > $jsonStart) { + $jsonBlock = substr($fullText, $jsonStart + strlen('###JSON_START###'), $jsonEnd - ($jsonStart + strlen('###JSON_START###'))); + $jsonBlock = trim($jsonBlock); + + // try to json_decode; if fails, attempt to clean common markdown fences + $decoded = json_decode($jsonBlock, true); + if ($decoded === null) { + // remove triple backticks if present + $jsonBlockClean = preg_replace('/^```json\s*/i', '', $jsonBlock); + $jsonBlockClean = preg_replace('/```$/', '', $jsonBlockClean); + $jsonBlockClean = trim($jsonBlockClean); + $decoded = json_decode($jsonBlockClean, true); + } + + if ($decoded !== null) { + $structured = $decoded; + } else { + // keep raw block as fallback + $structured = ['raw_json_block' => $jsonBlock]; + } +} + +/* Build final response */ +$responsePayload = [ + 'reply' => $fullText, + 'structured' => $structured, + 'debug' => null +]; + +echo json_encode($responsePayload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT); diff --git a/src/core/actions/mail/admin_report_config.php b/src/core/actions/mail/admin_report_config.php new file mode 100644 index 00000000..84fa1835 --- /dev/null +++ b/src/core/actions/mail/admin_report_config.php @@ -0,0 +1,77 @@ +prepare(" + SELECT r.title, u.email, u.username + FROM reports r + JOIN users u ON r.user_id = u.id + WHERE r.id = ? + "); + $stmt->bind_param("i", $report_id); + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows === 0) { + return false; + } + + $data = $result->fetch_assoc(); + $to = $data['email']; + $username = htmlspecialchars($data['username']); + $title = htmlspecialchars($data['title']); + + // Configuration + $from_email = "no-reply@yourcampus.com"; // CHANGE THIS + $headers = "MIME-Version: 1.0" . "\r\n"; + $headers .= "Content-type:text/html;charset=UTF-8" . "\r\n"; + $headers .= "From: <" . $from_email . ">" . "\r\n"; + + $subject = ""; + $body_content = ""; + $color_code = ""; + + if ($new_status === 'In Progress') { + $subject = "Update: Report #$report_id is In Progress"; + $color_code = "#eab308"; // Yellow/Orange + $body_content = "Good news! Your report regarding '$title' has been reviewed and our team is currently working on it."; + } elseif ($new_status === 'Resolved') { + $subject = "Resolved: Report #$report_id has been fixed"; + $color_code = "#22c55e"; // Green + $body_content = "Great news! The issue '$title' has been successfully resolved. Thank you for helping improve our campus."; + } else { + return false; + } + + // HTML Email Template + $message = " + + + + + +
+
+

Status Update

+
+
+

Hello $username,

+

$body_content

+

Current Status: $new_status

+
+ +
+ + + "; + + // Send Mail + return mail($to, $subject, $message, $headers); +} +?> \ No newline at end of file diff --git a/src/core/router.php b/src/core/router.php index ac99028d..33d2ec0a 100644 --- a/src/core/router.php +++ b/src/core/router.php @@ -30,18 +30,18 @@ $routes = [ 'file' => './assets/admin/_admin_overview.php', 'nav' => true, ], - 'activity-logs' => [ + 'system-logs' => [ 'role' => 'admin', - 'label' => 'Activity Logs', + 'label' => 'System Logs', 'icon' => 'clock', - 'file' => './assets/admin/_activity_logs.php', + 'file' => './assets/admin/_system_logs.php', 'nav' => true, ], - 'admin' => [ + 'admin-panel' => [ 'role' => 'admin', - 'label' => 'Admin', + 'label' => 'Admin Panel', 'icon' => 'settings', - 'file' => './assets/admin/_admin.php', + 'file' => './assets/admin/_admin_panel.php', 'nav' => true, ], ];