573 lines
21 KiB
PHP
573 lines
21 KiB
PHP
<?= $this->extend('layout/management_layout') ?>
|
||
<?= $this->section('content') ?>
|
||
<?= csrf_meta() ?>
|
||
|
||
<?php
|
||
// Exclude unwanted grade buckets globally (e.g., legacy Grade 10/11) unless the bucket is the Arabic program.
|
||
$hiddenGrades = [10, 11];
|
||
$hasArabic = static function (array $sections): bool {
|
||
foreach ($sections as $section) {
|
||
$name = strtolower((string)($section['class_section_name'] ?? ''));
|
||
$code = strtolower((string)($section['class_section_id'] ?? ''));
|
||
if (strpos($name, 'arabic') !== false || strpos($code, 'arabic') !== false) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
foreach ($hiddenGrades as $hid) {
|
||
if (isset($grades[$hid]) && $hasArabic($grades[$hid])) {
|
||
continue; // keep Arabic sections even if the class_id matches a hidden grade bucket
|
||
}
|
||
unset($grades[$hid]);
|
||
}
|
||
|
||
// Prune out sections with no students (extra safety so empty Arabic-2/3 never render)
|
||
$filteredGrades = [];
|
||
foreach ($grades as $cid => $sections) {
|
||
$kept = [];
|
||
foreach ($sections as $section) {
|
||
$secKey = (string)($section['class_section_id'] ?? ($section['id'] ?? ''));
|
||
if ($secKey !== '' && !empty($studentsBySection[$secKey])) {
|
||
$kept[] = $section;
|
||
}
|
||
}
|
||
if (!empty($kept)) {
|
||
$filteredGrades[$cid] = $kept;
|
||
}
|
||
}
|
||
$grades = $filteredGrades;
|
||
?>
|
||
|
||
<div class="container-fluid">
|
||
<div class="wrapper">
|
||
<div class="content mb-3"></div>
|
||
<h2 class="text-center mt-4 mb-3">Attendance Analysis</h2>
|
||
<?= $this->include('partials/academic_filter') ?>
|
||
|
||
<?= $this->section('styles') ?>
|
||
<style>
|
||
.attn-analysis-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(12, 1fr);
|
||
gap: 1rem;
|
||
}
|
||
.attn-analysis-card {
|
||
background: #fff;
|
||
border: 1px solid rgba(0,0,0,0.08);
|
||
border-radius: .5rem;
|
||
padding: 1rem;
|
||
}
|
||
.attn-analysis-card h6 { margin-bottom: .75rem; }
|
||
.attn-analysis-wide { grid-column: span 12; }
|
||
.attn-analysis-half { grid-column: span 6; }
|
||
.attn-analysis-table {
|
||
width: 100%;
|
||
font-size: .9rem;
|
||
}
|
||
.attn-analysis-table th,
|
||
.attn-analysis-table td { padding: .35rem .5rem; }
|
||
.attn-analysis-scroll {
|
||
max-height: none;
|
||
overflow: visible;
|
||
}
|
||
.attn-pie-wrap {
|
||
width: 320px;
|
||
height: 320px;
|
||
margin: 0 auto;
|
||
}
|
||
@media (max-width: 1200px) {
|
||
.attn-analysis-half { grid-column: span 12; }
|
||
}
|
||
@media (max-width: 576px) {
|
||
.attn-pie-wrap {
|
||
width: 180px;
|
||
height: 180px;
|
||
}
|
||
}
|
||
</style>
|
||
<?= $this->endSection() ?>
|
||
|
||
<?php
|
||
$req = request();
|
||
$startDateRaw = trim((string)($req->getGet('start_date') ?? ''));
|
||
$endDateRaw = trim((string)($req->getGet('end_date') ?? ''));
|
||
$isValidDate = static function (string $d): bool {
|
||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $d)) return false;
|
||
[$y, $m, $day] = array_map('intval', explode('-', $d));
|
||
return checkdate($m, $day, $y);
|
||
};
|
||
$filterStart = ($startDateRaw !== '' && $isValidDate($startDateRaw)) ? $startDateRaw : '';
|
||
$filterEnd = ($endDateRaw !== '' && $isValidDate($endDateRaw)) ? $endDateRaw : '';
|
||
|
||
// ---- Build labels and a stable order: KG -> Grade 1..12 -> Youth ----
|
||
$labelsByClassId = [];
|
||
$gradeNumberById = [];
|
||
$kgIds = [];
|
||
$arabicIds = [];
|
||
$youthIds = [];
|
||
|
||
foreach ($grades as $classId => $sections) {
|
||
$cid = (int)$classId;
|
||
$sampleName = '';
|
||
$isArabicProgram = false;
|
||
foreach ($sections as $s) {
|
||
$sampleName = (string)($s['class_section_name'] ?? '');
|
||
$lowName = strtolower($sampleName);
|
||
$lowCode = strtolower((string)($s['class_section_id'] ?? ''));
|
||
if (strpos($lowName, 'arabic') !== false || strpos($lowCode, 'arabic') !== false) {
|
||
$isArabicProgram = true;
|
||
}
|
||
if ($sampleName !== '') break;
|
||
}
|
||
|
||
if ($isArabicProgram || $cid === 14) {
|
||
$labelsByClassId[$cid] = 'Arabic';
|
||
$arabicIds[] = $cid;
|
||
} elseif (preg_match('/\bKG\b/i', $sampleName)) {
|
||
$labelsByClassId[$cid] = 'KG';
|
||
$kgIds[] = $cid;
|
||
} elseif (preg_match('/\bYouth\b/i', $sampleName)) {
|
||
$labelsByClassId[$cid] = 'Youth';
|
||
$youthIds[] = $cid;
|
||
} elseif (preg_match('/Grade\s*(\d+)/i', $sampleName, $m)) {
|
||
$labelsByClassId[$cid] = 'Grade ' . (int)$m[1];
|
||
$gradeNumberById[$cid] = (int)$m[1];
|
||
} else {
|
||
if ($cid === 0) {
|
||
$labelsByClassId[$cid] = 'KG';
|
||
$kgIds[] = $cid;
|
||
} elseif ($cid === 13) {
|
||
$labelsByClassId[$cid] = 'Youth';
|
||
$youthIds[] = $cid;
|
||
} else {
|
||
$labelsByClassId[$cid] = 'Grade ' . $cid;
|
||
$gradeNumberById[$cid] = $cid;
|
||
}
|
||
}
|
||
}
|
||
|
||
$sortedClassIds = [];
|
||
if ($kgIds) {
|
||
sort($kgIds, SORT_NUMERIC);
|
||
$sortedClassIds = array_merge($sortedClassIds, $kgIds);
|
||
}
|
||
if ($gradeNumberById) {
|
||
asort($gradeNumberById, SORT_NUMERIC);
|
||
$sortedClassIds = array_merge($sortedClassIds, array_keys($gradeNumberById));
|
||
}
|
||
if ($youthIds) {
|
||
sort($youthIds, SORT_NUMERIC);
|
||
$sortedClassIds = array_merge($sortedClassIds, $youthIds);
|
||
}
|
||
if ($arabicIds) {
|
||
sort($arabicIds, SORT_NUMERIC);
|
||
$sortedClassIds = array_merge($sortedClassIds, $arabicIds);
|
||
}
|
||
$presentIds = array_map('intval', array_keys($grades));
|
||
$missingIds = array_values(array_diff($presentIds, $sortedClassIds));
|
||
if (!empty($missingIds)) {
|
||
$sortedClassIds = array_merge($sortedClassIds, $missingIds);
|
||
}
|
||
|
||
$labelFor = function (int $id) use ($labelsByClassId): string {
|
||
return $labelsByClassId[$id] ?? ('Class ' . $id);
|
||
};
|
||
|
||
// ---- Attendance analysis ----
|
||
$analysisOverall = ['present' => 0, 'late' => 0, 'absent' => 0];
|
||
$analysisByGrade = [];
|
||
$analysisBySection = [];
|
||
$analysisTotal = 0;
|
||
|
||
foreach ($grades as $classId => $sections) {
|
||
$gradeLabel = $labelFor((int)$classId);
|
||
if (!isset($analysisByGrade[$gradeLabel])) {
|
||
$analysisByGrade[$gradeLabel] = ['present' => 0, 'late' => 0, 'absent' => 0];
|
||
}
|
||
|
||
foreach ($sections as $section) {
|
||
$sectionKey = (string)($section['class_section_id'] ?? ($section['id'] ?? ''));
|
||
if ($sectionKey === '' || empty($studentsBySection[$sectionKey])) continue;
|
||
|
||
$secNameRaw = trim((string)($section['class_section_name'] ?? ''));
|
||
$secLabel = $secNameRaw !== '' ? $secNameRaw : ('Section ' . $sectionKey);
|
||
if (!isset($analysisBySection[$secLabel])) {
|
||
$analysisBySection[$secLabel] = ['present' => 0, 'late' => 0, 'absent' => 0, 'total_students' => 0];
|
||
}
|
||
$sectionTotalStudents = count($studentsBySection[$sectionKey]);
|
||
if ($sectionTotalStudents > ($analysisBySection[$secLabel]['total_students'] ?? 0)) {
|
||
$analysisBySection[$secLabel]['total_students'] = $sectionTotalStudents;
|
||
}
|
||
|
||
foreach ($studentsBySection[$sectionKey] as $stu) {
|
||
$sid = (int)($stu['id'] ?? 0);
|
||
if ($sid <= 0) continue;
|
||
$entries = $attendanceData[$sectionKey][$sid] ?? [];
|
||
if (!is_array($entries)) continue;
|
||
foreach ($entries as $e) {
|
||
$d = substr((string)($e['date'] ?? ''), 0, 10);
|
||
if ($d === '') continue;
|
||
if ($filterStart !== '' && $d < $filterStart) continue;
|
||
if ($filterEnd !== '' && $d > $filterEnd) continue;
|
||
$st = strtolower(trim((string)($e['status'] ?? '')));
|
||
if (!in_array($st, ['present', 'late', 'absent'], true)) continue;
|
||
$analysisOverall[$st]++;
|
||
$analysisByGrade[$gradeLabel][$st]++;
|
||
$analysisBySection[$secLabel][$st]++;
|
||
$analysisTotal++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
$overallPercent = [];
|
||
foreach (['present', 'late', 'absent'] as $st) {
|
||
$overallPercent[$st] = $analysisTotal > 0 ? round(($analysisOverall[$st] * 100) / $analysisTotal, 1) : 0;
|
||
}
|
||
|
||
$analysisGradeLabels = [];
|
||
$analysisGradeAbsent = [];
|
||
$analysisGradeLate = [];
|
||
$analysisGradeTotals = [];
|
||
foreach ($sortedClassIds as $cid) {
|
||
$lbl = $labelFor((int)$cid);
|
||
$g = $analysisByGrade[$lbl] ?? ['present' => 0, 'late' => 0, 'absent' => 0];
|
||
$analysisGradeLabels[] = $lbl;
|
||
$analysisGradeAbsent[] = (int)($g['absent'] ?? 0);
|
||
$analysisGradeLate[] = (int)($g['late'] ?? 0);
|
||
$analysisGradeTotals[] = (int)($g['present'] ?? 0) + (int)($g['late'] ?? 0) + (int)($g['absent'] ?? 0);
|
||
}
|
||
|
||
$analysisSectionLabels = array_keys($analysisBySection);
|
||
$analysisSectionAbsent = [];
|
||
$analysisSectionLate = [];
|
||
$analysisSectionTotals = [];
|
||
foreach ($analysisSectionLabels as $lbl) {
|
||
$s = $analysisBySection[$lbl] ?? ['present' => 0, 'late' => 0, 'absent' => 0];
|
||
$analysisSectionAbsent[] = (int)($s['absent'] ?? 0);
|
||
$analysisSectionLate[] = (int)($s['late'] ?? 0);
|
||
$analysisSectionTotals[] = (int)($s['total_students'] ?? 0);
|
||
}
|
||
|
||
// ---- Student absences/late list ----
|
||
$sectionLabelByKey = [];
|
||
foreach ($grades as $classId => $sections) {
|
||
foreach ($sections as $section) {
|
||
$sectionKey = (string)($section['class_section_id'] ?? ($section['id'] ?? ''));
|
||
if ($sectionKey === '') continue;
|
||
$secNameRaw = trim((string)($section['class_section_name'] ?? ''));
|
||
$sectionLabelByKey[$sectionKey] = $secNameRaw !== '' ? $secNameRaw : ('Section ' . $sectionKey);
|
||
}
|
||
}
|
||
|
||
$studentIssueRows = [];
|
||
foreach ($studentsBySection as $sectionKey => $students) {
|
||
foreach ($students as $stu) {
|
||
$sid = (int)($stu['id'] ?? 0);
|
||
if ($sid <= 0) continue;
|
||
$entries = $attendanceData[$sectionKey][$sid] ?? [];
|
||
if (!is_array($entries)) continue;
|
||
$abs = 0;
|
||
$late = 0;
|
||
foreach ($entries as $e) {
|
||
$d = substr((string)($e['date'] ?? ''), 0, 10);
|
||
if ($d === '') continue;
|
||
if ($filterStart !== '' && $d < $filterStart) continue;
|
||
if ($filterEnd !== '' && $d > $filterEnd) continue;
|
||
$st = strtolower(trim((string)($e['status'] ?? '')));
|
||
if ($st === 'absent') {
|
||
$abs++;
|
||
} elseif ($st === 'late') {
|
||
$late++;
|
||
}
|
||
}
|
||
if (($abs + $late) <= 0) continue;
|
||
$studentIssueRows[] = [
|
||
'name' => trim((string)($stu['firstname'] ?? '') . ' ' . (string)($stu['lastname'] ?? '')),
|
||
'section' => $sectionLabelByKey[(string)$sectionKey] ?? ('Section ' . $sectionKey),
|
||
'absent' => $abs,
|
||
'late' => $late,
|
||
];
|
||
}
|
||
}
|
||
|
||
usort($studentIssueRows, static function ($a, $b) {
|
||
$sec = strcmp($a['section'], $b['section']);
|
||
if ($sec !== 0) return $sec;
|
||
return strcmp($a['name'], $b['name']);
|
||
});
|
||
|
||
$totalDaysForPercent = 0;
|
||
if ($filterStart === '' && $filterEnd === '' && !empty($totalPassedDays)) {
|
||
$totalDaysForPercent = (int)$totalPassedDays;
|
||
} elseif (!empty($dateList) && is_array($dateList)) {
|
||
foreach ($dateList as $d) {
|
||
$d = (string)$d;
|
||
if ($d === '') continue;
|
||
if ($filterStart !== '' && $d < $filterStart) continue;
|
||
if ($filterEnd !== '' && $d > $filterEnd) continue;
|
||
if (!empty($noSchoolDays[$d])) continue;
|
||
$totalDaysForPercent++;
|
||
}
|
||
}
|
||
|
||
$backUrl = site_url('administrator/daily_attendance');
|
||
$queryParts = [];
|
||
if (!empty($semester)) $queryParts[] = 'semester=' . urlencode((string)$semester);
|
||
if (!empty($schoolYear)) $queryParts[] = 'school_year=' . urlencode((string)$schoolYear);
|
||
if ($queryParts) $backUrl .= '?' . implode('&', $queryParts);
|
||
?>
|
||
|
||
<div class="row justify-content-center mb-3">
|
||
<div class="col-auto">
|
||
<a class="btn btn-outline-secondary" href="<?= esc($backUrl) ?>">
|
||
Back to Daily Attendance
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<form class="row g-2 justify-content-center mb-4" method="get" action="<?= esc(current_url()) ?>">
|
||
<?php if (!empty($semester)): ?>
|
||
<input type="hidden" name="semester" value="<?= esc((string)$semester) ?>">
|
||
<?php endif; ?>
|
||
<?php if (!empty($schoolYear)): ?>
|
||
<input type="hidden" name="school_year" value="<?= esc((string)$schoolYear) ?>">
|
||
<?php endif; ?>
|
||
<div class="col-12 col-md-3">
|
||
<label class="form-label">Start date</label>
|
||
<input type="date" class="form-control" name="start_date" value="<?= esc($filterStart) ?>">
|
||
</div>
|
||
<div class="col-12 col-md-3">
|
||
<label class="form-label">End date</label>
|
||
<input type="date" class="form-control" name="end_date" value="<?= esc($filterEnd) ?>">
|
||
</div>
|
||
<div class="col-12 col-md-auto d-flex align-items-end gap-2">
|
||
<button type="submit" class="btn btn-primary">Apply</button>
|
||
<a class="btn btn-outline-secondary" href="<?= esc(current_url()) ?>">Clear</a>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="attn-analysis-grid">
|
||
<div class="attn-analysis-card attn-analysis-half">
|
||
<h6>Overall Distribution</h6>
|
||
<div class="attn-pie-wrap">
|
||
<canvas id="attnPieChart" aria-label="Overall attendance pie chart"></canvas>
|
||
</div>
|
||
<table class="attn-analysis-table mt-3 no-mgmt-sticky" data-no-mgmt-sticky>
|
||
<thead>
|
||
<tr>
|
||
<th title="Attendance status for the selected date range">Status</th>
|
||
<th class="text-end" title="Number of attendance entries with this status">Count</th>
|
||
<th class="text-end" title="Percent of total attendance entries">Percent</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Present</td>
|
||
<td class="text-end"><?= (int)$analysisOverall['present'] ?></td>
|
||
<td class="text-end"><?= number_format($overallPercent['present'], 1) ?>%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Late</td>
|
||
<td class="text-end"><?= (int)$analysisOverall['late'] ?></td>
|
||
<td class="text-end"><?= number_format($overallPercent['late'], 1) ?>%</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Absent</td>
|
||
<td class="text-end"><?= (int)$analysisOverall['absent'] ?></td>
|
||
<td class="text-end"><?= number_format($overallPercent['absent'], 1) ?>%</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="attn-analysis-card attn-analysis-half">
|
||
<h6>By Section (Absent / Late)</h6>
|
||
<canvas id="attnSectionChart" height="200" aria-label="Attendance by section bar chart"></canvas>
|
||
<div class="attn-analysis-scroll mt-3">
|
||
<table id="sectionAnalysisTable" class="attn-analysis-table no-mgmt-sticky" data-no-mgmt-sticky>
|
||
<thead>
|
||
<tr>
|
||
<th title="Class section name">Section</th>
|
||
<th class="text-end" title="Total semester days in range (excluding no-school days)">Semester Days</th>
|
||
<th class="text-end" title="Total students in the class section">Total Students</th>
|
||
<th class="text-end" title="Number of absent entries in range">Absent</th>
|
||
<th class="text-end" title="Absent entries as a percent of total possible attendance entries (total students × total days in range)">Absent%</th>
|
||
<th class="text-end" title="Number of late entries in range">Late</th>
|
||
<th class="text-end" title="Late entries as a percent of total possible attendance entries (total students × total days in range)">Late%</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($analysisSectionLabels as $i => $lbl): ?>
|
||
<?php
|
||
$tot = (int)($analysisSectionTotals[$i] ?? 0);
|
||
$abs = (int)($analysisSectionAbsent[$i] ?? 0);
|
||
$late = (int)($analysisSectionLate[$i] ?? 0);
|
||
$denom = $totalDaysForPercent > 0 ? ($totalDaysForPercent * $tot) : 0;
|
||
$absPct = $denom > 0 ? round(($abs * 100) / $denom, 1) : 0;
|
||
$latePct = $denom > 0 ? round(($late * 100) / $denom, 1) : 0;
|
||
?>
|
||
<tr>
|
||
<td><?= esc($lbl) ?></td>
|
||
<td class="text-end"><?= (int)$totalDaysForPercent ?></td>
|
||
<td class="text-end"><?= $tot ?></td>
|
||
<td class="text-end"><?= $abs ?></td>
|
||
<td class="text-end"><?= number_format($absPct, 1) ?>%</td>
|
||
<td class="text-end"><?= $late ?></td>
|
||
<td class="text-end"><?= number_format($latePct, 1) ?>%</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="attn-analysis-card attn-analysis-wide">
|
||
<h6>Students With Absences / Late</h6>
|
||
<div class="attn-analysis-scroll">
|
||
<table id="studentIssueTable" class="attn-analysis-table no-mgmt-sticky" data-no-mgmt-sticky>
|
||
<thead>
|
||
<tr>
|
||
<th>Student Name</th>
|
||
<th>Class Section</th>
|
||
<th class="text-end">Nbr of ABS</th>
|
||
<th class="text-end">Nbr of LATE</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if (empty($studentIssueRows)): ?>
|
||
<tr>
|
||
<td colspan="4" class="text-center text-muted">No absences or late records in the selected range.</td>
|
||
</tr>
|
||
<?php else: ?>
|
||
<?php foreach ($studentIssueRows as $row): ?>
|
||
<tr>
|
||
<td><?= esc($row['name']) ?></td>
|
||
<td><?= esc($row['section']) ?></td>
|
||
<td class="text-end"><?= (int)$row['absent'] ?></td>
|
||
<td class="text-end"><?= (int)$row['late'] ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?= $this->endSection() ?>
|
||
|
||
<?= $this->section('scripts') ?>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||
<script>
|
||
const ATTN_ANALYSIS = <?= json_encode([
|
||
'overall' => [
|
||
'labels' => ['Present', 'Late', 'Absent'],
|
||
'counts' => [
|
||
(int)($analysisOverall['present'] ?? 0),
|
||
(int)($analysisOverall['late'] ?? 0),
|
||
(int)($analysisOverall['absent'] ?? 0),
|
||
],
|
||
],
|
||
'byGrade' => [
|
||
'labels' => $analysisGradeLabels,
|
||
'absent' => $analysisGradeAbsent,
|
||
'late' => $analysisGradeLate,
|
||
],
|
||
'bySection' => [
|
||
'labels' => $analysisSectionLabels,
|
||
'absent' => $analysisSectionAbsent,
|
||
'late' => $analysisSectionLate,
|
||
],
|
||
]) ?>;
|
||
|
||
function percentOfTotal(value, total) {
|
||
if (!total) return '0%';
|
||
return (Math.round((value * 1000) / total) / 10).toFixed(1) + '%';
|
||
}
|
||
|
||
function buildAttendanceCharts() {
|
||
if (!window.Chart) return;
|
||
const pieEl = document.getElementById('attnPieChart');
|
||
const sectionEl = document.getElementById('attnSectionChart');
|
||
if (!pieEl || !sectionEl) return;
|
||
|
||
const overall = ATTN_ANALYSIS.overall || { labels: [], counts: [] };
|
||
const total = (overall.counts || []).reduce((a, b) => a + (parseInt(b, 10) || 0), 0);
|
||
|
||
new Chart(pieEl.getContext('2d'), {
|
||
type: 'pie',
|
||
data: {
|
||
labels: overall.labels,
|
||
datasets: [{
|
||
data: overall.counts,
|
||
backgroundColor: ['#198754', '#f0ad4e', '#dc3545'],
|
||
borderWidth: 1,
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
tooltip: {
|
||
callbacks: {
|
||
label: function(ctx) {
|
||
const val = parseInt(ctx.parsed, 10) || 0;
|
||
return ctx.label + ': ' + val + ' (' + percentOfTotal(val, total) + ')';
|
||
}
|
||
}
|
||
},
|
||
legend: { position: 'bottom' }
|
||
}
|
||
}
|
||
});
|
||
|
||
new Chart(sectionEl.getContext('2d'), {
|
||
type: 'bar',
|
||
data: {
|
||
labels: ATTN_ANALYSIS.bySection?.labels || [],
|
||
datasets: [
|
||
{
|
||
label: 'Absent',
|
||
data: ATTN_ANALYSIS.bySection?.absent || [],
|
||
backgroundColor: '#dc3545',
|
||
},
|
||
{
|
||
label: 'Late',
|
||
data: ATTN_ANALYSIS.bySection?.late || [],
|
||
backgroundColor: '#f0ad4e',
|
||
}
|
||
]
|
||
},
|
||
options: {
|
||
plugins: { legend: { position: 'bottom' } },
|
||
scales: {
|
||
y: { beginAtZero: true, ticks: { precision: 0 } }
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', buildAttendanceCharts);
|
||
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
if (window.jQuery && $.fn.DataTable) {
|
||
$('#sectionAnalysisTable').DataTable({
|
||
paging: false,
|
||
searching: false,
|
||
info: false,
|
||
order: [[0, 'asc']]
|
||
});
|
||
$('#studentIssueTable').DataTable({
|
||
paging: true,
|
||
searching: true,
|
||
info: true,
|
||
order: [[1, 'asc'], [0, 'asc']],
|
||
pageLength: 25
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
<?= $this->endSection() ?>
|