357 lines
18 KiB
PHP
357 lines
18 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Activity Log</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') ?>">
|
|
<style>
|
|
.stat-card .ov-stat__icon {
|
|
background: var(--bs-primary) !important;
|
|
color: white !important;
|
|
}
|
|
.stat-card:nth-child(2) .ov-stat__icon {
|
|
background: var(--bs-success) !important;
|
|
}
|
|
.stat-card:nth-child(3) .ov-stat__icon {
|
|
background: var(--bs-info) !important;
|
|
}
|
|
.avatar-circle {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
}
|
|
.avatar-circle.small {
|
|
width: 24px;
|
|
height: 24px;
|
|
font-size: 12px;
|
|
}
|
|
.description-cell {
|
|
max-width: 300px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.table-responsive {
|
|
border-radius: 0.375rem;
|
|
}
|
|
.table thead th {
|
|
border-bottom: 2px solid #dee2e6;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
font-size: 0.875rem;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.badge {
|
|
font-weight: 500;
|
|
}
|
|
.btn-link {
|
|
text-decoration: none;
|
|
}
|
|
.btn-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.form-check-input:checked {
|
|
background-color: #0d6efd;
|
|
border-color: #0d6efd;
|
|
}
|
|
.btn:disabled {
|
|
opacity: 0.6;
|
|
}
|
|
#selectedCount {
|
|
font-weight: 500;
|
|
color: #6c757d;
|
|
}
|
|
.input-group-text {
|
|
background-color: #f8f9fa;
|
|
}
|
|
</style> <link rel="stylesheet" href="<?= base_url('css/doctors.css') ?>">
|
|
</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">Manage</div>
|
|
<div class="ov-nav__dropdown">
|
|
<a href="#" class="ov-nav__link d-flex justify-content-between align-items-center" onclick="toggleNavDropdown(event, this)">
|
|
<span><i class="bi bi-person-badge"></i> Doctors</span>
|
|
<i class="bi bi-chevron-down dropdown-icon"></i>
|
|
</a>
|
|
<div class="ov-dropdown-menu">
|
|
<a href="<?= base_url('admin/doctors') ?>" class="ov-nav__sublink">Doctor List</a>
|
|
<a href="<?= base_url('admin/doctors/add') ?>" class="ov-nav__sublink">Add Doctor</a>
|
|
</div>
|
|
</div>
|
|
<div class="ov-nav__dropdown">
|
|
<a href="#" class="ov-nav__link d-flex justify-content-between align-items-center" onclick="toggleNavDropdown(event, this)">
|
|
<span><i class="bi bi-people"></i> Patients</span>
|
|
<i class="bi bi-chevron-down dropdown-icon"></i>
|
|
</a>
|
|
<div class="ov-dropdown-menu">
|
|
<a href="<?= base_url('admin/patients') ?>" class="ov-nav__sublink">Patient List</a>
|
|
<a href="<?= base_url('admin/patients/add') ?>" class="ov-nav__sublink">Add Patient</a>
|
|
</div>
|
|
</div>
|
|
<a href="<?= base_url('admin/appointments') ?>" class="ov-nav__link"><i class="bi bi-calendar2-check"></i> Appointments</a>
|
|
<a href="<?= base_url('admin/activity-log') ?>" class="ov-nav__link active"><i class="bi bi-clipboard-data"></i> Activity Log</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 Log</p>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="ov-content">
|
|
<!-- Flash Messages -->
|
|
<?php if (session()->getFlashdata('success')): ?>
|
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
<i class="bi bi-check-circle me-2"></i><?= esc(session()->getFlashdata('success')) ?>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if (session()->getFlashdata('error')): ?>
|
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
<i class="bi bi-exclamation-triangle me-2"></i><?= esc(session()->getFlashdata('error')) ?>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Search & Filter Section -->
|
|
<div class="ov-panel mb-4">
|
|
<div class="ov-panel__header">
|
|
<h2 class="ov-panel__title mb-0">
|
|
<button class="btn btn-link p-0 text-decoration-none" onclick="toggleFilters()" id="filterToggle">
|
|
<i class="bi bi-funnel me-2"></i>Search & Filters
|
|
<i class="bi bi-chevron-down ms-2" id="filterIcon"></i>
|
|
</button>
|
|
</h2>
|
|
</div>
|
|
<div class="ov-panel__body" id="filterSection" style="display: none;">
|
|
<form method="GET" action="<?= base_url('admin/activity-log') ?>" class="row g-3">
|
|
<div class="col-md-4">
|
|
<label for="search" class="form-label">Search</label>
|
|
<input type="text" class="form-control" id="search" name="search"
|
|
value="<?= esc($filters['search'] ?? '') ?>"
|
|
placeholder="Search by name, email, or description">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="action" class="form-label">Action</label>
|
|
<select class="form-select" id="action" name="action">
|
|
<option value="">All Actions</option>
|
|
<?php foreach ($availableActions as $actionType): ?>
|
|
<option value="<?= esc($actionType) ?>" <?= ($filters['action'] ?? '') === $actionType ? 'selected' : '' ?>>
|
|
<?= esc(ucwords(str_replace('_', ' ', $actionType))) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="user_type" class="form-label">User Type</label>
|
|
<select class="form-select" id="user_type" name="user_type">
|
|
<option value="">All Types</option>
|
|
<option value="admin" <?= ($filters['user_type'] ?? '') === 'admin' ? 'selected' : '' ?>>Admin</option>
|
|
<option value="doctor" <?= ($filters['user_type'] ?? '') === 'doctor' ? 'selected' : '' ?>>Doctor</option>
|
|
<option value="patient" <?= ($filters['user_type'] ?? '') === 'patient' ? 'selected' : '' ?>>Patient</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="date_from" class="form-label">From Date</label>
|
|
<input type="date" class="form-control" id="date_from" name="date_from"
|
|
value="<?= esc($filters['date_from'] ?? '') ?>">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label for="date_to" class="form-label">To Date</label>
|
|
<input type="date" class="form-control" id="date_to" name="date_to"
|
|
value="<?= esc($filters['date_to'] ?? '') ?>">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="ip" class="form-label">IP Address</label>
|
|
<input type="text" class="form-control" id="ip" name="ip"
|
|
value="<?= esc($filters['ip'] ?? '') ?>"
|
|
placeholder="Filter by IP address">
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-search me-1"></i>Search
|
|
</button>
|
|
<a href="<?= base_url('admin/activity-log') ?>" class="btn btn-outline-secondary">
|
|
<i class="bi bi-x-circle me-1"></i>Clear Filters
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-4">
|
|
<div class="ov-stat stat-card">
|
|
<div class="ov-stat__icon bg-primary">
|
|
<i class="bi bi-clipboard-data"></i>
|
|
</div>
|
|
<div>
|
|
<div class="ov-stat__label">Total Entries</div>
|
|
<p class="ov-stat__value"><?= esc($totalLogs) ?> (DB: <?= esc($totalInDb) ?>)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="ov-stat stat-card">
|
|
<div class="ov-stat__icon bg-success">
|
|
<i class="bi bi-list-check"></i>
|
|
</div>
|
|
<div>
|
|
<div class="ov-stat__label">Action Types</div>
|
|
<p class="ov-stat__value"><?= esc(count($actionSummary)) ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="ov-stat stat-card">
|
|
<div class="ov-stat__icon bg-info">
|
|
<i class="bi bi-people"></i>
|
|
</div>
|
|
<div>
|
|
<div class="ov-stat__label">Actor Roles</div>
|
|
<p class="ov-stat__value"><?= esc(count($roleSummary)) ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ov-panel mb-4">
|
|
<div class="ov-panel__header d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-2">
|
|
<div>
|
|
<h2 class="ov-panel__title mb-1">Recent Activity</h2>
|
|
<p class="text-muted mb-0">Latest actions recorded from the application.</p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<a href="<?= base_url('admin/activity/analytics') ?>" class="btn btn-sm btn-outline-secondary">View Analytics</a>
|
|
<form method="post" action="<?= base_url('admin/activity-log/clear') ?>" style="display: inline;" onsubmit="return confirm('Are you sure you want to clear all activity logs? This action cannot be undone.')">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger">Clear Log</button>
|
|
</form>
|
|
</div>
|
|
</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 class="text-nowrap">Timestamp</th>
|
|
<th>Actor</th>
|
|
<th>Role</th>
|
|
<th>Action</th>
|
|
<th>Target</th>
|
|
<th>Description</th>
|
|
<th>IP</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($logs)): ?>
|
|
<tr>
|
|
<td colspan="7" class="text-center text-muted py-5">
|
|
<i class="bi bi-inbox fs-1 d-block mb-2 text-muted"></i>
|
|
No activity logs found matching your criteria.
|
|
</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($logs as $log): ?>
|
|
<tr>
|
|
<td class="text-nowrap small">
|
|
<div class="d-flex flex-column">
|
|
<span class="fw-medium"><?= esc(date('M d, Y', strtotime($log['activity_at']))) ?></span>
|
|
<small class="text-muted"><?= esc(date('H:i:s', strtotime($log['activity_at']))) ?></small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-circle me-2 bg-<?= $log['activity_user_type'] === 'admin' ? 'primary' : ($log['activity_user_type'] === 'doctor' ? 'success' : 'info') ?> text-white small">
|
|
<?= strtoupper(substr(!empty($log['actor_name']) ? $log['actor_name'] : 'G', 0, 1)) ?>
|
|
</div>
|
|
<span class="fw-medium"><?= esc($log['actor_name'] ?? 'Guest') ?></span>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-<?= $log['activity_user_type'] === 'admin' ? 'primary' : ($log['activity_user_type'] === 'doctor' ? 'success' : 'info') ?> text-uppercase small">
|
|
<?= esc($log['activity_user_type'] ?? 'guest') ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary text-uppercase small">
|
|
<i class="bi bi-<?= esc($actionIcons[$log['action']] ?? 'activity') ?> me-1"></i>
|
|
<?= esc(str_replace('_', ' ', $log['action'])) ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<?php if ($log['target_user_type']): ?>
|
|
<span class="badge bg-light text-dark small">
|
|
<?= esc($log['target_user_type']) ?> #<?= esc($log['target_user_id']) ?>
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="text-muted">-</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<div class="description-cell">
|
|
<?= esc($log['description'] ?? '-') ?>
|
|
</div>
|
|
</td>
|
|
<td class="text-nowrap">
|
|
<code class="small bg-light px-2 py-1 rounded"><?= esc($log['ip'] ?? '-') ?></code>
|
|
</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 toggleNavDropdown(event, element) {
|
|
event.preventDefault();
|
|
element.parentElement.classList.toggle('open');
|
|
}
|
|
|
|
function toggleFilters() {
|
|
const filterSection = document.getElementById('filterSection');
|
|
const filterIcon = document.getElementById('filterIcon');
|
|
const isVisible = filterSection.style.display !== 'none';
|
|
|
|
filterSection.style.display = isVisible ? 'none' : 'block';
|
|
filterIcon.className = isVisible ? 'bi bi-chevron-down ms-2' : 'bi bi-chevron-up ms-2';
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|