Files
2026-03-08 16:33:24 -04:00

290 lines
11 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Controllers\View;
use App\Controllers\BaseController;
use App\Models\ConfigurationModel;
use App\Models\CalendarModel;
use App\Models\HomeworkModel;
class HomeworkTrackingController extends BaseController
{
protected $configModel;
protected $calendarModel;
protected $homeworkModel;
protected $db;
protected string $semester;
protected string $schoolYear;
/** cache for teacher assignment presence per term */
private array $teacherAssignmentCache = [];
public function __construct()
{
$this->configModel = new ConfigurationModel();
$this->calendarModel = new CalendarModel();
$this->homeworkModel = new HomeworkModel();
$this->db = \Config\Database::connect();
$this->semester = (string) ($this->configModel->getConfig('semester') ?? '');
$this->schoolYear = (string) ($this->configModel->getConfig('school_year') ?? '');
}
public function index()
{
// Semester-based date range (Fall or Spring) derived from school year start.
[$start, $end] = $this->getDateRangeForSemester($this->schoolYear, $this->semester);
// Move start to the first Sunday on/after start
if ((int)$start->format('w') !== 0) {
$start = (clone $start)->modify('next sunday');
}
// Collect all Sundays within range
$sundays = [];
$d = clone $start;
while ($d <= $end) {
$sundays[] = $d->format('Y-m-d');
$d->modify('+7 days');
}
// Map "no school" events by date (Y-m-d) using explicit DB flag
$events = $this->calendarModel->getEventsBySchoolYear($this->schoolYear) ?? [];
$eventDays = [];
foreach ($events as $ev) {
$ymd = substr((string)($ev['date'] ?? ''), 0, 10);
$no = (int)($ev['no_school'] ?? 0);
if ($ymd && $no === 1) {
$eventDays[$ymd] = true; // yellow highlight for no-school day
}
}
// Map Sundays to ordinal homework_index, skipping event Sundays
$dateToIndex = [];
$idx = 0;
foreach ($sundays as $ymd) {
if (!isset($eventDays[$ymd])) {
$idx++;
$dateToIndex[$ymd] = $idx;
} else {
$dateToIndex[$ymd] = null; // event day (no homework expected)
}
}
$limitToSemester = $this->hasTeacherAssignments($this->schoolYear, $this->semester);
// Aggregate homework presence + first entered date per class_section_id + homework_index
$hwQ = $this->db->table('homework')
->select('class_section_id, homework_index, MIN(created_at) AS first_created, COUNT(*) AS cnt')
->where('school_year', $this->schoolYear);
if ($limitToSemester && $this->semester !== '') {
$hwQ->where('semester', $this->semester);
}
$rows = $hwQ->groupBy('class_section_id, homework_index')
->get()
->getResultArray();
$hasHomework = [];
$hwEnteredAt = [];
foreach ($rows as $r) {
$csid = (int)($r['class_section_id'] ?? 0);
$hi = (int)($r['homework_index'] ?? 0);
$cnt = (int)($r['cnt'] ?? 0);
if ($csid > 0 && $hi > 0 && $cnt > 0) {
$hasHomework[$csid][$hi] = true;
$dateStr = substr((string)($r['first_created'] ?? ''), 0, 10);
$hwEnteredAt[$csid][$hi] = $dateStr ?: null;
}
}
// Build date-based presence mapped to the nearest prior non-NoSchool Sunday.
$hb = $this->db->table('homework')
->select("class_section_id, DATE(created_at) AS hw_date, MIN(created_at) AS first_created, COUNT(*) AS cnt", false)
->where('school_year', $this->schoolYear);
if ($limitToSemester && $this->semester !== '') {
$hb->where('semester', $this->semester);
}
$rowsByDate = $hb->groupBy('class_section_id, DATE(created_at)', '', false)
->orderBy('hw_date', 'ASC', false)
->get()
->getResultArray();
$hasHomeworkByDate = [];
$hwEnteredAtByDate = [];
foreach ($rowsByDate as $r) {
$csid = (int)($r['class_section_id'] ?? 0);
$d = substr((string)($r['hw_date'] ?? ''), 0, 10);
$cnt = (int)($r['cnt'] ?? 0);
$firstCreated = substr((string)($r['first_created'] ?? ''), 0, 10);
if ($csid <= 0 || !$d || $cnt <= 0) { continue; }
// Find the index of the last Sunday on or before $d
$baseIndex = -1;
for ($i = count($sundays) - 1; $i >= 0; $i--) {
if ($sundays[$i] <= $d) { $baseIndex = $i; break; }
}
if ($baseIndex < 0) { continue; }
// Map this homework day to only the nearest prior nonNo School Sunday
$j = $baseIndex;
while ($j >= 0 && !empty($eventDays[$sundays[$j]])) { $j--; }
if ($j < 0) { continue; }
$sd = $sundays[$j];
// Mark homework on that Sunday for this class_section (single mapping)
$hasHomeworkByDate[$csid][$sd] = true;
if (empty($hwEnteredAtByDate[$csid][$sd])) {
$hwEnteredAtByDate[$csid][$sd] = $firstCreated ?: $d;
} else {
// Keep the earliest date for display
$existing = (string)$hwEnteredAtByDate[$csid][$sd];
$candidate = $firstCreated ?: $d;
if ($candidate && (!$existing || $candidate < $existing)) {
$hwEnteredAtByDate[$csid][$sd] = $candidate;
}
}
}
// Fetch teachers and TAs per section for this term
$tb = $this->db->table('teacher_class tc')
->select('tc.class_section_id, tc.position, cs.class_section_name, cs.class_id, u.firstname, u.lastname')
->join('classSection cs', 'cs.class_section_id = tc.class_section_id', 'left')
->join('users u', 'u.id = tc.teacher_id', 'left')
->where('tc.school_year', $this->schoolYear);
$teacherRows = $tb->where('tc.class_section_id IS NOT NULL', null, false)
->whereIn('tc.position', ['main','ta'])
->orderBy('cs.class_id', 'ASC')
->orderBy('cs.class_section_name', 'ASC')
->orderBy("FIELD(tc.position, 'main','ta')", '', false)
->orderBy('u.firstname', 'ASC')
->get()
->getResultArray();
$bySection = [];
foreach ($teacherRows as $r) {
$sid = (int)($r['class_section_id'] ?? 0);
if ($sid <= 0) continue;
if (!isset($bySection[$sid])) {
$bySection[$sid] = [
'class_section_id' => $sid,
'class_id' => (int)($r['class_id'] ?? 0),
'class_section_name' => (string)($r['class_section_name'] ?? ''),
'teachers' => [],
'tas' => [],
];
}
$name = trim(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? ''));
$pos = strtolower((string)($r['position'] ?? ''));
if ($name !== '') {
if ($pos === 'main') { $bySection[$sid]['teachers'][] = $name; }
elseif ($pos === 'ta') { $bySection[$sid]['tas'][] = $name; }
}
}
$teachers = array_values($bySection);
// Sort by grade order: KG(13) → Grade 1..11 → Youth(12) → others
usort($teachers, function ($a, $b) {
$ai = (int)($a['class_id'] ?? 0);
$bi = (int)($b['class_id'] ?? 0);
$ord = function ($cid) {
if ($cid === 13) return 0; // KG first
if ($cid >= 1 && $cid <= 11) return $cid; // Grades 1..11
if ($cid === 12) return 99; // Youth last of known
return 200 + $cid; // any others after
};
$cmp = $ord($ai) <=> $ord($bi);
if ($cmp !== 0) return $cmp;
return strnatcasecmp($a['class_section_name'] ?? '', $b['class_section_name'] ?? '');
});
// Simple server-side pagination for rows (8 lines per page)
$perPage = 8;
$totalRows = count($teachers);
$totalPages = max(1, (int)ceil($totalRows / $perPage));
$page = (int) ($this->request->getGet('page') ?? 1);
if ($page < 1) $page = 1;
if ($page > $totalPages) $page = $totalPages;
$offset = ($page - 1) * $perPage;
$teachersPage = array_slice($teachers, $offset, $perPage);
return view('grading/homework_tracking', [
'semester' => $this->semester,
'schoolYear' => $this->schoolYear,
'sundays' => $sundays,
'eventDays' => $eventDays,
'dateToIndex' => $dateToIndex,
'teachers' => $teachersPage,
'hasHomework' => $hasHomework,
'hwEnteredAt' => $hwEnteredAt,
'hasHomeworkByDate' => $hasHomeworkByDate,
'hwEnteredAtByDate' => $hwEnteredAtByDate,
'page' => $page,
'totalPages' => $totalPages,
'perPage' => $perPage,
'totalRows' => $totalRows,
]);
}
/**
* Returns true if teacher_class has any rows for the given school year.
* The semester argument is ignored because assignments are tracked per year.
*/
private function hasTeacherAssignments(string $schoolYear, string $semester): bool
{
$year = trim((string)$schoolYear);
if ($year === '') {
return false;
}
if (array_key_exists($year, $this->teacherAssignmentCache)) {
return $this->teacherAssignmentCache[$year];
}
$cnt = $this->db->table('teacher_class')
->where('school_year', $year)
->countAllResults();
$this->teacherAssignmentCache[$year] = $cnt > 0;
return $this->teacherAssignmentCache[$year];
}
/**
* Compute start/end dates for the given semester within the school year.
* Fall: 09/21/{startYear} to 01/18/{startYear+1}
* Spring: 01/25/{startYear+1} to 05/31/{startYear+1}
* Defaults to today if parsing fails.
*/
private function getDateRangeForSemester(string $schoolYear, string $semester): array
{
$startYear = null;
if (preg_match('/\b(20\d{2})\b/', $schoolYear, $m)) {
$startYear = (int)$m[1];
}
if ($startYear === null) {
$today = new \DateTime('today');
return [$today, $today];
}
$nextYear = $startYear + 1;
$sem = strtolower(trim($semester));
$start = $end = null;
try {
if ($sem === 'spring') {
$start = new \DateTime("{$nextYear}-01-25");
$end = new \DateTime("{$nextYear}-05-31");
} else {
// default/fall
$start = new \DateTime("{$startYear}-09-21");
$end = new \DateTime("{$nextYear}-01-18");
}
} catch (\Throwable $e) {
$today = new \DateTime('today');
return [$today, $today];
}
return [$start, $end];
}
}