recreate project
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="wrapper">
|
||||
<div class="content mb-3"></div>
|
||||
<h2 class="text-center mt-4 mb-3">Grading Management</h2>
|
||||
<?php
|
||||
// Expect: $grades is keyed by class_id (1..11, 12=Youth, 13=KG)
|
||||
// Build stable order: KG (13) -> Grades 1..11 -> Youth (12) -> any other ids (ascending)
|
||||
|
||||
$semester = $semester ?? (session()->get('semester') ?? 'Fall');
|
||||
$schoolYear = $schoolYear ?? (session()->get('school_year') ?? date('Y') . '-' . (date('Y') + 1));
|
||||
$requestedClassId = isset($requestedClassId) ? (int)$requestedClassId : 0;
|
||||
$semesterOptions = $semesterOptions ?? [];
|
||||
$scoreLocks = $scoreLocks ?? [];
|
||||
if (empty($semesterOptions)) {
|
||||
$semesterOptions = ['Fall', 'Spring'];
|
||||
}
|
||||
if ($semester !== '' && !in_array($semester, $semesterOptions, true)) {
|
||||
$semesterOptions[] = $semester;
|
||||
}
|
||||
$semesterOptions = array_values(array_unique($semesterOptions));
|
||||
|
||||
// Slug from class_id
|
||||
$slugFor = function (int $cid): string {
|
||||
if ($cid === 13) return 'kg';
|
||||
if ($cid === 12) return 'youth';
|
||||
if ($cid >= 1 && $cid <= 11) return 'g' . $cid;
|
||||
return (string)$cid;
|
||||
};
|
||||
|
||||
// Compose ordered class ids from keys actually present in $grades
|
||||
$presentIds = array_map('intval', array_keys($grades));
|
||||
$presentSet = array_flip($presentIds);
|
||||
|
||||
$sortedClassIds = [];
|
||||
if (isset($presentSet[13])) $sortedClassIds[] = 13; // KG first
|
||||
for ($g = 1; $g <= 11; $g++) if (isset($presentSet[$g])) $sortedClassIds[] = $g; // Grades 1..11
|
||||
if (isset($presentSet[12])) $sortedClassIds[] = 12; // Youth last
|
||||
|
||||
// Include any other unexpected ids (edge cases) without breaking
|
||||
$others = array_diff($presentIds, $sortedClassIds);
|
||||
sort($others, SORT_NUMERIC);
|
||||
$sortedClassIds = array_merge($sortedClassIds, $others);
|
||||
|
||||
// Label overrides for non-standard class ids (e.g., Arabic-1)
|
||||
$classLabelById = [];
|
||||
foreach ($grades as $cid => $sections) {
|
||||
if (!empty($sections[0]['class_section_name'])) {
|
||||
$classLabelById[(int) $cid] = (string) $sections[0]['class_section_name'];
|
||||
}
|
||||
}
|
||||
|
||||
// Label from class_id
|
||||
$labelFor = function (int $cid) use ($classLabelById): string {
|
||||
if ($cid === 13) return 'KG';
|
||||
if ($cid === 12) return 'Youth';
|
||||
if ($cid >= 1 && $cid <= 11) return 'Grade ' . $cid;
|
||||
if (!empty($classLabelById[$cid])) return $classLabelById[$cid];
|
||||
return 'Class ' . $cid;
|
||||
};
|
||||
|
||||
// Distinct student counts per class_id for tab badges
|
||||
$studentsCountByClassId = [];
|
||||
foreach ($grades as $cid => $sections) {
|
||||
$uniq = [];
|
||||
foreach ($sections as $section) {
|
||||
$secId = (int)($section['class_section_id'] ?? 0);
|
||||
if ($secId > 0 && !empty($studentsBySection[$secId])) {
|
||||
foreach ($studentsBySection[$secId] as $stu) {
|
||||
if (!empty($stu['id'])) $uniq[(int)$stu['id']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$studentsCountByClassId[(int)$cid] = count($uniq);
|
||||
}
|
||||
|
||||
$scoreStatusByClass = [];
|
||||
foreach ($grades as $cid => $sections) {
|
||||
$allLocked = true;
|
||||
$hasSection = false;
|
||||
foreach ($sections as $section) {
|
||||
$secId = (int)($section['class_section_id'] ?? 0);
|
||||
if ($secId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$hasSection = true;
|
||||
if (empty($scoreLocks[$secId])) {
|
||||
$allLocked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$scoreStatusByClass[(int)$cid] = $hasSection && $allLocked;
|
||||
}
|
||||
|
||||
// Default active tab
|
||||
$defaultId = ($requestedClassId > 0 && isset($presentSet[$requestedClassId]))
|
||||
? $requestedClassId
|
||||
: ($sortedClassIds[0] ?? null);
|
||||
|
||||
// Sort each class’s sections by displayed name (e.g., 1-A, 1-B…)
|
||||
foreach ($grades as $cid => &$sections) {
|
||||
usort($sections, function ($a, $b) {
|
||||
return strnatcasecmp($a['class_section_name'] ?? '', $b['class_section_name'] ?? '');
|
||||
});
|
||||
}
|
||||
unset($sections);
|
||||
?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-2 mb-2 flex-wrap gap-2">
|
||||
<div class="text-muted">
|
||||
<?= esc(ucfirst($semester)) ?> • <?= esc($schoolYear) ?>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<?php
|
||||
$scoresReleasedFall = $scoresReleasedFall ?? false;
|
||||
$scoresReleasedSpring = $scoresReleasedSpring ?? false;
|
||||
?>
|
||||
<form action="<?= base_url('grading/release-scores') ?>" method="post" class="d-flex align-items-center gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="semester" value="Fall">
|
||||
<button type="submit" class="btn btn-sm <?= $scoresReleasedFall ? 'btn-outline-danger' : 'btn-outline-success' ?>">
|
||||
<?= $scoresReleasedFall ? 'Release scores for Parent - Fall: ON' : 'Release scores for Parent - Fall: OFF' ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form action="<?= base_url('grading/release-scores') ?>" method="post" class="d-flex align-items-center gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="semester" value="Spring">
|
||||
<button type="submit" class="btn btn-sm <?= $scoresReleasedSpring ? 'btn-outline-danger' : 'btn-outline-success' ?>">
|
||||
<?= $scoresReleasedSpring ? 'Release scores for Parent - Spring: ON' : 'Release scores for Parent - Spring: OFF' ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form action="<?= base_url('grading/lock-all-scores') ?>" method="post" class="d-flex align-items-center gap-2">
|
||||
<?= 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-sm btn-outline-danger">
|
||||
Lock All Scores
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a class="btn btn-outline-primary btn-sm"
|
||||
href="<?= base_url('grading/below-60?semester=' . rawurlencode($semester) . '&school_year=' . rawurlencode($schoolYear)) ?>">
|
||||
Below 60 Summary
|
||||
</a>
|
||||
|
||||
<form id="semesterFilterForm" class="d-flex align-items-center gap-2" method="get">
|
||||
<label for="gradingSemesterSelect" class="mb-0 text-secondary small">Semester</label>
|
||||
<select id="gradingSemesterSelect" name="semester" class="form-select form-select-sm">
|
||||
<?php foreach ($semesterOptions as $option): ?>
|
||||
<option value="<?= esc($option) ?>" <?= strcasecmp($option, $semester) === 0 ? 'selected' : '' ?>>
|
||||
<?= esc($option) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if ($requestedClassId > 0): ?>
|
||||
<input type="hidden" name="class_id" value="<?= esc($requestedClassId) ?>">
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (session()->get('status')): ?>
|
||||
<div class="alert alert-success">
|
||||
<?= session()->get('status') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->get('error')): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?= session()->get('error') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (empty($sortedClassIds)): ?>
|
||||
<div class="alert alert-warning text-center d-inline-block">
|
||||
No class data available for the current semester.
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<!-- Tabs navigation -->
|
||||
<ul class="nav nav-tabs justify-content-center" id="gradingTabs" role="tablist">
|
||||
<?php foreach ($sortedClassIds as $cid): ?>
|
||||
<?php
|
||||
$slug = $slugFor($cid);
|
||||
$label = $labelFor($cid);
|
||||
$cnt = (int)($studentsCountByClassId[$cid] ?? 0);
|
||||
$statusDone = !empty($scoreStatusByClass[$cid]);
|
||||
$statusTitle = $statusDone
|
||||
? 'Scores submitted for all sections'
|
||||
: 'Scores pending for one or more sections';
|
||||
?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($cid === $defaultId) ? 'active' : '' ?>"
|
||||
id="grade<?= esc($slug) ?>-tab"
|
||||
data-bs-toggle="tab"
|
||||
href="#grade<?= esc($slug) ?>"
|
||||
role="tab"
|
||||
aria-controls="grade<?= esc($slug) ?>"
|
||||
aria-selected="<?= ($cid === $defaultId) ? 'true' : 'false' ?>"
|
||||
data-class-id="<?= (int)$cid ?>">
|
||||
<span class="score-status-light <?= $statusDone ? 'done' : 'pending' ?>"
|
||||
title="<?= esc($statusTitle) ?>"
|
||||
aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?= esc($statusTitle) ?></span>
|
||||
<?= esc($label) ?>
|
||||
<span class="badge bg-secondary ms-1" title="Students in <?= esc($label) ?>">
|
||||
<?= $cnt ?>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<!-- Tabs content -->
|
||||
<div class="tab-content mt-3">
|
||||
<?php foreach ($sortedClassIds as $cid): ?>
|
||||
<?php
|
||||
$slug = $slugFor($cid);
|
||||
$label = $labelFor($cid);
|
||||
$sections = $grades[$cid] ?? [];
|
||||
?>
|
||||
<div class="tab-pane fade <?= ($cid === $defaultId) ? 'show active' : '' ?>"
|
||||
id="grade<?= esc($slug) ?>"
|
||||
role="tabpanel"
|
||||
aria-labelledby="grade<?= esc($slug) ?>-tab">
|
||||
|
||||
<h3 class="text-center"><?= esc($label) ?> — Class Sections</h3>
|
||||
|
||||
<?php if (empty($sections)): ?>
|
||||
<div class="alert alert-light border mt-3 text-center">No sections in this class.</div>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
// Safely extract a scalar from possibly nested/array values
|
||||
$scalar = function ($value, array $tryKeys = []) {
|
||||
if (is_null($value)) return null;
|
||||
if (is_scalar($value)) return $value;
|
||||
|
||||
if (is_array($value)) {
|
||||
// Try a few common keys by priority, then the first numeric value
|
||||
foreach ($tryKeys as $k) {
|
||||
if (array_key_exists($k, $value) && is_scalar($value[$k])) {
|
||||
return $value[$k];
|
||||
}
|
||||
}
|
||||
foreach ($value as $v) {
|
||||
if (is_scalar($v)) return $v;
|
||||
}
|
||||
}
|
||||
// Fallback: don’t break rendering
|
||||
return null;
|
||||
};
|
||||
$displayScore = function ($value) {
|
||||
if ($value === null || $value === '' || (is_string($value) && strtolower(trim($value)) === 'null')) {
|
||||
return '-';
|
||||
}
|
||||
return esc($value);
|
||||
};
|
||||
?>
|
||||
|
||||
<?php
|
||||
$gradingQuery = 'semester=' . rawurlencode($semester) . '&school_year=' . rawurlencode($schoolYear);
|
||||
foreach ($sections as $section):
|
||||
?>
|
||||
<?php
|
||||
$sectionId = (int)($section['class_section_id'] ?? 0);
|
||||
$sectionName = (string)($section['class_section_name'] ?? '');
|
||||
$students = $sectionId ? ($studentsBySection[$sectionId] ?? []) : [];
|
||||
$locked = !empty($scoreLocks[$sectionId]);
|
||||
?>
|
||||
<?php if ($sectionId <= 0): continue;
|
||||
endif; ?>
|
||||
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>Section:</strong> <?= esc($sectionName) ?>
|
||||
<span class="badge bg-info ms-2"><?= count($students) ?> students</span>
|
||||
<?php if ($locked): ?>
|
||||
<span class="badge bg-success ms-2">Scores submitted</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-danger ms-2">Scores Not submitted</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<!-- Per-section toolbar -->
|
||||
<form action="<?= base_url('grading/toggle-score-lock') ?>" method="post" class="d-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="class_section_id" value="<?= esc($sectionId) ?>">
|
||||
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
|
||||
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
|
||||
<button type="submit" class="btn btn-sm <?= $locked ? 'btn-outline-danger' : 'btn-outline-success' ?>">
|
||||
<?= $locked ? 'Unlock Scores' : 'Lock Scores' ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a href="<?= base_url('grading/homework?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Homework</a>
|
||||
|
||||
<a href="<?= base_url('grading/quiz?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Quiz</a>
|
||||
|
||||
<a href="<?= base_url('grading/project?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Project</a>
|
||||
|
||||
<a href="<?= base_url('grading/participation?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Participation</a>
|
||||
|
||||
<?php if (strtolower($semester) === 'fall'): ?>
|
||||
<a href="<?= base_url('grading/midterm?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Midterm</a>
|
||||
<?php elseif (strtolower($semester) === 'spring'): ?>
|
||||
<a href="<?= base_url('grading/final?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Final</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?= base_url('grading/comments?class_section_id=' . $sectionId . '&' . $gradingQuery) ?>"
|
||||
class="btn btn-secondary btn-sm">Comments</a>
|
||||
|
||||
<form action="<?= base_url('grading/refresh-semester-scores') ?>" method="post" class="d-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="class_section_id" value="<?= esc($sectionId) ?>">
|
||||
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
|
||||
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm">Refresh Scores</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<?php if (empty($students)): ?>
|
||||
<div class="alert alert-light border text-center mb-0">No students in this section.</div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped w-100 grading-table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>School ID</th>
|
||||
<th>Student First Name</th>
|
||||
<th>Student Last Name</th>
|
||||
<th>Homework Avg</th>
|
||||
<th>Project Avg</th>
|
||||
<th>Participation</th>
|
||||
<th>Test/Quiz Avg</th>
|
||||
<th>Attendance</th>
|
||||
<th>PTAP</th>
|
||||
<?php if (strtolower($semester) === 'fall'): ?>
|
||||
<th>Midterm</th>
|
||||
<?php elseif (strtolower($semester) === 'spring'): ?>
|
||||
<th>Final</th>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($_GET['debug'])): ?>
|
||||
<th>DBG Biz CSID</th>
|
||||
<th>DBG PK CSID</th>
|
||||
<?php endif; ?>
|
||||
<th>Semester Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($students as $student): ?>
|
||||
<tr>
|
||||
<td><?= esc($student['school_id'] ?? 'N/A') ?></td>
|
||||
<td>
|
||||
<?php $sid = (int)($student['id'] ?? 0); ?>
|
||||
<?php if ($sid): ?>
|
||||
<a href="#" class="text-decoration-none" data-family-student-id="<?= $sid ?>">
|
||||
<?= esc($student['firstname'] ?? 'N/A') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= esc($student['firstname'] ?? 'N/A') ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($sid)): ?>
|
||||
<a href="#" class="text-decoration-none" data-family-student-id="<?= $sid ?>">
|
||||
<?= esc($student['lastname'] ?? 'N/A') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= esc($student['lastname'] ?? 'N/A') ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-center"><?= $displayScore($student['homework_avg'] ?? null) ?></td>
|
||||
<td class="text-center"><?= $displayScore($student['project_avg'] ?? null) ?></td>
|
||||
<td class="text-center"><?= $displayScore($student['participation'] ?? null) ?></td>
|
||||
<td class="text-center"><?= $displayScore($student['quiz_avg'] ?? null) ?></td>
|
||||
<td class="text-center"><?= $displayScore($student['attendance'] ?? null) ?></td>
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$ptapRaw = $student['ptap_score'] ?? $student['ptap'] ?? null;
|
||||
$ptapVal = $scalar($ptapRaw, ['ptap_score', 'ptap', 'score', 'value']);
|
||||
echo $displayScore($ptapVal);
|
||||
?>
|
||||
</td>
|
||||
<?php if (strtolower($semester) === 'fall'): ?>
|
||||
<td class="text-center"><?= $displayScore($student['midterm_exam'] ?? null) ?></td>
|
||||
<?php elseif (strtolower($semester) === 'spring'): ?>
|
||||
<td class="text-center"><?= $displayScore($student['final_exam'] ?? null) ?></td>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($_GET['debug'])): ?>
|
||||
<td class="text-center"><?= esc($student['matched_biz_csid'] ?? '') ?></td>
|
||||
<td class="text-center"><?= esc($student['matched_pk_csid'] ?? '') ?></td>
|
||||
<?php endif; ?>
|
||||
|
||||
<td class="text-center">
|
||||
<?php
|
||||
$semRaw = $student['semester_score'] ?? null;
|
||||
$semVal = $scalar($semRaw, ['semester_score', 'final', 'score', 'value']);
|
||||
echo $displayScore($semVal);
|
||||
?>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<style>
|
||||
.nav-tabs .nav-link.active { font-weight: 600; }
|
||||
.score-status-light {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: .35rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(0, 0, 0, .25);
|
||||
display: inline-block;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.score-status-light.done {
|
||||
background-color: #198754;
|
||||
box-shadow: inset 0 0 0 1px rgba(25, 135, 84, .35);
|
||||
}
|
||||
.score-status-light.pending {
|
||||
background-color: #dc3545;
|
||||
box-shadow: inset 0 0 0 1px rgba(220, 53, 69, .35);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(function() {
|
||||
function getSelectedClassIdFromURL() {
|
||||
const p = new URLSearchParams(window.location.search);
|
||||
return p.get('class_id');
|
||||
}
|
||||
|
||||
function setSelectedClassIdInURL(classId, replace = true) {
|
||||
const url = new URL(window.location.href);
|
||||
if (classId) url.searchParams.set('class_id', classId);
|
||||
else url.searchParams.delete('class_id');
|
||||
if (replace) history.replaceState(null, '', url.toString());
|
||||
else history.pushState(null, '', url.toString());
|
||||
}
|
||||
|
||||
function getSavedClassId() {
|
||||
try {
|
||||
return localStorage.getItem('grading.selectedClassId');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveClassId(classId) {
|
||||
try {
|
||||
localStorage.setItem('grading.selectedClassId', classId ?? '');
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function activateByClassId(tabsEl, classId) {
|
||||
if (!tabsEl) return;
|
||||
let target = null;
|
||||
if (classId) {
|
||||
target = tabsEl.querySelector(`a[data-class-id="${classId}"]`);
|
||||
// also try numeric vs string mismatch
|
||||
if (!target) target = tabsEl.querySelector(`a[data-class-id="${parseInt(classId,10)}"]`);
|
||||
}
|
||||
if (!target) {
|
||||
// fallback: keep current active or first tab
|
||||
target = tabsEl.querySelector('a.nav-link.active') || tabsEl.querySelector('a.nav-link');
|
||||
}
|
||||
if (target) new bootstrap.Tab(target).show();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tabsEl = document.getElementById('gradingTabs');
|
||||
if (!tabsEl) return;
|
||||
|
||||
// 1) Decide which tab to show: URL -> localStorage -> server default
|
||||
const urlClassId = getSelectedClassIdFromURL();
|
||||
const lsClassId = getSavedClassId();
|
||||
const initial = urlClassId || lsClassId || null;
|
||||
activateByClassId(tabsEl, initial);
|
||||
|
||||
// 2) When a tab is shown, persist selection and update URL (no history spam)
|
||||
tabsEl.addEventListener('shown.bs.tab', function(evt) {
|
||||
const link = evt.target; // newly activated <a>
|
||||
const classId = link.getAttribute('data-class-id');
|
||||
saveClassId(classId);
|
||||
setSelectedClassIdInURL(classId, /*replace*/ true);
|
||||
|
||||
// Adjust any visible DataTables after the tab becomes visible
|
||||
if (window.$ && $.fn && $.fn.dataTable) {
|
||||
$.fn.dataTable.tables({
|
||||
visible: true,
|
||||
api: true
|
||||
})
|
||||
.columns.adjust().draw(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Handle back/forward navigation
|
||||
window.addEventListener('popstate', function() {
|
||||
// Re-parse URL on history changes
|
||||
const id = getSelectedClassIdFromURL() || getSavedClassId();
|
||||
activateByClassId(tabsEl, id);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
function getFixedHeaderOffset() {
|
||||
let total = 0;
|
||||
const stack = [];
|
||||
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top');
|
||||
if (header) stack.push(header);
|
||||
const mgmt = document.getElementById('navbarManagement');
|
||||
if (mgmt && (mgmt.classList.contains('sticky-top') || mgmt.classList.contains('fixed-top'))) stack.push(mgmt);
|
||||
document.querySelectorAll('.navbar.sticky-top, .navbar.fixed-top').forEach(el => { if (!stack.includes(el)) stack.push(el); });
|
||||
stack.forEach(el => { const h = el.offsetHeight || el.getBoundingClientRect().height || 0; total += Math.max(0, Math.round(h)); });
|
||||
return total;
|
||||
}
|
||||
|
||||
// Initialize DataTables for all grading tables with FixedHeader
|
||||
$(function() {
|
||||
if (!$.fn || !$.fn.DataTable) return;
|
||||
$('.grading-table').each(function(){
|
||||
try {
|
||||
const dt = $(this).DataTable({
|
||||
stateSave: true,
|
||||
pageLength: 100,
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
order: [[1, 'asc']],
|
||||
fixedHeader: { header: true, headerOffset: getFixedHeaderOffset() }
|
||||
});
|
||||
// If FixedHeader plugin was not available globally, CSS sticky still applies via layout
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
// Ensure columns adjust when a tab is shown (if DOM changed after init)
|
||||
$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function() {
|
||||
if ($.fn && $.fn.dataTable) {
|
||||
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust().draw(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('semesterFilterForm');
|
||||
const select = document.getElementById('gradingSemesterSelect');
|
||||
if (!form || !select) return;
|
||||
select.addEventListener('change', function() {
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
Reference in New Issue
Block a user