recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
@@ -0,0 +1,289 @@
<?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];
}
}