Files
alrahma_sunday_school/app/Views/administrator/administratordashboard.php
T

698 lines
39 KiB
PHP

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
helper('url');
$dashboardEndpoint = $dashboardEndpoint ?? site_url('api/administrator/dashboard');
?>
<style>
/* --- Circular Stats --- */
.stat-circle {
position: relative;
width: 320px;
height: 320px;
border-radius: 50%;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #dff0cfff;
font-weight: 700;
box-shadow: 0 6px 18px rgba(0, 0, 0, .15);
transition: transform .2s ease, box-shadow .2s ease;
}
.stat-circle:hover {
transform: scale(1.05);
box-shadow: 0 10px 28px rgba(0, 0, 0, .2);
}
.stat-value {
font-size: 2rem;
line-height: 1;
}
.stat-icon {
font-size: 2.5rem;
margin-bottom: .25rem;
opacity: .85;
}
.stat-title {
font-size: 1.6rem;
margin-top: .25rem;
font-weight: 500;
opacity: .9;
text-align: center;
line-height: 1.2;
word-break: break-word;
}
.circle-primary {
background: radial-gradient(circle at 30% 30%, #0d6efd, #0044aa);
}
.circle-success {
background: radial-gradient(circle at 30% 30%, #198754, #0c4f2c);
}
.circle-warning {
background: radial-gradient(circle at 30% 30%, #daa544ff, #a66f00);
}
.circle-info {
background: radial-gradient(circle at 30% 30%, #0dcaf0, #047e9c);
}
.circle-light {
background: radial-gradient(circle at 30% 30%, #63af4cff, #00eb7dff);
}
.stats-row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: center;
}
/* Search results */
.sr-badge {
font-size: .825rem;
}
.small-muted {
font-size: .875rem;
color: #6c757d;
}
.admin-dashboard table.mgmt-sticky > thead > tr > th,
.admin-dashboard table.mgmt-sticky > thead > tr > td,
.admin-dashboard table thead th,
.admin-dashboard table thead td {
position: static !important;
top: auto !important;
z-index: auto !important;
box-shadow: none !important;
}
</style>
<div class="container-fluid admin-dashboard" data-dashboard-endpoint="<?= esc($dashboardEndpoint) ?>">
<div class="wrapper">
<div class="content"></div>
<main>
<!-- === Welcome Section === -->
<section class="mb-4">
<h2>Welcome, <?= esc((string)session()->get('firstname')) . ' ' . esc((string)session()->get('lastname')); ?></h2>
<p>Here is an overview of your site's activity.</p>
</section>
<!-- === Search Section === -->
<section class="mb-5">
<h2 class="mb-3">Search</h2>
<div id="dashboardAlert" class="alert alert-danger d-none" role="alert"></div>
<form action="<?= site_url('administrator/administratordashboard') ?>" method="get" class="mb-3">
<div class="input-group">
<input type="text"
class="form-control"
placeholder="Search name, email, phone, school ID, RFID…"
name="query"
value="<?= esc((string)($query ?? '')) ?>"
required>
<button class="btn btn-outline-secondary" type="submit">Search</button>
</div>
<div class="form-text">Supports Users, Parents, Staff, Students, and Emergency Contacts.</div>
</form>
<?php
// Convenience flags
$hasResults = !empty($results);
$isMerged = $hasResults && array_is_list($results); // new bundle format
$isLegacy = $hasResults && !$isMerged; // old flat arrays
?>
<?php if (!empty($query)): ?>
<div class="mb-2 small-muted">
Showing results for: <strong><?= esc($query) ?></strong>
</div>
<?php endif; ?>
<?php if ($hasResults): ?>
<?php if ($isMerged): ?>
<!-- ===== New merged bundle format ===== -->
<div class="accordion" id="searchResultsAccordion">
<?php foreach ($results as $idx => $bundle): ?>
<?php
$u = $bundle['user'] ?? null;
$fullName = trim(($u['firstname'] ?? '') . ' ' . ($u['lastname'] ?? ''));
$title = $fullName !== '' ? $fullName : 'User ID ' . (int)($u['id'] ?? 0);
$badgeCount =
count($bundle['students'] ?? []) +
count($bundle['emergency_contacts'] ?? []) +
count($bundle['parents'] ?? []) +
count($bundle['staff'] ?? []);
$headerId = 'sr-head-' . $idx;
$bodyId = 'sr-body-' . $idx;
?>
<div class="accordion-item mb-2">
<h2 class="accordion-header" id="<?= esc($headerId) ?>">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#<?= esc($bodyId) ?>" aria-expanded="false"
aria-controls="<?= esc($bodyId) ?>">
<div class="d-flex flex-column w-100">
<div class="d-flex align-items-center justify-content-between w-100">
<span>
<i class="bi bi-person-circle me-2"></i>
<?= esc($title) ?>
</span>
<span class="badge bg-primary sr-badge"><?= (int)$badgeCount ?></span>
</div>
<div class="small text-muted">
<?php if ($u): ?>
<?= esc($u['email'] ?? '') ?>
<?php if (!empty($u['cellphone'])): ?>
· <?= esc($u['cellphone']) ?>
<?php endif; ?>
<?php if (!empty($u['city']) || !empty($u['state'])): ?>
· <?= esc(trim(($u['city'] ?? '') . ', ' . ($u['state'] ?? ''), ', ')) ?>
<?php endif; ?>
<?php else: ?>
<em>No user profile row; matched via family/students.</em>
<?php endif; ?>
</div>
</div>
</button>
</h2>
<div id="<?= esc($bodyId) ?>" class="accordion-collapse collapse" aria-labelledby="<?= esc($headerId) ?>" data-bs-parent="#searchResultsAccordion">
<div class="accordion-body">
<!-- Parents / Family -->
<?php if (!empty($bundle['parents'])): ?>
<h6 class="mb-2"><i class="bi bi-people me-1"></i> Family</h6>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>First Parent ID</th>
<th>Second Parent</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['parents'] as $i => $p): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($p['firstparent_id']) ?></td>
<td><?= esc(trim(($p['secondparent_firstname'] ?? '') . ' ' . ($p['secondparent_lastname'] ?? ''))) ?></td>
<td><?= esc($p['secondparent_email'] ?? '') ?></td>
<td><?= esc($p['secondparent_phone'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Students -->
<?php if (!empty($bundle['students'])): ?>
<h6 class="mb-2"><i class="bi bi-mortarboard me-1"></i> Students</h6>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>School ID</th>
<th>Name</th>
<th>Gender</th>
<th>DOB</th>
<th>RFID</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['students'] as $i => $s): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($s['school_id']) ?></td>
<td><?= esc(trim(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? ''))) ?></td>
<td><?= esc($s['gender'] ?? '') ?></td>
<td><?= esc($s['dob'] ?? '') ?></td>
<td><?= esc($s['rfid_tag'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Emergency Contacts -->
<?php if (!empty($bundle['emergency_contacts'])): ?>
<h6 class="mb-2"><i class="bi bi-telephone-outbound me-1"></i> Emergency Contacts</h6>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Relation</th>
<th>Phone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['emergency_contacts'] as $i => $e): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($e['emergency_contact_name'] ?? '') ?></td>
<td><?= esc($e['relation'] ?? '') ?></td>
<td><?= esc($e['cellphone'] ?? '') ?></td>
<td><?= esc($e['email'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Staff roles -->
<?php if (!empty($bundle['staff'])): ?>
<h6 class="mb-2"><i class="bi bi-person-badge me-1"></i> Staff</h6>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>Role</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['staff'] as $i => $st): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($st['role_name'] ?? '') ?></td>
<td><?= esc(trim(($st['firstname'] ?? '') . ' ' . ($st['lastname'] ?? ''))) ?></td>
<td><?= esc($st['email'] ?? '') ?></td>
<td><?= esc($st['phone'] ?? '') ?></td>
<td><?= esc($st['active_role'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (empty($bundle['parents']) && empty($bundle['students']) && empty($bundle['emergency_contacts']) && empty($bundle['staff'])): ?>
<div class="text-muted">No related records.</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php elseif ($isLegacy): ?>
<!-- ===== Legacy flat format fallback ===== -->
<div class="row g-3">
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-people"></i> Users</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['users'])): ?>
<?php foreach ($results['users'] as $i => $r): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc(trim(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? ''))) ?></td>
<td><?= esc($r['email'] ?? '') ?></td>
<td><?= esc($r['cellphone'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-muted">No users found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-mortarboard"></i> Students</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>School ID</th>
<th>Name</th>
<th>DOB</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['students'])): ?>
<?php foreach ($results['students'] as $i => $s): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($s['school_id'] ?? '') ?></td>
<td><?= esc(trim(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? ''))) ?></td>
<td><?= esc($s['dob'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-muted">No students found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-people"></i> Parents</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>First Parent ID</th>
<th>Second Parent</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['parents'])): ?>
<?php foreach ($results['parents'] as $i => $p): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($p['firstparent_id'] ?? '') ?></td>
<td><?= esc(trim(($p['secondparent_firstname'] ?? '') . ' ' . ($p['secondparent_lastname'] ?? ''))) ?></td>
<td><?= esc($p['secondparent_email'] ?? '') ?></td>
<td><?= esc($p['secondparent_phone'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-muted">No parents found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-person-badge"></i> Staff</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>Role</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['staff'])): ?>
<?php foreach ($results['staff'] as $i => $st): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($st['role_name'] ?? '') ?></td>
<td><?= esc(trim(($st['firstname'] ?? '') . ' ' . ($st['lastname'] ?? ''))) ?></td>
<td><?= esc($st['email'] ?? '') ?></td>
<td><?= esc($st['phone'] ?? '') ?></td>
<td><?= esc($st['active_role'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-muted">No staff found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header"><i class="bi bi-telephone-outbound"></i> Emergency Contacts</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Relation</th>
<th>Phone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['emergency_contacts'])): ?>
<?php foreach ($results['emergency_contacts'] as $i => $e): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($e['emergency_contact_name'] ?? '') ?></td>
<td><?= esc($e['relation'] ?? '') ?></td>
<td><?= esc($e['cellphone'] ?? '') ?></td>
<td><?= esc($e['email'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-muted">No emergency contacts found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php elseif (!empty($query)): ?>
<div class="alert alert-warning mb-0">No matches found.</div>
<?php endif; ?>
</section>
<!-- === Statistics Section (circular design) === -->
<section class="mb-5">
<h2 class="mb-4 text-center">Statistics</h2>
<div class="stats-row">
<div class="stat-circle circle-primary">
<div class="stat-icon"><i class="fas fa-user-graduate"></i></div>
<div class="stat-value" data-stat="students">—</div>
<div class="stat-title">Students</div>
</div>
<div class="stat-circle circle-info">
<div class="stat-icon"><i class="fas fa-chalkboard-teacher"></i></div>
<div class="stat-value" data-stat="teachers">—</div>
<div class="stat-title">Teachers</div>
</div>
<div class="stat-circle circle-success">
<div class="stat-icon"><i class="fas fa-user-cog"></i></div>
<div class="stat-value" data-stat="teacherAssistants">—</div>
<div class="stat-title">TAs</div>
</div>
<div class="stat-circle circle-warning">
<div class="stat-icon"><i class="fas fa-user-shield"></i></div>
<div class="stat-value" data-stat="admins">—</div>
<div class="stat-title">Admins</div>
</div>
<div class="stat-circle circle-light">
<div class="stat-icon"><i class="fas fa-users"></i></div>
<div class="stat-value" data-stat="parents">—</div>
<div class="stat-title">Parents</div>
</div>
</div>
</section>
<!-- === Recent Activities Section === -->
<section>
<h2 class="mb-3">Recent Activities</h2>
<table class="table mt-2" data-no-mgmt-sticky>
<thead>
<tr>
<th>Date</th>
<th>Activity</th>
<th>User</th>
</tr>
</thead>
<tbody data-activities-body>
<tr data-activities-placeholder>
<td colspan="3" class="text-muted">Loading recent activity…</td>
</tr>
</tbody>
</table>
</section>
</main>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-dashboard-endpoint]');
if (!container) {
return;
}
const endpoint = container.dataset.dashboardEndpoint;
const alertBox = document.getElementById('dashboardAlert');
const numberFormatter = new Intl.NumberFormat();
const statElements = container.querySelectorAll('[data-stat]');
const activitiesBody = container.querySelector('[data-activities-body]');
const showError = (message) => {
if (!alertBox) {
return;
}
alertBox.textContent = message;
alertBox.classList.remove('d-none');
};
fetch(endpoint, {
headers: {
'Accept': 'application/json',
},
credentials: 'same-origin',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
})
.then((payload) => {
const counts = payload && typeof payload === 'object' && payload.counts
? payload.counts
: {};
statElements.forEach((element) => {
const key = element.dataset.stat;
if (!key) {
return;
}
const value = counts[key];
element.textContent = Number.isFinite(value)
? numberFormatter.format(value)
: '—';
});
if (!activitiesBody) {
return;
}
activitiesBody.innerHTML = '';
const activities = payload && Array.isArray(payload.recentActivities)
? payload.recentActivities
: [];
if (activities.length === 0) {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 3;
cell.classList.add('text-muted');
cell.textContent = 'No recent activities found.';
row.appendChild(cell);
activitiesBody.appendChild(row);
return;
}
const formatDateTime = (value) => {
if (!value) {
return '—';
}
const date = new Date(value);
if (Number.isNaN(date.valueOf())) {
return String(value);
}
const pad = (num) => String(num).padStart(2, '0');
const datePart = `${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${date.getFullYear()}`;
const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
return `${datePart} ${timePart}`;
};
activities.forEach((activity) => {
const row = document.createElement('tr');
const toDisplayString = (value) => {
if (value === null || value === undefined || value === '') {
return '—';
}
return String(value);
};
const formattedTime = formatDateTime(activity && activity.login_time);
const timeCell = document.createElement('td');
timeCell.textContent = formattedTime;
const activityCell = document.createElement('td');
activityCell.textContent = 'Logged in';
const userCell = document.createElement('td');
userCell.textContent = toDisplayString(activity && activity.email);
row.appendChild(timeCell);
row.appendChild(activityCell);
row.appendChild(userCell);
activitiesBody.appendChild(row);
});
})
.catch((error) => {
if (activitiesBody) {
activitiesBody.innerHTML = '';
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 3;
cell.classList.add('text-danger');
cell.textContent = 'Failed to load recent activities.';
row.appendChild(cell);
activitiesBody.appendChild(row);
}
showError(error.message || 'Unable to load dashboard metrics.');
});
});
</script>
<?= $this->endSection() ?>