Attendance Management
= $this->include('partials/academic_filter') ?>
= $this->section('styles') ?>
= $this->endSection() ?>
$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;
?>
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) {
// Arabic program: tab label is "Arabic" (sections show their own names e.g., Arabic-1)
$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);
}
// Keep Arabic after Youth (if present) and before any unknown leftovers
if ($arabicIds) {
sort($arabicIds, SORT_NUMERIC);
$sortedClassIds = array_merge($sortedClassIds, $arabicIds);
}
// Append any remaining class IDs (e.g., Arabic class) that didn’t fit standard buckets
$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);
};
$slugFor = function (int $id) use ($labelsByClassId, $gradeNumberById, $arabicIds): string {
$lbl = $labelsByClassId[$id] ?? '';
if ($lbl === 'KG') return 'kg';
if ($lbl === 'Youth') return 'youth';
if (in_array($id, $arabicIds, true) || strcasecmp($lbl, 'Arabic') === 0) return 'arabic';
$n = $gradeNumberById[$id] ?? null;
return $n !== null ? 'g' . $n : (string)$id;
};
// Distinct student counts per classId for tab badges
$studentsCountByClassId = [];
foreach ($grades as $classId => $sections) {
$cid = (int)$classId;
$uniq = [];
foreach ($sections as $section) {
$secId = $section['class_section_id'] ?? null;
if ($secId && !empty($studentsBySection[$secId])) {
foreach ($studentsBySection[$secId] as $stu) {
if (!empty($stu['id'])) $uniq[(int)$stu['id']] = true;
}
}
}
$studentsCountByClassId[$cid] = count($uniq);
}
// Default active tab = first in sorted order (can still be overridden by ?class_id)
$defaultId = $sortedClassIds[0] ?? null;
// =====================================================================
// NEW UTILITIES (dates pivot logic) — includes "today if Sunday, else next Sunday" column
// =====================================================================
$__datesBySection = (isset($datesBySection) && is_array($datesBySection)) ? $datesBySection : [];
$__dateList = (isset($dateList) && is_array($dateList)) ? array_values(array_filter(array_map('strval', $dateList))) : [];
$__attendanceData = (isset($attendanceData) && is_array($attendanceData)) ? $attendanceData : [];
$__noSchoolDays = (isset($noSchoolDays) && is_array($noSchoolDays)) ? $noSchoolDays : [];
try {
$tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$tzObj = new DateTimeZone($tzName ?: 'UTC');
} catch (\Throwable $e) {
try {
$tzObj = new DateTimeZone(user_timezone() ?: 'UTC');
} catch (\Throwable $e2) {
$tzObj = new DateTimeZone('UTC');
}
}
try {
$__nowDate = new DateTime('now', $tzObj);
} catch (\Throwable $e) {
$__nowDate = new DateTime('now');
}
$weekday = (int) $__nowDate->format('w');
if ($weekday === 0) {
$__anchorSunday = $__nowDate->format('Y-m-d');
} else {
$__anchorSunday = (clone $__nowDate)->modify('next sunday')->format('Y-m-d');
}
$buildDateCols = function ($sectionId) use ($__datesBySection, $__dateList, $__attendanceData, $__anchorSunday, $__nowDate, $__noSchoolDays) {
$filterDates = function (array $items) use ($__noSchoolDays) {
$map = [];
foreach ($items as $item) {
$d = (string)$item;
if ($d === '' || !empty($__noSchoolDays[$d])) {
continue;
}
$map[$d] = $d;
}
return array_values($map);
};
$candidates = [];
if (!empty($__datesBySection[$sectionId]) && is_array($__datesBySection[$sectionId])) {
$candidates = array_merge($candidates, $__datesBySection[$sectionId]);
}
if (!empty($__dateList) && is_array($__dateList)) {
$candidates = array_merge($candidates, $__dateList);
}
$cols = $filterDates($candidates);
if (!$cols && !empty($__attendanceData[$sectionId]) && is_array($__attendanceData[$sectionId])) {
$set = [];
foreach ($__attendanceData[$sectionId] as $sid => $entries) {
if (!is_array($entries)) continue;
foreach ($entries as $e) {
$d = (string)($e['date'] ?? '');
if ($d !== '') $set[$d] = $d;
}
}
$cols = $filterDates(array_keys($set));
}
if (!$cols) {
$nowRef = clone $__nowDate;
$fallback = [
(clone $nowRef)->modify('-7 days')->format('Y-m-d'),
$nowRef->format('Y-m-d'),
];
$cols = $filterDates($fallback);
}
if ($cols) {
sort($cols, SORT_STRING);
}
if (!empty($__anchorSunday) && empty($__noSchoolDays[$__anchorSunday]) && !in_array($__anchorSunday, $cols, true)) {
$cols[] = $__anchorSunday;
sort($cols, SORT_STRING);
}
return $cols;
};
$formatDateLabel = function ($ymd) {
$ymd = (string)$ymd;
if ($ymd === '') return '';
if (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})$/', $ymd, $m)) {
return $m[2] . '-' . $m[3] . '-' . $m[1];
}
try {
return local_date($ymd, 'm-d-Y');
} catch (\Throwable $e) {
return $ymd;
}
};
$todayDate = $__nowDate->format('Y-m-d');
$todayLabel = $todayDate ? $formatDateLabel($todayDate) : '';
// Build {date => {status, is_reported}}
$recAt = function (array $entries) {
$map = [];
foreach ($entries as $e) {
$d = (string)($e['date'] ?? '');
if ($d !== '') {
$map[$d] = [
'status' => (string)($e['status'] ?? ''),
'is_reported' => (string)($e['is_reported'] ?? 'no'),
];
}
}
return $map;
};
// Dropdown options + badge meta (first is placeholder)
$optionDefs = [
'none' => ['label' => 'Select attendance', 'status' => '', 'is_reported' => '', 'badge' => 'secondary', 'short' => '—'],
'present' => ['label' => 'Present', 'status' => 'present', 'is_reported' => 'no', 'badge' => 'success', 'short' => 'P'],
'late_yes' => ['label' => 'Late — Reported', 'status' => 'late', 'is_reported' => 'yes', 'badge' => 'warning text-dark', 'short' => 'LR'],
'late_no' => ['label' => 'Late — Not-reported', 'status' => 'late', 'is_reported' => 'no', 'badge' => 'warning text-dark', 'short' => 'LnR'],
'absent_yes' => ['label' => 'Absent — Reported', 'status' => 'absent', 'is_reported' => 'yes', 'badge' => 'danger', 'short' => 'AR'],
'absent_no' => ['label' => 'Absent — Not-reported', 'status' => 'absent', 'is_reported' => 'no', 'badge' => 'danger', 'short' => 'AnR'],
];
?>
$maxNameLen) $maxNameLen = $len_;
}
$minNameCh = max(12, $maxNameLen + 2);
// Compute longest school ID length (characters) for this section (ensure header fits: 9ch)
$maxIdLen = 0;
foreach ($studentsBySection[$sectionKey] as $s_) {
$id_ = trim((string)($s_['school_id'] ?? ''));
$len_ = function_exists('mb_strlen') ? mb_strlen($id_, 'UTF-8') : strlen($id_);
if ($len_ > $maxIdLen) $maxIdLen = $len_;
}
$minIdCh = max(9, $maxIdLen);
// Derive a human-friendly section label above the table
$rawSecName = trim((string)($section['class_section_name'] ?? ''));
// Base label from section name (fallback to tab label)
$sectionLabel = ($rawSecName !== '') ? $rawSecName : $label;
// Add "Grade " prefix unless already present, but NEVER for KG, Youth, or Arabic program
if (preg_match('/^(KG(\b|-)|Youth(\b|-))/i', $sectionLabel)) {
$headerText = $sectionLabel;
} elseif (strcasecmp($label, 'Arabic') === 0 || stripos($sectionLabel, 'Arabic') === 0) {
$headerText = $sectionLabel;
} elseif (preg_match('/^\s*Grade\b/i', $sectionLabel)) {
$headerText = $sectionLabel;
} else {
$headerText = 'Grade ' . $sectionLabel;
}
?>
= esc($headerText) ?>
| School ID |
Student Name |
= esc($dLabel) ?> |
P |
L |
A |
T |
| = $schoolId ?> |
= $fullName ?>
|
"bg-warning text-dark" or "bg-secondary"
$dLabel = $formatDateLabel($dcol);
?>
= esc($short) ?>
Parent report
|
= (int)$tPresent ?> |
= (int)$tLate ?> |
= (int)$tAbsent ?> |
= (int)($totalPassedDays ?? 0) ?> |