diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6a2cdd7895..462e188e6b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -36,6 +36,7 @@ v28.0.00 System: refactored the Fast Finder using HTMX and Alpine System: updated Core modules to have Gibbon Foundation as author Attendance: added an index to the attendance log to help speed up attendance pages + Attendance: added an indicator to Student Not Present/Onsite when attendance logs conflict Activities: updated Activity Attendance to always count participants for dates in the past Markbook: updated markbook columns to grey out students who joined after the Go Live date Messenger: limited the individual message target to a maximum of 50 people diff --git a/modules/Attendance/attendance_take_byPerson.php b/modules/Attendance/attendance_take_byPerson.php index 7c5b41f3b9..b7fbedf7f4 100644 --- a/modules/Attendance/attendance_take_byPerson.php +++ b/modules/Attendance/attendance_take_byPerson.php @@ -129,8 +129,8 @@ if (empty($log['timestampTaken'])) return Format::small(__('N/A')); return $currentDate != substr($log['timestampTaken'], 0, 10) - ? Format::dateReadable($log['timestampTaken'], Format::MEDIUM, Format::SHORT) - : Format::dateReadable($log['timestampTaken'], Format::NONE, Format::SHORT); + ? Format::dateReadable($log['timestampTaken']) + : Format::dateReadable($log['timestampTaken']); }); $table->addColumn('direction', __('Attendance')) @@ -196,12 +196,10 @@ // Class Attendance if ($countClassAsSchool == 'N') { - if ($classLogCount > 0) { - $classTable = clone $table; - $classTable->setTitle(__('Class Attendance')); + $classTable = clone $table; + $classTable->setTitle(__('Class Attendance')); - echo $classTable->render($classLogs); - } + echo $classTable->render($classLogs); } echo '
'; diff --git a/modules/Attendance/report_studentsNotOnsite_byDate.php b/modules/Attendance/report_studentsNotOnsite_byDate.php index 3d7cafeca3..25f3a5340c 100644 --- a/modules/Attendance/report_studentsNotOnsite_byDate.php +++ b/modules/Attendance/report_studentsNotOnsite_byDate.php @@ -26,6 +26,7 @@ use Gibbon\Tables\Prefab\ReportTable; use Gibbon\Module\Attendance\AttendanceView; use Gibbon\Domain\Attendance\AttendanceLogPersonGateway; +use Gibbon\Http\Url; // Module includes require_once __DIR__ . '/moduleFunctions.php'; @@ -104,6 +105,10 @@ $attendance = $attendanceGateway->queryStudentsNotOnsite($criteria, $session->get('gibbonSchoolYearID'), $currentDate, $allStudents, $countClassAsSchool); + $students = $attendance->getColumn('gibbonPersonID'); + $attendanceConflictData = $attendanceGateway->selectNonAbsentAttendanceLogsByDate($students, $currentDate)->fetchKeyPair(); + $attendance->joinColumn('gibbonPersonID', 'conflicts', $attendanceConflictData); + $table = ReportTable::createPaginated('attendanceReport', $criteria)->setViewMode($viewMode, $session); $table->setTitle(__('Report Data')); @@ -114,13 +119,19 @@ $table->addColumn('name', __('Name')) ->context('primary') ->sortable(['gibbonPerson.surname', 'gibbonPerson.preferredName']) - ->format(function ($student) { - return Format::nameLinked($student['gibbonPersonID'], '', $student['preferredName'], $student['surname'], 'Student', true, true, ['subpage' => 'Attendance']); + ->format(function ($student) use ($currentDate) { + $url = Url::fromModuleRoute('Attendance', 'attendance_take_byPerson.php')->withQueryParams(['gibbonPersonID' => $student['gibbonPersonID'], 'currentDate' => $currentDate]); + return Format::link($url, Format::name('', $student['preferredName'], $student['surname'], 'Student', true, true)); }); $table->addColumn('status', __('Status')) ->context('primary') + ->width('20%') ->format(function ($student) { - return !empty($student['type']) ? __($student['type']) : Format::small(__('Not registered')); + $output = !empty($student['type']) ? __($student['type']) : Format::small(__('Not registered')); + if (!empty($student['conflicts']) && $student['conflicts'] != $student['type'] && stripos($student['type'], 'Left') === false) { + $output .= Format::tag(__('Conflict'), 'warning text-xxs ml-2', $student['conflicts']); + } + return $output; }); $table->addColumn('reason', __('Reason'))->context('secondary'); $table->addColumn('comment', __('Comment')) diff --git a/modules/Attendance/report_studentsNotPresent_byDate.php b/modules/Attendance/report_studentsNotPresent_byDate.php index ec8e939ded..f23c8ad9e9 100644 --- a/modules/Attendance/report_studentsNotPresent_byDate.php +++ b/modules/Attendance/report_studentsNotPresent_byDate.php @@ -26,6 +26,7 @@ use Gibbon\Tables\Prefab\ReportTable; use Gibbon\Module\Attendance\AttendanceView; use Gibbon\Domain\Attendance\AttendanceLogPersonGateway; +use Gibbon\Http\Url; // Module includes require_once __DIR__ . '/moduleFunctions.php'; @@ -104,6 +105,10 @@ $attendance = $attendanceGateway->queryStudentsNotPresent($criteria, $session->get('gibbonSchoolYearID'), $currentDate, $allStudents, $countClassAsSchool); + $students = $attendance->getColumn('gibbonPersonID'); + $attendanceConflictData = $attendanceGateway->selectNonAbsentAttendanceLogsByDate($students, $currentDate)->fetchKeyPair(); + $attendance->joinColumn('gibbonPersonID', 'conflicts', $attendanceConflictData); + $table = ReportTable::createPaginated('attendanceReport', $criteria)->setViewMode($viewMode, $session); $table->setTitle(__('Report Data')); @@ -114,13 +119,19 @@ $table->addColumn('name', __('Name')) ->context('primary') ->sortable(['gibbonPerson.surname', 'gibbonPerson.preferredName']) - ->format(function ($student) { - return Format::nameLinked($student['gibbonPersonID'], '', $student['preferredName'], $student['surname'], 'Student', true, true, ['subpage' => 'Attendance']); + ->format(function ($student) use ($currentDate) { + $url = Url::fromModuleRoute('Attendance', 'attendance_take_byPerson.php')->withQueryParams(['gibbonPersonID' => $student['gibbonPersonID'], 'currentDate' => $currentDate]); + return Format::link($url, Format::name('', $student['preferredName'], $student['surname'], 'Student', true, true)); }); $table->addColumn('status', __('Status')) ->context('primary') + ->width('20%') ->format(function ($student) { - return !empty($student['type']) ? __($student['type']) : Format::small(__('Not registered')); + $output = !empty($student['type']) ? __($student['type']) : Format::small(__('Not registered')); + if (!empty($student['conflicts']) && $student['conflicts'] != $student['type'] && stripos($student['type'], 'Left') === false) { + $output .= Format::tag(__('Conflict'), 'warning text-xxs ml-2', $student['conflicts']); + } + return $output; }); $table->addColumn('reason', __('Reason'))->context('secondary'); $table->addColumn('comment', __('Comment')) diff --git a/src/Domain/Attendance/AttendanceLogPersonGateway.php b/src/Domain/Attendance/AttendanceLogPersonGateway.php index 4588c57d57..15dbc9591f 100644 --- a/src/Domain/Attendance/AttendanceLogPersonGateway.php +++ b/src/Domain/Attendance/AttendanceLogPersonGateway.php @@ -405,6 +405,24 @@ function selectAttendanceLogsByPersonAndDate($gibbonPersonID, $date, $crossFillC return $this->db()->select($sql, $data); } + function selectNonAbsentAttendanceLogsByDate($gibbonPersonIDList, $date) + { + $gibbonPersonIDList = is_array($gibbonPersonIDList) ? implode(',', $gibbonPersonIDList) : $gibbonPersonIDList; + + $data = ['gibbonPersonIDList' => $gibbonPersonIDList, 'date' => $date]; + $sql = "SELECT gibbonAttendanceLogPerson.gibbonPersonID, GROUP_CONCAT(DISTINCT type SEPARATOR ',') + FROM gibbonAttendanceLogPerson + JOIN gibbonPerson ON (gibbonAttendanceLogPerson.gibbonPersonID=gibbonPerson.gibbonPersonID) + WHERE date=:date + AND gibbonAttendanceLogPerson.context='Class' + AND gibbonAttendanceLogPerson.type<>'Absent' + AND FIND_IN_SET(gibbonAttendanceLogPerson.gibbonPersonID, :gibbonPersonIDList) + GROUP BY gibbonAttendanceLogPerson.gibbonPersonID + ORDER BY timestampTaken DESC"; + + return $this->db()->select($sql, $data); + } + public function selectAdHocAttendanceStudents($gibbonSchoolYearID, $target, $targetID, $currentDate) { switch ($target) {