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. #### 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/) [![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/) [![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!** > **🥰 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"> <div class="text-sm font-semibold text-white">
<?php echo htmlspecialchars($current_email); ?> <?php echo htmlspecialchars($current_email); ?>
</div> </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'; ?> <?php echo ($is_admin ?? false) ? 'Administrator' : 'User'; ?>
</div> </div>
</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): ?> <?php if ($is_admin): ?>
<a href="?page=status-board" <a href="?page=admin_overview"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'status-board' ? $activeClass : $inactiveClass; ?>"> 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 === 'status-board' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors" <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"> fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <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> </svg>
Status Board Admin Overview
</a> </a>
<a href="?page=activity-logs" <a href="?page=admin-panel"
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'activity-logs' ? $activeClass : $inactiveClass; ?>"> 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 === 'activity-logs' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors" <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="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"
fill="none" stroke="currentColor" viewBox="0 0 24 24"> fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <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" /> 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" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg> </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> </a>
<?php endif; ?> <?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) { function validateFile(input) {
const file = input.files[0]; const file = input.files[0];
const labelMain = document.getElementById("file-label-main"); const labelMain = document.getElementById("file-label-main");

View File

@@ -1,12 +1,10 @@
<?php <?php
date_default_timezone_set('Asia/Kolkata');
$conn->query("SET time_zone = '+05:30'");
$totalResult = $conn->query("SELECT COUNT(*) FROM reports"); $openedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Opened'");
$totalReports = $totalResult->fetch_row()[0]; $openedReports = $openedResult->fetch_row()[0];
$activeResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status IN ('Opened', 'In Progress')"); $inProgressResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'In Progress'");
$activeReports = $activeResult->fetch_row()[0]; $inProgressReports = $inProgressResult->fetch_row()[0];
$resolvedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Resolved'"); $resolvedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Resolved'");
$resolvedReports = $resolvedResult->fetch_row()[0]; $resolvedReports = $resolvedResult->fetch_row()[0];
@@ -15,13 +13,14 @@ $recentSql = "SELECT r.*, u.username
FROM reports r FROM reports r
JOIN users u ON r.user_id = u.id JOIN users u ON r.user_id = u.id
ORDER BY r.updated_at DESC ORDER BY r.updated_at DESC
LIMIT 5"; LIMIT 6";
$recentResult = $conn->query($recentSql); $recentResult = $conn->query($recentSql);
$recentActivity = $recentResult->fetch_all(MYSQLI_ASSOC); $recentActivity = $recentResult->fetch_all(MYSQLI_ASSOC);
if (!function_exists('time_elapsed_string')) { if (!function_exists('time_elapsed_string')) {
function time_elapsed_string($datetime, $full = false) { function time_elapsed_string($datetime, $full = false)
{
$now = new DateTime; $now = new DateTime;
$ago = new DateTime($datetime); $ago = new DateTime($datetime);
$diff = $now->diff($ago); $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'; 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="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> <div>
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-blue-400 transition-colors">Total Reports</h3> <h3
<p class="text-2xl font-bold text-white mt-1"><?= $totalReports ?></p> class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-blue-400 transition-colors">
<p class="text-[10px] text-neutral-500 mt-1">All submissions overview</p> 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>
<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"> <div
<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" /> 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> </svg>
</div> </div>
</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> <div>
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-yellow-400 transition-colors">Reports In Progress</h3> <h3
<p class="text-2xl font-bold text-white mt-1"><?= $activeReports ?></p> 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> <p class="text-[10px] text-neutral-500 mt-1">In progress / Open</p>
</div> </div>
<div class="p-3 bg-yellow-500/10 rounded-lg group-hover:bg-yellow-500/20 transition-colors"> <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"> <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> </svg>
</div> </div>
</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> <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-2xl font-bold text-white mt-1"><?= $resolvedReports ?></p>
<p class="text-[10px] text-neutral-500 mt-1">Successfully closed</p> <p class="text-[10px] text-neutral-500 mt-1">Successfully closed</p>
</div> </div>
<div class="p-3 bg-green-500/10 rounded-lg group-hover:bg-green-500/20 transition-colors"> <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"> <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> </svg>
</div> </div>
</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="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"> <div
<svg class="w-4 h-4 text-<?= $statusColor ?>-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 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 ?>" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<?= $iconPath ?>" />
</svg> </svg>
</div> </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-1 min-w-0 grid gap-0.5">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<p class="text-sm font-medium text-neutral-200 truncate"> <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> </p>
<span class="text-[10px] text-neutral-400 whitespace-nowrap"> <span class="text-[10px] text-neutral-400 whitespace-nowrap">
<?= time_elapsed_string($row['updated_at']) ?> <?= 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="Furniture & Fixtures Issue">Furniture & Fixtures Issue</option>
<option value="Library & Study Issue">Library & Study Issue</option> <option value="Library & Study Issue">Library & Study Issue</option>
<option value="Lost & Stolen Issue">Lost & Stolen 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> <option value="Other Issue">Other Issue</option>
</select> </select>
<svg class="absolute right-3 top-3 h-4 w-4 text-neutral-500 pointer-events-none" <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': case 'Library & Study Issue':
return LIBRARY_EMAIL; return LIBRARY_EMAIL;
case 'Medical/Health Issue':
return HEALTH_EMAIL;
case 'Other Issue': case 'Other Issue':
default: default:
return IT_HELPDESK_EMAIL; return IT_HELPDESK_EMAIL;

View File

@@ -54,7 +54,7 @@ $secPhone = $_ENV['SECURITY_PHONE'];
$systemPrompt = <<<PROMPT $systemPrompt = <<<PROMPT
You are the AI assistant for '{$domain}', a campus facility dashboard. 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 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:** **IDENTITY:**
1. **{$app_name}** is the name of the college/university you serve. 1. **{$app_name}** is the name of the college/university you serve.

View File

@@ -23,31 +23,31 @@ $routes = [
'file' => './assets/users/_reports.php', 'file' => './assets/users/_reports.php',
'nav' => true, 'nav' => true,
], ],
'status-board' => [ 'admin_overview' => [
'role' => 'admin', 'role' => 'admin',
'label' => 'Status Board', 'label' => 'Admin Overview',
'icon' => 'activity', 'icon' => 'activity',
'file' => './assets/admin/_status_board.php', 'file' => './assets/admin/_admin_overview.php',
'nav' => true, 'nav' => true,
], ],
'activity-logs' => [ 'system-logs' => [
'role' => 'admin', 'role' => 'admin',
'label' => 'Activity Logs', 'label' => 'System Logs',
'icon' => 'clock', 'icon' => 'clock',
'file' => './assets/admin/_activity_logs.php', 'file' => './assets/admin/_system_logs.php',
'nav' => true, 'nav' => true,
], ],
'admin' => [ 'admin-panel' => [
'role' => 'admin', 'role' => 'admin',
'label' => 'Admin', 'label' => 'Admin Panel',
'icon' => 'settings', 'icon' => 'settings',
'file' => './assets/admin/_admin.php', 'file' => './assets/admin/_admin_panel.php',
'nav' => true, 'nav' => true,
], ],
]; ];
if (!isset($routes[$page])) { if (!isset($routes[$page])) {
$page = $is_admin ? 'status-board' : 'overview'; $page = $is_admin ? 'admin_overview' : 'overview';
} }
$routeRole = $routes[$page]['role']; $routeRole = $routes[$page]['role'];
@@ -56,7 +56,7 @@ if (
($routeRole === 'admin' && !$is_admin) || ($routeRole === 'admin' && !$is_admin) ||
($routeRole === 'user' && $is_admin) ($routeRole === 'user' && $is_admin)
) { ) {
$page = $is_admin ? 'status-board' : 'overview'; $page = $is_admin ? 'admin_overview' : 'overview';
} }
$current_route = $routes[$page]; $current_route = $routes[$page];

View File

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

View File

@@ -229,7 +229,7 @@ if (isset($_GET['code'])) {
Need Help? Checkout Github Docs</a> Need Help? Checkout Github Docs</a>
<span class="w-px h-3 bg-slate-800"></span> <span class="w-px h-3 bg-slate-800"></span>
<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>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View File

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