Compare commits

...

6 Commits

21 changed files with 1511 additions and 1092 deletions

View File

@@ -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.2-yellow.svg?style=flat)](https://github.com/xodivorce/infra-xodivorce-in/)
[![version](https://img.shields.io/badge/version-v1.3.6-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!**

View File

@@ -24,7 +24,7 @@ $user_initial = strtoupper($current_email[0]);
<div class="text-sm font-semibold text-white">
<?php echo htmlspecialchars($current_email); ?>
</div>
<div class="text-xs text-neutral-500 font-medium uppercase tracking-wide">
<div class="text-xs text-neutral-400 font-medium uppercase tracking-wide">
<?php echo ($is_admin ?? false) ? 'Administrator' : 'User'; ?>
</div>
</div>

View File

@@ -33,36 +33,36 @@ $inactiveClass = 'text-neutral-400 hover:text-white hover:bg-neutral-800/60 bord
<?php if ($is_admin): ?>
<a href="?page=status-board"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'status-board' ? $activeClass : $inactiveClass; ?>">
<svg class="w-5 h-5 mr-3 <?php echo $page === 'status-board' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
<a href="?page=admin_overview"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'admin_overview' ? $activeClass : $inactiveClass; ?>">
<svg class="w-5 h-5 mr-3 <?php echo $page === 'overview' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
Status Board
Admin Overview
</a>
<a href="?page=activity-logs"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'activity-logs' ? $activeClass : $inactiveClass; ?>">
<svg class="w-5 h-5 mr-3 <?php echo $page === 'activity-logs' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Activity Logs
</a>
<a href="?page=admin"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'admin' ? $activeClass : $inactiveClass; ?>">
<svg class="w-5 h-5 mr-3 <?php echo $page === 'admin' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
<a href="?page=admin-panel"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'admin-panel' ? $activeClass : $inactiveClass; ?>">
<svg class="w-5 h-5 mr-3 <?php echo $page === 'admin-panel' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
System Admin
Admin Panel
</a>
<a href="?page=system-logs"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'system-logs' ? $activeClass : $inactiveClass; ?>">
<svg class="w-5 h-5 mr-3 <?php echo $page === 'system-logs' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
System Logs
</a>
<?php endif; ?>

View File

@@ -1,244 +0,0 @@
<section class="space-y-6">
<header>
<h1 class="text-2xl font-semibold text-white">Activity Logs</h1>
<p class="mt-1 text-sm text-neutral-400">Recent system and user activities</p>
</header>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label class="block text-xs font-medium text-neutral-400 mb-1.5">Event Type</label>
<select class="w-full bg-neutral-900 border border-neutral-700 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>All</option>
<option>Reports</option>
<option>Status Changes</option>
<option>Admin Actions</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-neutral-400 mb-1.5">Severity</label>
<select class="w-full bg-neutral-900 border border-neutral-700 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>All</option>
<option>Info</option>
<option>Warning</option>
<option>Critical</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-neutral-400 mb-1.5">Date Range</label>
<select class="w-full bg-neutral-900 border border-neutral-700 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>Today</option>
<option>Last 7 days</option>
<option>Last 30 days</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-neutral-400 mb-1.5">Search</label>
<input type="text" placeholder="Search activity…" class="w-full bg-neutral-900 border border-neutral-700 text-white placeholder-neutral-500 text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-neutral-900 border-b border-neutral-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Timestamp</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Actor</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Action</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Related Entity</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Severity</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700">
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 14:32:15</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">admin@system</td>
<td class="px-6 py-4 text-sm text-neutral-300">Updated service configuration</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">API Gateway - US-East</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Info</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 14:28:43</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">System</td>
<td class="px-6 py-4 text-sm text-neutral-300">Service health check failed</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">Database - EU-West</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Warning</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 14:15:22</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">john.doe@company.com</td>
<td class="px-6 py-4 text-sm text-neutral-300">Generated monthly report</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">Report #1247</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Info</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 13:58:09</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">System</td>
<td class="px-6 py-4 text-sm text-neutral-300">Critical alert triggered</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">Load Balancer - APAC</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-900 text-red-200">Critical</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 13:45:31</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">admin@system</td>
<td class="px-6 py-4 text-sm text-neutral-300">Modified user permissions</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">User: jane.smith@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Info</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 13:22:17</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">System</td>
<td class="px-6 py-4 text-sm text-neutral-300">Backup completed successfully</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">All Services</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Info</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 12:58:44</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">mark.wilson@company.com</td>
<td class="px-6 py-4 text-sm text-neutral-300">Updated service status</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">CDN - US-West</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Info</span>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">2025-12-27 12:31:05</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">System</td>
<td class="px-6 py-4 text-sm text-neutral-300">High memory usage detected</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">Cache Server - EU-Central</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Warning</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="hidden bg-neutral-800 rounded-lg border border-neutral-700 p-12">
<div class="flex flex-col items-center justify-center text-center">
<svg class="w-16 h-16 text-neutral-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<h3 class="text-lg font-medium text-white mb-1">No activity logs available</h3>
<p class="text-sm text-neutral-400">System and user actions will appear here</p>
</div>
</div>
<div class="hidden bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-neutral-900 border-b border-neutral-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Timestamp</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Actor</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Action</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Related Entity</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Severity</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700">
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-28"></div>
</td>
<td class="px-6 py-4">
<div class="h-4 bg-neutral-700 rounded w-48"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-36"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
</tr>
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-24"></div>
</td>
<td class="px-6 py-4">
<div class="h-4 bg-neutral-700 rounded w-56"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-40"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
</tr>
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-36"></div>
</td>
<td class="px-6 py-4">
<div class="h-4 bg-neutral-700 rounded w-44"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
</tr>
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-20"></div>
</td>
<td class="px-6 py-4">
<div class="h-4 bg-neutral-700 rounded w-52"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-44"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
</tr>
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-28"></div>
</td>
<td class="px-6 py-4">
<div class="h-4 bg-neutral-700 rounded w-48"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-40"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>

View File

@@ -1,438 +0,0 @@
<section class="space-y-6">
<header>
<h1 class="text-2xl font-semibold text-white">Admin Panel</h1>
<p class="mt-1 text-sm text-neutral-400">System management and administrative controls</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Total Users</p>
<p class="mt-2 text-3xl font-semibold text-white">1,247</p>
</div>
<div class="w-12 h-12 bg-blue-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-blue-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z">
</path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">Active: 1,189 | Suspended: 58</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Pending Reports</p>
<p class="mt-2 text-3xl font-semibold text-white">34</p>
</div>
<div class="w-12 h-12 bg-yellow-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-yellow-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
</path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">High: 8 | Medium: 15 | Low: 11</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Active Incidents</p>
<p class="mt-2 text-3xl font-semibold text-white">7</p>
</div>
<div class="w-12 h-12 bg-red-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-red-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z">
</path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">Critical: 2 | Warning: 5</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">System Health</p>
<p class="mt-2 text-3xl font-semibold text-green-400">98.5%</p>
</div>
<div class="w-12 h-12 bg-green-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-green-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">All services operational</p>
</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700">
<div class="px-6 py-4 border-b border-neutral-700">
<h2 class="text-lg font-semibold text-white">User Management</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-neutral-900 border-b border-neutral-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
User ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Role</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700">
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#1001</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">John Anderson</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">john.anderson@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-900 text-purple-200">Admin</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-900 text-green-200">Active</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-blue-400 hover:text-blue-300 mr-3">View</button>
<button class="text-red-400 hover:text-red-300">Disable</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#1002</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">Sarah Mitchell</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">sarah.mitchell@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Moderator</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-900 text-green-200">Active</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-blue-400 hover:text-blue-300 mr-3">View</button>
<button class="text-red-400 hover:text-red-300">Disable</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#1003</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">Michael Chen</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">michael.chen@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-neutral-700 text-neutral-300">User</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-900 text-green-200">Active</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-blue-400 hover:text-blue-300 mr-3">View</button>
<button class="text-red-400 hover:text-red-300">Disable</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#1004</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">Emily Rodriguez</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">emily.rodriguez@company.com
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-neutral-700 text-neutral-300">User</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-900 text-red-200">Suspended</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-blue-400 hover:text-blue-300 mr-3">View</button>
<button class="text-green-400 hover:text-green-300">Enable</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#1005</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">David Thompson</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">david.thompson@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Moderator</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-900 text-green-200">Active</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-blue-400 hover:text-blue-300 mr-3">View</button>
<button class="text-red-400 hover:text-red-300">Disable</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700">
<div class="px-6 py-4 border-b border-neutral-700">
<h2 class="text-lg font-semibold text-white">Report Moderation</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-neutral-900 border-b border-neutral-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Report ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Title</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Submitted By</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Priority</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700">
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#2045</td>
<td class="px-6 py-4 text-sm text-white">Database Connection Issues</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">alex.kim@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-900 text-red-200">High</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Pending</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-green-400 hover:text-green-300 mr-2">Approve</button>
<button class="text-orange-400 hover:text-orange-300 mr-2">Escalate</button>
<button class="text-red-400 hover:text-red-300">Delete</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#2044</td>
<td class="px-6 py-4 text-sm text-white">API Response Time Degradation</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">maria.santos@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Medium</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Pending</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-green-400 hover:text-green-300 mr-2">Approve</button>
<button class="text-orange-400 hover:text-orange-300 mr-2">Escalate</button>
<button class="text-red-400 hover:text-red-300">Delete</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#2043</td>
<td class="px-6 py-4 text-sm text-white">UI Rendering Bug</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">james.wilson@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-900 text-blue-200">Low</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Pending</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-green-400 hover:text-green-300 mr-2">Approve</button>
<button class="text-orange-400 hover:text-orange-300 mr-2">Escalate</button>
<button class="text-red-400 hover:text-red-300">Delete</button>
</td>
</tr>
<tr class="hover:bg-neutral-750">
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-300">#2042</td>
<td class="px-6 py-4 text-sm text-white">Memory Leak in Production</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-neutral-400">lisa.park@company.com</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-900 text-red-200">High</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-900 text-yellow-200">Pending</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-green-400 hover:text-green-300 mr-2">Approve</button>
<button class="text-orange-400 hover:text-orange-300 mr-2">Escalate</button>
<button class="text-red-400 hover:text-red-300">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700">
<div class="px-6 py-4 border-b border-neutral-700">
<h2 class="text-lg font-semibold text-white">System Controls</h2>
</div>
<div class="p-6 space-y-4">
<div class="flex items-center justify-between p-4 bg-neutral-900 rounded-lg border border-neutral-700">
<div>
<h3 class="text-sm font-medium text-white">Maintenance Mode</h3>
<p class="mt-1 text-xs text-neutral-400">Enable maintenance mode to perform system updates</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer">
<div
class="w-11 h-6 bg-neutral-700 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-neutral-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600">
</div>
</label>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<button
class="flex items-center justify-center px-4 py-3 bg-neutral-900 border border-neutral-700 rounded-lg text-sm font-medium text-white hover:bg-neutral-750 transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16">
</path>
</svg>
Clear Cache
</button>
<button
class="flex items-center justify-center px-4 py-3 bg-neutral-900 border border-neutral-700 rounded-lg text-sm font-medium text-white hover:bg-neutral-750 transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15">
</path>
</svg>
Force Status Refresh
</button>
<button
class="flex items-center justify-center px-4 py-3 bg-yellow-900 border border-yellow-700 rounded-lg text-sm font-medium text-yellow-200 hover:bg-yellow-800 transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15">
</path>
</svg>
Restart Services
</button>
</div>
</div>
</div>
<div class="hidden bg-neutral-800 rounded-lg border border-neutral-700 p-12">
<div class="flex flex-col items-center justify-center text-center">
<svg class="w-16 h-16 text-neutral-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4">
</path>
</svg>
<h3 class="text-lg font-medium text-white mb-1">No data available</h3>
<p class="text-sm text-neutral-400">Administrative data will appear here when available</p>
</div>
</div>
<div class="hidden bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-neutral-900 border-b border-neutral-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
User ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Role</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">
Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700">
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-32"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-48"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-24"></div>
</td>
</tr>
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-28"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-44"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-24"></div>
</td>
</tr>
<tr class="animate-pulse">
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-36"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-52"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-5 bg-neutral-700 rounded-full w-16"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="h-4 bg-neutral-700 rounded w-24"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>

View File

@@ -0,0 +1,267 @@
<?php
$departments = [
'WiFi & Network Issue',
'Electrical Issue',
'Water & Plumbing Issue',
'HVAC (AC/Heating) Issue',
'Furniture & Fixtures Issue',
'Cleaning & Janitorial Issue',
'Security & Safety Issue',
'Road & Pathway Damage Issue',
'Library & Study Issue',
'Lost & Stolen Issue',
'Medical/Health Issue',
'Other Issues',
];
$services = [];
$sql = "
SELECT
REPLACE(category, '&amp;', '&') AS category,
SUM(TRIM(status) = 'Opened') AS opened_count,
SUM(TRIM(status) = 'In Progress') AS progress_count
FROM reports
GROUP BY category
";
$result = $conn->query($sql);
$deptStats = [];
while ($row = $result->fetch_assoc()) {
$deptStats[$row['category']] = $row;
}
function getStatusConfig($status) {
switch ($status) {
case 'Operational':
return [
'color' => 'green',
'hover_border' => 'hover:border-green-500/50 hover:shadow-green-500/5',
'group_hover_icon' => 'group-hover:text-green-400 group-hover:bg-green-500/10 group-hover:border-green-500/20'
];
case 'Maintenance':
return [
'color' => 'yellow',
'hover_border' => 'hover:border-yellow-500/50 hover:shadow-yellow-500/5',
'group_hover_icon' => 'group-hover:text-yellow-400 group-hover:bg-yellow-500/10 group-hover:border-yellow-500/20'
];
case 'Outage':
return [
'color' => 'red',
'hover_border' => 'hover:border-red-500/50 hover:shadow-red-500/5',
'group_hover_icon' => 'group-hover:text-red-400 group-hover:bg-red-500/10 group-hover:border-red-500/20'
];
default:
return [
'color' => 'gray',
'hover_border' => 'hover:border-neutral-500/50',
'group_hover_icon' => 'group-hover:text-white'
];
}
}
function getIconPath($name) {
if (strpos($name, 'WiFi') !== false) return 'M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0';
if (strpos($name, 'Electrical') !== false) return 'M13 10V3L4 14h7v7l9-11h-7z';
if (strpos($name, 'Water') !== false) return 'M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z';
if (strpos($name, 'HVAC') !== false) return 'M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5';
if (strpos($name, 'Furniture') !== false) return 'M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4';
if (strpos($name, 'Cleaning') !== false) return 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10';
if (strpos($name, 'Security') !== false) return 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z';
if (strpos($name, 'Road') !== false) return 'M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7';
if (strpos($name, 'Library') !== false) return 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253';
if (strpos($name, 'Lost') !== false) return 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z';
if (strpos($name, 'Medical') !== false) return 'M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z';
return 'M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z';
}
$activeServices = 0;
$maintenanceCount = 0;
$highPriority = 0;
foreach ($departments as $dept) {
$opened = $deptStats[$dept]['opened_count'] ?? 0;
$progress = $deptStats[$dept]['progress_count'] ?? 0;
if ($opened > 0) {
$status = 'Outage';
$msg = "$opened active issue(s)";
$highPriority++;
} elseif ($progress > 0) {
$status = 'Maintenance';
$msg = "$progress issue(s) in progress";
$maintenanceCount++;
} else {
$status = 'Operational';
$msg = 'No active issues';
$activeServices++;
}
$services[] = [
'name' => $dept,
'status' => $status,
'msg' => $msg
];
}
$totalServices = count($services);
$activeIncidents = $maintenanceCount + $highPriority;
if ($highPriority > 0) {
$sysData = [
'status' => 'System Alert',
'msg' => 'Critical issues detected',
'text' => 'text-red-400',
'icon' => 'text-red-500',
'bg' => 'bg-red-500/10 border-red-500/20',
'hover' => 'hover:border-red-500/50',
'svg' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>'
];
} elseif ($maintenanceCount > 0) {
$sysData = [
'status' => 'Maintenance',
'msg' => 'Performance degraded',
'text' => 'text-yellow-400',
'icon' => 'text-yellow-500',
'bg' => 'bg-yellow-500/10 border-yellow-500/20',
'hover' => 'hover:border-yellow-500/50',
'svg' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>'
];
} else {
$sysData = [
'status' => 'Operational',
'msg' => 'All core systems nominal',
'text' => 'text-green-400',
'icon' => 'text-green-500',
'bg' => 'bg-green-500/10 border-green-500/20',
'hover' => 'hover:border-green-500/50',
'svg' => '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>'
];
}
?>
<div class="space-y-6 max-w-7xl mx-auto pb-10">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pb-4 border-b border-neutral-800">
<div>
<h1 class="text-2xl font-bold text-white tracking-tight">Campus Status</h1>
<p class="text-sm text-neutral-400 mt-1">Live infrastructure and service health</p>
</div>
<div class="flex items-center gap-4 text-xs bg-neutral-900/50 p-2 rounded-lg border border-neutral-800">
<span class="flex items-center gap-2 text-neutral-300">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
Operational
</span>
<span class="flex items-center gap-2 text-neutral-300">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-yellow-500"></span>
</span>
Maintenance
</span>
<span class="flex items-center gap-2 text-neutral-300">
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
</span>
Outage
</span>
</div>
</div>
<section class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-neutral-800 border border-neutral-700/50 rounded-xl p-5 flex items-center justify-between <?= $sysData['hover'] ?> transition-colors duration-300">
<div>
<p class="text-xs font-semibold text-neutral-400 uppercase tracking-wider">Overall System</p>
<p class="text-xl font-bold <?= $sysData['text'] ?> mt-1"><?= $sysData['status'] ?></p>
<p class="text-xs text-neutral-400 mt-1"><?= $sysData['msg'] ?></p>
</div>
<div class="w-12 h-12 rounded-lg <?= $sysData['bg'] ?> flex items-center justify-center <?= $sysData['icon'] ?>">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<?= $sysData['svg'] ?>
</svg>
</div>
</div>
<div class="bg-neutral-800 border border-neutral-700/50 rounded-xl p-5 flex items-center justify-between hover:border-blue-500/50 transition-colors duration-300">
<div>
<p class="text-xs font-semibold text-neutral-400 uppercase tracking-wider">Active System</p>
<div class="flex items-baseline gap-1 mt-1">
<p class="text-2xl font-bold text-white"><?= $activeServices ?></p>
<span class="text-sm text-neutral-400">/ <?= $totalServices ?></span>
</div>
<p class="text-xs text-neutral-400 mt-1"><?= ($totalServices - $activeServices) ?> services inactive</p>
</div>
<div class="w-12 h-12 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"></path></svg>
</div>
</div>
<div class="bg-neutral-800 border border-neutral-700/50 rounded-xl p-5 flex items-center justify-between hover:border-yellow-500/50 transition-colors duration-300">
<div>
<p class="text-xs font-semibold text-neutral-400 uppercase tracking-wider">In Maintenance</p>
<p class="text-2xl font-bold text-white mt-1"><?= $maintenanceCount ?></p>
<p class="text-xs text-neutral-400 mt-1">Fixing the things</p>
</div>
<div class="w-12 h-12 rounded-lg bg-yellow-500/10 border border-yellow-500/20 flex items-center justify-center text-yellow-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path></svg>
</div>
</div>
<div class="bg-neutral-800 border border-neutral-700/50 rounded-xl p-5 flex items-center justify-between hover:border-red-500/50 transition-colors duration-300">
<div>
<p class="text-xs font-semibold text-neutral-400 uppercase tracking-wider">Outage System</p>
<p class="text-2xl font-bold text-white mt-1"><?= $highPriority ?></p>
<p class="text-xs text-neutral-400 mt-1">Take an action now</p>
</div>
<div class="w-12 h-12 rounded-lg bg-red-500/10 border border-red-500/20 flex items-center justify-center text-red-500 <?php echo ($highPriority > 0) ? 'animate-pulse' : ''; ?>">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
</div>
</div>
</section>
<section>
<h2 class="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg>
Services Status
</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<?php foreach ($services as $s):
$config = getStatusConfig($s['status']);
$icon = getIconPath($s['name']);
$dotColor = $config['color'];
?>
<div class="group bg-neutral-800 border border-neutral-700/50 rounded-xl p-4 transition-all duration-300 hover:shadow-lg <?= $config['hover_border'] ?>">
<div class="flex items-center justify-between mb-3">
<div class="w-10 h-10 rounded-lg bg-neutral-900 border border-neutral-700 flex items-center justify-center text-neutral-400 transition-all <?= $config['group_hover_icon'] ?>">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<?= $icon ?>"></path>
</svg>
</div>
<div class="relative flex h-3 w-3">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-<?= $dotColor ?>-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-<?= $dotColor ?>-500"></span>
</div>
</div>
<h3 class="font-semibold text-white text-sm truncate"><?= htmlspecialchars($s['name']) ?></h3>
<p class="text-xs text-neutral-400 mt-1 truncate"><?= htmlspecialchars($s['msg']) ?></p>
</div>
<?php endforeach; ?>
</div>
</section>
</div>

516
src/assets/admin/_admin_panel.php Executable file
View File

@@ -0,0 +1,516 @@
<?php
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] != 1) {
echo "<div class='text-red-500 p-6'>Access Denied</div>";
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 "<script>window.location.href = window.location.href;</script>";
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'])
];
}
}
?>
<section class="space-y-6" x-data="{ currentTab: 'reports' }">
<header>
<h1 class="text-2xl font-semibold text-white">Admin Panel</h1>
<p class="mt-1 text-sm text-neutral-400">System management and administrative controls</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Total Users</p>
<p class="mt-2 text-3xl font-semibold text-white"><?php echo number_format($total_users); ?></p>
</div>
<div class="w-12 h-12 bg-purple-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-purple-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">Registered accounts</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Active Reporters</p>
<p class="mt-2 text-3xl font-semibold text-white"><?php echo number_format($active_users); ?></p>
</div>
<div class="w-12 h-12 bg-blue-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-blue-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">Users who submitted reports</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Pending Issues</p>
<p class="mt-2 text-3xl font-semibold text-white"><?php echo number_format($pending_reports); ?></p>
</div>
<div class="w-12 h-12 bg-red-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-red-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">Requires attention</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-neutral-400">Issues Resolved</p>
<p class="mt-2 text-3xl font-semibold text-green-400"><?php echo number_format($resolved_reports); ?></p>
</div>
<div class="w-12 h-12 bg-green-900 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-green-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
<p class="mt-4 text-xs text-neutral-500">Successfully closed reports</p>
</div>
</div>
<div class="space-y-6 pb-10">
<div class="flex items-center justify-between">
<div class="bg-neutral-900 p-1 rounded-xl border border-neutral-800 inline-flex">
<button onclick="switchTab('reports')" id="tab-btn-reports" class="px-4 py-2 text-sm font-medium rounded-lg bg-neutral-800 text-white shadow-sm border border-neutral-700 transition-all cursor-default flex items-center gap-2">
Campus Reports
</button>
<button onclick="switchTab('gemini')" id="tab-btn-gemini" class="px-4 py-2 text-sm font-medium rounded-lg text-neutral-400 hover:text-white hover:bg-neutral-800 transition-all cursor-pointer flex items-center gap-2">
<svg class="w-4 h-4 text-purple-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z" />
</svg>
Ask Gemini
</button>
</div>
</div>
<div id="view-reports" class="space-y-6">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-lg font-semibold text-white">Report Moderation</h2>
<p class="mt-1 text-sm text-neutral-400">Manage facility and infrastructure issues</p>
</div>
</div>
<div class="bg-neutral-800 rounded-xl border border-neutral-700 p-4 shadow-sm">
<div class="flex flex-col lg:flex-row gap-4 justify-between">
<div class="flex overflow-x-auto pb-2 lg:pb-0 gap-2 no-scrollbar" id="status-filters">
<button onclick="setFilter('status', 'All')" id="filter-all" class="px-4 py-2 bg-blue-600/10 text-blue-400 border border-blue-600/20 text-sm font-medium rounded-lg whitespace-nowrap transition-all">All Reports</button>
<button onclick="setFilter('status', 'Opened')" id="filter-opened" class="px-4 py-2 hover:bg-neutral-700 text-neutral-400 hover:text-white text-sm font-medium rounded-lg transition-colors whitespace-nowrap border border-transparent">Opened</button>
<button onclick="setFilter('status', 'In Progress')" id="filter-progress" class="px-4 py-2 hover:bg-neutral-700 text-neutral-400 hover:text-white text-sm font-medium rounded-lg transition-colors whitespace-nowrap border border-transparent">In Progress</button>
<button onclick="setFilter('status', 'Resolved')" id="filter-fixed" class="px-4 py-2 hover:bg-neutral-700 text-neutral-400 hover:text-white text-sm font-medium rounded-lg transition-colors whitespace-nowrap border border-transparent">Resolved</button>
</div>
<div class="flex flex-col sm:flex-row gap-3">
<div class="relative min-w-[200px]">
<select onchange="setFilter('date', this.value)" class="w-full appearance-none px-4 py-2 bg-neutral-900 border border-neutral-700 rounded-lg text-sm text-neutral-300 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition-all cursor-pointer">
<option value="all">All time</option>
<option value="7">Last 7 days</option>
<option value="30">Last 30 days</option>
</select>
<svg class="absolute right-3 top-2.5 h-4 w-4 text-neutral-500 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<div class="relative w-full sm:w-64">
<input type="text" placeholder="Search..." onkeyup="setFilter('search', this.value)" class="w-full pl-10 pr-4 py-2 bg-neutral-900 border border-neutral-700 rounded-lg text-sm text-white placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition-all">
<svg class="absolute left-3 top-2.5 h-4 w-4 text-neutral-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
</div>
</div>
<div class="hidden md:block bg-neutral-800 rounded-xl border border-neutral-700 overflow-hidden shadow-sm">
<div class="overflow-x-auto custom-scrollbar">
<table class="w-full">
<thead class="bg-neutral-900/50 border-b border-neutral-700">
<tr>
<th class="px-6 py-4 text-left text-xs font-semibold text-neutral-400 uppercase tracking-wider w-12">ID</th>
<th class="px-6 py-4 text-left text-xs font-semibold text-neutral-400 uppercase tracking-wider w-1/4">Title</th>
<th class="px-6 py-4 text-left text-xs font-semibold text-neutral-400 uppercase tracking-wider">Category</th>
<th class="px-6 py-4 text-left text-xs font-semibold text-neutral-400 uppercase tracking-wider">Priority</th>
<th class="px-6 py-4 text-center text-xs font-semibold text-neutral-400 uppercase tracking-wider">Image</th>
<th class="px-6 py-4 text-center text-xs font-semibold text-neutral-400 uppercase tracking-wider">Location</th>
<th class="px-6 py-4 text-center text-xs font-semibold text-neutral-400 uppercase tracking-wider w-32">Date</th>
<th class="px-6 py-4 text-center text-xs font-semibold text-neutral-400 uppercase tracking-wider w-48">Actions</th>
</tr>
</thead>
<tbody id="reports-table-body" class="divide-y divide-neutral-700/50">
</tbody>
</table>
</div>
<div class="bg-neutral-900 border-t border-neutral-700 px-6 py-4 flex items-center justify-between">
<span class="text-sm text-neutral-400" id="pagination-info">Showing page 1 of 1</span>
<div class="flex gap-2">
<button id="btn-prev" onclick="changePage(-1)" class="px-3 py-1 bg-neutral-800 hover:bg-neutral-700 border border-neutral-700 rounded-lg text-sm text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed">Previous</button>
<button id="btn-next" onclick="changePage(1)" class="px-3 py-1 bg-neutral-800 hover:bg-neutral-700 border border-neutral-700 rounded-lg text-sm text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed">Next</button>
</div>
</div>
</div>
</div>
<div id="view-gemini" class="hidden h-[75vh] flex flex-col bg-neutral-800 rounded-xl border border-neutral-700 overflow-hidden relative shadow-2xl">
<div class="flex-none px-6 py-4 border-b border-neutral-700 bg-neutral-900/50 backdrop-blur flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center shadow-lg shadow-purple-900/20">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div>
<h2 class="text-lg font-bold text-white tracking-tight">Gemini Assistant</h2>
<p class="text-xs text-neutral-400">Analysis & Response Generation</p>
</div>
</div>
<div class="flex-1 overflow-y-auto p-6 space-y-6 custom-scrollbar bg-neutral-800" id="chat-container">
<div class="flex gap-4">
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-600 to-purple-600 flex-shrink-0 flex items-center justify-center mt-1">
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="space-y-1 max-w-[80%]">
<span class="text-xs font-bold text-purple-400 ml-1">Gemini</span>
<div class="bg-neutral-700/50 border border-neutral-600 rounded-2xl rounded-tl-none px-5 py-3 text-neutral-200 text-sm leading-relaxed shadow-sm">
Hello Admin. I can assist with prioritizing facility issues, drafting responses to students, and analyzing infrastructure trends.
</div>
</div>
</div>
</div>
<div class="flex-none p-4 bg-neutral-900 border-t border-neutral-700">
<div class="relative flex items-end gap-2 max-w-4xl mx-auto">
<textarea id="gemini-prompt" rows="1" placeholder="Ask about specific reports..." class="w-full pl-5 pr-14 py-3.5 bg-neutral-800 border border-neutral-700 rounded-2xl text-white placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 transition-all resize-none custom-scrollbar text-sm leading-relaxed" style="min-height: 52px; max-height: 150px;"></textarea>
<button type="button" onclick="sendGeminiRequest()" id="send-btn" class="absolute right-2 bottom-2.5 p-2 bg-purple-600 hover:bg-purple-500 text-white rounded-xl transition-all shadow-lg shadow-purple-900/20">
<svg class="w-5 h-5 transform -rotate-90" fill="currentColor" viewBox="0 0 24 24">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
</button>
</div>
</div>
</div>
</div>
</section>
<script>
const reportsData = <?php echo json_encode($reports); ?>;
let state = {
filterStatus: 'All',
filterDate: 'all',
filterSearch: '',
currentPage: 1,
itemsPerPage: 5
};
function youtubeSearchLink(query) {
return `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`;
}
function renderGeminiText(text) {
if (!text) return '';
text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
let safeText = text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
safeText = safeText.replace(/\*\*(.*?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>');
safeText = safeText.replace(
/(https?:\/\/[^\s<]+)/g,
'<a href="$1" target="_blank" rel="noopener noreferrer" class="text-blue-400 hover:text-blue-300 underline break-all">$1</a>'
);
const lines = safeText.split('\n');
let html = '';
lines.forEach(line => {
const trimLine = line.trim();
if (trimLine.startsWith('### ')) {
html += `<div class="text-white font-semibold mt-3 mb-1 text-sm">${line.replace('### ', '')}</div>`;
}
else if (trimLine.startsWith('## ')) {
html += `<div class="text-white font-bold mt-4 mb-2 text-base">${line.replace('## ', '')}</div>`;
}
else if (trimLine.match(/^(\*{3,}|-{3,})$/)) {
html += `<hr class="my-3 border-neutral-600">`;
}
else if (trimLine.match(/^[\-\*]\s+/)) {
const content = line.replace(/^\s*[\-\*]\s+/, '');
html += `<div class="flex gap-2 ml-2 mt-1"><span class="text-blue-400 shrink-0 leading-relaxed">•</span><span class="text-neutral-200 leading-relaxed">${content}</span></div>`;
}
else if (trimLine.match(/^\d+\.\s+/)) {
const match = line.match(/^(\s*\d+\.)\s+(.*)/);
if (match) {
html += `<div class="flex gap-2 ml-2 mt-1"><span class="font-mono text-blue-400 shrink-0 leading-relaxed">${match[1]}</span><span class="text-neutral-200 leading-relaxed">${match[2]}</span></div>`;
} else {
html += `<div class="mb-1 text-neutral-200">${line}</div>`;
}
}
else if (trimLine.length > 0) {
html += `<div class="mb-1 text-neutral-200 leading-relaxed">${line}</div>`;
}
else {
html += `<div class="h-2"></div>`;
}
});
return html;
}
function setFilter(type, value) {
if (type === 'status') state.filterStatus = value;
if (type === 'date') state.filterDate = value;
if (type === 'search') state.filterSearch = value.toLowerCase();
state.currentPage = 1;
renderReports();
updateFilterUI();
}
function changePage(direction) {
state.currentPage += direction;
renderReports();
}
function updateFilterUI() {
const statuses = ['All', 'Opened', 'In Progress', 'Resolved'];
const btnMap = { 'All': 'filter-all', 'Opened': 'filter-opened', 'In Progress': 'filter-progress', 'Resolved': 'filter-fixed' };
const activeClass = "px-4 py-2 bg-blue-600/10 text-blue-400 border border-blue-600/20 text-sm font-medium rounded-lg whitespace-nowrap transition-all";
const inactiveClass = "px-4 py-2 hover:bg-neutral-700 text-neutral-400 hover:text-white text-sm font-medium rounded-lg transition-colors whitespace-nowrap border border-transparent";
statuses.forEach(s => {
const btn = document.getElementById(btnMap[s]);
if (btn) btn.className = (state.filterStatus === s) ? activeClass : inactiveClass;
});
}
function advanceStatus(id, currentStatus) {
let nextStatus = '';
if (currentStatus === 'Opened') nextStatus = 'In Progress';
else if (currentStatus === 'In Progress') nextStatus = 'Resolved';
else return;
const form = document.createElement('form');
form.method = 'POST';
form.action = '';
const inputAction = document.createElement('input'); inputAction.type = 'hidden'; inputAction.name = 'action'; inputAction.value = 'update_status'; form.appendChild(inputAction);
const inputId = document.createElement('input'); inputId.type = 'hidden'; inputId.name = 'report_id'; inputId.value = id; form.appendChild(inputId);
const inputStatus = document.createElement('input'); inputStatus.type = 'hidden'; inputStatus.name = 'new_status'; inputStatus.value = nextStatus; form.appendChild(inputStatus);
document.body.appendChild(form);
form.submit();
}
function askAboutReportAdmin(id, title, category, priority, status) {
switchTab('gemini');
const promptInput = document.getElementById('gemini-prompt');
promptInput.value = `Report ID: #${id}\nTitle: "${title}"\nCategory: ${category}\nPriority: ${priority}\nStatus: ${status}\n\nAnalyze this report and suggest internal admin actions:`;
promptInput.focus();
}
function renderReports() {
const tbody = document.getElementById('reports-table-body');
tbody.innerHTML = '';
let filtered = reportsData.filter(item => {
if (state.filterStatus !== 'All' && item.status !== state.filterStatus) return false;
const searchStr = state.filterSearch;
if (searchStr) {
const matchesId = item.id.toString().includes(searchStr);
const matchesTitle = item.title.toLowerCase().includes(searchStr);
const matchesUser = item.user.toLowerCase().includes(searchStr);
if (!matchesId && !matchesTitle && !matchesUser) return false;
}
if (state.filterDate !== 'all') {
const itemDate = new Date(item.date);
const limitDate = new Date();
limitDate.setDate(limitDate.getDate() - parseInt(state.filterDate));
if (itemDate < limitDate) return false;
}
return true;
});
const totalPages = Math.ceil(filtered.length / state.itemsPerPage) || 1;
if (state.currentPage > totalPages) state.currentPage = totalPages;
if (state.currentPage < 1) state.currentPage = 1;
const start = (state.currentPage - 1) * state.itemsPerPage;
const pageData = filtered.slice(start, start + state.itemsPerPage);
if (pageData.length === 0) {
tbody.innerHTML = `<tr><td colspan="8" class="px-6 py-10 text-center text-neutral-500">No reports found matching criteria</td></tr>`;
} else {
pageData.forEach(row => {
const dateObj = new Date(row.date);
const dateStr = dateObj.toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' });
let priorityColor = row.priority === 'High' ? 'text-red-400' : (row.priority === 'Medium' ? 'text-orange-400' : 'text-blue-400');
let actionBtn = '';
if (row.status === 'Opened') {
actionBtn = `<button onclick="advanceStatus(${row.id}, '${row.status}')" class="group w-32 relative py-1.5 px-3 bg-yellow-600/10 hover:bg-yellow-600/20 text-yellow-500 border border-yellow-500/30 hover:border-yellow-500/50 rounded-lg text-xs font-semibold transition-all shadow-lg shadow-yellow-900/10 flex items-center justify-between mx-auto"><span class="flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-yellow-500"></span>In Progress</span><svg class="w-3 h-3 transform group-hover:translate-x-0.5 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" /></svg></button>`;
} else if (row.status === 'In Progress') {
actionBtn = `<button onclick="advanceStatus(${row.id}, '${row.status}')" class="group w-32 relative py-1.5 px-3 bg-green-600/10 hover:bg-green-600/20 text-green-400 border border-green-500/30 hover:border-green-500/50 rounded-lg text-xs font-semibold transition-all shadow-lg shadow-green-900/10 flex items-center justify-between mx-auto"><span class="flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse"></span>Resolved</span><svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg></button>`;
} else {
actionBtn = `<div class="w-32 py-1.5 px-3 bg-blue-500/10 border border-blue-500/20 text-blue-400 rounded-lg text-xs font-semibold text-center shadow-sm cursor-default flex items-center justify-center gap-2 mx-auto"><span class="w-1.5 h-1.5 rounded-full bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.6)]"></span>Resolved</div>`;
}
let geminiBtn = '';
if (row.status !== 'Resolved') {
geminiBtn = `<button onclick="askAboutReportAdmin(${row.id},'${row.title.replace(/'/g, "\\'")}','${row.category.replace(/'/g, "\\'")}','${row.priority}','${row.status}')" class="w-32 mt-2 py-1 px-3 text-purple-400 hover:text-purple-300 hover:bg-purple-500/10 rounded-md text-[10px] font-medium transition-colors flex items-center justify-center gap-1.5 border border-transparent hover:border-purple-500/20 mx-auto"><svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>Ask Gemini</button>`;
}
const viewImageLink = `./core/actions/submit_report.php?view_id=${row.image}`;
const html = `<tr class="hover:bg-neutral-700/30 transition-colors duration-200"><td class="px-6 py-4 text-sm font-mono text-neutral-300">#${row.id}</td><td class="px-6 py-4"><div class="text-sm font-medium text-white">${row.title}</div><div class="text-xs text-neutral-400 mt-0.5">By ${row.user}</div></td><td class="px-6 py-4 text-sm text-neutral-200">${row.category}</td><td class="px-6 py-4 text-sm font-medium ${priorityColor}">${row.priority}</td><td class="px-6 py-4 text-center"><a href="${viewImageLink}" target="_blank" class="inline-block p-2 hover:bg-neutral-700 rounded-lg text-neutral-400 transition-colors"><svg class="h-5 w-5 mx-auto text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg></a></td><td class="px-6 py-4 text-center"><a href="${row.location}" target="_blank" class="inline-block p-2 hover:bg-neutral-700 rounded-lg text-blue-400 transition-colors"><svg class="h-5 w-5 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg></a></td><td class="px-6 py-4 text-center text-sm text-neutral-400">${dateStr}</td><td class="px-6 py-4 w-48 text-center"><div class="flex flex-col items-center">${actionBtn}${geminiBtn}</div></td></tr>`;
tbody.innerHTML += html;
});
}
document.getElementById('pagination-info').innerHTML = `Showing page <span class="text-white font-medium">${state.currentPage}</span> of ${totalPages}`;
document.getElementById('btn-prev').disabled = state.currentPage === 1;
document.getElementById('btn-next').disabled = state.currentPage === totalPages;
}
function switchTab(tabId) {
const btnReports = document.getElementById('tab-btn-reports');
const btnGemini = document.getElementById('tab-btn-gemini');
const activeClass = "px-4 py-2 text-sm font-medium rounded-lg bg-neutral-800 text-white shadow-sm border border-neutral-700 cursor-default flex items-center gap-2 transition-all";
const inactiveClass = "px-4 py-2 text-sm font-medium rounded-lg text-neutral-400 hover:text-white hover:bg-neutral-800 border-transparent cursor-pointer flex items-center gap-2 transition-all";
if (tabId === 'reports') {
btnReports.className = activeClass;
btnGemini.className = inactiveClass;
document.getElementById('view-reports').classList.remove('hidden');
document.getElementById('view-gemini').classList.add('hidden');
} else {
btnGemini.className = activeClass;
btnReports.className = inactiveClass;
document.getElementById('view-reports').classList.add('hidden');
document.getElementById('view-gemini').classList.remove('hidden');
}
}
async function sendGeminiRequest() {
const input = document.getElementById('gemini-prompt');
const chatContainer = document.getElementById('chat-container');
const prompt = input.value.trim();
const sendBtn = document.getElementById('send-btn');
if (!prompt) return;
chatContainer.innerHTML += `
<div class="flex gap-4 flex-row-reverse">
<div class="w-8 h-8 rounded-full bg-neutral-700 flex-shrink-0 flex items-center justify-center mt-1">
<svg class="w-4 h-4 text-neutral-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
</div>
<div class="space-y-1 max-w-[80%]">
<div class="bg-blue-600 rounded-2xl rounded-tr-none px-5 py-3 text-white text-sm leading-relaxed shadow-md">${prompt.replace(/\n/g, '<br>')}</div>
</div>
</div>`;
input.value = '';
input.disabled = true;
sendBtn.disabled = true;
chatContainer.scrollTop = chatContainer.scrollHeight;
try {
const response = await fetch('./core/actions/admin_gemini.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ prompt: prompt })
});
const data = await response.json();
let replyText = data.reply ? renderGeminiText(data.reply) : `<span class="text-red-400">Error: ${data.error || "Unknown error"}</span>`;
chatContainer.innerHTML += `
<div class="flex gap-4">
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-blue-600 to-purple-600 flex-shrink-0 flex items-center justify-center mt-1">
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
</div>
<div class="space-y-1 max-w-[80%]">
<span class="text-xs font-bold text-purple-400 ml-1">Gemini</span>
<div class="bg-neutral-700/50 border border-neutral-600 rounded-2xl rounded-tl-none px-5 py-3 text-neutral-200 text-sm leading-relaxed shadow-sm">
${replyText}
</div>
</div>
</div>`;
} catch (e) {
console.error(e);
chatContainer.innerHTML += `<div class="text-red-500 text-center text-xs mt-2">Connection Error: ${e.message}</div>`;
}
input.disabled = false;
sendBtn.disabled = false;
input.focus();
chatContainer.scrollTop = chatContainer.scrollHeight;
}
renderReports();
</script>

View File

@@ -1,296 +0,0 @@
<div class="space-y-6">
<!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pb-4 border-b border-neutral-800">
<div>
<h1 class="text-2xl font-bold text-white">Status Board</h1>
<p class="text-sm text-neutral-400 mt-1">Live infrastructure and service health</p>
</div>
<div class="flex items-center gap-4 text-xs">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-green-500"></div>
<span class="text-neutral-300">Operational</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-yellow-500"></div>
<span class="text-neutral-300">Degraded</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-red-500"></div>
<span class="text-neutral-300">Outage</span>
</div>
</div>
</div>
<!-- Global Status Summary -->
<section>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-neutral-400">Overall System</span>
<div class="w-2 h-2 rounded-full bg-green-500"></div>
</div>
<div class="text-2xl font-bold text-white">Operational</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-neutral-400">Services Operational</span>
<div class="w-2 h-2 rounded-full bg-green-500"></div>
</div>
<div class="text-2xl font-bold text-white">12/14</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-neutral-400">Active Incidents</span>
<div class="w-2 h-2 rounded-full bg-yellow-500"></div>
</div>
<div class="text-2xl font-bold text-white">2</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-neutral-400">Regions Affected</span>
<div class="w-2 h-2 rounded-full bg-green-500"></div>
</div>
<div class="text-2xl font-bold text-white">0/8</div>
</div>
</div>
</section>
<!-- Services Status Grid -->
<section>
<h2 class="text-lg font-semibold text-white mb-4">Services Status</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<h3 class="font-semibold text-white">Power Grid</h3>
</div>
<span class="px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">Operational</span>
</div>
<p class="text-sm text-neutral-400 mb-2">Running normally</p>
<p class="text-xs text-neutral-500">Last updated: 2 minutes ago</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<h3 class="font-semibold text-white">Water Supply</h3>
</div>
<span class="px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">Operational</span>
</div>
<p class="text-sm text-neutral-400 mb-2">Running normally</p>
<p class="text-xs text-neutral-500">Last updated: 5 minutes ago</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
<h3 class="font-semibold text-white">Network Infrastructure</h3>
</div>
<span class="px-2 py-1 text-xs font-medium rounded bg-yellow-500/10 text-yellow-400 border border-yellow-500/20">Degraded</span>
</div>
<p class="text-sm text-neutral-400 mb-2">Partial outage in Zone C</p>
<p class="text-xs text-neutral-500">Last updated: 1 minute ago</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<h3 class="font-semibold text-white">Road Network</h3>
</div>
<span class="px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">Operational</span>
</div>
<p class="text-sm text-neutral-400 mb-2">All routes clear</p>
<p class="text-xs text-neutral-500">Last updated: 3 minutes ago</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full bg-red-500"></div>
<h3 class="font-semibold text-white">Public Transit</h3>
</div>
<span class="px-2 py-1 text-xs font-medium rounded bg-red-500/10 text-red-400 border border-red-500/20">Outage</span>
</div>
<p class="text-sm text-neutral-400 mb-2">Service suspended on Line 5</p>
<p class="text-xs text-neutral-500">Last updated: 8 minutes ago</p>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<h3 class="font-semibold text-white">Emergency Services</h3>
</div>
<span class="px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">Operational</span>
</div>
<p class="text-sm text-neutral-400 mb-2">All systems nominal</p>
<p class="text-xs text-neutral-500">Last updated: 1 minute ago</p>
</div>
</div>
</section>
<!-- Location / Region Status -->
<section>
<h2 class="text-lg font-semibold text-white mb-4">Regional Status</h2>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-neutral-900 border-b border-neutral-700">
<tr>
<th class="text-left px-4 py-3 text-xs font-semibold text-neutral-300 uppercase tracking-wider">Region / Area</th>
<th class="text-left px-4 py-3 text-xs font-semibold text-neutral-300 uppercase tracking-wider">Affected Services</th>
<th class="text-left px-4 py-3 text-xs font-semibold text-neutral-300 uppercase tracking-wider">Status</th>
<th class="text-left px-4 py-3 text-xs font-semibold text-neutral-300 uppercase tracking-wider">Last Update</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700">
<tr>
<td class="px-4 py-3 text-sm text-white whitespace-nowrap">North District</td>
<td class="px-4 py-3 text-sm text-neutral-400">None</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-2 px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">
<div class="w-2 h-2 rounded-full bg-green-500"></div>
Operational
</span>
</td>
<td class="px-4 py-3 text-sm text-neutral-500 whitespace-nowrap">2 minutes ago</td>
</tr>
<tr>
<td class="px-4 py-3 text-sm text-white whitespace-nowrap">South District</td>
<td class="px-4 py-3 text-sm text-neutral-400">None</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-2 px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">
<div class="w-2 h-2 rounded-full bg-green-500"></div>
Operational
</span>
</td>
<td class="px-4 py-3 text-sm text-neutral-500 whitespace-nowrap">4 minutes ago</td>
</tr>
<tr>
<td class="px-4 py-3 text-sm text-white whitespace-nowrap">East District</td>
<td class="px-4 py-3 text-sm text-neutral-400">Network, Transit</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-2 px-2 py-1 text-xs font-medium rounded bg-yellow-500/10 text-yellow-400 border border-yellow-500/20">
<div class="w-2 h-2 rounded-full bg-yellow-500"></div>
Degraded
</span>
</td>
<td class="px-4 py-3 text-sm text-neutral-500 whitespace-nowrap">1 minute ago</td>
</tr>
<tr>
<td class="px-4 py-3 text-sm text-white whitespace-nowrap">West District</td>
<td class="px-4 py-3 text-sm text-neutral-400">None</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-2 px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">
<div class="w-2 h-2 rounded-full bg-green-500"></div>
Operational
</span>
</td>
<td class="px-4 py-3 text-sm text-neutral-500 whitespace-nowrap">3 minutes ago</td>
</tr>
<tr>
<td class="px-4 py-3 text-sm text-white whitespace-nowrap">Central District</td>
<td class="px-4 py-3 text-sm text-neutral-400">None</td>
<td class="px-4 py-3">
<span class="inline-flex items-center gap-2 px-2 py-1 text-xs font-medium rounded bg-green-500/10 text-green-400 border border-green-500/20">
<div class="w-2 h-2 rounded-full bg-green-500"></div>
Operational
</span>
</td>
<td class="px-4 py-3 text-sm text-neutral-500 whitespace-nowrap">5 minutes ago</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- Incident Snapshot -->
<section>
<h2 class="text-lg font-semibold text-white mb-4">Active Incidents</h2>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 divide-y divide-neutral-700">
<div class="p-4">
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-2">
<h3 class="font-semibold text-white">Network connectivity issues in Zone C</h3>
<span class="px-2 py-1 text-xs font-medium rounded bg-yellow-500/10 text-yellow-400 border border-yellow-500/20 self-start">High</span>
</div>
<div class="flex items-center gap-3 text-sm">
<span class="px-2 py-1 text-xs font-medium rounded bg-blue-500/10 text-blue-400 border border-blue-500/20">Investigating</span>
<span class="text-neutral-500">15 minutes ago</span>
</div>
</div>
<div class="p-4">
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-2">
<h3 class="font-semibold text-white">Transit Line 5 service disruption</h3>
<span class="px-2 py-1 text-xs font-medium rounded bg-red-500/10 text-red-400 border border-red-500/20 self-start">Critical</span>
</div>
<div class="flex items-center gap-3 text-sm">
<span class="px-2 py-1 text-xs font-medium rounded bg-orange-500/10 text-orange-400 border border-orange-500/20">Identified</span>
<span class="text-neutral-500">32 minutes ago</span>
</div>
</div>
</div>
</section>
<!-- Loading State (Hidden by default) -->
<div class="space-y-6 hidden" data-loading-state>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4 animate-pulse">
<div class="h-4 bg-neutral-700 rounded w-1/2 mb-3"></div>
<div class="h-8 bg-neutral-700 rounded w-3/4"></div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4 animate-pulse">
<div class="h-4 bg-neutral-700 rounded w-1/2 mb-3"></div>
<div class="h-8 bg-neutral-700 rounded w-3/4"></div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4 animate-pulse">
<div class="h-4 bg-neutral-700 rounded w-1/2 mb-3"></div>
<div class="h-8 bg-neutral-700 rounded w-3/4"></div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4 animate-pulse">
<div class="h-4 bg-neutral-700 rounded w-1/2 mb-3"></div>
<div class="h-8 bg-neutral-700 rounded w-3/4"></div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4 animate-pulse">
<div class="h-5 bg-neutral-700 rounded w-1/3 mb-3"></div>
<div class="h-4 bg-neutral-700 rounded w-full mb-2"></div>
<div class="h-3 bg-neutral-700 rounded w-1/4"></div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4 animate-pulse">
<div class="h-5 bg-neutral-700 rounded w-1/3 mb-3"></div>
<div class="h-4 bg-neutral-700 rounded w-full mb-2"></div>
<div class="h-3 bg-neutral-700 rounded w-1/4"></div>
</div>
</div>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-4">
<div class="space-y-3">
<div class="h-10 bg-neutral-700 rounded animate-pulse"></div>
<div class="h-10 bg-neutral-700 rounded animate-pulse"></div>
<div class="h-10 bg-neutral-700 rounded animate-pulse"></div>
</div>
</div>
</div>
<!-- Empty State for Incidents -->
<div class="bg-neutral-800 rounded-lg border border-neutral-700 p-12 text-center hidden" data-empty-incidents>
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-green-500/10 mb-4">
<svg class="w-8 h-8 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<p class="text-lg font-semibold text-white mb-1">No active incidents</p>
<p class="text-sm text-neutral-400">All systems are operating normally</p>
</div>
</div>

191
src/assets/admin/_system_logs.php Executable file
View File

@@ -0,0 +1,191 @@
<?php
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] != 1) {
echo "<div class='text-red-500 p-6'>Access Denied</div>";
exit;
}
$sql = "SELECT r.id, r.title, r.priority, r.status, r.created_at, r.updated_at, u.username, u.email, u.is_admin
FROM reports r
JOIN users u ON r.user_id = u.id";
$result = $conn->query($sql);
$logs = [];
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$logs[] = [
'timestamp' => strtotime($row['created_at']),
'formatted_time' => date("M j, Y • H:i", strtotime($row['created_at'])),
'actor_name' => $row['username'],
'actor_email' => $row['email'],
'actor_role' => $row['is_admin'] ? 'Administrator' : 'User',
'action' => 'Created report',
'report_id' => $row['id'],
'title' => htmlspecialchars($row['title']),
'priority' => $row['priority'],
'status' => 'Opened',
'is_update' => false
];
if (strtotime($row['updated_at']) > strtotime($row['created_at'])) {
$actionText = 'Updated status';
if ($row['status'] === 'Resolved') $actionText = 'Resolved report';
if ($row['status'] === 'In Progress') $actionText = 'Marked In Progress';
$logs[] = [
'timestamp' => strtotime($row['updated_at']),
'formatted_time' => date("M j, Y • H:i", strtotime($row['updated_at'])),
'actor_name' => 'System Admin',
'actor_email' => 'admin@system',
'actor_role' => 'Administrator',
'action' => $actionText,
'report_id' => $row['id'],
'title' => htmlspecialchars($row['title']),
'priority' => $row['priority'],
'status' => $row['status'],
'is_update' => true
];
}
}
}
usort($logs, function($a, $b) {
return $b['timestamp'] - $a['timestamp'];
});
$itemsPerPage = 6;
$totalItems = count($logs);
$totalPages = ceil($totalItems / $itemsPerPage);
$page = isset($_GET['p']) ? (int)$_GET['p'] : 1;
if ($page < 1) $page = 1;
if ($page > $totalPages && $totalPages > 0) $page = $totalPages;
$offset = ($page - 1) * $itemsPerPage;
$currentLogs = array_slice($logs, $offset, $itemsPerPage);
?>
<section class="space-y-6">
<header>
<h1 class="text-2xl font-semibold text-white">System Logs</h1>
<p class="mt-1 text-sm text-neutral-400">
Administrative and user actions across reports
</p>
</header>
<div class="bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden shadow-sm">
<div class="overflow-x-auto custom-scrollbar">
<table class="w-full">
<thead class="bg-neutral-900/50 border-b border-neutral-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Time</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Actor</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Action</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Report</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Priority</th>
<th class="px-6 py-3 text-left text-xs font-medium text-neutral-400 uppercase tracking-wider">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-700/50">
<?php if (empty($currentLogs)): ?>
<tr>
<td colspan="6" class="px-6 py-8 text-center text-neutral-500">
No system logs found.
</td>
</tr>
<?php else: ?>
<?php foreach ($currentLogs as $log): ?>
<?php
$priorityColor = match($log['priority']) {
'High' => 'text-red-400',
'Medium' => 'text-yellow-400',
'Low' => 'text-blue-400',
default => 'text-neutral-400'
};
$statusConfig = match($log['status']) {
'Resolved' => ['text' => 'text-green-400', 'bg' => 'bg-green-500/10', 'border' => 'border-green-500/20', 'icon' => 'text-green-500', 'path' => 'M5 13l4 4L19 7'],
'In Progress' => ['text' => 'text-yellow-400', 'bg' => 'bg-yellow-500/10', 'border' => 'border-yellow-500/20', 'icon' => 'text-yellow-500', 'path' => 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z'],
'Opened' => ['text' => 'text-blue-400', 'bg' => 'bg-blue-500/10', 'border' => 'border-blue-500/20', 'icon' => 'text-blue-500', 'path' => 'M12 4v16m8-8H4'],
default => ['text' => 'text-neutral-400', 'bg' => 'bg-neutral-500/10', 'border' => 'border-neutral-500/20', 'icon' => 'text-neutral-500', 'path' => 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z']
};
?>
<tr class="hover:bg-neutral-700/30 transition-colors">
<td class="px-6 py-4 text-sm text-neutral-300 font-mono text-xs whitespace-nowrap">
<?php echo $log['formatted_time']; ?>
</td>
<td class="px-6 py-4 text-sm text-white">
<div class="font-medium"><?php echo htmlspecialchars($log['actor_name']); ?></div>
<div class="text-[11px] text-neutral-500"><?php echo htmlspecialchars($log['actor_role']); ?></div>
</td>
<td class="px-6 py-4 text-sm text-neutral-300">
<?php echo $log['action']; ?>
</td>
<td class="px-6 py-4 text-sm text-neutral-200">
<span class="text-neutral-500 font-mono mr-1">#<?php echo $log['report_id']; ?></span>
<?php echo $log['title']; ?>
</td>
<td class="px-6 py-4">
<span class="text-xs font-medium <?php echo $priorityColor; ?>">
<?php echo $log['priority']; ?>
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full <?php echo $statusConfig['bg'] . ' ' . $statusConfig['border']; ?> border flex items-center justify-center">
<svg class="w-3.5 h-3.5 <?php echo $statusConfig['icon']; ?>" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<?php echo $statusConfig['path']; ?>" />
</svg>
</div>
<span class="text-xs <?php echo $statusConfig['text']; ?>">
<?php echo $log['status']; ?>
</span>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="bg-neutral-900 border-t border-neutral-700 px-6 py-4 flex items-center justify-between">
<span class="text-sm text-neutral-400">
Showing page <span class="font-medium text-white"><?php echo $page; ?></span> of <?php echo max(1, $totalPages); ?>
</span>
<div class="flex gap-2">
<?php
$prevParams = array_merge($_GET, ['p' => max(1, $page - 1)]);
$nextParams = array_merge($_GET, ['p' => min($totalPages, $page + 1)]);
$prevDisabled = ($page <= 1) ? 'opacity-50 cursor-not-allowed pointer-events-none' : '';
$nextDisabled = ($page >= $totalPages) ? 'opacity-50 cursor-not-allowed pointer-events-none' : '';
?>
<a href="?<?php echo http_build_query($prevParams); ?>" class="px-3 py-1 bg-neutral-800 hover:bg-neutral-700 border border-neutral-700 rounded-lg text-sm text-white transition-colors <?php echo $prevDisabled; ?>">
Previous
</a>
<a href="?<?php echo http_build_query($nextParams); ?>" class="px-3 py-1 bg-neutral-800 hover:bg-neutral-700 border border-neutral-700 rounded-lg text-sm text-white transition-colors <?php echo $nextDisabled; ?>">
Next
</a>
</div>
</div>
</div>
</section>

BIN
src/assets/images/preview/infra-xodivorce-in-preview.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 KiB

After

Width:  |  Height:  |  Size: 602 KiB

View File

@@ -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");

View File

@@ -1,12 +1,10 @@
<?php
date_default_timezone_set('Asia/Kolkata');
$conn->query("SET time_zone = '+05:30'");
$totalResult = $conn->query("SELECT COUNT(*) FROM reports");
$totalReports = $totalResult->fetch_row()[0];
$openedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Opened'");
$openedReports = $openedResult->fetch_row()[0];
$activeResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status IN ('Opened', 'In Progress')");
$activeReports = $activeResult->fetch_row()[0];
$inProgressResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'In Progress'");
$inProgressReports = $inProgressResult->fetch_row()[0];
$resolvedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Resolved'");
$resolvedReports = $resolvedResult->fetch_row()[0];
@@ -15,13 +13,14 @@ $recentSql = "SELECT r.*, u.username
FROM reports r
JOIN users u ON r.user_id = u.id
ORDER BY r.updated_at DESC
LIMIT 5";
LIMIT 6";
$recentResult = $conn->query($recentSql);
$recentActivity = $recentResult->fetch_all(MYSQLI_ASSOC);
if (!function_exists('time_elapsed_string')) {
function time_elapsed_string($datetime, $full = false) {
function time_elapsed_string($datetime, $full = false)
{
$now = new DateTime;
$ago = new DateTime($datetime);
$diff = $now->diff($ago);
@@ -38,7 +37,8 @@ if (!function_exists('time_elapsed_string')) {
}
}
if (!$full) $string = array_slice($string, 0, 1);
if (!$full)
$string = array_slice($string, 0, 1);
return $string ? implode(', ', $string) . ' ago' : 'just now';
}
}
@@ -48,41 +48,55 @@ if (!function_exists('time_elapsed_string')) {
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-blue-500/30 transition-all duration-300 flex items-center justify-between">
<div
class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-blue-500/30 transition-all duration-300 flex items-center justify-between">
<div>
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-blue-400 transition-colors">Total Reports</h3>
<p class="text-2xl font-bold text-white mt-1"><?= $totalReports ?></p>
<p class="text-[10px] text-neutral-500 mt-1">All submissions overview</p>
<h3
class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-blue-400 transition-colors">
Opened Reports
</h3>
<p class="text-2xl font-bold text-white mt-1"><?= $openedReports ?></p>
<p class="text-[10px] text-neutral-500 mt-1">Newly reported issues</p>
</div>
<div class="p-3 bg-blue-500/10 rounded-lg group-hover:bg-blue-500/20 transition-colors">
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
<div
class="w-10 h-10 rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
</div>
</div>
<div class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-yellow-500/30 transition-all duration-300 flex items-center justify-between">
<div
class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-yellow-500/30 transition-all duration-300 flex items-center justify-between">
<div>
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-yellow-400 transition-colors">Reports In Progress</h3>
<p class="text-2xl font-bold text-white mt-1"><?= $activeReports ?></p>
<h3
class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-yellow-400 transition-colors">
Reports In Progress</h3>
<p class="text-2xl font-bold text-white mt-1"><?= $inProgressReports ?></p>
<p class="text-[10px] text-neutral-500 mt-1">In progress / Open</p>
</div>
<div class="p-3 bg-yellow-500/10 rounded-lg group-hover:bg-yellow-500/20 transition-colors">
<svg class="w-6 h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<div class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-green-500/30 transition-all duration-300 flex items-center justify-between">
<div
class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-green-500/30 transition-all duration-300 flex items-center justify-between">
<div>
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-green-400 transition-colors">Resolved Reports</h3>
<h3
class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-green-400 transition-colors">
Resolved Reports</h3>
<p class="text-2xl font-bold text-white mt-1"><?= $resolvedReports ?></p>
<p class="text-[10px] text-neutral-500 mt-1">Successfully closed</p>
</div>
<div class="p-3 bg-green-500/10 rounded-lg group-hover:bg-green-500/20 transition-colors">
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
@@ -113,8 +127,10 @@ if (!function_exists('time_elapsed_string')) {
?>
<div class="px-4 py-3 hover:bg-neutral-700/30 transition-colors flex items-center gap-3">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-<?= $statusColor ?>-500/10 border border-<?= $statusColor ?>-500/20 flex items-center justify-center">
<svg class="w-4 h-4 text-<?= $statusColor ?>-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div
class="flex-shrink-0 w-8 h-8 rounded-full bg-<?= $statusColor ?>-500/10 border border-<?= $statusColor ?>-500/20 flex items-center justify-center">
<svg class="w-4 h-4 text-<?= $statusColor ?>-500" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<?= $iconPath ?>" />
</svg>
</div>
@@ -122,7 +138,8 @@ if (!function_exists('time_elapsed_string')) {
<div class="flex-1 min-w-0 grid gap-0.5">
<div class="flex items-center justify-between">
<p class="text-sm font-medium text-neutral-200 truncate">
Issue <span class="text-white">#<?= $row['id'] ?></span> <?= htmlspecialchars($row['status']) ?>
Issue <span class="text-white">#<?= $row['id'] ?></span>
<?= htmlspecialchars($row['status']) ?>
</p>
<span class="text-[10px] text-neutral-400 whitespace-nowrap">
<?= time_elapsed_string($row['updated_at']) ?>

View File

@@ -404,6 +404,7 @@ $result = $conn->query($sql);
<option value="Furniture & Fixtures Issue">Furniture & Fixtures Issue</option>
<option value="Library & Study Issue">Library & Study Issue</option>
<option value="Lost & Stolen Issue">Lost & Stolen Issue</option>
<option value="Medical/Health Issue">Medical & Health Issue</option>
<option value="Other Issue">Other Issue</option>
</select>
<svg class="absolute right-3 top-3 h-4 w-4 text-neutral-500 pointer-events-none"

View File

@@ -0,0 +1,205 @@
<?php
header('Content-Type: application/json');
$basePath = dirname(dirname(__DIR__));
require_once $basePath . '/core/init.php';
require_once $basePath . '/core/connection.php';
if (session_status() === PHP_SESSION_NONE) session_start();
if (empty($_SESSION) || !isset($_SESSION['is_admin']) || $_SESSION['is_admin'] != 1) {
http_response_code(403);
echo json_encode(['error' => '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 = <<<PROMPT
You are the AI assistant for '{$domain}', a campus facility Admin dashboard.
Your main goal is to help Admins with campus facility and infrastructure issues only
(WiFi & Network, Electrical, Water & Plumbing, HVAC (AC/Heating), Furniture & Fixtures, Cleaning & Janitorial, Security & Safety, Road & Pathway Damage, Library & Study, Lost & Stolen, Medical/Health Issue or Others).
**IDENTITY:**
1. **{$app_name}** is the name of the college/university you serve.
2. You are part of the **{$app_name} Admin Dashboard** system.
---
### SCOPE ENFORCEMENT (IMPORTANT):
1. **You are strictly a facility Admin assistant.**
Your ONLY purpose is to help Admins report and suggest about handling campus facility or infrastructure issues.
You MUST NOT assist with academic, personal, entertainment, or unrelated topics.
2. **Refusal of Inappropriate or Off-Topic Queries**
If a user asks about inappropriate, illegal, entertainment, gambling, gaming, or unrelated topics:
Respond ONLY with:
> "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 <how-to style query>. 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);

View File

@@ -0,0 +1,77 @@
<?php
function sendReportStatusEmail($conn, $report_id, $new_status) {
// Fetch user email and report details
$stmt = $conn->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 <strong>'$title'</strong> 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 <strong>'$title'</strong> has been successfully resolved. Thank you for helping improve our campus.";
} else {
return false;
}
// HTML Email Template
$message = "
<html>
<head>
<style>
.container { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
.header { background-color: $color_code; color: white; padding: 10px 20px; border-radius: 6px 6px 0 0; }
.content { padding: 20px; color: #333; line-height: 1.6; }
.footer { margin-top: 20px; font-size: 12px; color: #777; border-top: 1px solid #eee; padding-top: 10px; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h2>Status Update</h2>
</div>
<div class='content'>
<p>Hello $username,</p>
<p>$body_content</p>
<p><strong>Current Status:</strong> <span style='color: $color_code; font-weight: bold;'>$new_status</span></p>
</div>
<div class='footer'>
<p>This is an automated message. Please do not reply directly to this email.</p>
</div>
</div>
</body>
</html>
";
// Send Mail
return mail($to, $subject, $message, $headers);
}
?>

View File

@@ -31,6 +31,9 @@ function getDepartmentEmail($category)
case 'Library & Study Issue':
return LIBRARY_EMAIL;
case 'Medical/Health Issue':
return HEALTH_EMAIL;
case 'Other Issue':
default:
return IT_HELPDESK_EMAIL;

View File

@@ -54,7 +54,7 @@ $secPhone = $_ENV['SECURITY_PHONE'];
$systemPrompt = <<<PROMPT
You are the AI assistant for '{$domain}', a campus facility dashboard.
Your main goal is to help students with campus facility and infrastructure issues only
(WiFi & Network, Electrical, Water & Plumbing, HVAC (AC/Heating), Furniture & Fixtures, Cleaning & Janitorial, Security & Safety, Road & Pathway Damage, Library & Study, Lost & Stolen or Others).
(WiFi & Network, Electrical, Water & Plumbing, HVAC (AC/Heating), Furniture & Fixtures, Cleaning & Janitorial, Security & Safety, Road & Pathway Damage, Library & Study, Lost & Stolen, Medical/Health Issue or Others).
**IDENTITY:**
1. **{$app_name}** is the name of the college/university you serve.

View File

@@ -23,31 +23,31 @@ $routes = [
'file' => './assets/users/_reports.php',
'nav' => true,
],
'status-board' => [
'admin_overview' => [
'role' => 'admin',
'label' => 'Status Board',
'label' => 'Admin Overview',
'icon' => 'activity',
'file' => './assets/admin/_status_board.php',
'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,
],
];
if (!isset($routes[$page])) {
$page = $is_admin ? 'status-board' : 'overview';
$page = $is_admin ? 'admin_overview' : 'overview';
}
$routeRole = $routes[$page]['role'];
@@ -56,7 +56,7 @@ if (
($routeRole === 'admin' && !$is_admin) ||
($routeRole === 'user' && $is_admin)
) {
$page = $is_admin ? 'status-board' : 'overview';
$page = $is_admin ? 'admin_overview' : 'overview';
}
$current_route = $routes[$page];

View File

@@ -33,6 +33,7 @@ error_reporting(0);
<!-- Main Stylesheet (Tailwind CSS) -->
<link rel="stylesheet" href="./src/output.css">
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts: Lexend Deca -->
<link rel="preconnect" href="https://fonts.googleapis.com">

View File

@@ -229,7 +229,7 @@ if (isset($_GET['code'])) {
Need Help? Checkout Github Docs</a>
<span class="w-px h-3 bg-slate-800"></span>
<span
class="text-xs font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-500/20">v1.3.2</span>
class="text-xs font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-500/20">v1.3.6</span>
</div>
<div class="flex items-center gap-2">

View File

@@ -15,7 +15,6 @@
--color-red-900: oklch(39.6% 0.141 25.723);
--color-orange-300: oklch(83.7% 0.128 66.29);
--color-orange-400: oklch(75% 0.183 55.934);
--color-orange-500: oklch(70.5% 0.213 47.604);
--color-amber-400: oklch(82.8% 0.189 84.429);
--color-amber-500: oklch(76.9% 0.188 70.08);
--color-yellow-200: oklch(94.5% 0.129 101.54);
@@ -112,6 +111,7 @@
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--blur-sm: 8px;
--blur-md: 12px;
@@ -592,15 +592,6 @@
.w-1\.5 {
width: calc(var(--spacing) * 1.5);
}
.w-1\/2 {
width: calc(1/2 * 100%);
}
.w-1\/3 {
width: calc(1/3 * 100%);
}
.w-1\/4 {
width: calc(1/4 * 100%);
}
.w-1\/6 {
width: calc(1/6 * 100%);
}
@@ -610,9 +601,6 @@
.w-3 {
width: calc(var(--spacing) * 3);
}
.w-3\/4 {
width: calc(3/4 * 100%);
}
.w-4 {
width: calc(var(--spacing) * 4);
}
@@ -755,6 +743,9 @@
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.animate-ping {
animation: var(--animate-ping);
}
.animate-pulse {
animation: var(--animate-pulse);
}
@@ -797,6 +788,9 @@
.flex-wrap {
flex-wrap: wrap;
}
.items-baseline {
align-items: baseline;
}
.items-center {
align-items: center;
}
@@ -1072,12 +1066,6 @@
.border-neutral-900 {
border-color: var(--color-neutral-900);
}
.border-orange-500\/20 {
border-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent);
}
}
.border-red-500\/20 {
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -1297,12 +1285,6 @@
background-color: color-mix(in oklab, var(--color-neutral-900) 95%, transparent);
}
}
.bg-orange-500\/10 {
background-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-orange-500) 10%, transparent);
}
}
.bg-purple-500\/10 {
background-color: color-mix(in srgb, oklch(62.7% 0.265 303.9) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -1823,6 +1805,9 @@
.opacity-70 {
opacity: 70%;
}
.opacity-75 {
opacity: 75%;
}
.opacity-80 {
opacity: 80%;
}
@@ -2021,6 +2006,16 @@
}
}
}
.group-hover\:border-green-500\/20 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
border-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-green-500) 20%, transparent);
}
}
}
}
.group-hover\:border-neutral-700 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
@@ -2028,12 +2023,32 @@
}
}
}
.group-hover\:bg-blue-500\/20 {
.group-hover\:border-red-500\/20 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
background-color: color-mix(in srgb, oklch(62.3% 0.214 259.815) 20%, transparent);
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-blue-500) 20%, transparent);
border-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
}
}
}
}
.group-hover\:border-yellow-500\/20 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
border-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-yellow-500) 20%, transparent);
}
}
}
}
.group-hover\:bg-green-500\/10 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
background-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-green-500) 10%, transparent);
}
}
}
@@ -2048,6 +2063,26 @@
}
}
}
.group-hover\:bg-red-500\/10 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-red-500) 10%, transparent);
}
}
}
}
.group-hover\:bg-yellow-500\/10 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
background-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-yellow-500) 10%, transparent);
}
}
}
}
.group-hover\:bg-yellow-500\/20 {
&:is(:where(.group):hover *) {
@media (hover: hover) {
@@ -2340,6 +2375,26 @@
}
}
}
.hover\:border-green-500\/50 {
&:hover {
@media (hover: hover) {
border-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-green-500) 50%, transparent);
}
}
}
}
.hover\:border-neutral-500\/50 {
&:hover {
@media (hover: hover) {
border-color: color-mix(in srgb, oklch(55.6% 0 0) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-neutral-500) 50%, transparent);
}
}
}
}
.hover\:border-neutral-600 {
&:hover {
@media (hover: hover) {
@@ -2367,6 +2422,16 @@
}
}
}
.hover\:border-red-500\/50 {
&:hover {
@media (hover: hover) {
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
}
}
}
}
.hover\:border-yellow-500\/30 {
&:hover {
@media (hover: hover) {
@@ -2377,6 +2442,16 @@
}
}
}
.hover\:border-yellow-500\/50 {
&:hover {
@media (hover: hover) {
border-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-yellow-500) 50%, transparent);
}
}
}
}
.hover\:bg-blue-500\/20 {
&:hover {
@media (hover: hover) {
@@ -2558,6 +2633,26 @@
}
}
}
.hover\:shadow-green-500\/5 {
&:hover {
@media (hover: hover) {
--tw-shadow-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 5%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-green-500) 5%, transparent) var(--tw-shadow-alpha), transparent);
}
}
}
}
.hover\:shadow-red-500\/5 {
&:hover {
@media (hover: hover) {
--tw-shadow-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 5%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 5%, transparent) var(--tw-shadow-alpha), transparent);
}
}
}
}
.hover\:shadow-red-500\/20 {
&:hover {
@media (hover: hover) {
@@ -2568,6 +2663,16 @@
}
}
}
.hover\:shadow-yellow-500\/5 {
&:hover {
@media (hover: hover) {
--tw-shadow-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 5%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-yellow-500) 5%, transparent) var(--tw-shadow-alpha), transparent);
}
}
}
}
.focus\:border-blue-500 {
&:focus {
border-color: var(--color-blue-500);
@@ -2689,11 +2794,6 @@
align-items: center;
}
}
.sm\:items-start {
@media (width >= 40rem) {
align-items: flex-start;
}
}
.sm\:justify-between {
@media (width >= 40rem) {
justify-content: space-between;
@@ -2968,11 +3068,6 @@
margin-top: calc(var(--spacing) * 10);
}
}
.lg\:grid-cols-2 {
@media (width >= 64rem) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.lg\:grid-cols-4 {
@media (width >= 64rem) {
grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -3332,6 +3427,12 @@
initial-value: "";
inherits: false;
}
@keyframes ping {
75%, 100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes pulse {
50% {
opacity: 0.5;