mirror of
https://github.com/xodivorce/infra-xodivorce-in.git
synced 2026-02-04 15:52:21 +05:30
Compare commits
8 Commits
devupdates
...
0900b0990d
@@ -6,7 +6,7 @@
|
||||
#### A real-time map-based reporting system for campus infrastructure issues, built to improve visibility, accountability, and resolution efficiency.
|
||||
|
||||
[](https://github.com/xodivorce/infra-xodivorce-in/)
|
||||
[](https://github.com/xodivorce/infra-xodivorce-in/)
|
||||
[](https://github.com/xodivorce/infra-xodivorce-in/)
|
||||
[](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!**
|
||||
|
||||
@@ -24,7 +24,7 @@ $user_initial = strtoupper($current_email[0]);
|
||||
<div class="text-sm font-semibold text-white">
|
||||
<?php echo htmlspecialchars($current_email); ?>
|
||||
</div>
|
||||
<div class="text-xs text-neutral-500 font-medium uppercase tracking-wide">
|
||||
<div class="text-xs text-neutral-400 font-medium uppercase tracking-wide">
|
||||
<?php echo ($is_admin ?? false) ? 'Administrator' : 'User'; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,36 +33,36 @@ $inactiveClass = 'text-neutral-400 hover:text-white hover:bg-neutral-800/60 bord
|
||||
|
||||
<?php if ($is_admin): ?>
|
||||
|
||||
<a href="?page=status-board"
|
||||
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'status-board' ? $activeClass : $inactiveClass; ?>">
|
||||
<svg class="w-5 h-5 mr-3 <?php echo $page === 'status-board' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
|
||||
<a href="?page=admin_overview"
|
||||
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'admin_overview' ? $activeClass : $inactiveClass; ?>">
|
||||
<svg class="w-5 h-5 mr-3 <?php echo $page === 'overview' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
||||
</svg>
|
||||
Status Board
|
||||
Admin Overview
|
||||
</a>
|
||||
|
||||
<a href="?page=activity-logs"
|
||||
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'activity-logs' ? $activeClass : $inactiveClass; ?>">
|
||||
<svg class="w-5 h-5 mr-3 <?php echo $page === 'activity-logs' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Activity Logs
|
||||
</a>
|
||||
|
||||
<a href="?page=admin"
|
||||
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'admin' ? $activeClass : $inactiveClass; ?>">
|
||||
<svg class="w-5 h-5 mr-3 <?php echo $page === 'admin' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
|
||||
<a href="?page=admin-panel"
|
||||
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'admin-panel' ? $activeClass : $inactiveClass; ?>">
|
||||
<svg class="w-5 h-5 mr-3 <?php echo $page === 'admin-panel' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
System Admin
|
||||
Admin Panel
|
||||
</a>
|
||||
|
||||
<a href="?page=system-logs"
|
||||
class="group flex items-center px-4 py-3 text-sm font-medium rounded-xl <?php echo $page === 'system-logs' ? $activeClass : $inactiveClass; ?>">
|
||||
<svg class="w-5 h-5 mr-3 <?php echo $page === 'system-logs' ? 'text-blue-400' : 'text-neutral-500 group-hover:text-white'; ?> transition-colors"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
System Logs
|
||||
</a>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
286
src/assets/admin/_admin_overview.php
Executable file
286
src/assets/admin/_admin_overview.php
Executable file
@@ -0,0 +1,286 @@
|
||||
<?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 Issue',
|
||||
];
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
REPLACE(category, '&', '&') AS category,
|
||||
SUM(status = 'Opened') AS opened_count,
|
||||
SUM(status = 'In Progress') AS progress_count
|
||||
FROM reports
|
||||
WHERE status IN ('Opened', 'In Progress')
|
||||
GROUP BY category
|
||||
";
|
||||
|
||||
$result = $conn->query($sql);
|
||||
|
||||
$deptStats = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$deptStats[$row['category']] = $row;
|
||||
}
|
||||
|
||||
foreach ($departments as $dept) {
|
||||
if (!isset($deptStats[$dept])) {
|
||||
$deptStats[$dept] = [
|
||||
'opened_count' => 0,
|
||||
'progress_count' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
$services = [];
|
||||
$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);
|
||||
|
||||
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"/>'
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($_GET['ajax']) && $_GET['ajax'] === '1') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'services' => $services,
|
||||
'stats' => [
|
||||
'activeServices' => $activeServices,
|
||||
'maintenance' => $maintenanceCount,
|
||||
'outage' => $highPriority,
|
||||
'system' => $sysData
|
||||
]
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<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" id="activeServices"><?= $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" id="maintenanceCount"><?= $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" id="outageCount"><?= $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 id="servicesGrid" 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>
|
||||
575
src/assets/admin/_admin_panel.php
Executable file
575
src/assets/admin/_admin_panel.php
Executable file
@@ -0,0 +1,575 @@
|
||||
<?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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
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 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 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>
|
||||
@@ -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
191
src/assets/admin/_system_logs.php
Executable 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
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 |
@@ -376,3 +376,37 @@ async function handleChatSubmit() {
|
||||
appendGeminiError("Unable to connect to Gemini. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshStatus() {
|
||||
try {
|
||||
const res = await fetch(window.location.pathname + '?ajax=1');
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('activeServices').textContent = data.stats.activeServices;
|
||||
document.getElementById('maintenanceCount').textContent = data.stats.maintenance;
|
||||
document.getElementById('outageCount').textContent = data.stats.outage;
|
||||
|
||||
const grid = document.getElementById('servicesGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
data.services.forEach(s => {
|
||||
const color =
|
||||
s.status === 'Outage' ? 'red' :
|
||||
s.status === 'Maintenance' ? 'yellow' :
|
||||
'green';
|
||||
|
||||
grid.innerHTML += `
|
||||
<div class="bg-neutral-800 border border-neutral-700/50 rounded-xl p-4">
|
||||
<h3 class="font-semibold text-white text-sm">${s.name}</h3>
|
||||
<p class="text-xs text-neutral-400">${s.msg}</p>
|
||||
<span class="inline-block mt-2 w-2 h-2 rounded-full bg-${color}-500"></span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error('Status refresh failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(refreshStatus, 3000);
|
||||
@@ -1,12 +1,10 @@
|
||||
<?php
|
||||
date_default_timezone_set('Asia/Kolkata');
|
||||
$conn->query("SET time_zone = '+05:30'");
|
||||
|
||||
$totalResult = $conn->query("SELECT COUNT(*) FROM reports");
|
||||
$totalReports = $totalResult->fetch_row()[0];
|
||||
$openedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Opened'");
|
||||
$openedReports = $openedResult->fetch_row()[0];
|
||||
|
||||
$activeResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status IN ('Opened', 'In Progress')");
|
||||
$activeReports = $activeResult->fetch_row()[0];
|
||||
$inProgressResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'In Progress'");
|
||||
$inProgressReports = $inProgressResult->fetch_row()[0];
|
||||
|
||||
$resolvedResult = $conn->query("SELECT COUNT(*) FROM reports WHERE status = 'Resolved'");
|
||||
$resolvedReports = $resolvedResult->fetch_row()[0];
|
||||
@@ -15,13 +13,14 @@ $recentSql = "SELECT r.*, u.username
|
||||
FROM reports r
|
||||
JOIN users u ON r.user_id = u.id
|
||||
ORDER BY r.updated_at DESC
|
||||
LIMIT 5";
|
||||
LIMIT 6";
|
||||
|
||||
$recentResult = $conn->query($recentSql);
|
||||
$recentActivity = $recentResult->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
if (!function_exists('time_elapsed_string')) {
|
||||
function time_elapsed_string($datetime, $full = false) {
|
||||
function time_elapsed_string($datetime, $full = false)
|
||||
{
|
||||
$now = new DateTime;
|
||||
$ago = new DateTime($datetime);
|
||||
$diff = $now->diff($ago);
|
||||
@@ -38,7 +37,8 @@ if (!function_exists('time_elapsed_string')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!$full) $string = array_slice($string, 0, 1);
|
||||
if (!$full)
|
||||
$string = array_slice($string, 0, 1);
|
||||
return $string ? implode(', ', $string) . ' ago' : 'just now';
|
||||
}
|
||||
}
|
||||
@@ -48,41 +48,55 @@ if (!function_exists('time_elapsed_string')) {
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
|
||||
<div class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-blue-500/30 transition-all duration-300 flex items-center justify-between">
|
||||
<div
|
||||
class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-blue-500/30 transition-all duration-300 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-blue-400 transition-colors">Total Reports</h3>
|
||||
<p class="text-2xl font-bold text-white mt-1"><?= $totalReports ?></p>
|
||||
<p class="text-[10px] text-neutral-500 mt-1">All submissions overview</p>
|
||||
<h3
|
||||
class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-blue-400 transition-colors">
|
||||
Opened Reports
|
||||
</h3>
|
||||
<p class="text-2xl font-bold text-white mt-1"><?= $openedReports ?></p>
|
||||
<p class="text-[10px] text-neutral-500 mt-1">Newly reported issues</p>
|
||||
</div>
|
||||
<div class="p-3 bg-blue-500/10 rounded-lg group-hover:bg-blue-500/20 transition-colors">
|
||||
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-blue-500/10 border border-blue-500/20 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-yellow-500/30 transition-all duration-300 flex items-center justify-between">
|
||||
<div
|
||||
class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-yellow-500/30 transition-all duration-300 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-yellow-400 transition-colors">Reports In Progress</h3>
|
||||
<p class="text-2xl font-bold text-white mt-1"><?= $activeReports ?></p>
|
||||
<h3
|
||||
class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-yellow-400 transition-colors">
|
||||
Reports In Progress</h3>
|
||||
<p class="text-2xl font-bold text-white mt-1"><?= $inProgressReports ?></p>
|
||||
<p class="text-[10px] text-neutral-500 mt-1">In progress / Open</p>
|
||||
</div>
|
||||
<div class="p-3 bg-yellow-500/10 rounded-lg group-hover:bg-yellow-500/20 transition-colors">
|
||||
<svg class="w-6 h-6 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-green-500/30 transition-all duration-300 flex items-center justify-between">
|
||||
<div
|
||||
class="group bg-neutral-800 rounded-lg border border-neutral-700/50 p-4 shadow-sm hover:border-green-500/30 transition-all duration-300 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-green-400 transition-colors">Resolved Reports</h3>
|
||||
<h3
|
||||
class="text-xs font-semibold text-neutral-400 uppercase tracking-wider group-hover:text-green-400 transition-colors">
|
||||
Resolved Reports</h3>
|
||||
<p class="text-2xl font-bold text-white mt-1"><?= $resolvedReports ?></p>
|
||||
<p class="text-[10px] text-neutral-500 mt-1">Successfully closed</p>
|
||||
</div>
|
||||
<div class="p-3 bg-green-500/10 rounded-lg group-hover:bg-green-500/20 transition-colors">
|
||||
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,16 +127,19 @@ if (!function_exists('time_elapsed_string')) {
|
||||
?>
|
||||
|
||||
<div class="px-4 py-3 hover:bg-neutral-700/30 transition-colors flex items-center gap-3">
|
||||
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-<?= $statusColor ?>-500/10 border border-<?= $statusColor ?>-500/20 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-<?= $statusColor ?>-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<?= $iconPath ?>"/>
|
||||
<div
|
||||
class="flex-shrink-0 w-8 h-8 rounded-full bg-<?= $statusColor ?>-500/10 border border-<?= $statusColor ?>-500/20 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-<?= $statusColor ?>-500" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="<?= $iconPath ?>" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0 grid gap-0.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm font-medium text-neutral-200 truncate">
|
||||
Issue <span class="text-white">#<?= $row['id'] ?></span> <?= htmlspecialchars($row['status']) ?>
|
||||
Issue <span class="text-white">#<?= $row['id'] ?></span>
|
||||
<?= htmlspecialchars($row['status']) ?>
|
||||
</p>
|
||||
<span class="text-[10px] text-neutral-400 whitespace-nowrap">
|
||||
<?= time_elapsed_string($row['updated_at']) ?>
|
||||
|
||||
@@ -404,6 +404,7 @@ $result = $conn->query($sql);
|
||||
<option value="Furniture & Fixtures Issue">Furniture & Fixtures Issue</option>
|
||||
<option value="Library & Study Issue">Library & Study Issue</option>
|
||||
<option value="Lost & Stolen Issue">Lost & Stolen Issue</option>
|
||||
<option value="Medical/Health Issue">Medical & Health Issue</option>
|
||||
<option value="Other Issue">Other Issue</option>
|
||||
</select>
|
||||
<svg class="absolute right-3 top-3 h-4 w-4 text-neutral-500 pointer-events-none"
|
||||
|
||||
205
src/core/actions/admin_gemini.php
Normal file
205
src/core/actions/admin_gemini.php
Normal 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);
|
||||
77
src/core/actions/mail/admin_report_config.php
Normal file
77
src/core/actions/mail/admin_report_config.php
Normal 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);
|
||||
}
|
||||
?>
|
||||
@@ -31,6 +31,9 @@ function getDepartmentEmail($category)
|
||||
case 'Library & Study Issue':
|
||||
return LIBRARY_EMAIL;
|
||||
|
||||
case 'Medical/Health Issue':
|
||||
return HEALTH_EMAIL;
|
||||
|
||||
case 'Other Issue':
|
||||
default:
|
||||
return IT_HELPDESK_EMAIL;
|
||||
|
||||
@@ -54,7 +54,7 @@ $secPhone = $_ENV['SECURITY_PHONE'];
|
||||
$systemPrompt = <<<PROMPT
|
||||
You are the AI assistant for '{$domain}', a campus facility dashboard.
|
||||
Your main goal is to help students with campus facility and infrastructure issues only
|
||||
(WiFi & Network, Electrical, Water & Plumbing, HVAC (AC/Heating), Furniture & Fixtures, Cleaning & Janitorial, Security & Safety, Road & Pathway Damage, Library & Study, Lost & Stolen or Others).
|
||||
(WiFi & Network, Electrical, Water & Plumbing, HVAC (AC/Heating), Furniture & Fixtures, Cleaning & Janitorial, Security & Safety, Road & Pathway Damage, Library & Study, Lost & Stolen, Medical/Health Issue or Others).
|
||||
|
||||
**IDENTITY:**
|
||||
1. **{$app_name}** is the name of the college/university you serve.
|
||||
@@ -129,7 +129,7 @@ PROMPT;
|
||||
|
||||
$payload = [
|
||||
'systemInstruction' => [
|
||||
'parts' => [[ 'text' => $systemPrompt ]]
|
||||
'parts' => [['text' => $systemPrompt]]
|
||||
],
|
||||
'contents' => [
|
||||
[
|
||||
|
||||
@@ -23,31 +23,31 @@ $routes = [
|
||||
'file' => './assets/users/_reports.php',
|
||||
'nav' => true,
|
||||
],
|
||||
'status-board' => [
|
||||
'admin_overview' => [
|
||||
'role' => 'admin',
|
||||
'label' => 'Status Board',
|
||||
'label' => 'Admin Overview',
|
||||
'icon' => 'activity',
|
||||
'file' => './assets/admin/_status_board.php',
|
||||
'file' => './assets/admin/_admin_overview.php',
|
||||
'nav' => true,
|
||||
],
|
||||
'activity-logs' => [
|
||||
'system-logs' => [
|
||||
'role' => 'admin',
|
||||
'label' => 'Activity Logs',
|
||||
'label' => 'System Logs',
|
||||
'icon' => 'clock',
|
||||
'file' => './assets/admin/_activity_logs.php',
|
||||
'file' => './assets/admin/_system_logs.php',
|
||||
'nav' => true,
|
||||
],
|
||||
'admin' => [
|
||||
'admin-panel' => [
|
||||
'role' => 'admin',
|
||||
'label' => 'Admin',
|
||||
'label' => 'Admin Panel',
|
||||
'icon' => 'settings',
|
||||
'file' => './assets/admin/_admin.php',
|
||||
'file' => './assets/admin/_admin_panel.php',
|
||||
'nav' => true,
|
||||
],
|
||||
];
|
||||
|
||||
if (!isset($routes[$page])) {
|
||||
$page = $is_admin ? 'status-board' : 'overview';
|
||||
$page = $is_admin ? 'admin_overview' : 'overview';
|
||||
}
|
||||
|
||||
$routeRole = $routes[$page]['role'];
|
||||
@@ -56,7 +56,7 @@ if (
|
||||
($routeRole === 'admin' && !$is_admin) ||
|
||||
($routeRole === 'user' && $is_admin)
|
||||
) {
|
||||
$page = $is_admin ? 'status-board' : 'overview';
|
||||
$page = $is_admin ? 'admin_overview' : 'overview';
|
||||
}
|
||||
|
||||
$current_route = $routes[$page];
|
||||
|
||||
@@ -33,6 +33,7 @@ error_reporting(0);
|
||||
|
||||
<!-- Main Stylesheet (Tailwind CSS) -->
|
||||
<link rel="stylesheet" href="./src/output.css">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Google Fonts: Lexend Deca -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
|
||||
@@ -229,7 +229,7 @@ if (isset($_GET['code'])) {
|
||||
Need Help? Checkout Github Docs</a>
|
||||
<span class="w-px h-3 bg-slate-800"></span>
|
||||
<span
|
||||
class="text-xs font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-500/20">v1.3.2</span>
|
||||
class="text-xs font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-500/20">v1.3.8</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
--color-red-900: oklch(39.6% 0.141 25.723);
|
||||
--color-orange-300: oklch(83.7% 0.128 66.29);
|
||||
--color-orange-400: oklch(75% 0.183 55.934);
|
||||
--color-orange-500: oklch(70.5% 0.213 47.604);
|
||||
--color-amber-400: oklch(82.8% 0.189 84.429);
|
||||
--color-amber-500: oklch(76.9% 0.188 70.08);
|
||||
--color-yellow-200: oklch(94.5% 0.129 101.54);
|
||||
@@ -112,6 +111,7 @@
|
||||
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--blur-sm: 8px;
|
||||
--blur-md: 12px;
|
||||
@@ -592,15 +592,6 @@
|
||||
.w-1\.5 {
|
||||
width: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.w-1\/2 {
|
||||
width: calc(1/2 * 100%);
|
||||
}
|
||||
.w-1\/3 {
|
||||
width: calc(1/3 * 100%);
|
||||
}
|
||||
.w-1\/4 {
|
||||
width: calc(1/4 * 100%);
|
||||
}
|
||||
.w-1\/6 {
|
||||
width: calc(1/6 * 100%);
|
||||
}
|
||||
@@ -610,9 +601,6 @@
|
||||
.w-3 {
|
||||
width: calc(var(--spacing) * 3);
|
||||
}
|
||||
.w-3\/4 {
|
||||
width: calc(3/4 * 100%);
|
||||
}
|
||||
.w-4 {
|
||||
width: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -755,6 +743,9 @@
|
||||
.transform {
|
||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||
}
|
||||
.animate-ping {
|
||||
animation: var(--animate-ping);
|
||||
}
|
||||
.animate-pulse {
|
||||
animation: var(--animate-pulse);
|
||||
}
|
||||
@@ -797,6 +788,9 @@
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.items-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1072,12 +1066,6 @@
|
||||
.border-neutral-900 {
|
||||
border-color: var(--color-neutral-900);
|
||||
}
|
||||
.border-orange-500\/20 {
|
||||
border-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-orange-500) 20%, transparent);
|
||||
}
|
||||
}
|
||||
.border-red-500\/20 {
|
||||
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1297,12 +1285,6 @@
|
||||
background-color: color-mix(in oklab, var(--color-neutral-900) 95%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-orange-500\/10 {
|
||||
background-color: color-mix(in srgb, oklch(70.5% 0.213 47.604) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-orange-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-purple-500\/10 {
|
||||
background-color: color-mix(in srgb, oklch(62.7% 0.265 303.9) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -1823,6 +1805,9 @@
|
||||
.opacity-70 {
|
||||
opacity: 70%;
|
||||
}
|
||||
.opacity-75 {
|
||||
opacity: 75%;
|
||||
}
|
||||
.opacity-80 {
|
||||
opacity: 80%;
|
||||
}
|
||||
@@ -2021,6 +2006,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:border-green-500\/20 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-green-500) 20%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:border-neutral-700 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
@@ -2028,12 +2023,32 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:bg-blue-500\/20 {
|
||||
.group-hover\:border-red-500\/20 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, oklch(62.3% 0.214 259.815) 20%, transparent);
|
||||
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-blue-500) 20%, transparent);
|
||||
border-color: color-mix(in oklab, var(--color-red-500) 20%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:border-yellow-500\/20 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-yellow-500) 20%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:bg-green-500\/10 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-green-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2048,6 +2063,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:bg-red-500\/10 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:bg-yellow-500\/10 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
background-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 10%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-yellow-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.group-hover\:bg-yellow-500\/20 {
|
||||
&:is(:where(.group):hover *) {
|
||||
@media (hover: hover) {
|
||||
@@ -2340,6 +2375,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-green-500\/50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-green-500) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-neutral-500\/50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in srgb, oklch(55.6% 0 0) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-neutral-500) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-neutral-600 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2367,6 +2422,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-red-500\/50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-yellow-500\/30 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2377,6 +2442,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-yellow-500\/50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
border-color: color-mix(in oklab, var(--color-yellow-500) 50%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-blue-500\/20 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2558,6 +2633,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:shadow-green-500\/5 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-green-500) 5%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:shadow-red-500\/5 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 5%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:shadow-red-500\/20 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2568,6 +2663,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:shadow-yellow-500\/5 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
--tw-shadow-color: color-mix(in srgb, oklch(79.5% 0.184 86.047) 5%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-yellow-500) 5%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus\:border-blue-500 {
|
||||
&:focus {
|
||||
border-color: var(--color-blue-500);
|
||||
@@ -2689,11 +2794,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.sm\:items-start {
|
||||
@media (width >= 40rem) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
.sm\:justify-between {
|
||||
@media (width >= 40rem) {
|
||||
justify-content: space-between;
|
||||
@@ -2968,11 +3068,6 @@
|
||||
margin-top: calc(var(--spacing) * 10);
|
||||
}
|
||||
}
|
||||
.lg\:grid-cols-2 {
|
||||
@media (width >= 64rem) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.lg\:grid-cols-4 {
|
||||
@media (width >= 64rem) {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -3332,6 +3427,12 @@
|
||||
initial-value: "";
|
||||
inherits: false;
|
||||
}
|
||||
@keyframes ping {
|
||||
75%, 100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
|
||||
Reference in New Issue
Block a user