AVP-78 Feature to check if parents looked at report card
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
*.log
|
||||||
@@ -108,6 +108,7 @@ $routes->get('administrator/student-score-card', 'View\StudentController::scoreC
|
|||||||
// API for report card meta (students, class sections, school years)
|
// API for report card meta (students, class sections, school years)
|
||||||
$routes->get('api/printables/report-card/meta', 'View\ReportCardsController::reportCardMeta', ['filter' => 'auth']);
|
$routes->get('api/printables/report-card/meta', 'View\ReportCardsController::reportCardMeta', ['filter' => 'auth']);
|
||||||
$routes->get('api/printables/report-card/completeness', 'View\ReportCardsController::reportCardCompleteness', ['filter' => 'auth']);
|
$routes->get('api/printables/report-card/completeness', 'View\ReportCardsController::reportCardCompleteness', ['filter' => 'auth']);
|
||||||
|
$routes->get('api/printables/report-card/ack', 'View\ReportCardsController::reportCardAcknowledgement', ['filter' => 'auth']);
|
||||||
|
|
||||||
//Badges
|
//Badges
|
||||||
$routes->get('printables_reports/badge_form', 'View\BadgesController::badgeForm');
|
$routes->get('printables_reports/badge_form', 'View\BadgesController::badgeForm');
|
||||||
@@ -330,6 +331,9 @@ $routes->post('/administrator/scores/update/{id}', 'View\ScoreController::update
|
|||||||
// Route to delete a score record
|
// Route to delete a score record
|
||||||
$routes->get('/administrator/scores/delete/{id}', 'View\ScoreController::destroy');
|
$routes->get('/administrator/scores/delete/{id}', 'View\ScoreController::destroy');
|
||||||
$routes->get('/parent/scores', 'View\ScoreController::viewStudentScore');
|
$routes->get('/parent/scores', 'View\ScoreController::viewStudentScore');
|
||||||
|
$routes->get('parent/report-cards', 'ParentReportCardController::index', ['filter' => 'auth:parent']);
|
||||||
|
$routes->get('parent/report-cards/view/(:num)', 'ParentReportCardController::view/$1', ['filter' => 'auth:parent']);
|
||||||
|
$routes->post('parent/report-cards/sign/(:num)', 'ParentReportCardController::sign/$1', ['filter' => 'auth:parent']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Models\ClassSectionModel;
|
|||||||
use App\Models\CalendarModel;
|
use App\Models\CalendarModel;
|
||||||
use App\Models\ConfigurationModel;
|
use App\Models\ConfigurationModel;
|
||||||
use App\Models\StudentClassModel;
|
use App\Models\StudentClassModel;
|
||||||
|
use App\Models\SubjectCurriculumModel;
|
||||||
use App\Services\SemesterRangeService;
|
use App\Services\SemesterRangeService;
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
use CodeIgniter\HTTP\ResponseInterface;
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
@@ -21,6 +22,7 @@ class AdminProgressController extends BaseController
|
|||||||
protected CalendarModel $calendarModel;
|
protected CalendarModel $calendarModel;
|
||||||
protected ConfigurationModel $configModel;
|
protected ConfigurationModel $configModel;
|
||||||
protected SemesterRangeService $semesterRangeService;
|
protected SemesterRangeService $semesterRangeService;
|
||||||
|
protected SubjectCurriculumModel $curriculumModel;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -32,6 +34,7 @@ class AdminProgressController extends BaseController
|
|||||||
$this->calendarModel = new CalendarModel();
|
$this->calendarModel = new CalendarModel();
|
||||||
$this->configModel = new ConfigurationModel();
|
$this->configModel = new ConfigurationModel();
|
||||||
$this->semesterRangeService = new SemesterRangeService($this->configModel);
|
$this->semesterRangeService = new SemesterRangeService($this->configModel);
|
||||||
|
$this->curriculumModel = new SubjectCurriculumModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
@@ -107,6 +110,7 @@ class AdminProgressController extends BaseController
|
|||||||
$filterEnd
|
$filterEnd
|
||||||
);
|
);
|
||||||
$sectionStats = $this->buildSectionSubmissionStats($rows, $activeDatesSet, $expectedDays);
|
$sectionStats = $this->buildSectionSubmissionStats($rows, $activeDatesSet, $expectedDays);
|
||||||
|
$sectionSubjectCounts = $this->buildSectionSubjectCounts($rows);
|
||||||
|
|
||||||
return view('admin/class_progress_list', [
|
return view('admin/class_progress_list', [
|
||||||
'reportGroupsBySection' => $reportGroupsBySection,
|
'reportGroupsBySection' => $reportGroupsBySection,
|
||||||
@@ -115,6 +119,7 @@ class AdminProgressController extends BaseController
|
|||||||
'statusOptions' => ClassProgressController::STATUS_OPTIONS,
|
'statusOptions' => ClassProgressController::STATUS_OPTIONS,
|
||||||
'subjectSections' => ClassProgressController::SUBJECT_SECTIONS,
|
'subjectSections' => ClassProgressController::SUBJECT_SECTIONS,
|
||||||
'sectionStats' => $sectionStats,
|
'sectionStats' => $sectionStats,
|
||||||
|
'sectionSubjectCounts' => $sectionSubjectCounts,
|
||||||
'expectedDays' => $expectedDays,
|
'expectedDays' => $expectedDays,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -381,50 +386,166 @@ class AdminProgressController extends BaseController
|
|||||||
|
|
||||||
protected function buildSectionSubmissionStats(array $rows, array $activeDatesSet, int $expectedDays): array
|
protected function buildSectionSubmissionStats(array $rows, array $activeDatesSet, int $expectedDays): array
|
||||||
{
|
{
|
||||||
$requiredSubjects = [];
|
|
||||||
foreach (ClassProgressController::SUBJECT_SECTIONS as $section) {
|
|
||||||
$requiredSubjects[] = $section['db_subject'] ?? $section['label'] ?? '';
|
|
||||||
}
|
|
||||||
$requiredSubjects = array_values(array_filter($requiredSubjects));
|
|
||||||
|
|
||||||
$submittedBySection = [];
|
$submittedBySection = [];
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$sectionId = (int) ($row['class_section_id'] ?? 0);
|
$sectionId = (int) ($row['class_section_id'] ?? 0);
|
||||||
$weekStart = (string) ($row['week_start'] ?? '');
|
$weekStart = (string) ($row['week_start'] ?? '');
|
||||||
$subject = (string) ($row['subject'] ?? '');
|
if ($sectionId === 0 || $weekStart === '') {
|
||||||
if ($sectionId === 0 || $weekStart === '' || $subject === '') {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (! empty($activeDatesSet) && empty($activeDatesSet[$weekStart])) {
|
$submittedBySection[$sectionId][$weekStart] = true;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (! in_array($subject, $requiredSubjects, true)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$submittedBySection[$sectionId][$weekStart][$subject] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$stats = [];
|
$stats = [];
|
||||||
foreach ($submittedBySection as $sectionId => $weeks) {
|
foreach ($submittedBySection as $sectionId => $weeks) {
|
||||||
$submitted = 0;
|
$submitted = count($weeks);
|
||||||
foreach ($weeks as $subjects) {
|
|
||||||
$all = true;
|
|
||||||
foreach ($requiredSubjects as $subject) {
|
|
||||||
if (empty($subjects[$subject])) {
|
|
||||||
$all = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($all) {
|
|
||||||
$submitted++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$stats[$sectionId] = $this->buildSectionStat($submitted, $expectedDays);
|
$stats[$sectionId] = $this->buildSectionStat($submitted, $expectedDays);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $stats;
|
return $stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function buildSectionSubjectCounts(array $rows): array
|
||||||
|
{
|
||||||
|
$allowedSubjects = [];
|
||||||
|
foreach (ClassProgressController::SUBJECT_SECTIONS as $section) {
|
||||||
|
$allowedSubjects[] = $section['db_subject'] ?? $section['label'] ?? '';
|
||||||
|
}
|
||||||
|
$allowedSubjects = array_values(array_filter($allowedSubjects));
|
||||||
|
|
||||||
|
$counts = [];
|
||||||
|
$latestWeekBySection = [];
|
||||||
|
$sectionClassMap = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$sectionId = (int) ($row['class_section_id'] ?? 0);
|
||||||
|
$subject = (string) ($row['subject'] ?? '');
|
||||||
|
$weekStart = (string) ($row['week_start'] ?? '');
|
||||||
|
if ($sectionId === 0 || $subject === '' || $weekStart === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! empty($allowedSubjects) && ! in_array($subject, $allowedSubjects, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! isset($sectionClassMap[$sectionId])) {
|
||||||
|
$sectionClassMap[$sectionId] = $this->classSectionModel->getClassId($sectionId);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
! isset($latestWeekBySection[$sectionId])
|
||||||
|
|| $weekStart > $latestWeekBySection[$sectionId]
|
||||||
|
) {
|
||||||
|
$latestWeekBySection[$sectionId] = $weekStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$curriculumChapters = $this->buildCurriculumChapterMap(array_values(array_filter($sectionClassMap)));
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$sectionId = (int) ($row['class_section_id'] ?? 0);
|
||||||
|
$subject = (string) ($row['subject'] ?? '');
|
||||||
|
$weekStart = (string) ($row['week_start'] ?? '');
|
||||||
|
if ($sectionId === 0 || $subject === '' || $weekStart === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! empty($allowedSubjects) && ! in_array($subject, $allowedSubjects, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (empty($latestWeekBySection[$sectionId]) || $weekStart !== $latestWeekBySection[$sectionId]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$subjectSlug = $this->resolveSubjectSlug($subject);
|
||||||
|
$classId = $sectionClassMap[$sectionId] ?? null;
|
||||||
|
$chapterSet = [];
|
||||||
|
if ($classId && $subjectSlug && ! empty($curriculumChapters[$classId][$subjectSlug])) {
|
||||||
|
$chapterSet = $curriculumChapters[$classId][$subjectSlug];
|
||||||
|
}
|
||||||
|
$counts[$sectionId] = ($counts[$sectionId] ?? 0) + $this->countChapterSegments(
|
||||||
|
(string) ($row['unit_title'] ?? ''),
|
||||||
|
$chapterSet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildCurriculumChapterMap(array $classIds): array
|
||||||
|
{
|
||||||
|
$classIds = array_values(array_filter(array_map('intval', $classIds)));
|
||||||
|
if (empty($classIds)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $this->curriculumModel
|
||||||
|
->whereIn('class_id', $classIds)
|
||||||
|
->findAll();
|
||||||
|
|
||||||
|
$map = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$classId = (int) ($row['class_id'] ?? 0);
|
||||||
|
$subject = (string) ($row['subject'] ?? '');
|
||||||
|
$chapter = trim((string) ($row['chapter_name'] ?? ''));
|
||||||
|
if ($classId === 0 || $subject === '' || $chapter === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$map[$classId][$subject][$chapter] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveSubjectSlug(string $subject): ?string
|
||||||
|
{
|
||||||
|
foreach (ClassProgressController::SUBJECT_SECTIONS as $slug => $section) {
|
||||||
|
$dbSubject = (string) ($section['db_subject'] ?? '');
|
||||||
|
$label = (string) ($section['label'] ?? '');
|
||||||
|
if ($subject === $dbSubject || $subject === $label) {
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function countChapterSegments(string $unitTitle, array $chapterSet): int
|
||||||
|
{
|
||||||
|
$unitTitle = trim($unitTitle);
|
||||||
|
if ($unitTitle === '') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$parts = array_filter(array_map('trim', explode(';', $unitTitle)), static fn ($part) => $part !== '');
|
||||||
|
if (! $parts) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$seen = [];
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$chapter = $this->extractChapterFromSegment($part);
|
||||||
|
$key = $chapter !== '' ? $chapter : $part;
|
||||||
|
if (! empty($chapterSet) && $chapter !== '' && empty($chapterSet[$chapter])) {
|
||||||
|
$key = $part;
|
||||||
|
}
|
||||||
|
if (isset($seen[$key])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[$key] = true;
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count > 0 ? $count : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractChapterFromSegment(string $segment): string
|
||||||
|
{
|
||||||
|
$segment = trim($segment);
|
||||||
|
if ($segment === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$pos = strrpos($segment, '/');
|
||||||
|
if ($pos === false) {
|
||||||
|
return $segment;
|
||||||
|
}
|
||||||
|
return trim(substr($segment, $pos + 1));
|
||||||
|
}
|
||||||
|
|
||||||
protected function buildSectionStat(int $submitted, int $expectedDays): array
|
protected function buildSectionStat(int $submitted, int $expectedDays): array
|
||||||
{
|
{
|
||||||
$percent = $expectedDays > 0 ? round(($submitted * 100) / $expectedDays, 1) : 0.0;
|
$percent = $expectedDays > 0 ? round(($submitted * 100) / $expectedDays, 1) : 0.0;
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Models\ConfigurationModel;
|
||||||
|
use App\Models\ReportCardAcknowledgementModel;
|
||||||
|
use App\Models\StudentModel;
|
||||||
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
|
use CodeIgniter\Database\BaseConnection;
|
||||||
|
|
||||||
|
class ParentReportCardController extends BaseController
|
||||||
|
{
|
||||||
|
protected ConfigurationModel $configModel;
|
||||||
|
protected ReportCardAcknowledgementModel $ackModel;
|
||||||
|
protected StudentModel $studentModel;
|
||||||
|
protected BaseConnection $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
helper(['url', 'form']);
|
||||||
|
$this->db = \Config\Database::connect();
|
||||||
|
$this->configModel = new ConfigurationModel();
|
||||||
|
$this->ackModel = new ReportCardAcknowledgementModel();
|
||||||
|
$this->studentModel = new StudentModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$parentId = $this->resolvePrimaryParentId();
|
||||||
|
if (! $parentId) {
|
||||||
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$schoolYear = trim((string) ($this->request->getGet('school_year') ?? $this->configModel->getConfig('school_year') ?? ''));
|
||||||
|
$semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? ''));
|
||||||
|
|
||||||
|
$builder = $this->db->table('students s')
|
||||||
|
->select('s.id, s.firstname, s.lastname, cs.class_section_name')
|
||||||
|
->join('student_class sc', 'sc.student_id = s.id', 'left')
|
||||||
|
->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left')
|
||||||
|
->where('s.parent_id', $parentId)
|
||||||
|
->orderBy('s.firstname', 'ASC')
|
||||||
|
->orderBy('s.lastname', 'ASC');
|
||||||
|
|
||||||
|
if ($schoolYear !== '') {
|
||||||
|
$builder->where('sc.school_year', $schoolYear);
|
||||||
|
}
|
||||||
|
if ($semester !== '') {
|
||||||
|
$builder->where('sc.semester', $semester);
|
||||||
|
}
|
||||||
|
|
||||||
|
$students = $builder->get()->getResultArray();
|
||||||
|
$studentIds = array_values(array_filter(array_map(static fn ($s) => (int) ($s['id'] ?? 0), $students)));
|
||||||
|
|
||||||
|
$ackMap = [];
|
||||||
|
if (! empty($studentIds)) {
|
||||||
|
$rows = $this->ackModel
|
||||||
|
->where('parent_id', $parentId)
|
||||||
|
->where('school_year', $schoolYear)
|
||||||
|
->where('semester', $semester)
|
||||||
|
->whereIn('student_id', $studentIds)
|
||||||
|
->findAll();
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$ackMap[(int) $row['student_id']] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('parent/report_cards', [
|
||||||
|
'students' => $students,
|
||||||
|
'ackMap' => $ackMap,
|
||||||
|
'schoolYear' => $schoolYear,
|
||||||
|
'semester' => $semester,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view($studentId)
|
||||||
|
{
|
||||||
|
$parentId = $this->resolvePrimaryParentId();
|
||||||
|
if (! $parentId) {
|
||||||
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$student = $this->studentModel->find((int) $studentId);
|
||||||
|
if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) {
|
||||||
|
throw new PageNotFoundException('Student not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$schoolYear = trim((string) ($this->request->getGet('school_year') ?? $this->configModel->getConfig('school_year') ?? ''));
|
||||||
|
$semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? ''));
|
||||||
|
|
||||||
|
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
||||||
|
'viewed_at' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$url = site_url('report-card/student/' . (int) $studentId);
|
||||||
|
$query = [];
|
||||||
|
if ($schoolYear !== '') {
|
||||||
|
$query['school_year'] = $schoolYear;
|
||||||
|
}
|
||||||
|
if ($semester !== '') {
|
||||||
|
$query['semester'] = $semester;
|
||||||
|
}
|
||||||
|
if ($query) {
|
||||||
|
$url .= '?' . http_build_query($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->to($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sign($studentId)
|
||||||
|
{
|
||||||
|
$parentId = $this->resolvePrimaryParentId();
|
||||||
|
if (! $parentId) {
|
||||||
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$student = $this->studentModel->find((int) $studentId);
|
||||||
|
if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) {
|
||||||
|
throw new PageNotFoundException('Student not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = trim((string) $this->request->getPost('signed_name'));
|
||||||
|
if ($name === '') {
|
||||||
|
return redirect()->back()->with('error', 'Please type your full name to sign.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$schoolYear = trim((string) ($this->configModel->getConfig('school_year') ?? ''));
|
||||||
|
$semester = trim((string) ($this->configModel->getConfig('semester') ?? ''));
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
||||||
|
'viewed_at' => $now,
|
||||||
|
'signed_at' => $now,
|
||||||
|
'signed_name' => $name,
|
||||||
|
'signer_ip' => $this->request->getIPAddress(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->to(site_url('parent/report-cards'))->with('success', 'Report card acknowledged.');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolvePrimaryParentId(): ?int
|
||||||
|
{
|
||||||
|
$parentId = (int) (session()->get('user_id') ?? 0);
|
||||||
|
$userType = (string) ($_SESSION['user_type'] ?? '');
|
||||||
|
if ($userType === 'primary') {
|
||||||
|
return $parentId ?: null;
|
||||||
|
}
|
||||||
|
if ($userType === 'secondary') {
|
||||||
|
$row = $this->db->table('parents')
|
||||||
|
->select('parent_id')
|
||||||
|
->where('secondparent_user_id', $parentId)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
return $row ? (int) ($row['parent_id'] ?? 0) : null;
|
||||||
|
}
|
||||||
|
if ($userType === 'tertiary') {
|
||||||
|
$row = $this->db->table('authorized_users')
|
||||||
|
->select('user_id as parent_id')
|
||||||
|
->where('authorized_user_id', $parentId)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
return $row ? (int) ($row['parent_id'] ?? 0) : null;
|
||||||
|
}
|
||||||
|
return $parentId ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function touchAcknowledgement(
|
||||||
|
int $parentId,
|
||||||
|
int $studentId,
|
||||||
|
string $schoolYear,
|
||||||
|
string $semester,
|
||||||
|
array $values
|
||||||
|
): void {
|
||||||
|
$criteria = [
|
||||||
|
'parent_id' => $parentId,
|
||||||
|
'student_id' => $studentId,
|
||||||
|
'school_year' => $schoolYear,
|
||||||
|
'semester' => $semester,
|
||||||
|
];
|
||||||
|
$existing = $this->ackModel->where($criteria)->first();
|
||||||
|
if ($existing) {
|
||||||
|
$this->ackModel->update((int) $existing['id'], $values);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->ackModel->insert(array_merge($criteria, $values));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,18 +4,21 @@ namespace App\Controllers\View;
|
|||||||
|
|
||||||
use App\Models\CalendarModel;
|
use App\Models\CalendarModel;
|
||||||
use App\Models\AttendanceDataModel;
|
use App\Models\AttendanceDataModel;
|
||||||
|
use App\Models\ReportCardAcknowledgementModel;
|
||||||
use App\Services\SemesterRangeService;
|
use App\Services\SemesterRangeService;
|
||||||
|
|
||||||
class ReportCardsController extends PrintablesBaseController
|
class ReportCardsController extends PrintablesBaseController
|
||||||
{
|
{
|
||||||
protected $calendarModel;
|
protected $calendarModel;
|
||||||
protected $semesterRangeService;
|
protected $semesterRangeService;
|
||||||
|
protected $ackModel;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->calendarModel = new CalendarModel();
|
$this->calendarModel = new CalendarModel();
|
||||||
$this->semesterRangeService = new SemesterRangeService($this->configModel);
|
$this->semesterRangeService = new SemesterRangeService($this->configModel);
|
||||||
|
$this->ackModel = new ReportCardAcknowledgementModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function report_card()
|
public function report_card()
|
||||||
@@ -369,6 +372,24 @@ class ReportCardsController extends PrintablesBaseController
|
|||||||
|
|
||||||
$isNumeric = static fn($v) => $v !== null && $v !== '' && is_numeric($v);
|
$isNumeric = static fn($v) => $v !== null && $v !== '' && is_numeric($v);
|
||||||
|
|
||||||
|
$ackMap = [];
|
||||||
|
try {
|
||||||
|
$ackRows = $this->ackModel
|
||||||
|
->whereIn('student_id', $studentIds)
|
||||||
|
->where('school_year', $year)
|
||||||
|
->where('semester', $sem)
|
||||||
|
->orderBy('updated_at', 'DESC')
|
||||||
|
->findAll();
|
||||||
|
foreach ($ackRows as $row) {
|
||||||
|
$sid = (int) ($row['student_id'] ?? 0);
|
||||||
|
if ($sid > 0 && ! isset($ackMap[$sid])) {
|
||||||
|
$ackMap[$sid] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$ackMap = [];
|
||||||
|
}
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
$completeCount = 0;
|
$completeCount = 0;
|
||||||
$warningCount = 0;
|
$warningCount = 0;
|
||||||
@@ -471,12 +492,16 @@ class ReportCardsController extends PrintablesBaseController
|
|||||||
$warningCount++;
|
$warningCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ack = $sid > 0 ? ($ackMap[$sid] ?? null) : null;
|
||||||
$results[] = [
|
$results[] = [
|
||||||
'id' => $sid,
|
'id' => $sid,
|
||||||
'name' => $name !== '' ? $name : 'N/A',
|
'name' => $name !== '' ? $name : 'N/A',
|
||||||
'missing' => $missing,
|
'missing' => $missing,
|
||||||
'warnings' => $warnings,
|
'warnings' => $warnings,
|
||||||
'complete' => $isComplete,
|
'complete' => $isComplete,
|
||||||
|
'viewed_at' => $ack['viewed_at'] ?? null,
|
||||||
|
'signed_at' => $ack['signed_at'] ?? null,
|
||||||
|
'signed_name' => $ack['signed_name'] ?? null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,6 +526,37 @@ class ReportCardsController extends PrintablesBaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API: report card parent acknowledgement status
|
||||||
|
* GET params: student_id, school_year, semester
|
||||||
|
*/
|
||||||
|
public function reportCardAcknowledgement()
|
||||||
|
{
|
||||||
|
$studentId = (int) ($this->request->getGet('student_id') ?? 0);
|
||||||
|
if ($studentId <= 0) {
|
||||||
|
return $this->response->setJSON(['ok' => false, 'error' => 'Missing student_id'])->setStatusCode(400);
|
||||||
|
}
|
||||||
|
$schoolYear = trim((string) ($this->request->getGet('school_year') ?? $this->schoolYear ?? ''));
|
||||||
|
$semester = trim((string) ($this->request->getGet('semester') ?? $this->semester ?? ''));
|
||||||
|
|
||||||
|
$row = $this->ackModel
|
||||||
|
->where('student_id', $studentId)
|
||||||
|
->where('school_year', $schoolYear)
|
||||||
|
->where('semester', $semester)
|
||||||
|
->orderBy('updated_at', 'DESC')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'ok' => true,
|
||||||
|
'student_id' => $studentId,
|
||||||
|
'school_year' => $schoolYear,
|
||||||
|
'semester' => $semester,
|
||||||
|
'viewed_at' => $row['viewed_at'] ?? null,
|
||||||
|
'signed_at' => $row['signed_at'] ?? null,
|
||||||
|
'signed_name' => $row['signed_name'] ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
protected function fetchStudentsByClass(int $sectionId, ?string $schoolYear)
|
protected function fetchStudentsByClass(int $sectionId, ?string $schoolYear)
|
||||||
{
|
{
|
||||||
$b = $this->studentClassModel
|
$b = $this->studentClassModel
|
||||||
|
|||||||
@@ -1378,6 +1378,7 @@ $students = $this->db->table('students')
|
|||||||
$releaseScores = (strcasecmp($semester, 'Fall') === 0) ? $releaseFall
|
$releaseScores = (strcasecmp($semester, 'Fall') === 0) ? $releaseFall
|
||||||
: ((strcasecmp($semester, 'Spring') === 0) ? $releaseSpring : $releaseAny);
|
: ((strcasecmp($semester, 'Spring') === 0) ? $releaseSpring : $releaseAny);
|
||||||
$scores[$selectedYear][$semester][$studentId] = [
|
$scores[$selectedYear][$semester][$studentId] = [
|
||||||
|
'student_id' => $studentId,
|
||||||
'school_id' => $student['school_id'],
|
'school_id' => $student['school_id'],
|
||||||
'student_firstname' => $student['firstname'],
|
'student_firstname' => $student['firstname'],
|
||||||
'student_lastname' => $student['lastname'],
|
'student_lastname' => $student['lastname'],
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class AllowCompetitionWinnerRankTies extends Migration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($db->indexExists('competition_winners', 'competition_id_class_section_id_rank')) {
|
if ($this->indexExists($db, 'competition_winners', 'competition_id_class_section_id_rank')) {
|
||||||
$db->query('DROP INDEX competition_id_class_section_id_rank ON competition_winners');
|
$db->query('DROP INDEX competition_id_class_section_id_rank ON competition_winners');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$db->indexExists('competition_winners', 'competition_id_class_section_id_rank')) {
|
if (! $this->indexExists($db, 'competition_winners', 'competition_id_class_section_id_rank')) {
|
||||||
$db->query(
|
$db->query(
|
||||||
'CREATE INDEX competition_id_class_section_id_rank ON competition_winners (competition_id, class_section_id, rank)'
|
'CREATE INDEX competition_id_class_section_id_rank ON competition_winners (competition_id, class_section_id, rank)'
|
||||||
);
|
);
|
||||||
@@ -31,7 +31,7 @@ class AllowCompetitionWinnerRankTies extends Migration
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($db->indexExists('competition_winners', 'competition_id_class_section_id_rank')) {
|
if ($this->indexExists($db, 'competition_winners', 'competition_id_class_section_id_rank')) {
|
||||||
$db->query('DROP INDEX competition_id_class_section_id_rank ON competition_winners');
|
$db->query('DROP INDEX competition_id_class_section_id_rank ON competition_winners');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,4 +39,23 @@ class AllowCompetitionWinnerRankTies extends Migration
|
|||||||
'ALTER TABLE competition_winners ADD UNIQUE KEY competition_id_class_section_id_rank (competition_id, class_section_id, rank)'
|
'ALTER TABLE competition_winners ADD UNIQUE KEY competition_id_class_section_id_rank (competition_id, class_section_id, rank)'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function indexExists($db, string $table, string $index): bool
|
||||||
|
{
|
||||||
|
if (method_exists($db, 'indexExists')) {
|
||||||
|
return $db->indexExists($table, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbName = $db->getDatabase();
|
||||||
|
$builder = $db->table('information_schema.STATISTICS');
|
||||||
|
$row = $builder
|
||||||
|
->select('INDEX_NAME')
|
||||||
|
->where('TABLE_SCHEMA', $dbName)
|
||||||
|
->where('TABLE_NAME', $table)
|
||||||
|
->where('INDEX_NAME', $index)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
|
||||||
|
return ! empty($row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,37 @@
|
|||||||
Print all
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class AddPageSelectionToPrintRequests extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
if (! $this->db->tableExists('print_requests')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->db->fieldExists('page_selection', 'print_requests')) {
|
||||||
|
$this->forge->addColumn('print_requests', [
|
||||||
|
'page_selection' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 255,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'num_copies',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
if (! $this->db->tableExists('print_requests')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->db->fieldExists('page_selection', 'print_requests')) {
|
||||||
|
$this->forge->dropColumn('print_requests', 'page_selection');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class CreateReportCardAcknowledgements extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
if ($this->db->tableExists('report_card_acknowledgements')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->forge->addField([
|
||||||
|
'id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'constraint' => 11,
|
||||||
|
'unsigned' => true,
|
||||||
|
'auto_increment' => true,
|
||||||
|
],
|
||||||
|
'parent_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'constraint' => 11,
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'student_id' => [
|
||||||
|
'type' => 'INT',
|
||||||
|
'constraint' => 11,
|
||||||
|
'unsigned' => true,
|
||||||
|
],
|
||||||
|
'school_year' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 20,
|
||||||
|
],
|
||||||
|
'semester' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 20,
|
||||||
|
],
|
||||||
|
'viewed_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'signed_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'signed_name' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 120,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'signer_ip' => [
|
||||||
|
'type' => 'VARCHAR',
|
||||||
|
'constraint' => 45,
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'type' => 'DATETIME',
|
||||||
|
'null' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->forge->addKey('id', true);
|
||||||
|
$this->forge->addKey(['parent_id', 'student_id', 'school_year', 'semester'], false, true);
|
||||||
|
$this->forge->createTable('report_card_acknowledgements');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
$this->forge->dropTable('report_card_acknowledgements', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
|
||||||
|
class ReportCardAcknowledgementModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'report_card_acknowledgements';
|
||||||
|
protected $primaryKey = 'id';
|
||||||
|
protected $allowedFields = [
|
||||||
|
'parent_id',
|
||||||
|
'student_id',
|
||||||
|
'school_year',
|
||||||
|
'semester',
|
||||||
|
'viewed_at',
|
||||||
|
'signed_at',
|
||||||
|
'signed_name',
|
||||||
|
'signer_ip',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
];
|
||||||
|
protected $useTimestamps = true;
|
||||||
|
protected $createdField = 'created_at';
|
||||||
|
protected $updatedField = 'updated_at';
|
||||||
|
protected $returnType = 'array';
|
||||||
|
}
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
$subjectSections = $subjectSections ?? [];
|
$subjectSections = $subjectSections ?? [];
|
||||||
$classSections = $classSections ?? [];
|
$classSections = $classSections ?? [];
|
||||||
$sectionStats = $sectionStats ?? [];
|
$sectionStats = $sectionStats ?? [];
|
||||||
|
$sectionSubjectCounts = $sectionSubjectCounts ?? [];
|
||||||
$expectedDays = (int) ($expectedDays ?? 0);
|
$expectedDays = (int) ($expectedDays ?? 0);
|
||||||
?>
|
?>
|
||||||
<?php if (empty($classSections)): ?>
|
<?php if (empty($classSections)): ?>
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
$collapseId = 'progress-section-' . $sectionId;
|
$collapseId = 'progress-section-' . $sectionId;
|
||||||
$headingId = 'progress-heading-' . $sectionId;
|
$headingId = 'progress-heading-' . $sectionId;
|
||||||
$stat = $sectionStats[$sectionId] ?? null;
|
$stat = $sectionStats[$sectionId] ?? null;
|
||||||
|
$subjectCount = (int) ($sectionSubjectCounts[$sectionId] ?? 0);
|
||||||
if (! $stat) {
|
if (! $stat) {
|
||||||
if ($expectedDays === 0) {
|
if ($expectedDays === 0) {
|
||||||
$stat = [
|
$stat = [
|
||||||
@@ -113,12 +115,14 @@
|
|||||||
$submissionLabel = $expectedDays > 0
|
$submissionLabel = $expectedDays > 0
|
||||||
? ('Submitted: ' . (int) $stat['submitted'] . ' / ' . (int) $expectedDays . ' (' . $percentLabel . ')')
|
? ('Submitted: ' . (int) $stat['submitted'] . ' / ' . (int) $expectedDays . ' (' . $percentLabel . ')')
|
||||||
: 'Submitted: N/A';
|
: 'Submitted: N/A';
|
||||||
|
$subjectLabel = 'Subjects: ' . $subjectCount;
|
||||||
?>
|
?>
|
||||||
<div class="accordion-item mb-2">
|
<div class="accordion-item mb-2">
|
||||||
<h2 class="accordion-header" id="<?= esc($headingId) ?>">
|
<h2 class="accordion-header" id="<?= esc($headingId) ?>">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#<?= esc($collapseId) ?>" aria-expanded="false" aria-controls="<?= esc($collapseId) ?>">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#<?= esc($collapseId) ?>" aria-expanded="false" aria-controls="<?= esc($collapseId) ?>">
|
||||||
<?= esc($sectionName) ?>
|
<?= esc($sectionName) ?>
|
||||||
<span class="badge <?= esc($stat['badgeClass']) ?> ms-2"><?= esc($submissionLabel) ?></span>
|
<span class="badge <?= esc($stat['badgeClass']) ?> ms-2"><?= esc($submissionLabel) ?></span>
|
||||||
|
<span class="badge bg-info text-dark ms-2"><?= esc($subjectLabel) ?></span>
|
||||||
<?php if ($hasReports): ?>
|
<?php if ($hasReports): ?>
|
||||||
<span class="badge bg-secondary ms-2"><?= count($sectionGroups) ?> weeks</span>
|
<span class="badge bg-secondary ms-2"><?= count($sectionGroups) ?> weeks</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -121,6 +121,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Report Cards -->
|
||||||
|
<div class="col-auto">
|
||||||
|
<a href="<?= base_url('parent/report-cards') ?>" class="d-inline-block text-decoration-none">
|
||||||
|
<div class="circle-box bg-dark text-white position-relative" role="button" aria-label="Open report cards">
|
||||||
|
<h4 class="fw-bold mb-0">Report Cards</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Enrollment -->
|
<!-- Enrollment -->
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="circle-box bg-info text-white">
|
<div class="circle-box bg-info text-white">
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?= $this->extend('layout/main_layout') ?>
|
||||||
|
<?= $this->section('content') ?>
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-success mb-1" style="font-family: Arial, sans-serif;">Report Cards</h3>
|
||||||
|
<div class="text-muted small">
|
||||||
|
<?= esc($schoolYear ?: 'N/A') ?> <?= $semester ? '• ' . esc($semester) . ' Semester' : '' ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (session()->getFlashdata('error')): ?>
|
||||||
|
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (session()->getFlashdata('success')): ?>
|
||||||
|
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($students)): ?>
|
||||||
|
<div class="alert alert-info">No students available for report cards.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Student</th>
|
||||||
|
<th>Class Section</th>
|
||||||
|
<th>Viewed</th>
|
||||||
|
<th>Signature</th>
|
||||||
|
<th class="text-end">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($students as $student): ?>
|
||||||
|
<?php
|
||||||
|
$sid = (int) ($student['id'] ?? 0);
|
||||||
|
$ack = $ackMap[$sid] ?? null;
|
||||||
|
$viewedAt = $ack['viewed_at'] ?? '';
|
||||||
|
$signedAt = $ack['signed_at'] ?? '';
|
||||||
|
$signedName = $ack['signed_name'] ?? '';
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?></td>
|
||||||
|
<td><?= esc($student['class_section_name'] ?? 'N/A') ?></td>
|
||||||
|
<td><?= $viewedAt ? esc(local_datetime($viewedAt, 'm-d-Y H:i')) : 'Not viewed' ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($signedAt): ?>
|
||||||
|
<?= esc($signedName ?: 'Signed') ?><br>
|
||||||
|
<small class="text-muted"><?= esc(local_datetime($signedAt, 'm-d-Y H:i')) ?></small>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">Not signed</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<a class="btn btn-sm btn-outline-primary" target="_blank" href="<?= base_url('parent/report-cards/view/' . $sid) ?>">View Report</a>
|
||||||
|
<?php if (! $signedAt): ?>
|
||||||
|
<form class="d-inline-flex align-items-center gap-2 ms-2" method="post" action="<?= base_url('parent/report-cards/sign/' . $sid) ?>">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
<input type="text" name="signed_name" class="form-control form-control-sm" placeholder="Full name" required>
|
||||||
|
<button class="btn btn-sm btn-success" type="submit">Sign</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<?= $this->extend('layout/main_layout') ?>
|
<?= $this->extend('layout/main_layout') ?>
|
||||||
<?= $this->section('content') ?>
|
<?= $this->section('content') ?>
|
||||||
<div class="container my-5">
|
<div class="container my-5">
|
||||||
<h3 class="text-center text-success" style="font-family: Arial, sans-serif;">Scores</h2>
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||||
|
<h3 class="text-success mb-0" style="font-family: Arial, sans-serif;">Scores</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter Form -->
|
<!-- Filter Form -->
|
||||||
@@ -73,6 +75,7 @@ $showExamScoresBySemester = $showExamScoresBySemester ?? [];
|
|||||||
<?php if ($showExamScoresForSemester): ?><th>Exam</th><?php endif; ?>
|
<?php if ($showExamScoresForSemester): ?><th>Exam</th><?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if ($showExamScoresForSemester): ?><th>Semester Score</th><?php endif; ?>
|
<?php if ($showExamScoresForSemester): ?><th>Semester Score</th><?php endif; ?>
|
||||||
|
<?php if ($showExamScoresForSemester): ?><th>Report Card</th><?php endif; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -96,6 +99,32 @@ $showExamScoresBySemester = $showExamScoresBySemester ?? [];
|
|||||||
<?php if ($showExamScoresForSemester): ?><td><?= esc($student['scores']['midterm_exam']['score'] ?? $student['scores']['final_exam']['score'] ?? '-') ?></td><?php endif; ?>
|
<?php if ($showExamScoresForSemester): ?><td><?= esc($student['scores']['midterm_exam']['score'] ?? $student['scores']['final_exam']['score'] ?? '-') ?></td><?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if ($showExamScoresForSemester): ?><td><?= esc($student['scores']['semester']['score'] ?? '-') ?></td><?php endif; ?>
|
<?php if ($showExamScoresForSemester): ?><td><?= esc($student['scores']['semester']['score'] ?? '-') ?></td><?php endif; ?>
|
||||||
|
<?php if ($showExamScoresForSemester): ?>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$reportStudentId = (int) ($student['student_id'] ?? 0);
|
||||||
|
if ($reportStudentId > 0) {
|
||||||
|
$reportYear = (string) ($selectedYear ?? '');
|
||||||
|
$reportSemester = (string) ($semester ?? '');
|
||||||
|
$reportDate = date('Y-m-d');
|
||||||
|
$reportUrl = base_url('parent/report-cards/view/' . $reportStudentId);
|
||||||
|
$query = http_build_query([
|
||||||
|
'school_year' => $reportYear,
|
||||||
|
'semester' => $reportSemester,
|
||||||
|
'report_date' => $reportDate,
|
||||||
|
]);
|
||||||
|
if ($query) {
|
||||||
|
$reportUrl .= '?' . $query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php if ($reportStudentId > 0): ?>
|
||||||
|
<a class="btn btn-sm btn-outline-primary" href="<?= esc($reportUrl) ?>" target="_blank" rel="noopener">View</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">Unavailable</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<?php endif; ?>
|
||||||
</tr>
|
</tr>
|
||||||
<?php if (!empty($student['comment'])): ?>
|
<?php if (!empty($student['comment'])): ?>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -334,6 +334,12 @@ switch ($role) {
|
|||||||
'label' => 'Scores',
|
'label' => 'Scores',
|
||||||
'title' => 'Review progressive academic score for the year (homeworks, projects and exams).',
|
'title' => 'Review progressive academic score for the year (homeworks, projects and exams).',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'href' => base_url('/parent/report-cards'),
|
||||||
|
'icon' => 'bi-file-earmark-text',
|
||||||
|
'label' => 'Report Cards',
|
||||||
|
'title' => 'View and acknowledge student report cards.',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'href' => base_url('/parent/progress'),
|
'href' => base_url('/parent/progress'),
|
||||||
'icon' => 'bi-journal-check',
|
'icon' => 'bi-journal-check',
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
<a id="downloadStudentBtn" href="#" class="btn btn-success external-link" target="_blank">Download</a>
|
<a id="downloadStudentBtn" href="#" class="btn btn-success external-link" target="_blank">Download</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-muted small text-center" id="ackStatus"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="card shadow-sm mb-3">
|
<div class="card shadow-sm mb-3">
|
||||||
@@ -67,10 +68,12 @@
|
|||||||
<table class="table table-sm table-bordered align-middle mb-0">
|
<table class="table table-sm table-bordered align-middle mb-0">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 35%;">Student</th>
|
<th style="width: 28%;">Student</th>
|
||||||
<th style="width: 15%;">Status</th>
|
<th style="width: 15%;">Status</th>
|
||||||
<th>Missing</th>
|
<th>Missing</th>
|
||||||
<th>Warnings</th>
|
<th>Warnings</th>
|
||||||
|
<th style="width: 16%;">Viewed</th>
|
||||||
|
<th style="width: 16%;">Signed</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="completenessBody"></tbody>
|
<tbody id="completenessBody"></tbody>
|
||||||
@@ -91,6 +94,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function(){
|
document.addEventListener('DOMContentLoaded', function(){
|
||||||
const API = <?= json_encode(site_url('api/printables/report-card/meta')) ?>;
|
const API = <?= json_encode(site_url('api/printables/report-card/meta')) ?>;
|
||||||
const COMPLETENESS_API = <?= json_encode(site_url('api/printables/report-card/completeness')) ?>;
|
const COMPLETENESS_API = <?= json_encode(site_url('api/printables/report-card/completeness')) ?>;
|
||||||
|
const ACK_API = <?= json_encode(site_url('api/printables/report-card/ack')) ?>;
|
||||||
|
|
||||||
// add cache-busting cb param
|
// add cache-busting cb param
|
||||||
const withCB = (u) => u + (u.includes('?') ? '&' : '?') + 'cb=' + Date.now();
|
const withCB = (u) => u + (u.includes('?') ? '&' : '?') + 'cb=' + Date.now();
|
||||||
@@ -119,6 +123,7 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||||||
const $completenessSummary = document.getElementById('completenessSummary');
|
const $completenessSummary = document.getElementById('completenessSummary');
|
||||||
const $completenessWrap = document.getElementById('completenessTableWrap');
|
const $completenessWrap = document.getElementById('completenessTableWrap');
|
||||||
const $completenessBody = document.getElementById('completenessBody');
|
const $completenessBody = document.getElementById('completenessBody');
|
||||||
|
const $ackStatus = document.getElementById('ackStatus');
|
||||||
|
|
||||||
function opt(el, val, label) {
|
function opt(el, val, label) {
|
||||||
const o = document.createElement('option'); o.value = String(val); o.textContent = label; el.appendChild(o);
|
const o = document.createElement('option'); o.value = String(val); o.textContent = label; el.appendChild(o);
|
||||||
@@ -157,10 +162,40 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||||||
// Classes
|
// Classes
|
||||||
$class.innerHTML = '';
|
$class.innerHTML = '';
|
||||||
(data.classSections || []).forEach(c => opt($class, c.class_section_id || c.id, c.class_section_name));
|
(data.classSections || []).forEach(c => opt($class, c.class_section_id || c.id, c.class_section_name));
|
||||||
|
|
||||||
|
updateAckStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAckStatus() {
|
||||||
|
if (! $ackStatus) return;
|
||||||
|
const sid = $student.value;
|
||||||
|
const y = $year.value || '';
|
||||||
|
const s = $sem.value || '';
|
||||||
|
if (!sid) {
|
||||||
|
$ackStatus.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = `${ACK_API}?student_id=${encodeURIComponent(sid)}&school_year=${encodeURIComponent(y)}&semester=${encodeURIComponent(s)}`;
|
||||||
|
const res = await fetch(url, { credentials: 'same-origin' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok || data?.ok === false) {
|
||||||
|
$ackStatus.textContent = 'Parent acknowledgement: unavailable';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const viewed = data.viewed_at ? `Viewed: ${data.viewed_at}` : 'Viewed: not yet';
|
||||||
|
const signed = data.signed_at
|
||||||
|
? `Signed: ${data.signed_at} (${data.signed_name || 'name missing'})`
|
||||||
|
: 'Signed: not yet';
|
||||||
|
$ackStatus.textContent = `Parent acknowledgement — ${viewed} · ${signed}`;
|
||||||
|
} catch (err) {
|
||||||
|
$ackStatus.textContent = 'Parent acknowledgement: unavailable';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$year.addEventListener('change', function(){ loadMeta(this.value, $sem.value).catch(console.error); });
|
$year.addEventListener('change', function(){ loadMeta(this.value, $sem.value).catch(console.error); });
|
||||||
$sem.addEventListener('change', function(){ loadMeta($year.value, this.value).catch(console.error); });
|
$sem.addEventListener('change', function(){ loadMeta($year.value, this.value).catch(console.error); });
|
||||||
|
$student.addEventListener('change', function(){ updateAckStatus(); });
|
||||||
|
|
||||||
$viewS.addEventListener('click', function(){
|
$viewS.addEventListener('click', function(){
|
||||||
const sid = $student.value; const y = $year.value; const s=$sem.value;
|
const sid = $student.value; const y = $year.value; const s=$sem.value;
|
||||||
@@ -254,10 +289,18 @@ document.addEventListener('DOMContentLoaded', function(){
|
|||||||
tdMissing.textContent = missing.length ? missing.join(', ') : 'None';
|
tdMissing.textContent = missing.length ? missing.join(', ') : 'None';
|
||||||
const tdWarnings = document.createElement('td');
|
const tdWarnings = document.createElement('td');
|
||||||
tdWarnings.textContent = warningsList.length ? warningsList.join(', ') : 'None';
|
tdWarnings.textContent = warningsList.length ? warningsList.join(', ') : 'None';
|
||||||
|
const tdViewed = document.createElement('td');
|
||||||
|
tdViewed.textContent = row.viewed_at ? row.viewed_at : 'Not viewed';
|
||||||
|
const tdSigned = document.createElement('td');
|
||||||
|
tdSigned.textContent = row.signed_at
|
||||||
|
? `${row.signed_at}${row.signed_name ? ` (${row.signed_name})` : ''}`
|
||||||
|
: 'Not signed';
|
||||||
tr.appendChild(tdName);
|
tr.appendChild(tdName);
|
||||||
tr.appendChild(tdStatus);
|
tr.appendChild(tdStatus);
|
||||||
tr.appendChild(tdMissing);
|
tr.appendChild(tdMissing);
|
||||||
tr.appendChild(tdWarnings);
|
tr.appendChild(tdWarnings);
|
||||||
|
tr.appendChild(tdViewed);
|
||||||
|
tr.appendChild(tdSigned);
|
||||||
$completenessBody.appendChild(tr);
|
$completenessBody.appendChild(tr);
|
||||||
});
|
});
|
||||||
$completenessWrap.hidden = false;
|
$completenessWrap.hidden = false;
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,66 +0,0 @@
|
|||||||
ERROR - 2026-02-10 07:23:30 --> mysqli_sql_exception: Column 'school_year' in where clause is ambiguous in /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Database/MySQLi/Connection.php:327
|
|
||||||
Stack trace:
|
|
||||||
#0 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Database/MySQLi/Connection.php(327): mysqli->query()
|
|
||||||
#1 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Database/BaseConnection.php(729): CodeIgniter\Database\MySQLi\Connection->execute()
|
|
||||||
#2 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Database/BaseConnection.php(646): CodeIgniter\Database\BaseConnection->simpleQuery()
|
|
||||||
#3 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Database/BaseBuilder.php(1649): CodeIgniter\Database\BaseConnection->query()
|
|
||||||
#4 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Model.php(286): CodeIgniter\Database\BaseBuilder->get()
|
|
||||||
#5 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/BaseModel.php(677): CodeIgniter\Model->doFindAll()
|
|
||||||
#6 /opt/lampp/htdocs/alrahma_school_sunday/app/Models/StudentClassModel.php(55): CodeIgniter\BaseModel->findAll()
|
|
||||||
#7 /opt/lampp/htdocs/alrahma_school_sunday/app/Controllers/View/AttendanceTrackingController.php(70): App\Models\StudentClassModel->findAll()
|
|
||||||
#8 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/CodeIgniter.php(933): App\Controllers\View\AttendanceTrackingController->pendingViolationsView()
|
|
||||||
#9 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/CodeIgniter.php(507): CodeIgniter\CodeIgniter->runController()
|
|
||||||
#10 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/CodeIgniter.php(354): CodeIgniter\CodeIgniter->handleRequest()
|
|
||||||
#11 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Boot.php(363): CodeIgniter\CodeIgniter->run()
|
|
||||||
#12 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/Boot.php(68): CodeIgniter\Boot::runCodeIgniter()
|
|
||||||
#13 /opt/lampp/htdocs/alrahma_school_sunday/public/index.php(60): CodeIgniter\Boot::bootWeb()
|
|
||||||
#14 /opt/lampp/htdocs/alrahma_school_sunday/vendor/codeigniter4/framework/system/rewrite.php(44): require_once('...')
|
|
||||||
#15 {main}
|
|
||||||
CRITICAL - 2026-02-10 07:23:30 --> CodeIgniter\Database\Exceptions\DatabaseException: Column 'school_year' in where clause is ambiguous
|
|
||||||
[Method: GET, Route: attendance/violations]
|
|
||||||
in SYSTEMPATH/Database/BaseConnection.php on line 684.
|
|
||||||
1 SYSTEMPATH/Database/BaseBuilder.php(1649): CodeIgniter\Database\BaseConnection->query()
|
|
||||||
2 SYSTEMPATH/Model.php(286): CodeIgniter\Database\BaseBuilder->get()
|
|
||||||
3 SYSTEMPATH/BaseModel.php(677): CodeIgniter\Model->doFindAll()
|
|
||||||
4 APPPATH/Models/StudentClassModel.php(55): CodeIgniter\BaseModel->findAll()
|
|
||||||
5 APPPATH/Controllers/View/AttendanceTrackingController.php(70): App\Models\StudentClassModel->findAll()
|
|
||||||
6 SYSTEMPATH/CodeIgniter.php(933): App\Controllers\View\AttendanceTrackingController->pendingViolationsView()
|
|
||||||
7 SYSTEMPATH/CodeIgniter.php(507): CodeIgniter\CodeIgniter->runController()
|
|
||||||
8 SYSTEMPATH/CodeIgniter.php(354): CodeIgniter\CodeIgniter->handleRequest()
|
|
||||||
9 SYSTEMPATH/Boot.php(363): CodeIgniter\CodeIgniter->run()
|
|
||||||
10 SYSTEMPATH/Boot.php(68): CodeIgniter\Boot::runCodeIgniter()
|
|
||||||
11 FCPATH/index.php(60): CodeIgniter\Boot::bootWeb()
|
|
||||||
12 SYSTEMPATH/rewrite.php(44): require_once('/opt/lampp/htdocs/alrahma_school_sunday/public/index.php')
|
|
||||||
CRITICAL - 2026-02-10 07:23:30 --> [Caused by] CodeIgniter\Database\Exceptions\DatabaseException: Column 'school_year' in where clause is ambiguous
|
|
||||||
in SYSTEMPATH/Database/MySQLi/Connection.php on line 332.
|
|
||||||
1 SYSTEMPATH/Database/BaseConnection.php(729): CodeIgniter\Database\MySQLi\Connection->execute()
|
|
||||||
2 SYSTEMPATH/Database/BaseConnection.php(646): CodeIgniter\Database\BaseConnection->simpleQuery()
|
|
||||||
3 SYSTEMPATH/Database/BaseBuilder.php(1649): CodeIgniter\Database\BaseConnection->query()
|
|
||||||
4 SYSTEMPATH/Model.php(286): CodeIgniter\Database\BaseBuilder->get()
|
|
||||||
5 SYSTEMPATH/BaseModel.php(677): CodeIgniter\Model->doFindAll()
|
|
||||||
6 APPPATH/Models/StudentClassModel.php(55): CodeIgniter\BaseModel->findAll()
|
|
||||||
7 APPPATH/Controllers/View/AttendanceTrackingController.php(70): App\Models\StudentClassModel->findAll()
|
|
||||||
8 SYSTEMPATH/CodeIgniter.php(933): App\Controllers\View\AttendanceTrackingController->pendingViolationsView()
|
|
||||||
9 SYSTEMPATH/CodeIgniter.php(507): CodeIgniter\CodeIgniter->runController()
|
|
||||||
10 SYSTEMPATH/CodeIgniter.php(354): CodeIgniter\CodeIgniter->handleRequest()
|
|
||||||
11 SYSTEMPATH/Boot.php(363): CodeIgniter\CodeIgniter->run()
|
|
||||||
12 SYSTEMPATH/Boot.php(68): CodeIgniter\Boot::runCodeIgniter()
|
|
||||||
13 FCPATH/index.php(60): CodeIgniter\Boot::bootWeb()
|
|
||||||
14 SYSTEMPATH/rewrite.php(44): require_once('/opt/lampp/htdocs/alrahma_school_sunday/public/index.php')
|
|
||||||
CRITICAL - 2026-02-10 07:23:30 --> [Caused by] mysqli_sql_exception: Column 'school_year' in where clause is ambiguous
|
|
||||||
in SYSTEMPATH/Database/MySQLi/Connection.php on line 327.
|
|
||||||
1 SYSTEMPATH/Database/MySQLi/Connection.php(327): mysqli->query()
|
|
||||||
2 SYSTEMPATH/Database/BaseConnection.php(729): CodeIgniter\Database\MySQLi\Connection->execute()
|
|
||||||
3 SYSTEMPATH/Database/BaseConnection.php(646): CodeIgniter\Database\BaseConnection->simpleQuery()
|
|
||||||
4 SYSTEMPATH/Database/BaseBuilder.php(1649): CodeIgniter\Database\BaseConnection->query()
|
|
||||||
5 SYSTEMPATH/Model.php(286): CodeIgniter\Database\BaseBuilder->get()
|
|
||||||
6 SYSTEMPATH/BaseModel.php(677): CodeIgniter\Model->doFindAll()
|
|
||||||
7 APPPATH/Models/StudentClassModel.php(55): CodeIgniter\BaseModel->findAll()
|
|
||||||
8 APPPATH/Controllers/View/AttendanceTrackingController.php(70): App\Models\StudentClassModel->findAll()
|
|
||||||
9 SYSTEMPATH/CodeIgniter.php(933): App\Controllers\View\AttendanceTrackingController->pendingViolationsView()
|
|
||||||
10 SYSTEMPATH/CodeIgniter.php(507): CodeIgniter\CodeIgniter->runController()
|
|
||||||
11 SYSTEMPATH/CodeIgniter.php(354): CodeIgniter\CodeIgniter->handleRequest()
|
|
||||||
12 SYSTEMPATH/Boot.php(363): CodeIgniter\CodeIgniter->run()
|
|
||||||
13 SYSTEMPATH/Boot.php(68): CodeIgniter\Boot::runCodeIgniter()
|
|
||||||
14 FCPATH/index.php(60): CodeIgniter\Boot::bootWeb()
|
|
||||||
15 SYSTEMPATH/rewrite.php(44): require_once('/opt/lampp/htdocs/alrahma_school_sunday/public/index.php')
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ERROR - 2026-02-25 21:13:03 --> No class section found for user ID: 2
|
|
||||||
ERROR - 2026-02-25 21:13:45 --> No class section found for user ID: 1
|
|
||||||
ERROR - 2026-02-25 21:24:56 --> No class section found for user ID: 2
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ERROR - 2026-02-26 19:10:40 --> No class section found for user ID: 1
|
|
||||||
@@ -1 +1 @@
|
|||||||
__ci_last_regenerate|i:1772167652;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772167655;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
__ci_last_regenerate|i:1772168049;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772167655;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772171635;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"3";user_email|s:19:"kelabidi0@gmail.com";user_name|s:15:"Khadija Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772170259;roles|a:2:{i:0;s:7:"teacher";i:1;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|i:10;class_section_ids|a:1:{i:0;i:10;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772173263;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"2";user_email|s:24:"moulay.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772172575;roles|a:1:{i:0;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|N;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772168666;_ci_previous_url|s:42:"http://localhost:8080/admin/print-requests";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772167655;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";class_section_id|i:21;class_section_ids|a:1:{i:0;i:21;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772171325;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"3";user_email|s:19:"kelabidi0@gmail.com";user_name|s:15:"Khadija Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772170259;roles|a:2:{i:0;s:7:"teacher";i:1;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|i:10;class_section_ids|a:1:{i:0;i:10;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772169271;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772168936;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772173263;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"2";user_email|s:24:"moulay.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772172575;roles|a:1:{i:0;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|N;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772172343;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"3";user_email|s:19:"kelabidi0@gmail.com";user_name|s:15:"Khadija Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772170259;roles|a:2:{i:0;s:7:"teacher";i:1;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|i:10;class_section_ids|a:1:{i:0;i:10;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772169728;_ci_previous_url|s:43:"http://localhost:8080/admin/progress/view/1";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772168936;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772170251;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772151602;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772172244;_ci_previous_url|s:52:"http://localhost:8080/printables_reports/report_card";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772168936;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -1 +1 @@
|
|||||||
__ci_last_regenerate|i:1772151598;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772151602;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
__ci_last_regenerate|i:1772168444;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772151602;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772172244;_ci_previous_url|s:52:"http://localhost:8080/printables_reports/report_card";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772168936;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772170606;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"3";user_email|s:19:"kelabidi0@gmail.com";user_name|s:15:"Khadija Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772170259;roles|a:2:{i:0;s:7:"teacher";i:1;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|i:10;class_section_ids|a:1:{i:0;i:10;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772170913;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"3";user_email|s:19:"kelabidi0@gmail.com";user_name|s:15:"Khadija Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772170259;roles|a:2:{i:0;s:7:"teacher";i:1;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|i:10;class_section_ids|a:1:{i:0;i:10;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772172006;_ci_previous_url|s:35:"http://localhost:8080/parent/scores";user_id|s:1:"3";user_email|s:19:"kelabidi0@gmail.com";user_name|s:15:"Khadija Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772170259;roles|a:2:{i:0;s:7:"teacher";i:1;s:6:"parent";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:6:"parent";class_section_id|i:10;class_section_ids|a:1:{i:0;i:10;}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__ci_last_regenerate|i:1772171020;_ci_previous_url|s:36:"http://localhost:8080/admin/progress";user_id|s:1:"1";user_email|s:23:"larbi.elabidi@gmail.com";user_name|s:20:"Moulay Larbi Elabidi";user_type|s:7:"primary";is_logged_in|b:1;login_time|i:1772168936;roles|a:20:{i:0;s:5:"admin";i:1;s:13:"administrator";i:2;s:15:"csm_contributor";i:3;s:15:"fes_contributor";i:4;s:21:"financial_contributor";i:5;s:14:"fs_contributor";i:6;s:11:"head of csm";i:7;s:28:"head of department education";i:8;s:10:"head of fa";i:9;s:11:"head of fes";i:10;s:10:"head of fs";i:11;s:12:"head of itpd";i:12;s:16:"itpd contributor";i:13;s:6:"parent";i:14;s:9:"principal";i:15;s:15:"saf_contributor";i:16;s:10:"substitute";i:17;s:7:"teacher";i:18;s:17:"teacher_assistant";i:19;s:14:"vice principal";}semester|s:6:"Spring";school_year|s:9:"2025-2026";role|s:13:"administrator";
|
||||||
Reference in New Issue
Block a user