reportModel = new ClassProgressReportModel(); $this->attachmentModel = new ClassProgressAttachmentModel(); $this->classSectionModel = new ClassSectionModel(); $this->studentClassModel = new StudentClassModel(); } public function index() { $filters = [ 'from' => (string) $this->request->getGet('from'), 'to' => (string) $this->request->getGet('to'), 'class_section_id' => (string) $this->request->getGet('class_section_id'), 'status' => (string) $this->request->getGet('status'), ]; $builder = $this->reportModel ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name') ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left') ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left'); if ($filters['from']) { $builder->where('week_start >=', $filters['from']); } if ($filters['to']) { $builder->where('week_end <=', $filters['to']); } if ($filters['class_section_id']) { $builder->where('class_progress_reports.class_section_id', (int) $filters['class_section_id']); } if ($filters['status']) { $builder->where('class_progress_reports.status', $filters['status']); } $rows = $builder->orderBy('week_start', 'DESC')->get()->getResultArray(); $reportGroups = []; foreach ($rows as $row) { $row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown'; $key = ($row['week_start'] ?? '') . '_' . ($row['class_section_id'] ?? ''); if ($key === '_') { continue; } if (! isset($reportGroups[$key])) { $reportGroups[$key] = [ 'week_start' => $row['week_start'], 'week_end' => $row['week_end'], 'class_section_name' => $row['class_section_name'] ?? '', 'reports' => [], ]; } $reportGroups[$key]['reports'][$row['subject']] = $row; } $classSections = $this->classSectionModel->getClassSections(); $studentCounts = $this->studentClassModel->getStudentCountsBySection(); $filteredSections = array_values(array_filter($classSections, function ($section) use ($studentCounts) { $sectionId = (int) ($section['class_section_id'] ?? 0); return isset($studentCounts[$sectionId]) && $studentCounts[$sectionId] > 0; })); return view('admin/class_progress_list', [ 'reportGroups' => $reportGroups, 'filters' => $filters, 'classSections' => $filteredSections, 'statusOptions' => ClassProgressController::STATUS_OPTIONS, 'subjectSections' => ClassProgressController::SUBJECT_SECTIONS, ]); } public function view($id) { $row = $this->reportModel ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name') ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left') ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left') ->find((int) $id); if (! $row) { throw new PageNotFoundException('Progress report not found.'); } $row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown'; $row['flags'] = $this->decodeFlags($row['flags_json']); $weeklyReports = $this->reportModel ->select('class_progress_reports.*') ->where('class_section_id', $row['class_section_id']) ->where('week_start', $row['week_start']) ->orderBy('subject', 'ASC') ->findAll(); $attachmentMap = $this->loadAttachmentsForReports(array_column($weeklyReports, 'id')); foreach ($weeklyReports as &$report) { $report['attachments'] = $attachmentMap[$report['id']] ?? []; if (empty($report['attachments']) && ! empty($report['attachment_path'])) { $report['attachments'][] = [ 'id' => $report['id'], 'name' => basename((string) $report['attachment_path']), 'legacy' => true, ]; } } return view('admin/class_progress_view', [ 'row' => $row, 'weeklyReports' => $weeklyReports, 'subjectSections' => ClassProgressController::SUBJECT_SECTIONS, ]); } public function attachment($id) { $row = $this->reportModel->find((int)$id); if (! $row || empty($row['attachment_path'])) { throw new PageNotFoundException('Attachment not found.'); } $file = $this->resolveAttachmentFile($row); if (! $file) { throw new PageNotFoundException('Attachment missing.'); } return $this->response->download($file, null)->setFileName(basename($file)); } public function attachmentFile($id) { $attachment = $this->attachmentModel->find((int) $id); if (! $attachment || empty($attachment['file_path'])) { throw new PageNotFoundException('Attachment not found.'); } $file = $this->resolveAttachmentPath($attachment['file_path']); if (! $file) { throw new PageNotFoundException('Attachment missing.'); } $downloadName = $attachment['original_name'] ?: basename($file); return $this->response->download($file, null)->setFileName($downloadName); } protected function resolveAttachmentFile(array $row): ?string { $path = trim((string) ($row['attachment_path'] ?? '')); if ($path === '') { return null; } return $this->resolveAttachmentPath($path); } protected function resolveAttachmentPath(string $path): ?string { $relative = preg_replace('#^writable/uploads/#', '', $path); $absolute = WRITEPATH . 'uploads/' . ltrim($relative, '/'); return is_file($absolute) ? $absolute : null; } protected function loadAttachmentsForReports(array $reportIds): array { $reportIds = array_values(array_filter(array_map('intval', $reportIds))); if (empty($reportIds)) { return []; } $rows = $this->attachmentModel ->whereIn('report_id', $reportIds) ->orderBy('id', 'ASC') ->findAll(); $map = []; foreach ($rows as $row) { $reportId = (int) ($row['report_id'] ?? 0); if ($reportId === 0) { continue; } $map[$reportId][] = [ 'id' => (int) $row['id'], 'name' => $row['original_name'] ?: basename((string) $row['file_path']), ]; } return $map; } protected function decodeFlags(?string $json): array { if (! $json) { return []; } $flags = json_decode($json, true); return is_array($flags) ? $flags : []; } }