276 lines
11 KiB
PHP
Executable File
276 lines
11 KiB
PHP
Executable File
<?php
|
||
|
||
namespace App\Http\Controllers\Api;
|
||
|
||
use App\Models\Calendar;
|
||
use App\Models\Configuration;
|
||
use App\Models\Homework;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Symfony\Component\HttpFoundation\Response;
|
||
|
||
class HomeworkTrackingController extends BaseApiController
|
||
{
|
||
protected Calendar $calendar;
|
||
protected Homework $homework;
|
||
protected Configuration $config;
|
||
protected string $schoolYear;
|
||
protected string $semester;
|
||
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
$this->calendar = model(Calendar::class);
|
||
$this->homework = model(Homework::class);
|
||
$this->config = model(Configuration::class);
|
||
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
||
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
||
}
|
||
|
||
/**
|
||
* Get homework tracking data with Sunday-based schedule
|
||
*/
|
||
public function index()
|
||
{
|
||
try {
|
||
// Get date range from request or use defaults
|
||
$startDate = $this->request->getGet('start_date') ?? '2025-09-21';
|
||
$endDate = $this->request->getGet('end_date') ?? '2026-01-18';
|
||
|
||
// Parse dates
|
||
try {
|
||
$start = new \DateTime($startDate);
|
||
} catch (\Throwable $e) {
|
||
$start = new \DateTime('today');
|
||
}
|
||
|
||
try {
|
||
$end = new \DateTime($endDate);
|
||
} catch (\Throwable $e) {
|
||
$end = new \DateTime('today');
|
||
}
|
||
|
||
// 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->calendar->getEventsBySchoolYear($this->schoolYear) ?? [];
|
||
$eventDays = [];
|
||
|
||
foreach ($events as $ev) {
|
||
$ymd = substr((string)($ev['date'] ?? $ev['start_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)
|
||
}
|
||
}
|
||
|
||
// Aggregate homework presence + first entered date per class_section_id + homework_index
|
||
$rows = DB::table('homework')
|
||
->select('class_section_id', 'homework_index', DB::raw('MIN(created_at) AS first_created'), DB::raw('COUNT(*) AS cnt'))
|
||
->where('school_year', $this->schoolYear)
|
||
->where('semester', $this->semester)
|
||
->groupBy('class_section_id', 'homework_index')
|
||
->get()
|
||
->toArray();
|
||
|
||
$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
|
||
$rowsByDate = DB::table('homework')
|
||
->select(DB::raw('class_section_id, DATE(created_at) AS hw_date, MIN(created_at) AS first_created, COUNT(*) AS cnt'))
|
||
->where('school_year', $this->schoolYear)
|
||
->where('semester', $this->semester)
|
||
->groupBy(DB::raw('class_section_id, DATE(created_at)'))
|
||
->orderBy('hw_date', 'ASC')
|
||
->get()
|
||
->toArray();
|
||
|
||
$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 non–No 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
|
||
$teacherRows = DB::table('teacher_class as tc')
|
||
->select('tc.class_section_id', 'tc.position', 'cs.class_section_name', 'cs.class_id', 'u.firstname', 'u.lastname')
|
||
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
|
||
->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id')
|
||
->where('tc.school_year', $this->schoolYear)
|
||
->where('tc.semester', $this->semester)
|
||
->whereNotNull('tc.class_section_id')
|
||
->whereIn('tc.position', ['main', 'ta'])
|
||
->orderBy('cs.class_id', 'ASC')
|
||
->orderBy('cs.class_section_name', 'ASC')
|
||
->orderByRaw("FIELD(tc.position, 'main','ta')")
|
||
->orderBy('u.firstname', 'ASC')
|
||
->get()
|
||
->toArray();
|
||
|
||
$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 $this->success([
|
||
'semester' => $this->semester,
|
||
'school_year' => $this->schoolYear,
|
||
'sundays' => $sundays,
|
||
'event_days' => $eventDays,
|
||
'date_to_index' => $dateToIndex,
|
||
'teachers' => $teachersPage,
|
||
'has_homework' => $hasHomework,
|
||
'hw_entered_at' => $hwEnteredAt,
|
||
'has_homework_by_date' => $hasHomeworkByDate,
|
||
'hw_entered_at_by_date' => $hwEnteredAtByDate,
|
||
'pagination' => [
|
||
'page' => $page,
|
||
'total_pages' => $totalPages,
|
||
'per_page' => $perPage,
|
||
'total_rows' => $totalRows,
|
||
],
|
||
], 'Homework tracking retrieved successfully');
|
||
} catch (\Throwable $e) {
|
||
log_message('error', 'Homework tracking error: ' . $e->getMessage());
|
||
return $this->respondError('Failed to retrieve homework tracking: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
}
|