Compare commits

..

3 Commits

Author SHA1 Message Date
root 3737b3522d fix reportcard and certificate 2026-05-27 14:11:08 -04:00
root c294d7bed7 fix logo 2026-05-26 23:14:55 -04:00
root 5ed65a867c report cadr and trophy 2026-05-26 05:11:10 -04:00
45 changed files with 656 additions and 214 deletions
+1
View File
@@ -9,6 +9,7 @@ use CodeIgniter\Format\XMLFormatter;
class Format extends BaseConfig
{
public int $jsonEncodeDepth = 512;
/**
* --------------------------------------------------------------------------
* Available Response Formats
+37 -8
View File
@@ -177,7 +177,10 @@ class CertificateController extends BaseController
->table('certificate_records cr')
->select('cr.*, u.firstname AS admin_firstname, u.lastname AS admin_lastname')
->join('users u', 'u.id = cr.issued_by', 'left')
->where('cr.certificate_number', strtoupper($certNumber))
->groupStart()
->where('cr.verification_token', $certNumber)
->orWhere('cr.certificate_number', strtoupper($certNumber))
->groupEnd()
->get()->getRowArray();
return view('certificates/verify', ['record' => $record ?: null]);
@@ -210,6 +213,7 @@ class CertificateController extends BaseController
'lastname' => '',
'grade' => (string)($record['grade'] ?? ''),
'cert_number' => (string)($record['certificate_number'] ?? ''),
'verify_token' => $this->ensureVerificationTokenForRecord($record),
];
// student_name is stored as "Firstname Lastname" — split for the PDF builder
@@ -317,24 +321,28 @@ class CertificateController extends BaseController
// Load any existing certificates for these students this school year
$db = \Config\Database::connect();
$existingCerts = $db->table('certificate_records')
->select('student_id, certificate_number')
->select('id, student_id, certificate_number, verification_token')
->whereIn('student_id', array_column($students, 'id'))
->where('school_year', $schoolYear)
->get()->getResultArray();
$existingCertMap = [];
foreach ($existingCerts as $ec) {
$existingCertMap[(int)$ec['student_id']] = $ec['certificate_number'];
$existingCertMap[(int)$ec['student_id']] = $ec;
}
foreach ($students as &$student) {
$sid = (int)$student['id'];
if (isset($existingCertMap[$sid])) {
// Reuse existing certificate number — do not create a new record
$student['cert_number'] = $existingCertMap[$sid];
$existing = $existingCertMap[$sid];
$student['cert_number'] = (string)($existing['certificate_number'] ?? '');
$student['verify_token'] = $this->ensureVerificationTokenForRecord($existing);
} else {
$certNumber = $this->certRecordModel->nextNumber($schoolYear);
$verifyToken = $this->certRecordModel->generateVerificationToken();
$this->certRecordModel->insert([
'certificate_number' => $certNumber,
'verification_token' => $verifyToken,
'student_id' => $sid,
'student_name' => $student['firstname'] . ' ' . $student['lastname'],
'grade' => $this->formatGrade($student['grade'] ?? ''),
@@ -345,6 +353,7 @@ class CertificateController extends BaseController
'issued_at' => $issuedAt,
]);
$student['cert_number'] = $certNumber;
$student['verify_token'] = $verifyToken;
}
}
unset($student);
@@ -396,6 +405,24 @@ class CertificateController extends BaseController
return $clean;
}
private function ensureVerificationTokenForRecord(array $record): string
{
$token = trim((string)($record['verification_token'] ?? ''));
if ($token !== '') {
return $token;
}
$recordId = (int)($record['id'] ?? 0);
if ($recordId <= 0) {
return '';
}
$token = $this->certRecordModel->generateVerificationToken();
$this->certRecordModel->update($recordId, ['verification_token' => $token]);
return $token;
}
// ─── PDF rendering ─────────────────────────────────────────────────────────
private function buildPdf(array $students, string $certDate): string
@@ -426,9 +453,11 @@ class CertificateController extends BaseController
$name = $student['firstname'] . ' ' . $student['lastname'];
$grade = $this->formatGrade($student['grade'] ?? '');
$certNumber = $student['cert_number'] ?? '';
$verifyToken = $student['verify_token'] ?? '';
// Build verification URL for an inline TCPDF QR code.
$verifyUrl = site_url('verify/' . rawurlencode($certNumber));
$verifyUrl = $verifyToken !== ''
? site_url('verify/' . rawurlencode($verifyToken))
: null;
$this->drawCertificate(
$pdf, $W, $H,
@@ -487,11 +516,11 @@ class CertificateController extends BaseController
// ── Student name — center based on actual string width
$pdf->SetFont($edwardianFont, '', 38);
$nameX = ($W - $pdf->GetStringWidth($name)) / 2;
$this->drawGradientText($pdf, $edwardianFont, 38, $name, $nameX, 229.5);
$this->drawGradientText($pdf, $edwardianFont, 38, $name, $nameX, 221.5);
// ── Line under name
$pdf->SetFont('times', '', 20);
$pdf->SetXY(0, 243);
$pdf->SetXY(0, 236);
$pdf->Cell(600, 20, '___________________________________', 0, 0, 'R');
// ── Description
+160 -75
View File
@@ -1076,7 +1076,8 @@ class GradingController extends Controller
$requestedSemester = trim((string)($this->request->getGet('semester') ?? ''));
$requestedYear = trim((string)($this->request->getGet('school_year') ?? ''));
$semester = $requestedSemester !== '' ? $requestedSemester : ($configuredSemester !== '' ? $configuredSemester : 'Fall');
$isYearMode = (strtolower($requestedSemester) === 'year');
$semester = $isYearMode ? 'year' : ($requestedSemester !== '' ? $requestedSemester : ($configuredSemester !== '' ? $configuredSemester : 'Fall'));
$schoolYear = $requestedYear !== '' ? $requestedYear : $configuredYear;
$schoolYears = $this->getSchoolYearsForScores($schoolYear);
@@ -1090,6 +1091,8 @@ class GradingController extends Controller
'schoolYear' => $schoolYear,
'schoolYears' => $schoolYears,
'canViewGrading' => $canViewGrading,
'isYearMode' => $isYearMode,
'showAllSemesterOption' => true,
]);
}
@@ -1762,14 +1765,16 @@ class GradingController extends Controller
private function fetchBelowSixtyRows(string $schoolYear, string $semester): array
{
$semesterKey = strtolower(trim($semester));
$rows = $this->db->table('semester_scores ss')
$isYearMode = strtolower(trim($semester)) === 'year';
$semesterKey = $isYearMode ? '' : strtolower(trim($semester));
$builder = $this->db->table('semester_scores ss')
->select([
's.id AS student_id',
's.school_id',
's.firstname',
's.lastname',
'cs.class_section_name',
'ss.semester',
'ss.homework_avg',
'ss.project_avg',
'ss.participation_score',
@@ -1784,11 +1789,17 @@ class GradingController extends Controller
->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left')
->where('s.is_active', 1)
->where('ss.school_year', $schoolYear)
->where("LOWER(TRIM(ss.semester))", $semesterKey)
->where('ss.semester_score IS NOT NULL', null, false)
->where('ss.semester_score <', 60)
->where('ss.semester_score <', 60);
if (!$isYearMode) {
$builder->where("LOWER(TRIM(ss.semester))", $semesterKey);
}
$rows = $builder
->orderBy('s.lastname', 'ASC')
->orderBy('s.firstname', 'ASC')
->orderBy('ss.semester', 'ASC')
->get()
->getResultArray();
@@ -1804,19 +1815,22 @@ class GradingController extends Controller
$commentMap = [];
if (!empty($studentIds)) {
$commentRows = $this->db->table('score_comments')
->select('student_id, comment, created_at')
$commentBuilder = $this->db->table('score_comments')
->select('student_id, semester, comment, created_at')
->where('score_type', 'general')
->where('school_year', $schoolYear)
->where("LOWER(TRIM(semester))", $semesterKey)
->whereIn('student_id', $studentIds)
->orderBy('created_at', 'DESC')
->get()
->getResultArray();
->orderBy('created_at', 'DESC');
if (!$isYearMode) {
$commentBuilder->where("LOWER(TRIM(semester))", $semesterKey);
}
$commentRows = $commentBuilder->get()->getResultArray();
foreach ($commentRows as $row) {
$sid = (int)($row['student_id'] ?? 0);
if ($sid > 0 && !isset($commentMap[$sid])) {
$commentMap[$sid] = (string)($row['comment'] ?? '');
$sem = strtolower(trim((string)($row['semester'] ?? '')));
$key = $sid . '_' . $sem;
if ($sid > 0 && !isset($commentMap[$key])) {
$commentMap[$key] = (string)($row['comment'] ?? '');
}
}
}
@@ -1824,21 +1838,24 @@ class GradingController extends Controller
$statusMap = [];
$noteMap = [];
if (!empty($studentIds)) {
$flagRows = $this->db->table('current_flag')
->select('student_id, flag_state, open_description, close_description')
$flagBuilder = $this->db->table('current_flag')
->select('student_id, semester, flag_state, open_description, close_description')
->where('flag', 'grade')
->where('school_year', $schoolYear)
->where("LOWER(TRIM(semester))", $semesterKey)
->whereIn('student_id', $studentIds)
->get()
->getResultArray();
->whereIn('student_id', $studentIds);
if (!$isYearMode) {
$flagBuilder->where("LOWER(TRIM(semester))", $semesterKey);
}
$flagRows = $flagBuilder->get()->getResultArray();
foreach ($flagRows as $row) {
$sid = (int)($row['student_id'] ?? 0);
if ($sid <= 0) continue;
$statusMap[$sid] = (string)($row['flag_state'] ?? '');
$sem = strtolower(trim((string)($row['semester'] ?? '')));
$key = $sid . '_' . $sem;
$statusMap[$key] = (string)($row['flag_state'] ?? '');
$openNote = trim((string)($row['open_description'] ?? ''));
$closeNote = trim((string)($row['close_description'] ?? ''));
$noteMap[$sid] = [
$noteMap[$key] = [
'open' => $openNote,
'closed' => $closeNote,
];
@@ -1847,10 +1864,12 @@ class GradingController extends Controller
foreach ($rows as &$row) {
$sid = (int)($row['student_id'] ?? 0);
$row['comment'] = $commentMap[$sid] ?? '';
$flagState = strtolower(trim((string)($statusMap[$sid] ?? '')));
$sem = strtolower(trim((string)($row['semester'] ?? '')));
$key = $sid . '_' . $sem;
$row['comment'] = $commentMap[$key] ?? '';
$flagState = strtolower(trim((string)($statusMap[$key] ?? '')));
$row['status'] = ($flagState === 'closed' || $flagState === 'canceled') ? 'Closed' : 'Open';
$noteBag = $noteMap[$sid] ?? ['open' => '', 'closed' => ''];
$noteBag = $noteMap[$key] ?? ['open' => '', 'closed' => ''];
$rawNote = $row['status'] === 'Closed' ? (string)$noteBag['closed'] : (string)$noteBag['open'];
if ($rawNote !== '') {
$lines = preg_split('/\R/', $rawNote);
@@ -2539,20 +2558,17 @@ class GradingController extends Controller
public function allDecisions()
{
$configuredSemester = (string)$this->semester;
$configuredYear = (string)$this->schoolYear;
$semester = trim((string)($this->request->getGet('semester') ?? ''));
$schoolYear = trim((string)($this->request->getGet('school_year') ?? ''));
if ($semester === '') $semester = $configuredSemester !== '' ? $configuredSemester : 'Fall';
if ($schoolYear === '') $schoolYear = $configuredYear;
$schoolYears = $this->getSchoolYearsForScores($schoolYear);
// Load saved decisions for this term
// Load saved year decisions (semester='year') for this school year
$decModel = new StudentDecisionModel();
$saved = $decModel
->where('semester', $semester)
->where('semester', 'year')
->where('school_year', $schoolYear)
->findAll();
$savedMap = [];
@@ -2560,70 +2576,109 @@ class GradingController extends Controller
$savedMap[(int)$s['student_id']] = $s;
}
// All semester_scores rows for this term (one per student)
$semKey = strtolower(trim($semester));
$scoreRows = $this->db->table('semester_scores ss')
// Fetch Fall and Spring semester_scores per student for this school year
$allScoreRows = $this->db->table('semester_scores ss')
->select([
's.id AS student_id',
's.school_id',
's.firstname',
's.lastname',
'cs.class_section_name',
'LOWER(TRIM(ss.semester)) AS sem_key',
'ss.semester_score',
])
->join('students s', 's.id = ss.student_id', 'inner')
->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left')
->where('s.is_active', 1)
->where('ss.school_year', $schoolYear)
->where("LOWER(TRIM(ss.semester))", $semKey)
->whereIn('LOWER(TRIM(ss.semester))', ['fall', 'spring'])
->where('ss.semester_score IS NOT NULL', null, false)
->orderBy('cs.class_section_name', 'ASC')
->orderBy('s.lastname', 'ASC')
->orderBy('s.firstname', 'ASC')
->get()->getResultArray();
// Pull below-60 decisions
// Group by student: keep one base info row + fall/spring scores
$studentMap = [];
foreach ($allScoreRows as $sr) {
$sid = (int)$sr['student_id'];
if (!isset($studentMap[$sid])) {
$studentMap[$sid] = [
'student_id' => $sid,
'school_id' => $sr['school_id'] ?? '',
'firstname' => $sr['firstname'] ?? '',
'lastname' => $sr['lastname'] ?? '',
'class_section_name' => $sr['class_section_name'] ?? '',
'fall_score' => null,
'spring_score' => null,
];
}
$semKey = strtolower(trim((string)($sr['sem_key'] ?? '')));
$val = is_numeric($sr['semester_score']) ? (float)$sr['semester_score'] : null;
if ($semKey === 'fall') {
$studentMap[$sid]['fall_score'] = $val;
} elseif ($semKey === 'spring') {
$studentMap[$sid]['spring_score'] = $val;
}
}
// Pull below-60 decisions for either semester (use worst available)
$belowDecModel = new BelowSixtyDecisionModel();
$belowRows = $belowDecModel
->where('semester', $semester)
->where('school_year', $schoolYear)
->findAll();
$belowMap = [];
foreach ($belowRows as $b) {
$belowMap[(int)$b['student_id']] = $b;
$sid = (int)$b['student_id'];
// prefer a non-empty decision over empty
if (!isset($belowMap[$sid]) || (string)($belowMap[$sid]['decision'] ?? '') === '') {
$belowMap[$sid] = $b;
}
}
// Build rows combining live scores with saved/computed decisions
// Build final rows with year_score = (fall + spring) / 2
$rows = [];
foreach ($scoreRows as $sr) {
$sid = (int)$sr['student_id'];
$score = is_numeric($sr['semester_score']) ? (float)$sr['semester_score'] : null;
foreach ($studentMap as $sid => $info) {
$fall = $info['fall_score'];
$spring = $info['spring_score'];
if ($fall !== null && $spring !== null) {
$yearScore = round(($fall + $spring) / 2, 2);
} elseif ($fall !== null) {
$yearScore = $fall;
} elseif ($spring !== null) {
$yearScore = $spring;
} else {
$yearScore = null;
}
if (isset($savedMap[$sid])) {
$decision = (string)($savedMap[$sid]['decision'] ?? '');
$source = (string)($savedMap[$sid]['source'] ?? 'auto');
$notes = (string)($savedMap[$sid]['notes'] ?? '');
} elseif ($score !== null && $score >= 60) {
} elseif ($yearScore !== null && $yearScore >= 60) {
$decision = 'Pass';
$source = 'auto';
$notes = '';
} elseif ($score !== null && isset($belowMap[$sid])) {
} elseif ($yearScore !== null && isset($belowMap[$sid])) {
$decision = (string)($belowMap[$sid]['decision'] ?? '');
$source = $decision !== '' ? 'manual' : 'pending';
$notes = (string)($belowMap[$sid]['notes'] ?? '');
} else {
$decision = $score !== null ? '' : '';
$decision = '';
$source = 'pending';
$notes = '';
}
$rows[] = [
'student_id' => $sid,
'school_id' => $sr['school_id'] ?? '',
'firstname' => $sr['firstname'] ?? '',
'lastname' => $sr['lastname'] ?? '',
'class_section_name' => $sr['class_section_name'] ?? '',
'semester_score' => $score,
'school_id' => $info['school_id'],
'firstname' => $info['firstname'],
'lastname' => $info['lastname'],
'class_section_name' => $info['class_section_name'],
'fall_score' => $fall,
'spring_score' => $spring,
'year_score' => $yearScore,
'decision' => $decision,
'source' => $source,
'notes' => $notes,
@@ -2635,7 +2690,6 @@ class GradingController extends Controller
return view('grading/all_decisions', [
'rows' => $rows,
'semester' => $semester,
'schoolYear' => $schoolYear,
'schoolYears' => $schoolYears,
'generated' => $generated,
@@ -2644,49 +2698,73 @@ class GradingController extends Controller
public function generateAllDecisions()
{
$semester = trim((string)$this->request->getPost('semester'));
$schoolYear = trim((string)$this->request->getPost('school_year'));
if ($semester === '' || $schoolYear === '') {
return redirect()->back()->with('error', 'Missing semester or school year.');
if ($schoolYear === '') {
return redirect()->back()->with('error', 'Missing school year.');
}
$semKey = strtolower(trim($semester));
$scoreRows = $this->db->table('semester_scores ss')
// Fetch Fall and Spring scores per student
$allScoreRows = $this->db->table('semester_scores ss')
->select([
's.id AS student_id',
's.firstname',
's.lastname',
'cs.class_section_name',
'LOWER(TRIM(ss.semester)) AS sem_key',
'ss.semester_score',
])
->join('students s', 's.id = ss.student_id', 'inner')
->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left')
->where('s.is_active', 1)
->where('ss.school_year', $schoolYear)
->where("LOWER(TRIM(ss.semester))", $semKey)
->whereIn('LOWER(TRIM(ss.semester))', ['fall', 'spring'])
->where('ss.semester_score IS NOT NULL', null, false)
->get()->getResultArray();
if (empty($scoreRows)) {
return redirect()->back()->with('error', 'No semester scores found for this selection.');
if (empty($allScoreRows)) {
return redirect()->back()->with('error', 'No semester scores found for this school year.');
}
// Pull all below-60 decisions for this term
// Group by student
$studentMap = [];
foreach ($allScoreRows as $sr) {
$sid = (int)$sr['student_id'];
if (!isset($studentMap[$sid])) {
$studentMap[$sid] = [
'firstname' => $sr['firstname'] ?? '',
'lastname' => $sr['lastname'] ?? '',
'class_section_name' => $sr['class_section_name'] ?? '',
'fall_score' => null,
'spring_score' => null,
];
}
$semKey = strtolower(trim((string)($sr['sem_key'] ?? '')));
$val = is_numeric($sr['semester_score']) ? (float)$sr['semester_score'] : null;
if ($semKey === 'fall') {
$studentMap[$sid]['fall_score'] = $val;
} elseif ($semKey === 'spring') {
$studentMap[$sid]['spring_score'] = $val;
}
}
// Pull below-60 decisions for any semester of this year
$belowDecModel = new BelowSixtyDecisionModel();
$belowRows = $belowDecModel
->where('semester', $semester)
->where('school_year', $schoolYear)
->findAll();
$belowMap = [];
foreach ($belowRows as $b) {
$belowMap[(int)$b['student_id']] = $b;
$sid = (int)$b['student_id'];
if (!isset($belowMap[$sid]) || (string)($belowMap[$sid]['decision'] ?? '') === '') {
$belowMap[$sid] = $b;
}
}
// Load existing saved decisions to upsert
// Load existing year decisions to upsert
$decModel = new StudentDecisionModel();
$existing = $decModel
->where('semester', $semester)
->where('semester', 'year')
->where('school_year', $schoolYear)
->findAll();
$existingMap = [];
@@ -2695,16 +2773,23 @@ class GradingController extends Controller
}
$userId = (int)(session()->get('user_id') ?? 0) ?: null;
$now = utc_now();
$saved = 0;
$savedCount = 0;
foreach ($scoreRows as $sr) {
$sid = (int)$sr['student_id'];
$score = is_numeric($sr['semester_score']) ? (float)$sr['semester_score'] : null;
foreach ($studentMap as $sid => $info) {
$fall = $info['fall_score'];
$spring = $info['spring_score'];
if ($score === null) continue;
if ($fall !== null && $spring !== null) {
$yearScore = round(($fall + $spring) / 2, 2);
} elseif ($fall !== null) {
$yearScore = $fall;
} elseif ($spring !== null) {
$yearScore = $spring;
} else {
continue;
}
if ($score >= 60) {
if ($yearScore >= 60) {
$decision = 'Pass';
$source = 'auto';
$notes = null;
@@ -2720,10 +2805,10 @@ class GradingController extends Controller
$payload = [
'student_id' => $sid,
'semester' => $semester,
'semester' => 'year',
'school_year' => $schoolYear,
'class_section_name' => $sr['class_section_name'] ?? null,
'semester_score' => $score,
'class_section_name' => $info['class_section_name'] ?? null,
'semester_score' => $yearScore,
'decision' => $decision,
'source' => $source,
'notes' => $notes,
@@ -2735,12 +2820,12 @@ class GradingController extends Controller
} else {
$decModel->insert($payload);
}
$saved++;
$savedCount++;
}
$query = http_build_query(['semester' => $semester, 'school_year' => $schoolYear]);
$query = http_build_query(['school_year' => $schoolYear]);
return redirect()->to(base_url('grading/decisions') . '?' . $query)
->with('status', "Decisions generated for {$saved} students.");
->with('status', "Decisions generated for {$savedCount} students.");
}
public function getScoreComment()
+186 -26
View File
@@ -73,19 +73,6 @@ class ReportCardsController extends PrintablesBaseController
$semesters = array_values(array_filter(array_map(static fn($r) => (string)($r['semester'] ?? ''), $rs)));
} catch (\Throwable $e) {
}
try {
$rs2 = $this->db->table('student_class')
->select('DISTINCT semester', false)
->where('school_year', $year)
->where('semester IS NOT NULL', null, false)
->orderBy('semester', 'ASC')
->get()->getResultArray();
foreach ($rs2 as $r) {
$val = (string)($r['semester'] ?? '');
if ($val !== '' && !in_array($val, $semesters, true)) $semesters[] = $val;
}
} catch (\Throwable $e) {
}
// Ensure standard options are present even if data is missing (so Spring can be selected)
$defaults = array_values(array_filter([(string)($this->semester ?? ''), 'Fall', 'Spring'], static fn($v) => $v !== ''));
foreach ($defaults as $d) {
@@ -858,6 +845,7 @@ class ReportCardsController extends PrintablesBaseController
$studentName = trim(($data['student']['firstname'] ?? '') . ' ' . ($data['student']['lastname'] ?? ''));
$gradeLabel = (string)($data['grade'] ?? ($data['class_section_name'] ?? 'N/A'));
$today = (string)($data['report_date_display'] ?? date('m-d-Y'));
$termRank = trim((string)($data['term_rank_display'] ?? ''));
$firstSemScore = $data['first_semester_score'] ?? null;
$secondSemScore = $data['second_semester_score'] ?? ($data['total_score'] ?? null);
$finalAverage = $data['final_average'] ?? null;
@@ -993,7 +981,16 @@ class ReportCardsController extends PrintablesBaseController
$gradeY = $pdf->GetY();
$gradePad = max(0, 2 - (4 * 0.3528)); // reduce indentation by 4pt
$drawGradeCell = static function (\FPDF $pdf, float $x, float $y, float $w, float $h, string $label, string $value, float $pad) {
$drawGradeCell = static function (
\FPDF $pdf,
float $x,
float $y,
float $w,
float $h,
string $label,
string $value,
float $pad
) {
$pdf->Rect($x, $y, $w, $h);
$pdf->SetXY($x + $pad, $y + 3);
$pdf->SetFont('Helvetica', 'B', 11);
@@ -1003,43 +1000,71 @@ class ReportCardsController extends PrintablesBaseController
$pdf->SetXY($x + 2 + $labelWidth, $y + 3);
$pdf->Write(5, $value);
};
$drawRankCell = static function (
\FPDF $pdf,
float $x,
float $y,
float $w,
float $h,
string $rankValue,
float $pad
) {
$rankValue = trim($rankValue) !== '' ? trim($rankValue) : 'N/A';
$pdf->Rect($x, $y, $w, $h);
$pdf->SetXY($x + $pad, $y + 3);
$pdf->SetFont('Helvetica', 'B', 11);
$pdf->Write(5, ' Ranking: ');
$labelWidth = $pdf->GetStringWidth(' Ranking: ');
$pdf->SetFont('Helvetica', '', 12);
$pdf->SetXY($x + 2 + $labelWidth, $y + 3);
$pdf->Write(5, $rankValue);
};
if ($semNum === 2) {
$firstLabel = ' 1st Semester Grade:';
$firstValue = $numFmt($firstSemScore) . '/100';
$secondLabel = ' 2nd Semester Grade:';
$secondValue = $numFmt($effectiveSecond) . '/100';
$drawGradeCell($pdf, $gradeX, $gradeY, $gradeCellWidth, $gradeCellHeight, $firstLabel, $firstValue, $gradePad);
$drawGradeCell($pdf, $gradeX + $gradeCellWidth, $gradeY, $gradeCellWidth, $gradeCellHeight, $secondLabel, $secondValue, $gradePad);
} else {
$gradeLabel = ' 1st Semester Grade:';
$gradeValue = $numFmt($effectiveSecond) . '/100';
$drawGradeCell($pdf, $gradeX, $gradeY, $gradeCellWidth, $gradeCellHeight, $gradeLabel, $gradeValue, $gradePad);
// Fall: Ranking goes to the right of 1st Semester Grade.
$drawRankCell($pdf, $gradeX + $gradeCellWidth, $gradeY, $gradeCellWidth, $gradeCellHeight, $termRank, $gradePad);
}
$pdf->SetY($gradeY + $gradeCellHeight);
$finalScoreRowHeight = 12;
$finalScoreEndY = null;
// Show Final Score only for second semester / Spring
// Show Final Score only for second semester / Spring.
if ($semNum === 2) {
$finalLabel = 'Final Score****:';
$finalValue = (is_string($finalScoreVal) ? $finalScoreVal : $numFmt($finalScoreVal)) . '/100';
$finalCellWidth = 65; // match Total Semester Days column width
$finalCellWidth = 65;
$finalCellHeight = $finalScoreRowHeight;
$finalX = $pdf->GetX();
$finalY = $pdf->GetY();
$pdf->Rect($finalX, $finalY, $finalCellWidth, $finalCellHeight);
$finalPad = max(0, 2 - (4 * 0.3528)); // reduce indentation by 4pt
$pdf->SetXY($finalX + $finalPad, $finalY + 3);
$pdf->SetFont('Helvetica', 'B', 11);
$pdf->Write(5, ' ' . $finalLabel . ' ');
$finalLabelWidth = $pdf->GetStringWidth(' ' . $finalLabel . ' ');
$pdf->SetFont('Helvetica', '', 12);
$pdf->SetXY($finalX + 2 + $finalLabelWidth, $finalY + 3);
$pdf->Write(5, $finalValue);
$pdf->Ln($finalCellHeight);
$finalPad = max(0, 2 - (4 * 0.3528));
$drawGradeCell($pdf, $finalX, $finalY, $finalCellWidth, $finalCellHeight, ' ' . $finalLabel, $finalValue, $finalPad);
// Spring: Ranking goes to the right of Final Score.
$drawRankCell($pdf, $finalX + $finalCellWidth, $finalY, $finalCellWidth, $finalCellHeight, $termRank, $finalPad);
$pdf->SetY($finalY + $finalCellHeight);
$finalScoreEndY = $finalY + $finalCellHeight;
}
$scoresEndY = $pdf->GetY();
// Legend anchored near bottom with a 3mm buffer (dynamic placement)
@@ -1559,6 +1584,15 @@ class ReportCardsController extends PrintablesBaseController
$firstSemesterScore = $secondSemesterScore;
}
$termRanking = $this->calculateTermRanking(
$studentId,
$sectionCode,
$sectionId,
$refYear,
$refSemester !== '' ? $refSemester : (string)($score['semester'] ?? ''),
$secondSemesterScore
);
// Total semester days from attendance records (max total_attendance within same term)
$totalSemesterDays = null;
@@ -1685,12 +1719,138 @@ class ReportCardsController extends PrintablesBaseController
'second_semester_score' => $secondSemesterScore,
'first_semester_score' => $firstSemesterScore,
'final_average' => $finalAverage,
'term_rank' => $termRanking,
'term_rank_display' => $termRanking['display'] ?? null,
'comments' => $commentMap,
'total_score' => $secondSemesterScore,
'total_attendance_days' => $totalSemesterDays,
];
}
private function calculateTermRanking(
int $studentId,
int $sectionCode,
?int $sectionId,
string $schoolYear,
?string $semester,
?float $studentScore
): ?array {
if ($studentId <= 0 || $schoolYear === '' || $studentScore === null) {
return null;
}
$sectionIds = array_values(array_unique(array_filter([
$sectionCode > 0 ? $sectionCode : null,
$sectionId && $sectionId > 0 ? $sectionId : null,
])));
if (empty($sectionIds)) {
return null;
}
$builder = $this->db->table('semester_scores ss')
->select('ss.student_id, ss.semester_score, ss.updated_at, ss.id, s.firstname, s.lastname')
->join('students s', 's.id = ss.student_id', 'inner')
->where('s.is_active', 1)
->where('ss.school_year', $schoolYear)
->whereIn('ss.class_section_id', $sectionIds)
->orderBy('ss.updated_at', 'DESC')
->orderBy('ss.id', 'DESC');
if (trim((string)$semester) !== '') {
$this->applySemesterFilter($builder, (string)$semester, 'ss.semester');
}
$rows = $builder->get()->getResultArray();
if (empty($rows)) {
return null;
}
$scoresByStudent = [];
foreach ($rows as $row) {
$sid = (int)($row['student_id'] ?? 0);
if ($sid <= 0 || isset($scoresByStudent[$sid])) {
continue;
}
$scoreVal = $row['semester_score'] ?? null;
if (!is_numeric($scoreVal)) {
continue;
}
$scoresByStudent[$sid] = [
'student_id' => $sid,
'score' => round((float)$scoreVal, 4),
'firstname' => trim((string)($row['firstname'] ?? '')),
'lastname' => trim((string)($row['lastname'] ?? '')),
];
}
if (empty($scoresByStudent) || !isset($scoresByStudent[$studentId])) {
return null;
}
$rankable = array_values($scoresByStudent);
usort($rankable, static function (array $a, array $b): int {
$scoreCmp = $b['score'] <=> $a['score'];
if ($scoreCmp !== 0) {
return $scoreCmp;
}
$lastCmp = strcasecmp($a['lastname'], $b['lastname']);
if ($lastCmp !== 0) {
return $lastCmp;
}
$firstCmp = strcasecmp($a['firstname'], $b['firstname']);
if ($firstCmp !== 0) {
return $firstCmp;
}
return $a['student_id'] <=> $b['student_id'];
});
$position = null;
$previousScore = null;
foreach ($rankable as $index => $row) {
if ($previousScore === null || abs($row['score'] - $previousScore) > 0.0001) {
$position = $index + 1;
$previousScore = $row['score'];
}
if ((int)$row['student_id'] === $studentId) {
$total = count($rankable);
return [
'position' => $position,
'total' => $total,
'display' => $this->formatOrdinal($position) . ' of ' . $total,
];
}
}
return null;
}
private function formatOrdinal(?int $value): string
{
$n = (int)$value;
if ($n <= 0) {
return '';
}
$mod100 = $n % 100;
if ($mod100 >= 11 && $mod100 <= 13) {
return $n . 'th';
}
return match ($n % 10) {
1 => $n . 'st',
2 => $n . 'nd',
3 => $n . 'rd',
default => $n . 'th',
};
}
/**
* Resolve teacher & TA names for a class section (by PK or code),
* relaxing semester/year if needed. Returns ['teacher_name' => string, 'ta_names' => string[]].
@@ -0,0 +1,73 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddVerificationTokenToCertificateRecords extends Migration
{
private const INDEX_NAME = 'uniq_certificate_verification_token';
public function up()
{
if (!$this->db->tableExists('certificate_records')) {
return;
}
if (!$this->db->fieldExists('verification_token', 'certificate_records')) {
$this->forge->addColumn('certificate_records', [
'verification_token' => [
'type' => 'VARCHAR',
'constraint' => 64,
'null' => true,
'after' => 'certificate_number',
],
]);
}
$rows = $this->db->table('certificate_records')
->select('id, verification_token')
->get()
->getResultArray();
foreach ($rows as $row) {
if (trim((string)($row['verification_token'] ?? '')) !== '') {
continue;
}
$this->db->table('certificate_records')
->where('id', (int)$row['id'])
->update(['verification_token' => $this->generateToken()]);
}
$this->forge->addUniqueKey('verification_token', self::INDEX_NAME);
$this->forge->processIndexes('certificate_records');
}
public function down()
{
if (!$this->db->tableExists('certificate_records')) {
return;
}
if ($this->db->fieldExists('verification_token', 'certificate_records')) {
$this->forge->dropKey('certificate_records', self::INDEX_NAME);
$this->forge->dropColumn('certificate_records', 'verification_token');
}
}
private function generateToken(): string
{
do {
$token = bin2hex(random_bytes(16));
$exists = $this->db->table('certificate_records')
->select('id')
->where('verification_token', $token)
->limit(1)
->get()
->getRowArray();
} while ($exists);
return $token;
}
}
+18
View File
@@ -12,6 +12,7 @@ class CertificateRecordModel extends Model
protected $allowedFields = [
'certificate_number',
'verification_token',
'student_id',
'student_name',
'grade',
@@ -44,6 +45,23 @@ class CertificateRecordModel extends Model
return 'ARSS-' . $schoolYear . '-' . $seq;
}
public function generateVerificationToken(): string
{
$db = \Config\Database::connect();
do {
$token = bin2hex(random_bytes(16));
$exists = $db->table($this->table)
->select('id')
->where('verification_token', $token)
->limit(1)
->get()
->getRowArray();
} while ($exists);
return $token;
}
/** Returns paginated records with the issuing admin's name joined. */
public function getAuditLog(?string $schoolYear = null, int $perPage = 50): array
{
+1 -1
View File
@@ -44,7 +44,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -362,7 +362,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
$rank = 0;
foreach ($classResults as $cls):
foreach ($cls['students'] as $s):
if ($s['status'] === 'none') continue;
if (!in_array($s['status'], ['confirmed', 'surprise'], true)) continue;
$rank++;
$isMale = strtolower($s['gender'] ?? '') === 'male';
$statusLabel = match ($s['status']) {
+1 -1
View File
@@ -48,7 +48,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -14,7 +14,7 @@
<?= csrf_field() ?>
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -48,7 +48,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -11,7 +11,7 @@
.verify-card { max-width: 520px; margin: 60px auto; }
.badge-valid { background: #198754; }
.badge-invalid { background: #dc3545; }
.cert-logo { max-height: 70px; }
.cert-logo { width: 70px; height: 70px; border-radius: 50%; object-fit: cover; }
.field-label { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: #6c757d; }
.field-value { font-size: 1.05rem; font-weight: 500; }
</style>
+1 -1
View File
@@ -45,7 +45,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -44,7 +44,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -4,7 +4,7 @@
<div class="modal-content rounded-4 shadow border-0" style="max-width: 600px; margin: auto;">
<div class="modal-body text-center p-5">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Logo"
style="width: 180px; height: 120px;" class="mb-4">
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;" class="mb-4">
<h5 class="modal-title text-danger mb-3" id="blockedLabel">Access Blocked</h5>
<p class="lead mb-3">Too many password reset attempts have been made.</p>
<p>Please try again after 24 hours or contact support if you need urgent assistance.</p>
+1 -1
View File
@@ -5,7 +5,7 @@
<div class="bg-white p-5 rounded-5 shadow registration-form container" style="max-width: 600px; width: 100%;">
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -44,7 +44,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+36 -21
View File
@@ -5,11 +5,30 @@
<div class="wrapper">
<h2 class="text-center mt-4 mb-4">Student Decisions — All Students</h2>
<?= $this->include('partials/academic_filter') ?>
<!-- School Year filter only -->
<form method="get" class="row g-2 align-items-center justify-content-center mb-3">
<div class="col-auto"><label class="form-label mb-0">School year</label></div>
<div class="col-auto">
<select name="school_year" class="form-select form-select-sm" style="min-width: 180px;">
<?php $years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : []; ?>
<?php foreach ($years as $y):
$val = is_array($y) && isset($y['school_year']) ? (string)$y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ($schoolYear === $val ? 'selected' : '') ?>><?= esc($val) ?></option>
<?php endforeach; ?>
<?php if (empty($years) && $schoolYear !== ''): ?>
<option value="<?= esc($schoolYear) ?>" selected><?= esc($schoolYear) ?></option>
<?php endif; ?>
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
<a class="btn btn-outline-secondary btn-sm" href="?">Reset</a>
</div>
</form>
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<div class="text-muted">
<?= esc(ucfirst($semester ?? '')) ?> • <?= esc($schoolYear ?? '') ?>
<?= esc($schoolYear ?? '') ?>
<?php if ($generated): ?>
<span class="badge bg-success ms-2">Saved</span>
<?php else: ?>
@@ -17,10 +36,6 @@
<?php endif; ?>
</div>
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-outline-secondary btn-sm"
href="<?= site_url('grading/below-60/decisions?' . http_build_query(['semester' => $semester, 'school_year' => $schoolYear])) ?>">
Below-60 Decisions
</a>
<a class="btn btn-outline-secondary btn-sm" href="<?= base_url('grading') ?>">
Grading
</a>
@@ -43,22 +58,18 @@
<!-- Generate / Regenerate button -->
<form method="post" action="<?= site_url('grading/decisions/generate') ?>" class="mb-3">
<?= csrf_field() ?>
<input type="hidden" name="semester" value="<?= esc($semester ?? '') ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear ?? '') ?>">
<button type="submit" class="btn btn-primary">
<?= $generated ? 'Regenerate Decisions' : 'Generate Decisions' ?>
</button>
<span class="text-muted small ms-2">
Scores ≥ 60 → <strong>Pass</strong> (auto) &nbsp;|&nbsp;
Scores &lt; 60 → pulled from
<a href="<?= site_url('grading/below-60/decisions?' . http_build_query(['semester' => $semester, 'school_year' => $schoolYear])) ?>">
Below-60 Decisions
</a>
Year score ≥ 60 → <strong>Pass</strong> (auto) &nbsp;|&nbsp;
Year score &lt; 60 → pulled from below-60 decisions
</span>
</form>
<?php if (empty($rows)): ?>
<div class="alert alert-info">No semester scores found for this selection.</div>
<div class="alert alert-info">No semester scores found for this school year.</div>
<?php else: ?>
<?php
@@ -76,7 +87,6 @@
'pending' => ['bg-warning-subtle text-warning-emphasis', 'Pending'],
];
// Stats
$stats = ['Pass' => 0, 'Other' => 0, 'Pending' => 0];
foreach ($rows as $r) {
if ($r['decision'] === 'Pass') $stats['Pass']++;
@@ -112,7 +122,9 @@
<tr>
<th>Student Name</th>
<th>Section</th>
<th class="text-center">Score</th>
<th class="text-center">Fall Score</th>
<th class="text-center">Spring Score</th>
<th class="text-center">Year Score</th>
<th class="text-center">Decision</th>
<th class="text-center">Source</th>
<th>Notes</th>
@@ -121,23 +133,26 @@
<tbody>
<?php foreach ($rows as $row): ?>
<?php
$score = $row['semester_score'];
$yearScore = $row['year_score'];
$decision = (string)($row['decision'] ?? '');
$source = (string)($row['source'] ?? 'pending');
$scoreVal = is_numeric($score) ? (float)$score : null;
$yearVal = is_numeric($yearScore) ? (float)$yearScore : null;
$rowClass = '';
if ($scoreVal !== null && $scoreVal < 60) {
$rowClass = $scoreVal < 50 ? 'grade-red' : 'grade-orange';
if ($yearVal !== null && $yearVal < 60) {
$rowClass = $yearVal < 50 ? 'grade-red' : 'grade-orange';
}
$badge = $decisionBadge[$decision] ?? null;
[$srcCls, $srcLabel] = $sourceBadge[$source] ?? ['bg-light text-muted', $source];
$studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? ''));
$fmt = fn($v) => is_numeric($v) ? number_format((float)$v, 2) : '—';
?>
<tr class="<?= esc($rowClass) ?>">
<td><?= esc($studentName ?: 'N/A') ?></td>
<td><?= esc($row['class_section_name'] ?? '—') ?></td>
<td class="text-center"><?= esc($fmt($row['fall_score'] ?? null)) ?></td>
<td class="text-center"><?= esc($fmt($row['spring_score'] ?? null)) ?></td>
<td class="text-center fw-semibold">
<?= $scoreVal !== null ? esc(number_format($scoreVal, 2)) : '—' ?>
<?= esc($fmt($yearVal)) ?>
</td>
<td class="text-center">
<?php if ($decision !== '' && $badge): ?>
@@ -188,7 +203,7 @@
order: [[1, 'asc'], [0, 'asc']],
pageLength: 100,
lengthMenu: [25, 50, 100, 200],
columnDefs: [{ orderable: false, targets: [5] }]
columnDefs: [{ orderable: false, targets: [7] }]
});
} catch (_) {}
});
+15 -3
View File
@@ -9,13 +9,15 @@
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<div class="text-muted">
<?= esc(ucfirst($semester ?? '')) ?> • <?= esc($schoolYear ?? '') ?>
<?= !empty($isYearMode) ? 'Whole Year' : esc(ucfirst($semester ?? '')) ?> • <?= esc($schoolYear ?? '') ?>
</div>
<div class="d-flex gap-2">
<?php if (empty($isYearMode)): ?>
<a class="btn btn-outline-primary btn-sm"
href="<?= site_url('grading/below-60/decisions?' . http_build_query(['semester' => $semester ?? '', 'school_year' => $schoolYear ?? ''])) ?>">
Decisions
</a>
<?php endif; ?>
<?php if (!empty($canViewGrading)): ?>
<a class="btn btn-outline-secondary btn-sm" href="<?= base_url('grading') ?>">
Back to Grading
@@ -47,6 +49,7 @@
<tr>
<th>Student Name</th>
<th>Section</th>
<?php if (!empty($isYearMode)): ?><th>Semester</th><?php endif; ?>
<th>Hwk Avg</th>
<th>Project Avg</th>
<th>Participation</th>
@@ -54,10 +57,12 @@
<th>PTAP Score</th>
<th>Attendance</th>
<th>Midterm Score</th>
<th><?= strcasecmp($semester ?? '', 'fall') === 0 ? '1st Semester Score' : 'Semester Score' ?></th>
<th><?= !empty($isYearMode) ? 'Semester Score' : (strcasecmp($semester ?? '', 'fall') === 0 ? '1st Semester Score' : 'Semester Score') ?></th>
<?php if (empty($isYearMode)): ?>
<th>Status</th>
<th>Email Parent</th>
<th>Schedule Meeting</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
@@ -76,9 +81,13 @@
$studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? ''));
?>
<?php $isClosed = ($row['status'] ?? 'Open') === 'Closed'; ?>
<?php $rowSemester = ucfirst(strtolower(trim((string)($row['semester'] ?? ($semester ?? ''))))); ?>
<tr class="<?= esc($scoreClass) ?>">
<td><?= esc($studentName !== '' ? $studentName : 'N/A') ?></td>
<td><?= esc($row['class_section_name'] ?? '—') ?></td>
<?php if (!empty($isYearMode)): ?>
<td class="text-center"><?= esc($rowSemester) ?></td>
<?php endif; ?>
<td class="text-center"><?= $displayScore($row['homework_avg'] ?? null) ?></td>
<td class="text-center"><?= $displayScore($row['project_avg'] ?? null) ?></td>
<td class="text-center"><?= $displayScore($row['participation_score'] ?? null) ?></td>
@@ -87,6 +96,7 @@
<td class="text-center"><?= $displayScore($row['attendance_score'] ?? null) ?></td>
<td class="text-center"><?= $displayScore($row['midterm_exam_score'] ?? null) ?></td>
<td class="text-center"><?= $displayScore($row['semester_score'] ?? null) ?></td>
<?php if (empty($isYearMode)): ?>
<td class="text-center">
<form method="post" action="<?= site_url('grading/below-60/status') ?>" class="d-flex align-items-center gap-2 justify-content-center">
<?= csrf_field() ?>
@@ -121,6 +131,7 @@
</a>
<?php endif; ?>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
@@ -147,8 +158,9 @@
const table = $('.below-sixty-dt');
if (!table.length) return;
try {
const semesterOffset = <?= !empty($isYearMode) ? '1' : '0' ?>;
table.DataTable({
order: [[8, 'asc']],
order: [[8 + semesterOffset, 'asc']],
pageLength: 100,
lengthMenu: [10, 25, 50, 100, 200]
});
+1 -1
View File
@@ -412,7 +412,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 60px; width: 80px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 60px; width: 60px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title"></h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -57,7 +57,7 @@
<body>
<div class="email-container">
<div class="email-nopicture">
<img src="https://alrahmaisgl.org/assets/images/alrahma_logo.png" alt="" style="width: 150px; height: 110px">
<img src="https://alrahmaisgl.org/assets/images/alrahma_logo.png" alt="" style="width: 110px; height: 110px; border-radius: 50%; object-fit: cover;">
</div>
<div class="email-body">
+1
View File
@@ -172,6 +172,7 @@
#navbarManagement[data-mgmt-menu-mode="dark"] .navbar-toggler-icon { filter: invert(1) brightness(2); }
.logout-btn { border-color: var(--mgmt-primary) !important; color: var(--mgmt-primary) !important; }
.logout-btn:hover { background-color: var(--mgmt-primary) !important; color: #fff !important; }
.school-logo-circle { border-radius: 50%; object-fit: cover; }
/* Sidebar (hover to reveal) */
#navbarManagement.mgmt-sidebar {
+1 -1
View File
@@ -1,7 +1,7 @@
<!-- navbar.php -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" class="school-logo-circle" style="height: 40px; width: 40px; object-fit: cover; border-radius: 50%;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+3
View File
@@ -54,6 +54,9 @@ $classSectionVal = (string)($classSectionId ?? ($_GET['class_section_id'] ?? '')
<div class="col-auto">
<select name="semester" class="form-select form-select-sm" style="min-width: 140px;">
<option value="">—</option>
<?php if (!empty($showAllSemesterOption)): ?>
<option value="year" <?= (strcasecmp($semVal, 'year') === 0 ? 'selected' : '') ?>>Whole Year</option>
<?php endif; ?>
<option value="Fall" <?= (strcasecmp($semVal, 'Fall') === 0 ? 'selected' : '') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal, 'Spring') === 0 ? 'selected' : '') ?>>Spring</option>
</select>
+1 -1
View File
@@ -56,7 +56,7 @@
<div class="d-flex align-items-center w-100">
<!-- Brand (left) -->
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-1" href="/administrator/administratordashboard">
<img src="<?= base_url('assets/images/logo.png') ?>" alt="School Icon" style="width: 50px; height: 40px; margin-right: 8px;">
<img src="<?= base_url('assets/images/logo.png') ?>" alt="School Icon" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover; margin-right: 8px;">
<strong>School Management Dashboard</strong>
</a>
+1 -1
View File
@@ -15,7 +15,7 @@ Test PASSWORD: 9JC?].qM
<div class="mb-4">
<a href="<?= base_url('/parent_dashboard') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Alrahma Logo"
style="width: 180px; height: 120px;">
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -5,7 +5,7 @@
<div class="bg-white p-5 rounded-5 shadow registration-form container" style="max-width: 600px; width: 100%;">
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -43,7 +43,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -48,7 +48,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -48,7 +48,7 @@
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
+1 -1
View File
@@ -14,7 +14,7 @@
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt=""
style="width: 180px; height: 120px;">
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt=""
style="width: 180px; height: 120px;">
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
@@ -4,7 +4,7 @@
<div class="modal-content rounded-4 shadow border-0" style="max-width: 600px; margin: auto;">
<div class="modal-body text-center">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Logo" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Logo" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
<h5 class="modal-title text-success" id="emailConfirmLabel">Check Your Email</h5>
<p class="lead mt-3">
A link to reset the password has been sent to this email:<br>
+1 -1
View File
@@ -9,7 +9,7 @@
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-success shadow">
+1 -1
View File
@@ -20,7 +20,7 @@
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt=""
style="width: 180px; height: 120px;">
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -11,7 +11,7 @@
<?= csrf_field() ?>
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;"></a>
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;"></a>
</div>
<h3 class="text-center text-success" style="font-family: Arial, sans-serif;">Reset Your Password</h3>
<br>
@@ -6,7 +6,7 @@
<?= csrf_field(); ?>
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+1 -1
View File
@@ -8,7 +8,7 @@
<?= csrf_field(); ?>
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
</a>
</div>
+30
View File
@@ -0,0 +1,30 @@
services:
mysql:
image: mysql:8.4
container_name: mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: school
MYSQL_USER: root
MYSQL_PASSWORD: password
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
platform: linux/amd64
container_name: phpmyadmin
restart: unless-stopped
environment:
PMA_HOST: mysql
PMA_PORT: 3306
ports:
- "8081:80"
depends_on:
- mysql
volumes:
mysql_data:
+9 -3
View File
@@ -321,12 +321,18 @@ color: #080808;
.navbar-logo {
height: 80px !important;
width: auto !important;
object-fit: contain;
height: 50px !important;
width: 50px !important;
object-fit: cover;
border-radius: 50%;
margin-right: 1rem;
}
.school-logo-circle {
border-radius: 50%;
object-fit: cover;
}
.logout-btn {
font-weight: bold;
+3
View File
@@ -22,6 +22,9 @@ html {
.logo {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 20px;
}
@@ -22,9 +22,10 @@ body {
}
.modal-header .logo {
width: 100%;
max-width: 200px;
height: auto;
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 10px;
}
+4 -1
View File
@@ -125,7 +125,10 @@ body.centered {
}
.logo {
width: 200px;
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 20px;
}
+4 -2
View File
@@ -31,8 +31,10 @@ body {
}
.left-side .logo {
max-width: 100%;
height: auto;
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
margin-top: 10px;
}
+8 -8
View File
@@ -1,22 +1,22 @@
.left-side .logo {
width: 100%;
max-width: 300px;
/* Adjustable maximum size */
height: auto;
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
}
/* Responsive adjustments for smaller screens */
@media (max-width: 768px) {
.left-side .logo {
max-width: 300px;
/* Reduce size on smaller screens */
width: 90px;
height: 90px;
}
}
@media (max-width: 480px) {
.left-side .logo {
max-width: 150px;
/* Further reduce size for very small screens */
width: 70px;
height: 70px;
}
}