This commit is contained in:
@@ -5,7 +5,9 @@ namespace App\Controllers;
|
|||||||
use App\Controllers\ClassProgressController;
|
use App\Controllers\ClassProgressController;
|
||||||
use App\Models\ClassProgressAttachmentModel;
|
use App\Models\ClassProgressAttachmentModel;
|
||||||
use App\Models\ClassProgressReportModel;
|
use App\Models\ClassProgressReportModel;
|
||||||
|
use App\Models\ConfigurationModel;
|
||||||
use App\Models\EnrollmentModel;
|
use App\Models\EnrollmentModel;
|
||||||
|
use App\Services\SemesterRangeService;
|
||||||
use CodeIgniter\Database\BaseConnection;
|
use CodeIgniter\Database\BaseConnection;
|
||||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||||
use Config\Database;
|
use Config\Database;
|
||||||
@@ -15,8 +17,10 @@ class ParentProgressController extends BaseController
|
|||||||
protected ClassProgressReportModel $reportModel;
|
protected ClassProgressReportModel $reportModel;
|
||||||
protected ClassProgressAttachmentModel $attachmentModel;
|
protected ClassProgressAttachmentModel $attachmentModel;
|
||||||
protected EnrollmentModel $enrollmentModel;
|
protected EnrollmentModel $enrollmentModel;
|
||||||
|
protected ConfigurationModel $configModel;
|
||||||
protected BaseConnection $db;
|
protected BaseConnection $db;
|
||||||
private ?array $parentSectionIds = null;
|
private ?array $parentSectionIds = null;
|
||||||
|
private array $parentSectionIdsByYear = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -24,6 +28,7 @@ class ParentProgressController extends BaseController
|
|||||||
$this->reportModel = new ClassProgressReportModel();
|
$this->reportModel = new ClassProgressReportModel();
|
||||||
$this->attachmentModel = new ClassProgressAttachmentModel();
|
$this->attachmentModel = new ClassProgressAttachmentModel();
|
||||||
$this->enrollmentModel = new EnrollmentModel();
|
$this->enrollmentModel = new EnrollmentModel();
|
||||||
|
$this->configModel = new ConfigurationModel();
|
||||||
$this->db = Database::connect();
|
$this->db = Database::connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +48,8 @@ class ParentProgressController extends BaseController
|
|||||||
->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
|
->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('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
|
||||||
->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
|
->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
|
||||||
->whereIn('class_progress_reports.class_section_id', $sectionIds)
|
->whereIn('class_progress_reports.class_section_id', $sectionIds);
|
||||||
->where('class_progress_reports.school_year', $schoolYear);
|
$this->applyProgressSchoolYearScope($builder, $schoolYear);
|
||||||
|
|
||||||
$rows = $builder
|
$rows = $builder
|
||||||
->orderBy('week_start', 'DESC')
|
->orderBy('week_start', 'DESC')
|
||||||
@@ -82,21 +87,29 @@ class ParentProgressController extends BaseController
|
|||||||
->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
|
->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('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
|
||||||
->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
|
->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
|
||||||
->where('class_progress_reports.school_year', $schoolYear)
|
->where('class_progress_reports.id', (int) $id)
|
||||||
->find((int) $id);
|
->first();
|
||||||
|
|
||||||
if (! $row || ! $this->isSectionAccessible($row['class_section_id'] ?? null)) {
|
if (! $row) {
|
||||||
|
throw new PageNotFoundException('Progress report not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportSchoolYear = trim((string) ($row['school_year'] ?? ''));
|
||||||
|
$effectiveSchoolYear = $reportSchoolYear !== '' ? $reportSchoolYear : $schoolYear;
|
||||||
|
|
||||||
|
if (! $this->isSectionAccessible($row['class_section_id'] ?? null, $effectiveSchoolYear)) {
|
||||||
throw new PageNotFoundException('Progress report not found.');
|
throw new PageNotFoundException('Progress report not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
|
$row['status_label'] = ClassProgressController::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
|
||||||
$row['flags'] = $this->decodeFlags($row['flags_json']);
|
$row['flags'] = $this->decodeFlags($row['flags_json']);
|
||||||
|
|
||||||
$weeklyReports = $this->reportModel
|
$weeklyReportsQuery = $this->reportModel
|
||||||
->select('class_progress_reports.*')
|
->select('class_progress_reports.*')
|
||||||
->where('class_section_id', $row['class_section_id'])
|
->where('class_section_id', $row['class_section_id'])
|
||||||
->where('week_start', $row['week_start'])
|
->where('week_start', $row['week_start']);
|
||||||
->where('school_year', $schoolYear)
|
$this->applyProgressSchoolYearScope($weeklyReportsQuery, $effectiveSchoolYear);
|
||||||
|
$weeklyReports = $weeklyReportsQuery
|
||||||
->orderBy('subject', 'ASC')
|
->orderBy('subject', 'ASC')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
@@ -122,7 +135,14 @@ class ParentProgressController extends BaseController
|
|||||||
public function attachment($id)
|
public function attachment($id)
|
||||||
{
|
{
|
||||||
$row = $this->reportModel->find((int) $id);
|
$row = $this->reportModel->find((int) $id);
|
||||||
if (! $row || ! $this->isSectionAccessible($row['class_section_id'] ?? null) || empty($row['attachment_path'])) {
|
if (! $row) {
|
||||||
|
throw new PageNotFoundException('Attachment not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportSchoolYear = trim((string) ($row['school_year'] ?? ''));
|
||||||
|
$effectiveSchoolYear = $reportSchoolYear !== '' ? $reportSchoolYear : $this->currentSchoolYearName();
|
||||||
|
|
||||||
|
if (! $this->isSectionAccessible($row['class_section_id'] ?? null, $effectiveSchoolYear) || empty($row['attachment_path'])) {
|
||||||
throw new PageNotFoundException('Attachment not found.');
|
throw new PageNotFoundException('Attachment not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +162,14 @@ class ParentProgressController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$report = $this->reportModel->find((int) ($attachment['report_id'] ?? 0));
|
$report = $this->reportModel->find((int) ($attachment['report_id'] ?? 0));
|
||||||
if (! $report || ! $this->isSectionAccessible($report['class_section_id'] ?? null)) {
|
if (! $report) {
|
||||||
|
throw new PageNotFoundException('Attachment not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportSchoolYear = trim((string) ($report['school_year'] ?? ''));
|
||||||
|
$effectiveSchoolYear = $reportSchoolYear !== '' ? $reportSchoolYear : $this->currentSchoolYearName();
|
||||||
|
|
||||||
|
if (! $this->isSectionAccessible($report['class_section_id'] ?? null, $effectiveSchoolYear)) {
|
||||||
throw new PageNotFoundException('Attachment not found.');
|
throw new PageNotFoundException('Attachment not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,22 +182,30 @@ class ParentProgressController extends BaseController
|
|||||||
return $this->response->download($file, null)->setFileName($downloadName);
|
return $this->response->download($file, null)->setFileName($downloadName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getParentSectionIds(): array
|
protected function getParentSectionIds(?string $schoolYear = null): array
|
||||||
{
|
{
|
||||||
if ($this->parentSectionIds !== null) {
|
$schoolYear = trim((string) ($schoolYear ?? $this->currentSchoolYearName()));
|
||||||
|
if ($schoolYear === $this->currentSchoolYearName() && $this->parentSectionIds !== null) {
|
||||||
return $this->parentSectionIds;
|
return $this->parentSectionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->parentSectionIdsByYear[$schoolYear])) {
|
||||||
|
return $this->parentSectionIdsByYear[$schoolYear];
|
||||||
|
}
|
||||||
|
|
||||||
$parentId = (int) session()->get('user_id');
|
$parentId = (int) session()->get('user_id');
|
||||||
if ($parentId === 0) {
|
if ($parentId === 0) {
|
||||||
$this->parentSectionIds = [];
|
$this->parentSectionIdsByYear[$schoolYear] = [];
|
||||||
return $this->parentSectionIds;
|
if ($schoolYear === $this->currentSchoolYearName()) {
|
||||||
|
$this->parentSectionIds = [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $this->enrollmentModel
|
$rows = $this->enrollmentModel
|
||||||
->select('class_section_id')
|
->select('class_section_id')
|
||||||
->where('parent_id', $parentId)
|
->where('parent_id', $parentId)
|
||||||
->where('school_year', $this->currentSchoolYearName())
|
->where('school_year', $schoolYear)
|
||||||
->where('is_withdrawn', 0)
|
->where('is_withdrawn', 0)
|
||||||
->groupBy('class_section_id')
|
->groupBy('class_section_id')
|
||||||
->findAll();
|
->findAll();
|
||||||
@@ -183,8 +218,12 @@ class ParentProgressController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->parentSectionIds = array_values(array_unique($ids));
|
$ids = array_values(array_unique($ids));
|
||||||
return $this->parentSectionIds;
|
$this->parentSectionIdsByYear[$schoolYear] = $ids;
|
||||||
|
if ($schoolYear === $this->currentSchoolYearName()) {
|
||||||
|
$this->parentSectionIds = $ids;
|
||||||
|
}
|
||||||
|
return $ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildSectionOptions(array $sectionIds): array
|
protected function buildSectionOptions(array $sectionIds): array
|
||||||
@@ -263,12 +302,50 @@ class ParentProgressController extends BaseController
|
|||||||
return $reportGroups;
|
return $reportGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isSectionAccessible(?int $classSectionId): bool
|
protected function isSectionAccessible(?int $classSectionId, ?string $schoolYear = null): bool
|
||||||
{
|
{
|
||||||
if (! $classSectionId) {
|
if (! $classSectionId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return in_array((int) $classSectionId, $this->getParentSectionIds(), true);
|
return in_array((int) $classSectionId, $this->getParentSectionIds($schoolYear), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyProgressSchoolYearScope($builder, string $schoolYear): void
|
||||||
|
{
|
||||||
|
if ($schoolYear === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasReportYear = $this->db->fieldExists('school_year', 'class_progress_reports');
|
||||||
|
$semesterResolver = new SemesterRangeService($this->configModel);
|
||||||
|
[$rangeStart, $rangeEnd] = $semesterResolver->getSchoolYearRange($schoolYear);
|
||||||
|
|
||||||
|
if ($hasReportYear) {
|
||||||
|
if ($rangeStart !== '' && $rangeEnd !== '') {
|
||||||
|
$builder
|
||||||
|
->groupStart()
|
||||||
|
->where('class_progress_reports.school_year', $schoolYear)
|
||||||
|
->orGroupStart()
|
||||||
|
->groupStart()
|
||||||
|
->where('class_progress_reports.school_year IS NULL', null, false)
|
||||||
|
->orWhere('class_progress_reports.school_year', '')
|
||||||
|
->groupEnd()
|
||||||
|
->where('class_progress_reports.week_start >=', $rangeStart)
|
||||||
|
->where('class_progress_reports.week_start <=', $rangeEnd)
|
||||||
|
->groupEnd()
|
||||||
|
->groupEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder->where('class_progress_reports.school_year', $schoolYear);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rangeStart !== '' && $rangeEnd !== '') {
|
||||||
|
$builder
|
||||||
|
->where('class_progress_reports.week_start >=', $rangeStart)
|
||||||
|
->where('class_progress_reports.week_start <=', $rangeEnd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveAttachmentFile(array $row): ?string
|
protected function resolveAttachmentFile(array $row): ?string
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Exceptions\SchoolYear\SchoolYearWriteConflictException;
|
||||||
use App\Models\ConfigurationModel;
|
use App\Models\ConfigurationModel;
|
||||||
use App\Models\ReportCardAcknowledgementModel;
|
use App\Models\ReportCardAcknowledgementModel;
|
||||||
use App\Models\StudentModel;
|
use App\Models\StudentModel;
|
||||||
@@ -31,7 +32,8 @@ class ParentReportCardController extends BaseController
|
|||||||
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$schoolYear = trim((string) $this->currentSchoolYearName());
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$schoolYear = trim($schoolYearContext->yearName());
|
||||||
$semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? ''));
|
$semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? ''));
|
||||||
|
|
||||||
$builder = $this->db->table('students s')
|
$builder = $this->db->table('students s')
|
||||||
@@ -67,6 +69,7 @@ class ParentReportCardController extends BaseController
|
|||||||
'ackMap' => $ackMap,
|
'ackMap' => $ackMap,
|
||||||
'schoolYear' => $schoolYear,
|
'schoolYear' => $schoolYear,
|
||||||
'semester' => $semester,
|
'semester' => $semester,
|
||||||
|
'isEditable' => ! $schoolYearContext->isReadonly(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +85,15 @@ class ParentReportCardController extends BaseController
|
|||||||
throw new PageNotFoundException('Student not found.');
|
throw new PageNotFoundException('Student not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$schoolYear = trim((string) $this->currentSchoolYearName());
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$schoolYear = trim($schoolYearContext->yearName());
|
||||||
$semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? ''));
|
$semester = trim((string) ($this->request->getGet('semester') ?? $this->configModel->getConfig('semester') ?? ''));
|
||||||
|
|
||||||
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
if (! $schoolYearContext->isReadonly()) {
|
||||||
'viewed_at' => date('Y-m-d H:i:s'),
|
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
||||||
]);
|
'viewed_at' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$url = site_url('report-card/student/' . (int) $studentId);
|
$url = site_url('report-card/student/' . (int) $studentId);
|
||||||
$query = [];
|
$query = [];
|
||||||
@@ -111,6 +117,13 @@ class ParentReportCardController extends BaseController
|
|||||||
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
return redirect()->back()->with('error', 'Unable to retrieve student data. Please contact support.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
try {
|
||||||
|
$this->assertSchoolYearWritable($schoolYearContext);
|
||||||
|
} catch (SchoolYearWriteConflictException $e) {
|
||||||
|
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$student = $this->studentModel->find((int) $studentId);
|
$student = $this->studentModel->find((int) $studentId);
|
||||||
if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) {
|
if (! $student || (int) ($student['parent_id'] ?? 0) !== $parentId) {
|
||||||
throw new PageNotFoundException('Student not found.');
|
throw new PageNotFoundException('Student not found.');
|
||||||
@@ -121,7 +134,7 @@ class ParentReportCardController extends BaseController
|
|||||||
return redirect()->back()->with('error', 'Please type your full name to sign.');
|
return redirect()->back()->with('error', 'Please type your full name to sign.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$schoolYear = trim((string) $this->currentSchoolYearName());
|
$schoolYear = trim($schoolYearContext->yearName());
|
||||||
$semester = trim((string) ($this->configModel->getConfig('semester') ?? ''));
|
$semester = trim((string) ($this->configModel->getConfig('semester') ?? ''));
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
$this->touchAcknowledgement($parentId, (int) $studentId, $schoolYear, $semester, [
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\StudentClassModel;
|
|||||||
use App\Models\ClassSectionModel;
|
use App\Models\ClassSectionModel;
|
||||||
use App\Models\AttendanceDataModel;
|
use App\Models\AttendanceDataModel;
|
||||||
use App\Models\AttendanceRecordModel;
|
use App\Models\AttendanceRecordModel;
|
||||||
|
use App\Exceptions\SchoolYear\SchoolYearWriteConflictException;
|
||||||
use App\Services\SemesterRangeService;
|
use App\Services\SemesterRangeService;
|
||||||
|
|
||||||
class ParentAttendanceReportController extends BaseController
|
class ParentAttendanceReportController extends BaseController
|
||||||
@@ -58,7 +59,8 @@ class ParentAttendanceReportController extends BaseController
|
|||||||
[$sundays, $defaultDate] = $this->computeSundays();
|
[$sundays, $defaultDate] = $this->computeSundays();
|
||||||
|
|
||||||
// Load upcoming reports for preview/edit (today and forward within this school year)
|
// Load upcoming reports for preview/edit (today and forward within this school year)
|
||||||
$schoolYear = $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$schoolYear = $schoolYearContext->yearName();
|
||||||
$todayYmd = local_date(utc_now(), 'Y-m-d');
|
$todayYmd = local_date(utc_now(), 'Y-m-d');
|
||||||
$previewRows = $this->reportModel->builder()
|
$previewRows = $this->reportModel->builder()
|
||||||
->select('parent_attendance_reports.*, s.firstname, s.lastname')
|
->select('parent_attendance_reports.*, s.firstname, s.lastname')
|
||||||
@@ -80,6 +82,8 @@ class ParentAttendanceReportController extends BaseController
|
|||||||
'defaultDate' => $defaultDate, // preselected date (upcoming Sunday)
|
'defaultDate' => $defaultDate, // preselected date (upcoming Sunday)
|
||||||
'myReports' => $previewRows,
|
'myReports' => $previewRows,
|
||||||
'cutoffThreshold' => $cutoffThreshold,
|
'cutoffThreshold' => $cutoffThreshold,
|
||||||
|
'selectedYear' => $schoolYear,
|
||||||
|
'isEditable' => ! $schoolYearContext->isReadonly(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +95,13 @@ class ParentAttendanceReportController extends BaseController
|
|||||||
return redirect()->to('/login');
|
return redirect()->to('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
try {
|
||||||
|
$this->assertSchoolYearWritable($schoolYearContext);
|
||||||
|
} catch (SchoolYearWriteConflictException $e) {
|
||||||
|
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$post = $this->request->getPost();
|
$post = $this->request->getPost();
|
||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
@@ -141,7 +152,7 @@ class ParentAttendanceReportController extends BaseController
|
|||||||
$dismissTime = $post['dismiss_time'] ?? null;
|
$dismissTime = $post['dismiss_time'] ?? null;
|
||||||
$reasonRaw = $post['reason'] ?? null;
|
$reasonRaw = $post['reason'] ?? null;
|
||||||
$reason = is_string($reasonRaw) ? trim($reasonRaw) : null;
|
$reason = is_string($reasonRaw) ? trim($reasonRaw) : null;
|
||||||
$schoolYear = $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
|
$schoolYear = $schoolYearContext->yearName();
|
||||||
|
|
||||||
// Enforce Sunday-only and future-or-today selection
|
// Enforce Sunday-only and future-or-today selection
|
||||||
$todayCheck = new \DateTime('today');
|
$todayCheck = new \DateTime('today');
|
||||||
@@ -1508,6 +1519,13 @@ class ParentAttendanceReportController extends BaseController
|
|||||||
return redirect()->to('/login');
|
return redirect()->to('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
try {
|
||||||
|
$this->assertSchoolYearWritable($schoolYearContext);
|
||||||
|
} catch (SchoolYearWriteConflictException $e) {
|
||||||
|
return redirect()->back()->with('error', $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$id = (int) ($this->request->getPost('id') ?? 0);
|
$id = (int) ($this->request->getPost('id') ?? 0);
|
||||||
if ($id <= 0) {
|
if ($id <= 0) {
|
||||||
return redirect()->back()->with('error', 'Invalid report.');
|
return redirect()->back()->with('error', 'Invalid report.');
|
||||||
@@ -1517,6 +1535,9 @@ class ParentAttendanceReportController extends BaseController
|
|||||||
if (!$row || (int)($row['parent_id'] ?? 0) !== (int)$parentId) {
|
if (!$row || (int)($row['parent_id'] ?? 0) !== (int)$parentId) {
|
||||||
return redirect()->back()->with('error', 'Report not found.');
|
return redirect()->back()->with('error', 'Report not found.');
|
||||||
}
|
}
|
||||||
|
if ((string) ($row['school_year'] ?? '') !== $schoolYearContext->yearName()) {
|
||||||
|
return redirect()->back()->with('error', 'Report not found for the selected school year.');
|
||||||
|
}
|
||||||
|
|
||||||
// Only allow editing for 'new' status entries
|
// Only allow editing for 'new' status entries
|
||||||
$status = (string) ($row['status'] ?? '');
|
$status = (string) ($row['status'] ?? '');
|
||||||
|
|||||||
@@ -1064,6 +1064,10 @@ class ParentController extends BaseController
|
|||||||
|
|
||||||
$schoolIdService = new SchoolIdService();
|
$schoolIdService = new SchoolIdService();
|
||||||
$parentId = session()->get('user_id');
|
$parentId = session()->get('user_id');
|
||||||
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$this->assertSchoolYearWritable($schoolYearContext);
|
||||||
|
$selectedSchoolYear = $schoolYearContext->yearName();
|
||||||
|
$this->schoolYear = $selectedSchoolYear;
|
||||||
|
|
||||||
if (!$this->lastDayOfRegistration || !strtotime($this->lastDayOfRegistration)) {
|
if (!$this->lastDayOfRegistration || !strtotime($this->lastDayOfRegistration)) {
|
||||||
throw new \Exception('Invalid enrollment deadline date.');
|
throw new \Exception('Invalid enrollment deadline date.');
|
||||||
@@ -1270,7 +1274,9 @@ class ParentController extends BaseController
|
|||||||
|
|
||||||
private function getRegistrationData(int $parentId): array
|
private function getRegistrationData(int $parentId): array
|
||||||
{
|
{
|
||||||
$enrollments = $this->getEnrollmentsByParent($parentId, $this->schoolYear);
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$selectedSchoolYear = $schoolYearContext->yearName();
|
||||||
|
$enrollments = $this->getEnrollmentsByParent($parentId, $selectedSchoolYear);
|
||||||
|
|
||||||
$enrollmentMap = [];
|
$enrollmentMap = [];
|
||||||
if (!empty($enrollments)) {
|
if (!empty($enrollments)) {
|
||||||
@@ -1306,6 +1312,8 @@ class ParentController extends BaseController
|
|||||||
'maxChilds' => $this->maxChilds,
|
'maxChilds' => $this->maxChilds,
|
||||||
'maxEmergency' => $this->maxEmergency,
|
'maxEmergency' => $this->maxEmergency,
|
||||||
'enrollments' => $enrollments,
|
'enrollments' => $enrollments,
|
||||||
|
'selectedYear' => $selectedSchoolYear,
|
||||||
|
'isEditable' => ! $schoolYearContext->isReadonly(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1636,6 +1644,9 @@ $existing = $this->studentModel
|
|||||||
|
|
||||||
public function editEmergencyContact($id = null)
|
public function editEmergencyContact($id = null)
|
||||||
{
|
{
|
||||||
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$this->assertSchoolYearWritable($schoolYearContext);
|
||||||
|
|
||||||
if ($id === null) {
|
if ($id === null) {
|
||||||
$parentId = session()->get('user_id');
|
$parentId = session()->get('user_id');
|
||||||
$contacts = $this->emergencyContactModel->where('parent_id', $parentId)->findAll();
|
$contacts = $this->emergencyContactModel->where('parent_id', $parentId)->findAll();
|
||||||
@@ -1681,8 +1692,8 @@ $existing = $this->studentModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
$parentId = session()->get('user_id');
|
$parentId = session()->get('user_id');
|
||||||
$semester = session()->get('active_semester');
|
$semester = $this->semester;
|
||||||
$schoolYear = session()->get('active_school_year');
|
$schoolYear = $schoolYearContext->yearName();
|
||||||
|
|
||||||
$this->saveEmergencyContact($parentId, $semester, $schoolYear, [
|
$this->saveEmergencyContact($parentId, $semester, $schoolYear, [
|
||||||
'first_name' => $this->request->getPost('emergency_first_name'),
|
'first_name' => $this->request->getPost('emergency_first_name'),
|
||||||
@@ -1699,6 +1710,9 @@ $existing = $this->studentModel
|
|||||||
|
|
||||||
public function editStudent($id)
|
public function editStudent($id)
|
||||||
{
|
{
|
||||||
|
$schoolYearContext = $this->resolveSchoolYearContext();
|
||||||
|
$this->assertSchoolYearWritable($schoolYearContext);
|
||||||
|
$this->schoolYear = $schoolYearContext->yearName();
|
||||||
$schoolIdService = new \App\Services\SchoolIdService();
|
$schoolIdService = new \App\Services\SchoolIdService();
|
||||||
|
|
||||||
if (strtolower($this->request->getMethod()) !== 'post') {
|
if (strtolower($this->request->getMethod()) !== 'post') {
|
||||||
@@ -1752,6 +1766,8 @@ $existing = $this->studentModel
|
|||||||
|
|
||||||
public function deleteStudent($id)
|
public function deleteStudent($id)
|
||||||
{
|
{
|
||||||
|
$this->assertSchoolYearWritable($this->resolveSchoolYearContext());
|
||||||
|
|
||||||
if (strtolower($this->request->getMethod()) !== 'post') {
|
if (strtolower($this->request->getMethod()) !== 'post') {
|
||||||
return redirect()->back()->with('error', 'Invalid request method.');
|
return redirect()->back()->with('error', 'Invalid request method.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ final class SchoolYearSelectionController extends BaseController
|
|||||||
return $fallback;
|
return $fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$path = $this->normalizeSchoolYearReturnPath($path);
|
||||||
|
|
||||||
$query = [];
|
$query = [];
|
||||||
if (! empty($parts['query'])) {
|
if (! empty($parts['query'])) {
|
||||||
parse_str((string) $parts['query'], $query);
|
parse_str((string) $parts['query'], $query);
|
||||||
@@ -73,6 +75,15 @@ final class SchoolYearSelectionController extends BaseController
|
|||||||
return $path . ($cleanQuery !== '' ? '?' . $cleanQuery : '');
|
return $path . ($cleanQuery !== '' ? '?' . $cleanQuery : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function normalizeSchoolYearReturnPath(string $path): string
|
||||||
|
{
|
||||||
|
if (preg_match('#^/(parent|teacher|admin)/progress/view/\d+$#', $path, $matches) === 1) {
|
||||||
|
return '/' . $matches[1] . '/progress';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
private function dashboardRoute(): string
|
private function dashboardRoute(): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ final class SchoolYearContextService
|
|||||||
{
|
{
|
||||||
session()->remove('selected_school_year_id');
|
session()->remove('selected_school_year_id');
|
||||||
session()->remove('selected_school_year');
|
session()->remove('selected_school_year');
|
||||||
|
$this->clearDependentSessionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function active(): SchoolYearContext
|
public function active(): SchoolYearContext
|
||||||
@@ -188,6 +189,7 @@ final class SchoolYearContextService
|
|||||||
'class_section_id',
|
'class_section_id',
|
||||||
'semester',
|
'semester',
|
||||||
'active_semester',
|
'active_semester',
|
||||||
|
'active_school_year',
|
||||||
'teacher_scores_selected_semester',
|
'teacher_scores_selected_semester',
|
||||||
'grading_selected_semester',
|
'grading_selected_semester',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
$isEditable = isset($isEditable) ? (bool) $isEditable : true;
|
||||||
|
$selectedYear = trim((string) ($selectedYear ?? ''));
|
||||||
$disableStudentBtn = count($existingKids) >= $maxChilds;
|
$disableStudentBtn = count($existingKids) >= $maxChilds;
|
||||||
$disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
$disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
||||||
?>
|
?>
|
||||||
@@ -7,6 +9,14 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
<?= $this->section('content') ?>
|
<?= $this->section('content') ?>
|
||||||
<div class="container my-5">
|
<div class="container my-5">
|
||||||
<h3 class="text-center text-success mb-3" style="font-family: Arial, sans-serif;">Student Registration</h3>
|
<h3 class="text-center text-success mb-3" style="font-family: Arial, sans-serif;">Student Registration</h3>
|
||||||
|
<?php if ($selectedYear !== ''): ?>
|
||||||
|
<div class="text-center text-muted mb-3">School year: <?= esc($selectedYear) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!$isEditable): ?>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
This school year is read-only. Switch to the active school year to register or edit students.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if ($disableStudentBtn): ?>
|
<?php if ($disableStudentBtn): ?>
|
||||||
<div class="alert alert-info mt-2">You've reached the maximum number of students (<?= $maxChilds ?>).</div>
|
<div class="alert alert-info mt-2">You've reached the maximum number of students (<?= $maxChilds ?>).</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -100,7 +110,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
?>
|
?>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<?php if ($kid['enrollment'] == 0): ?>
|
<?php if ($isEditable && $kid['enrollment'] == 0): ?>
|
||||||
<form action="<?= base_url('/parent/delete_student/' . $kid['id']) ?>"
|
<form action="<?= base_url('/parent/delete_student/' . $kid['id']) ?>"
|
||||||
method="post"
|
method="post"
|
||||||
style="display:inline">
|
style="display:inline">
|
||||||
@@ -116,7 +126,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="Student is already enrolled">
|
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="<?= $isEditable ? 'Student is already enrolled' : 'This school year is read-only' ?>">
|
||||||
<i class="fas fa-ban"></i> Delete
|
<i class="fas fa-ban"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -162,7 +172,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
<?php
|
<?php
|
||||||
$today = local_date(utc_now(), 'Y-m-d');
|
$today = local_date(utc_now(), 'Y-m-d');
|
||||||
$disableDueToDate = ($today >= $lastDayOfRegistration);
|
$disableDueToDate = ($today >= $lastDayOfRegistration);
|
||||||
$disableAll = $disableStudentBtn || $disableDueToDate;
|
$disableAll = $disableStudentBtn || $disableDueToDate || ! $isEditable;
|
||||||
?>
|
?>
|
||||||
<form action="<?= base_url('/parent/register_student/save') ?>"
|
<form action="<?= base_url('/parent/register_student/save') ?>"
|
||||||
method="post"
|
method="post"
|
||||||
@@ -183,6 +193,8 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
title="<?php
|
title="<?php
|
||||||
if ($disableStudentBtn) {
|
if ($disableStudentBtn) {
|
||||||
echo "Maximum of $maxChilds students reached.";
|
echo "Maximum of $maxChilds students reached.";
|
||||||
|
} elseif (!$isEditable) {
|
||||||
|
echo "This school year is read-only.";
|
||||||
} elseif ($disableDueToDate) {
|
} elseif ($disableDueToDate) {
|
||||||
echo "Registration is closed after $lastDayOfRegistration.";
|
echo "Registration is closed after $lastDayOfRegistration.";
|
||||||
}
|
}
|
||||||
@@ -199,6 +211,8 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
|||||||
title="<?php
|
title="<?php
|
||||||
if ($disableStudentBtn) {
|
if ($disableStudentBtn) {
|
||||||
echo "Maximum of $maxChilds students reached.";
|
echo "Maximum of $maxChilds students reached.";
|
||||||
|
} elseif (!$isEditable) {
|
||||||
|
echo "This school year is read-only.";
|
||||||
} elseif ($disableDueToDate) {
|
} elseif ($disableDueToDate) {
|
||||||
echo "Registration is closed after $lastDayOfRegistration.";
|
echo "Registration is closed after $lastDayOfRegistration.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
$isEditable = (bool) ($isEditable ?? true);
|
||||||
|
$selectedYear = (string) ($selectedYear ?? '');
|
||||||
|
$disabledAttr = $isEditable ? '' : ' disabled';
|
||||||
// Build preview/edit table for upcoming reports
|
// Build preview/edit table for upcoming reports
|
||||||
$myReports = is_array($myReports ?? null) ? $myReports : [];
|
$myReports = is_array($myReports ?? null) ? $myReports : [];
|
||||||
$tzName = 'UTC';
|
$tzName = 'UTC';
|
||||||
@@ -23,6 +26,12 @@
|
|||||||
$nowTz = new DateTime('now', new DateTimeZone($tzName));
|
$nowTz = new DateTime('now', new DateTimeZone($tzName));
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php if (!$isEditable): ?>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
You are viewing <?= esc($selectedYear !== '' ? $selectedYear : 'a closed school year') ?>. Attendance report actions are read-only for closed years.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($myReports)): ?>
|
<?php if (!empty($myReports)): ?>
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card-header bg-light"><strong>Upcoming Submissions (Preview & Edit)</strong></div>
|
<div class="card-header bg-light"><strong>Upcoming Submissions (Preview & Edit)</strong></div>
|
||||||
@@ -53,7 +62,7 @@
|
|||||||
$cutoff = null; $editable = false;
|
$cutoff = null; $editable = false;
|
||||||
try { $cutoff = new DateTime($reportDate . ' 09:00:00', new DateTimeZone($tzName)); } catch (\Throwable $e) { $cutoff = null; }
|
try { $cutoff = new DateTime($reportDate . ' 09:00:00', new DateTimeZone($tzName)); } catch (\Throwable $e) { $cutoff = null; }
|
||||||
if ($cutoff) {
|
if ($cutoff) {
|
||||||
$editable = ($status === 'new') && ($nowTz < $cutoff);
|
$editable = $isEditable && ($status === 'new') && ($nowTz < $cutoff);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -149,7 +158,8 @@
|
|||||||
data-cutoff-date="<?= esc($cutoffDate) ?>"
|
data-cutoff-date="<?= esc($cutoffDate) ?>"
|
||||||
data-cutoff-time="<?= esc($cutoffTime) ?>"
|
data-cutoff-time="<?= esc($cutoffTime) ?>"
|
||||||
data-cutoff-threshold="<?= esc($cutoffThreshold) ?>"
|
data-cutoff-threshold="<?= esc($cutoffThreshold) ?>"
|
||||||
data-cutoff-blocked="0">
|
data-cutoff-blocked="0"
|
||||||
|
data-readonly="<?= $isEditable ? '0' : '1' ?>">
|
||||||
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>">
|
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>">
|
||||||
|
|
||||||
<div id="clientCheckAlert" class="alert d-none" role="alert"></div>
|
<div id="clientCheckAlert" class="alert d-none" role="alert"></div>
|
||||||
@@ -168,7 +178,7 @@
|
|||||||
$oldStudents = (array) old('student_ids');
|
$oldStudents = (array) old('student_ids');
|
||||||
$isChecked = in_array((string)$s['id'], array_map('strval', $oldStudents ?? []), true);
|
$isChecked = in_array((string)$s['id'], array_map('strval', $oldStudents ?? []), true);
|
||||||
?>
|
?>
|
||||||
<input class="form-check-input" type="checkbox" name="student_ids[]" id="stu<?= (int)$s['id'] ?>" value="<?= (int)$s['id'] ?>" <?= $isChecked ? 'checked' : '' ?>>
|
<input class="form-check-input" type="checkbox" name="student_ids[]" id="stu<?= (int)$s['id'] ?>" value="<?= (int)$s['id'] ?>" <?= $isChecked ? 'checked' : '' ?><?= $disabledAttr ?>>
|
||||||
<label class="form-check-label" for="stu<?= (int)$s['id'] ?>">
|
<label class="form-check-label" for="stu<?= (int)$s['id'] ?>">
|
||||||
<?= esc($s['firstname'] . ' ' . $s['lastname']) ?>
|
<?= esc($s['firstname'] . ' ' . $s['lastname']) ?>
|
||||||
</label>
|
</label>
|
||||||
@@ -187,7 +197,7 @@
|
|||||||
<div class="row g-3 mb-3">
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">Sunday Date(s)</label>
|
<label class="form-label fw-semibold">Sunday Date(s)</label>
|
||||||
<select name="dates[]" class="form-select" required multiple size="6" style="max-height: 220px; overflow-y: auto;">
|
<select name="dates[]" class="form-select" required multiple size="6" style="max-height: 220px; overflow-y: auto;"<?= $disabledAttr ?>>
|
||||||
<?php
|
<?php
|
||||||
$list = $sundays ?? [];
|
$list = $sundays ?? [];
|
||||||
$oldDatesRaw = old('dates');
|
$oldDatesRaw = old('dates');
|
||||||
@@ -214,7 +224,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label class="form-label fw-semibold">Type</label>
|
<label class="form-label fw-semibold">Type</label>
|
||||||
<select name="type" id="reportType" class="form-select" required>
|
<select name="type" id="reportType" class="form-select" required<?= $disabledAttr ?>>
|
||||||
<?php $oldType = (string) (old('type') ?: 'absent'); ?>
|
<?php $oldType = (string) (old('type') ?: 'absent'); ?>
|
||||||
<option value="absent" <?= $oldType === 'absent' ? 'selected' : '' ?>>Absent</option>
|
<option value="absent" <?= $oldType === 'absent' ? 'selected' : '' ?>>Absent</option>
|
||||||
<option value="late" <?= $oldType === 'late' ? 'selected' : '' ?>>Late</option>
|
<option value="late" <?= $oldType === 'late' ? 'selected' : '' ?>>Late</option>
|
||||||
@@ -223,22 +233,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3" id="arrivalTimeWrap" style="display:none;">
|
<div class="col-md-3" id="arrivalTimeWrap" style="display:none;">
|
||||||
<label class="form-label fw-semibold">Expected Arrival Time</label>
|
<label class="form-label fw-semibold">Expected Arrival Time</label>
|
||||||
<input type="time" name="arrival_time" class="form-control" placeholder="HH:MM" value="<?= esc(old('arrival_time') ?: '') ?>">
|
<input type="time" name="arrival_time" class="form-control" placeholder="HH:MM" value="<?= esc(old('arrival_time') ?: '') ?>"<?= $disabledAttr ?>>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3" id="dismissTimeWrap" style="display:none;">
|
<div class="col-md-3" id="dismissTimeWrap" style="display:none;">
|
||||||
<label class="form-label fw-semibold">Dismissal Time</label>
|
<label class="form-label fw-semibold">Dismissal Time</label>
|
||||||
<input type="time" name="dismiss_time" class="form-control" placeholder="HH:MM" value="<?= esc(old('dismiss_time') ?: '') ?>">
|
<input type="time" name="dismiss_time" class="form-control" placeholder="HH:MM" value="<?= esc(old('dismiss_time') ?: '') ?>"<?= $disabledAttr ?>>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold" id="reasonLabel">Reason</label>
|
<label class="form-label fw-semibold" id="reasonLabel">Reason</label>
|
||||||
<textarea name="reason" id="reasonInput" rows="3" class="form-control" placeholder="Brief reason to help teachers plan."><?= esc(old('reason') ?: '') ?></textarea>
|
<textarea name="reason" id="reasonInput" rows="3" class="form-control" placeholder="Brief reason to help teachers plan."<?= $disabledAttr ?>><?= esc(old('reason') ?: '') ?></textarea>
|
||||||
<div class="form-text" id="reasonHelp">Required for Absence/Late so we can support your child.</div>
|
<div class="form-text" id="reasonHelp">Required for Absence/Late so we can support your child.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="submit" class="btn btn-success">Submit</button>
|
<button type="submit" class="btn btn-success"<?= $disabledAttr ?>>Submit</button>
|
||||||
<?php $prev = previous_url() ?: site_url('/parent/attendance'); ?>
|
<?php $prev = previous_url() ?: site_url('/parent/attendance'); ?>
|
||||||
<a href="<?= esc($prev) ?>" class="btn btn-outline-secondary">Cancel</a>
|
<a href="<?= esc($prev) ?>" class="btn btn-outline-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,6 +335,14 @@
|
|||||||
const dateSel = form.querySelector('select[name="dates[]"]');
|
const dateSel = form.querySelector('select[name="dates[]"]');
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
const alertBox = document.getElementById('clientCheckAlert');
|
const alertBox = document.getElementById('clientCheckAlert');
|
||||||
|
const isReadonly = form.getAttribute('data-readonly') === '1';
|
||||||
|
|
||||||
|
if (isReadonly) {
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function selectedIds() {
|
function selectedIds() {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
@@ -520,6 +538,11 @@
|
|||||||
const cutoffDate = form.getAttribute('data-cutoff-date') || '';
|
const cutoffDate = form.getAttribute('data-cutoff-date') || '';
|
||||||
const cutoffTime = form.getAttribute('data-cutoff-time') || '';
|
const cutoffTime = form.getAttribute('data-cutoff-time') || '';
|
||||||
const cutoffThreshold = form.getAttribute('data-cutoff-threshold') || '09:00';
|
const cutoffThreshold = form.getAttribute('data-cutoff-threshold') || '09:00';
|
||||||
|
const isReadonly = form.getAttribute('data-readonly') === '1';
|
||||||
|
|
||||||
|
if (isReadonly && submitBtn) {
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
function parseTimeToMinutes(val) {
|
function parseTimeToMinutes(val) {
|
||||||
const parts = (val || '').split(':');
|
const parts = (val || '').split(':');
|
||||||
@@ -544,6 +567,10 @@
|
|||||||
if (cutoffAlert) {
|
if (cutoffAlert) {
|
||||||
cutoffAlert.classList.toggle('d-none', !blocked);
|
cutoffAlert.classList.toggle('d-none', !blocked);
|
||||||
}
|
}
|
||||||
|
if (submitBtn && isReadonly) {
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (submitBtn && blocked) {
|
if (submitBtn && blocked) {
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<?= $this->extend('layout/main_layout') ?>
|
<?= $this->extend('layout/main_layout') ?>
|
||||||
<?= $this->section('content') ?>
|
<?= $this->section('content') ?>
|
||||||
|
<?php
|
||||||
|
$isEditable = (bool) ($isEditable ?? true);
|
||||||
|
$disabledAttr = $isEditable ? '' : ' disabled';
|
||||||
|
?>
|
||||||
<div class="container my-5">
|
<div class="container my-5">
|
||||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -16,6 +20,11 @@
|
|||||||
<?php if (session()->getFlashdata('success')): ?>
|
<?php if (session()->getFlashdata('success')): ?>
|
||||||
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
|
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if (! $isEditable): ?>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
You are viewing <?= esc($schoolYear ?: 'a closed school year') ?>. Report card signatures are read-only for closed years.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (empty($students)): ?>
|
<?php if (empty($students)): ?>
|
||||||
<div class="alert alert-info">No students available for report cards.</div>
|
<div class="alert alert-info">No students available for report cards.</div>
|
||||||
@@ -57,8 +66,8 @@
|
|||||||
<?php if (! $signedAt): ?>
|
<?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) ?>">
|
<form class="d-inline-flex align-items-center gap-2 ms-2" method="post" action="<?= base_url('parent/report-cards/sign/' . $sid) ?>">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
<input type="text" name="signed_name" class="form-control form-control-sm" placeholder="Full name" required>
|
<input type="text" name="signed_name" class="form-control form-control-sm" placeholder="Full name" required<?= $disabledAttr ?>>
|
||||||
<button class="btn btn-sm btn-success" type="submit">Sign</button>
|
<button class="btn btn-sm btn-success" type="submit"<?= $disabledAttr ?>>Sign</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -17,6 +17,13 @@
|
|||||||
<?php
|
<?php
|
||||||
$query = service('request')->getUri()->getQuery();
|
$query = service('request')->getUri()->getQuery();
|
||||||
$returnTo = current_url() . ($query !== '' ? '?' . $query : '');
|
$returnTo = current_url() . ($query !== '' ? '?' . $query : '');
|
||||||
|
$activeSchoolYearId = null;
|
||||||
|
foreach ($schoolYearOptions as $year) {
|
||||||
|
if (strtolower((string) ($year['status'] ?? '')) === 'active') {
|
||||||
|
$activeSchoolYearId = (int) ($year['id'] ?? 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<div class="school-year-context-bar border-bottom bg-light py-2">
|
<div class="school-year-context-bar border-bottom bg-light py-2">
|
||||||
<div class="container-fluid px-3">
|
<div class="container-fluid px-3">
|
||||||
@@ -55,10 +62,11 @@
|
|||||||
|
|
||||||
<span class="text-muted small"><?= esc($schoolYearContext->yearName()) ?></span>
|
<span class="text-muted small"><?= esc($schoolYearContext->yearName()) ?></span>
|
||||||
</form>
|
</form>
|
||||||
<?php if ($schoolYearContext->isExplicitSelection()): ?>
|
<?php if ($schoolYearContext->isExplicitSelection() && $activeSchoolYearId !== null): ?>
|
||||||
<form method="post" action="<?= site_url('school-year/reset') ?>" class="d-inline-block mt-2 mt-sm-0 ms-sm-2">
|
<form method="post" action="<?= site_url('school-year/select') ?>" class="d-inline-block mt-2 mt-sm-0 ms-sm-2">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||||||
|
<input type="hidden" name="school_year_id" value="<?= (int) $activeSchoolYearId ?>">
|
||||||
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||||
Active year
|
Active year
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -37,11 +37,15 @@ export function validatePhoneLive(inputEl) {
|
|||||||
|
|
||||||
// Prevent form submission if phone is invalid
|
// Prevent form submission if phone is invalid
|
||||||
function preventInvalidSubmission() {
|
function preventInvalidSubmission() {
|
||||||
const forms = document.querySelectorAll('form');
|
const phoneInputs = document.querySelectorAll('#phoneInput');
|
||||||
forms.forEach(form => {
|
phoneInputs.forEach(phoneInput => {
|
||||||
|
const form = phoneInput.closest('form');
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
form.addEventListener('submit', function (e) {
|
form.addEventListener('submit', function (e) {
|
||||||
const phoneInput = document.getElementById('phoneInput');
|
if (!validatePhoneLive(phoneInput)) {
|
||||||
if (phoneInput && !validatePhoneLive(phoneInput)) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
phoneInput.focus();
|
phoneInput.focus();
|
||||||
return false;
|
return false;
|
||||||
@@ -53,6 +57,9 @@ function preventInvalidSubmission() {
|
|||||||
// Initialize the event listener when the page loads
|
// Initialize the event listener when the page loads
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const phoneInput = document.getElementById('phoneInput');
|
const phoneInput = document.getElementById('phoneInput');
|
||||||
|
if (!phoneInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add input event listener for live validation and formatting
|
// Add input event listener for live validation and formatting
|
||||||
phoneInput.addEventListener('input', function () {
|
phoneInput.addEventListener('input', function () {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ final class SchoolYearContextServiceTest extends CIUnitTestCase
|
|||||||
session()->set('selected_school_year_id', 1);
|
session()->set('selected_school_year_id', 1);
|
||||||
session()->set('class_section_id', 44);
|
session()->set('class_section_id', 44);
|
||||||
session()->set('semester', 'Fall');
|
session()->set('semester', 'Fall');
|
||||||
|
session()->set('active_school_year', '2025-2026');
|
||||||
|
|
||||||
$service = new SchoolYearContextService(new SchoolYearContextFakeModel([
|
$service = new SchoolYearContextService(new SchoolYearContextFakeModel([
|
||||||
1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closed'],
|
1 => ['id' => 1, 'name' => '2025-2026', 'status' => 'closed'],
|
||||||
@@ -113,5 +114,6 @@ final class SchoolYearContextServiceTest extends CIUnitTestCase
|
|||||||
$this->assertNull(session()->get('selected_school_year_id'));
|
$this->assertNull(session()->get('selected_school_year_id'));
|
||||||
$this->assertNull(session()->get('class_section_id'));
|
$this->assertNull(session()->get('class_section_id'));
|
||||||
$this->assertNull(session()->get('semester'));
|
$this->assertNull(session()->get('semester'));
|
||||||
|
$this->assertNull(session()->get('active_school_year'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user