Compare commits

..

7 Commits

Author SHA1 Message Date
root 39ab0d113e fix score comments 2026-05-29 01:52:17 -04:00
root ce56c96cdd fix paste button for comment review spring 2026-05-29 00:18:12 -04:00
root b4a06903e2 fix certificate qr code verification 2026-05-28 02:15:08 -04:00
root 0654668530 fix report ranking 2026-05-28 01:55:34 -04:00
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
50 changed files with 790 additions and 253 deletions
+1
View File
@@ -9,6 +9,7 @@ use CodeIgniter\Format\XMLFormatter;
class Format extends BaseConfig
{
public int $jsonEncodeDepth = 512;
/**
* --------------------------------------------------------------------------
* Available Response Formats
+3
View File
@@ -538,6 +538,9 @@ $routes->get('grading/(:segment)/(:num)/(:num)', 'View\GradingController::show/$
$routes->post('grading/update', 'View\GradingController::update');
$routes->get('grading', 'View\GradingController::grading');
$routes->post('grading/toggle-score-lock', 'View\GradingController::toggleScoreLock', ['filter' => 'auth:read']);
$routes->get('grading/toggle-score-lock', static function () {
return 'GET route hit. Something is calling this route incorrectly.';
});
$routes->post('grading/lock-all-scores', 'View\GradingController::lockAllScores', ['filter' => 'auth:read']);
$routes->post('grading/release-scores', 'View\GradingController::toggleParentScoresRelease', ['filter' => 'auth:read']);
$routes->post('grading/refresh-semester-scores', 'View\GradingController::refreshSemesterScores', ['filter' => 'auth:read']);
+27 -7
View File
@@ -10,8 +10,13 @@ class ProofreadController extends ResourceController
{
// Basic per-IP throttling: 10 requests per minute
$throttler = service('throttler');
$key = 'proofread-' . $this->request->getIPAddress();
if (!$throttler->check($key, 10, MINUTE)) {
// Cache/throttler keys cannot contain reserved characters.
// Raw IPv6 addresses contain ":", so hash the IP first.
$ip = $this->request->getIPAddress();
$key = 'proofread-' . hash('sha256', $ip);
if (! $throttler->check($key, 10, MINUTE)) {
return $this->respond([
'ok' => false,
'error' => 'Too many requests. Try again in a minute.',
@@ -20,8 +25,9 @@ class ProofreadController extends ResourceController
}
// Accept form-urlencoded payload to play nicely with CSRF protection
$validation = service('validation');
$payload = sanitize_request_value($this->request->getPost(['text']));
$validation = service('validation');
$validation->setRules([
'text' => 'required|min_length[1]|max_length[20000]',
]);
@@ -36,20 +42,34 @@ class ProofreadController extends ResourceController
$text = (string) $payload['text'];
$client = \Config\Services::curlrequest(['timeout' => 10]);
$client = \Config\Services::curlrequest([
'timeout' => 10,
]);
try {
$resp = $client->post('https://api.languagetool.org/v2/check', [
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
],
'form_params' => [
'text' => $text,
'language' => 'en-US',
],
]);
$result = json_decode($resp->getBody(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
return $this->respond([
'ok' => false,
'error' => 'Invalid response from proofread service.',
'csrfHash' => csrf_hash(),
], 502);
}
return $this->respond([
'ok' => true,
'result' => json_decode($resp->getBody(), true),
'result' => $result,
'csrfHash' => csrf_hash(),
]);
} catch (\Throwable $e) {
@@ -60,4 +80,4 @@ class ProofreadController extends ResourceController
], 502);
}
}
}
}
+39 -10
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,34 +321,39 @@ 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'] ?? ''),
'cert_date' => $certDateDb,
//'cert_date' => $certDateDb,
'school_year' => $schoolYear,
'class_section_id' => $classSectionId ?: null,
'issued_by' => $issuedBy,
//'issued_by' => $issuedBy,
'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
+161 -76
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;
$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()
+275 -59
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,54 +981,91 @@ 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) {
$pdf->Rect($x, $y, $w, $h);
$pdf->SetXY($x + $pad, $y + 3);
$pdf->SetFont('Helvetica', 'B', 11);
$pdf->Write(5, $label . ' ');
$labelWidth = $pdf->GetStringWidth($label . ' ');
$pdf->SetFont('Helvetica', '', 12);
$pdf->SetXY($x + 2 + $labelWidth, $y + 3);
$pdf->Write(5, $value);
};
$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);
$pdf->Write(5, $label . ' ');
$labelWidth = $pdf->GetStringWidth($label . ' ');
$pdf->SetFont('Helvetica', '', 12);
$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';
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);
}
$pdf->SetY($gradeY + $gradeCellHeight);
$pdf->Rect($x, $y, $w, $h);
$pdf->SetXY($x + $pad, $y + 3);
$pdf->SetFont('Helvetica', 'B', 11);
$pdf->Write(5, ' Ranking: ');
$finalScoreRowHeight = 12;
$finalScoreEndY = null;
// 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
$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);
$finalScoreEndY = $finalY + $finalCellHeight;
}
$scoresEndY = $pdf->GetY();
$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.
if ($semNum === 2) {
$finalLabel = 'Final Score****:';
$finalValue = (is_string($finalScoreVal) ? $finalScoreVal : $numFmt($finalScoreVal)) . '/100';
$finalCellWidth = 65;
$finalCellHeight = $finalScoreRowHeight;
$finalX = $pdf->GetX();
$finalY = $pdf->GetY();
$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)
$examLegendLabel = ($semNum === 1) ? 'Midterm Exam' : 'Final Exam';
@@ -1559,6 +1584,20 @@ class ReportCardsController extends PrintablesBaseController
$firstSemesterScore = $secondSemesterScore;
}
$rankingScore = $secondSemesterScore;
if ($normSemester === 'spring' && is_numeric($firstSemesterScore) && is_numeric($secondSemesterScore)) {
$rankingScore = round(((float)$firstSemesterScore + (float)$secondSemesterScore) / 2, 1);
}
$termRanking = $this->calculateTermRanking(
$studentId,
$sectionCode,
$sectionId,
$refYear,
$refSemester !== '' ? $refSemester : (string)($score['semester'] ?? ''),
$rankingScore
);
// Total semester days from attendance records (max total_attendance within same term)
$totalSemesterDays = null;
@@ -1685,12 +1724,189 @@ 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;
}
$semesterForRank = trim((string)$semester);
$rankByFinalScore = $this->normalizeSemester($semesterForRank) === 'spring';
$builder = $this->db->table('semester_scores ss')
->select('ss.student_id, ss.semester, 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 ($semesterForRank !== '') {
$this->applySemesterFilter($builder, $semesterForRank, 'ss.semester');
}
$rows = $builder->get()->getResultArray();
if (empty($rows)) {
return null;
}
$scoresByStudent = [];
$studentIds = [];
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'] ?? '')),
];
$studentIds[] = $sid;
}
if (empty($scoresByStudent) || !isset($scoresByStudent[$studentId])) {
return null;
}
if ($rankByFinalScore) {
$firstRowsBuilder = $this->db->table('semester_scores ss')
->select('ss.student_id, ss.semester_score, ss.updated_at, ss.id')
->where('ss.school_year', $schoolYear)
->whereIn('ss.class_section_id', $sectionIds)
->whereIn('ss.student_id', $studentIds)
->orderBy('ss.updated_at', 'DESC')
->orderBy('ss.id', 'DESC');
if ($semesterForRank !== '') {
$this->applySemesterExclusion($firstRowsBuilder, $semesterForRank, 'ss.semester');
}
$firstRows = $firstRowsBuilder->get()->getResultArray();
$firstScoresByStudent = [];
foreach ($firstRows as $row) {
$sid = (int)($row['student_id'] ?? 0);
if ($sid <= 0 || isset($firstScoresByStudent[$sid])) {
continue;
}
$scoreVal = $row['semester_score'] ?? null;
if (!is_numeric($scoreVal)) {
continue;
}
$firstScoresByStudent[$sid] = (float)$scoreVal;
}
foreach ($scoresByStudent as $sid => &$rankRow) {
if (!isset($firstScoresByStudent[$sid])) {
unset($scoresByStudent[$sid]);
continue;
}
$rankRow['score'] = round(((float)$firstScoresByStudent[$sid] + (float)$rankRow['score']) / 2, 4);
}
unset($rankRow);
if (empty($scoresByStudent) || !isset($scoresByStudent[$studentId])) {
return null;
}
$scoresByStudent[$studentId]['score'] = round((float)$studentScore, 4);
}
$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[]].
@@ -231,18 +231,36 @@ class ScoreCommentController extends BaseController
$rawCommentsBuilder->groupEnd();
}
if (!$isAllSections && $classSectionId > 0) {
// Prefer the exact class-section row over legacy NULL class-section rows.
// Without this, duplicate rows can overwrite each other unpredictably.
$rawCommentsBuilder
->orderBy("CASE WHEN class_section_id = {$classSectionId} THEN 0 ELSE 1 END", '', false)
->orderBy('id', 'DESC');
} else {
$rawCommentsBuilder->orderBy('id', 'DESC');
}
$rawComments = $rawCommentsBuilder->findAll();
$existingAttendance = [];
foreach ($rawComments as $c) {
$sid = (int)$c['student_id'];
$typ = (string)$c['score_type'];
$sid = (int) $c['student_id'];
$typ = strtolower(trim((string) $c['score_type']));
// The query is ordered with the best row first. Do not let legacy/older
// duplicate rows overwrite the selected comment/review.
if (isset($comments[$sid][$typ])) {
continue;
}
$comments[$sid][$typ] = [
'comment' => $c['comment'] ?? null,
'comment_review' => $c['comment_review'] ?? null,
'commented_by' => $c['commented_by'] ?? null,
'reviewed_by' => $c['reviewed_by'] ?? null,
];
if ($typ === 'attendance') {
$existingAttendance[$sid] = $c;
}
@@ -435,6 +453,16 @@ class ScoreCommentController extends BaseController
$existingQuery->groupEnd();
}
if (is_int($classSectionId) && $classSectionId > 0) {
// Prefer updating the real class-section row. If only a legacy NULL row
// exists, it will be picked next and migrated to the class section below.
$existingQuery
->orderBy("CASE WHEN class_section_id = {$classSectionId} THEN 0 ELSE 1 END", '', false)
->orderBy('id', 'DESC');
} else {
$existingQuery->orderBy('id', 'DESC');
}
$existing = $existingQuery->first();
$data = [
@@ -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">
+2 -2
View File
@@ -64,9 +64,9 @@
<th>Certificate #</th>
<th>Student</th>
<th>Grade</th>
<th>Cert Date</th>
<!--th>Cert Date</th-->
<th>School Year</th>
<th>Issued By</th>
<!--th>Issued By</th-->
<th>Issued At</th>
</tr>
</thead>
+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">
+2 -14
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>
@@ -51,19 +51,7 @@
<div class="col-6">
<div class="field-label">School Year</div>
<div class="field-value"><?= esc($record['school_year'] ?? '—') ?></div>
</div>
<div class="col-6">
<div class="field-label">Certificate Date</div>
<div class="field-value">
<?= $record['cert_date'] ? esc(date('F j, Y', strtotime($record['cert_date']))) : '—' ?>
</div>
</div>
<div class="col-6">
<div class="field-label">Issued By</div>
<div class="field-value">
<?= esc(trim(($record['admin_firstname'] ?? '') . ' ' . ($record['admin_lastname'] ?? '')) ?: '—') ?>
</div>
</div>
</div>
<div class="col-12">
<div class="field-label">Issued On</div>
<div class="field-value">
+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">
+38 -23
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
@@ -66,7 +77,7 @@
'Pass' => 'success',
'Repeat Class' => 'danger',
'Make-up exam in fall' => 'info',
'Deferred decision' => 'info',
'Deferred decision' => 'info',
'Expel' => 'danger',
'Withdrawn' => 'secondary',
];
@@ -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;
$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]
});
+13 -13
View File
@@ -32,7 +32,7 @@ $lockAttr = $scoresLocked ? 'disabled' : '';
</div>
<?php endif; ?>
<div class="d-flex justify-content-between mt-4 flex-wrap gap-2">
<button type="submit" class="btn btn-success" <?= $lockAttr ?>>
<button type="submit" form="commentsForm" class="btn btn-success" <?= $lockAttr ?>>
<i class="bi bi-save"></i> Save All Changes
</button>
<a href="<?= base_url('/grading') ?>" class="btn btn-secondary">
@@ -177,7 +177,10 @@ $lockAttr = $scoresLocked ? 'disabled' : '';
</td>
<?php elseif ($semester === 'Spring'): ?>
<td>
<?php $finalCommentId = 'final-comment-' . $studentId; ?>
<?php $finalReviewId = 'final-review-' . $studentId; ?>
<textarea
id="<?= esc($finalCommentId) ?>"
name="comments[<?= $studentId ?>][final]"
rows="2"
class="form-control"
@@ -186,7 +189,6 @@ $lockAttr = $scoresLocked ? 'disabled' : '';
maxlength="350"
placeholder="Enter Final comment" <?= $lockAttr ?>><?= esc($comments[$studentId]['final']['comment'] ?? '') ?></textarea>
</td>
<?php $finalReviewId = 'final-review-' . $studentId; ?>
<td>
<div class="d-flex gap-2 align-items-start">
<textarea
@@ -196,6 +198,13 @@ $lockAttr = $scoresLocked ? 'disabled' : '';
class="form-control review-field"
placeholder="Enter Final review" <?= $lockAttr ?>><?= esc($comments[$studentId]['final']['comment_review'] ?? '') ?></textarea>
<div class="d-flex flex-column gap-1 align-items-stretch">
<button
type="button"
class="btn btn-outline-secondary btn-sm copy-final-btn"
data-copy-source="#<?= esc($finalCommentId) ?>"
data-copy-target="#<?= esc($finalReviewId) ?>" <?= $lockAttr ?>>
Paste
</button>
<div class="small text-muted proofread-status" data-status-for="<?= esc($finalReviewId) ?>"></div>
<ul class="proofread-list small mb-0" data-list-for="<?= esc($finalReviewId) ?>"></ul>
</div>
@@ -474,18 +483,9 @@ $lockAttr = $scoresLocked ? 'disabled' : '';
});
};
document.querySelectorAll('.copy-ptap-btn').forEach(button => {
button.addEventListener('click', function() {
const source = document.querySelector(this.dataset.copySource || '');
const target = document.querySelector(this.dataset.copyTarget || '');
if (!source || !target) return;
target.value = source.value;
target.style.height = 'auto';
target.style.height = (target.scrollHeight) + 'px';
target.focus();
});
});
wireCopyButtons('.copy-ptap-btn');
wireCopyButtons('.copy-midterm-btn');
wireCopyButtons('.copy-final-btn');
});
</script>
<?= $this->endSection() ?>
+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;
}
}