doctor-appointment-system/app/Views/admin/activity_analytics.php
2026-04-17 10:46:29 +05:30

323 lines
14 KiB
PHP

<?php
// Helper function to format labels for charts
function formatLabel($label) {
if (empty($label)) return 'Unknown';
return strlen($label) > 20 ? substr($label, 0, 20) . '...' : $label;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Activity Analytics Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="<?= base_url('css/app.css') ?>">
<link rel="stylesheet" href="<?= base_url('css/dashboard.css') ?>">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.chart-container {
position: relative;
height: 350px;
margin-bottom: 2rem;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.stat-card.green { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }
.stat-card.blue { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }
.stat-card.orange { background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); }
.stat-value { font-size: 2.5rem; font-weight: bold; margin: 0; }
.stat-label { font-size: 0.9rem; opacity: 0.9; margin: 0.5rem 0 0 0; }
</style>
</head>
<body class="app-body overview-layout">
<aside class="ov-sidebar" id="sidebar">
<div class="ov-brand"><h1><i class="bi bi-hospital me-1"></i> DoctGuide</h1><span>Control Panel</span></div>
<nav class="ov-nav">
<div class="ov-nav__section">Main</div>
<a href="<?= base_url('admin/dashboard') ?>" class="ov-nav__link"><i class="bi bi-speedometer2"></i> Dashboard</a>
<div class="ov-nav__section">Tools</div>
<a href="<?= base_url('admin/activity-log') ?>" class="ov-nav__link"><i class="bi bi-clipboard-data"></i> Activity Log</a>
<a href="<?= base_url('admin/activity/analytics') ?>" class="ov-nav__link active"><i class="bi bi-graph-up"></i> Analytics</a>
</nav>
<div class="ov-sidebar__footer"><a href="<?= base_url('logout') ?>"><i class="bi bi-box-arrow-left"></i> Logout</a></div>
</aside>
<div class="ov-main" id="mainContent">
<header class="ov-topbar">
<div class="d-flex align-items-center">
<button class="ov-toggle-btn" onclick="toggleSidebar()" title="Toggle Sidebar"><i class="bi bi-list" id="toggleIcon"></i></button>
<p class="ov-topbar__title mb-0">Activity Analytics</p>
</div>
</header>
<main class="ov-content">
<div class="ov-panel mb-4">
<div class="ov-panel__header">
<h2 class="ov-panel__title">Analytics Dashboard</h2>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" style="width: auto;" onchange="changeAnalyticsPeriod(this.value)">
<option value="7_days" <?= $period === '7_days' ? 'selected' : '' ?>>Last 7 Days</option>
<option value="30_days" <?= $period === '30_days' ? 'selected' : '' ?>>Last 30 Days</option>
</select>
<a href="<?= base_url('admin/activity-log') ?>" class="btn btn-sm btn-outline-secondary">Back to Logs</a>
</div>
</div>
<div class="ov-panel__body">
<!-- Summary Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="stat-card">
<p class="stat-value"><?= $summary['total_actions'] ?? 0 ?></p>
<p class="stat-label">Total Actions</p>
</div>
</div>
<div class="col-md-3">
<div class="stat-card green">
<p class="stat-value"><?= count($summary['by_action'] ?? []) ?></p>
<p class="stat-label">Action Types</p>
</div>
</div>
<div class="col-md-3">
<div class="stat-card blue">
<p class="stat-value"><?= count($summary['by_role'] ?? []) ?></p>
<p class="stat-label">Active Roles</p>
</div>
</div>
<div class="col-md-3">
<div class="stat-card orange">
<p class="stat-value"><?= count($summary['most_active_users'] ?? []) ?></p>
<p class="stat-label">Active Users</p>
</div>
</div>
</div>
<?php if (($summary['total_actions'] ?? 0) == 0): ?>
<!-- No Data Message -->
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle me-2"></i>
<strong>No Activity Data Available</strong>
<p class="mb-0">There are no activity logs recorded yet. Once you start using the system (login, create/update records, etc.), the analytics dashboard will display detailed statistics and charts.</p>
</div>
<?php else: ?>
<!-- Charts -->
<div class="row">
<div class="col-lg-6">
<h5 class="mb-3">Actions Distribution</h5>
<div class="chart-container">
<canvas id="actionsChart"></canvas>
</div>
</div>
<div class="col-lg-6">
<h5 class="mb-3">Activity by Role</h5>
<div class="chart-container">
<canvas id="rolesChart"></canvas>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<h5 class="mb-3">Most Active Users</h5>
<div class="chart-container">
<canvas id="usersChart"></canvas>
</div>
</div>
<div class="col-lg-6">
<h5 class="mb-3">Top IP Addresses</h5>
<div style="max-height: 350px; overflow-y: auto;">
<table class="table table-sm table-hover mb-0">
<thead>
<tr>
<th>IP Address</th>
<th class="text-end">Count</th>
</tr>
</thead>
<tbody>
<?php if (!empty($uniqueIPs)): ?>
<?php foreach (array_slice($uniqueIPs, 0, 10) as $ip): ?>
<tr>
<td><code><?= esc($ip['ip']) ?></code></td>
<td class="text-end"><span class="badge bg-info"><?= $ip['count'] ?></span></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="2" class="text-muted text-center py-3">No IP data available</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Critical Actions Section -->
<div class="ov-panel">
<div class="ov-panel__header">
<h2 class="ov-panel__title">Critical Actions (Recent)</h2>
<span class="badge bg-danger"><?= count($criticalActions) ?> critical actions</span>
</div>
<div class="ov-panel__body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Target</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<?php if (empty($criticalActions)): ?>
<tr>
<td colspan="5" class="text-center text-muted py-3">No critical actions found</td>
</tr>
<?php else: ?>
<?php foreach (array_slice($criticalActions, 0, 50) as $action): ?>
<tr style="border-left: 4px solid #dc2626;">
<td class="text-nowrap small"><?= date('Y-m-d H:i', strtotime($action['activity_at'])) ?></td>
<td>
<strong><?= esc(trim($action['actor_name'] ?? 'System')) ?></strong><br>
<small class="text-muted"><?= esc($action['actor_email'] ?? '') ?></small>
</td>
<td><span class="badge bg-danger"><?= esc($action['action']) ?></span></td>
<td><?= esc($action['target_user_type'] ?? '-') ?> #<?= $action['target_user_id'] ?? 'N/A' ?></td>
<td><?= esc($action['description']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
<script>
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const main = document.getElementById('mainContent');
const icon = document.getElementById('toggleIcon');
sidebar.classList.toggle('collapsed');
main.classList.toggle('expanded');
icon.className = sidebar.classList.contains('collapsed') ? 'bi bi-layout-sidebar' : 'bi bi-list';
}
function changeAnalyticsPeriod(period) {
window.location.href = '<?= base_url('admin/activity/analytics') ?>?period=' + period;
}
// Chart color palettes
const chartColors = {
primary: '#667eea',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
info: '#3b82f6',
};
// Get data
const actionLabels = <?= $actionLabels ?>;
const actionCounts = <?= $actionCounts ?>;
const typeLabels = <?= $typeLabels ?>;
const typeCounts = <?= $typeCounts ?>;
const userLabels = <?= $userLabels ?>;
const userCounts = <?= $userCounts ?>;
// Only create charts if data is available
if (actionLabels.length > 0) {
// Actions Chart
const actionsCtx = document.getElementById('actionsChart')?.getContext('2d');
if (actionsCtx) {
new Chart(actionsCtx, {
type: 'doughnut',
data: {
labels: actionLabels.map(l => l.length > 15 ? l.substring(0, 15) + '...' : l),
datasets: [{
data: actionCounts,
backgroundColor: [
chartColors.primary, chartColors.success, chartColors.warning,
chartColors.danger, chartColors.info, '#8b5cf6', '#ec4899', '#f97316'
],
borderColor: '#fff',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' }
}
}
});
}
// User Types Chart
const rolesCtx = document.getElementById('rolesChart')?.getContext('2d');
if (rolesCtx && typeLabels.length > 0) {
new Chart(rolesCtx, {
type: 'bar',
data: {
labels: typeLabels,
datasets: [{
label: 'Count',
data: typeCounts,
backgroundColor: chartColors.primary,
borderColor: chartColors.primary,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
plugins: { legend: { display: false } },
scales: { x: { beginAtZero: true } }
}
});
}
// Users Chart
const usersCtx = document.getElementById('usersChart')?.getContext('2d');
if (usersCtx && userLabels.length > 0) {
new Chart(usersCtx, {
type: 'bar',
data: {
labels: userLabels.map(l => l && l.length > 15 ? l.substring(0, 15) + '...' : l),
datasets: [{
label: 'Actions',
data: userCounts,
backgroundColor: chartColors.success,
borderColor: chartColors.success,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true } }
}
});
}
}
</script>
</body>
</html>