recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
+727
View File
@@ -0,0 +1,727 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<?php helper('form'); ?>
<div class="w-100 py-3 px-3">
<?php if (session()->get('status')): ?>
<div class="alert alert-success text-center">
<?= session()->get('status') ?>
</div>
<?php endif; ?>
<?php if (session()->get('error')): ?>
<div class="alert alert-danger text-center">
<?= esc(session()->get('error')) ?>
</div>
<?php endif; ?>
<?php if ($errors = session()->get('errors')): ?>
<div class="alert alert-danger">
<p class="mb-2 text-center fw-semibold">
Errors were found in the PTAP or Midterm/Final comments. Your entries are kept below so you can fix the specific fields and resubmit.
</p>
<ul class="mb-0">
<?php foreach ($errors as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
<p class="small text-center text-muted mt-3 mb-0">
PTAP, Midterm, and Final comments must be 100350 characters and begin with the students first name for validation to pass.
</p>
</div>
<?php endif; ?>
<?php $semester = strtolower($activeSemester ?? ''); ?>
<?php $focusTarget = $focusTarget ?? null; ?>
<?php
$classSectionId = (int)($class_section_id ?? 0);
$scoresLocked = !empty($scoresLocked);
$lockAttr = $scoresLocked ? 'disabled' : '';
$displayScore = static function ($value) {
return ($value === null || $value === '') ? '' : esc($value);
};
$scoreClass = static function ($value) {
return ($value === null || $value === '') ? 'score-empty' : '';
};
$trimValue = static function ($value) {
return trim((string)($value ?? ''));
};
$commentClass = static function ($value) use ($trimValue) {
return $trimValue($value) === '' ? 'score-empty' : '';
};
$displayComment = static function ($value) use ($trimValue) {
$val = $trimValue($value);
return $val === '' ? '' : esc($val);
};
$preserveCommentClass = static function ($studentId, $scoreType, $existing) use ($commentClass) {
$old = old("comments[{$studentId}][{$scoreType}]");
return $commentClass($old !== null ? $old : $existing);
};
$preserveCommentValue = static function ($studentId, $scoreType, $existing) use ($displayComment) {
$old = old("comments[{$studentId}][{$scoreType}]");
return $displayComment($old !== null ? $old : $existing);
};
$rawCommentValue = static function ($studentId, $scoreType, $existing) use ($trimValue) {
$old = old("comments[{$studentId}][{$scoreType}]");
$val = $old !== null ? $old : $existing;
return $trimValue($val);
};
$extractScore = static function ($value) {
if (is_array($value)) {
return $value[0] ?? '';
}
return $value;
};
$scoreEmpty = static function ($value) {
return $value === null || $value === '';
};
?>
<form id="gradesForm" action="<?= base_url('/teacher/saveComments') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="selected_semester" value="<?= esc($selectedSemester ?? ($activeSemester ?? '')) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear ?? '') ?>">
<div class="text-center mb-3 w-100">
<h4 class="text-dark mb-1" style="font-family: Arial, sans-serif;">Students Class Scores</h4>
<?php if (!empty($mainsText ?? '')): ?>
<h5 class="mb-0" style="font-family: Arial, sans-serif;">Teachers: <?= esc($mainsText) ?></h5>
<?php else: ?>
<h5 class="mb-0" style="font-family: Arial, sans-serif;">Teacher: <?= esc($teacher_name); ?></h5>
<?php endif; ?>
<?php if (!empty($tasText ?? '')): ?>
<h6 class="mb-0 mt-1" style="font-family: Arial, sans-serif;">Teacher Assistants: <?= esc($tasText) ?></h6>
<?php endif; ?>
<?php if (!empty($activeSemester ?? '')): ?>
<p class="text-muted mb-0" style="font-size: 0.9rem;">Current semester: <?= esc(ucfirst($activeSemester)) ?></p>
<?php endif; ?>
<?php if ($scoresLocked): ?>
<div class="alert alert-warning mt-3 mb-0">
Scores are submitted and locked for this semester.
</div>
<?php endif; ?>
<div class="mt-3">
<a href="<?= base_url('/teacher/scores?choose_semester=1') ?>" class="btn btn-outline-secondary btn-sm">Choose another semester</a>
</div>
</div>
<input type="hidden" name="class_section_id" value="<?= esc($class_section_id ?? 0) ?>">
<div class="table-responsive">
<table id="myTable" class="table table-bordered table-striped w-100 scores-table">
<thead class="table-light">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th class="score-col">Attendance</th>
<th class="score-col">Homework Avg</th>
<th class="score-col">Project Avg</th>
<th class="score-col">Test/Quiz Avg</th>
<th class="score-col">Participation</th>
<th class="score-col">PTAP</th>
<th class="comment-col">PTAP Comment</th>
<th class="comment-col">PTAP Comment Review</th>
<?php if ($semester === 'fall'): ?>
<th class="score-col">Midterm</th>
<th class="comment-col">Midterm Comment</th>
<th class="comment-col">Midterm Comment Review</th>
<?php endif; ?>
<?php if ($semester === 'spring'): ?>
<th class="score-col">Final Exam</th>
<th class="comment-col">Final Comment</th>
<th class="comment-col">Final Comment Review</th>
<?php endif; ?>
<th class="score-col">Semester Score</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<?php
$studentId = $student['student_id'];
$studentName = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''));
$studentName = $studentName !== '' ? $studentName : ('Student #' . $studentId);
$missingOk = $student['missing_ok'] ?? [];
$homeworkVal = $extractScore($student['average_homework'] ?? null);
$projectVal = $extractScore($student['average_project'] ?? null);
$quizVal = $extractScore($student['average_quiz'] ?? null);
$participationVal = $extractScore($student['average_participation'] ?? null);
$midtermVal = $semester === 'fall' ? $extractScore($student['midterm'][0] ?? null) : null;
$finalVal = $semester === 'spring' ? $extractScore($student['final_exam'][0] ?? null) : null;
$ptapCommentRaw = $rawCommentValue($studentId, 'ptap', $student['ptap_comment'] ?? '');
$midtermCommentRaw = $semester === 'fall'
? $rawCommentValue($studentId, 'midterm', $student['midterm_comment'] ?? '')
: '';
$finalCommentRaw = $semester === 'spring'
? $rawCommentValue($studentId, 'final', $student['final_comment'] ?? '')
: '';
?>
<tr data-student-name="<?= esc($studentName) ?>" data-missing-details="<?= esc(json_encode(array_values($student['missing_items'] ?? [])), 'attr') ?>">
<td><?= esc($student['firstname']) ?></td>
<td><?= esc($student['lastname']) ?></td>
<td class="text-center score-col <?= $scoreClass($student['attendance_score'] ?? null) ?>"><?= $displayScore($student['attendance_score'] ?? null) ?></td>
<td class="text-center score-col <?= $scoreClass($homeworkVal) ?>" data-score-check="1" data-field-label="Homework Avg" data-field-value="<?= esc($homeworkVal) ?>">
<?= $displayScore($homeworkVal) ?>
</td>
<td class="text-center score-col <?= $scoreClass($projectVal) ?>" data-score-check="1" data-field-label="Project Avg" data-field-value="<?= esc($projectVal) ?>">
<?= $displayScore($projectVal) ?>
</td>
<td class="text-center score-col <?= $scoreClass($quizVal) ?>" data-score-check="1" data-field-label="Test/Quiz Avg" data-field-value="<?= esc($quizVal) ?>">
<?= $displayScore($quizVal) ?>
</td>
<td class="text-center score-col <?= $scoreClass($participationVal) ?>" data-score-check="1" data-field-label="Participation" data-field-value="<?= esc($participationVal) ?>">
<?= $displayScore($participationVal) ?>
</td>
<td class="text-center score-col <?= $scoreClass($student['ptap_score'] ?? null) ?>"><?= $displayScore($student['ptap_score'] ?? null) ?></td>
<td class="ptap-comment-cell comment-col">
<input type="hidden" name="student_ids[]" value="<?= $studentId ?>">
<div class="comment-check" data-field-label="PTAP Comment">
<textarea
name="comments[<?= $studentId ?>][ptap]"
class="form-control auto-expand comment-textarea <?= $preserveCommentClass($studentId, 'ptap', $student['ptap_comment'] ?? '') ?>"
data-first-name="<?= esc($student['firstname']) ?>"
minlength="100"
maxlength="350"
placeholder="PTAP Comment"
rows="1"
style="min-width: 100px; overflow:hidden; resize: none;"
spellcheck="true" <?= $lockAttr ?>><?= $preserveCommentValue($studentId, 'ptap', $student['ptap_comment'] ?? '') ?></textarea>
<?php $ptapMissingOk = !empty($missingOk['ptap_comment'][0]); ?>
<label class="missing-check mt-1 <?= $ptapCommentRaw === '' ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= $studentId ?>][ptap_comment]"
value="1"
class="missing-comment-checkbox"
data-field-label="PTAP Comment" <?= $lockAttr ?> <?= $ptapMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</div>
<div class="char-count-wrapper" style="font-size: 0.75rem; text-align: right; color: #6c757d;">
<span class="char-count">0</span> / 350
</div>
<div class="comment-error text-danger small" role="alert" aria-live="polite"></div>
</td>
<td class="comment-review ptap-comment-review-cell comment-col">
<span class="<?= $commentClass($student['ptap_comment_review'] ?? '') ?>">
<?= $displayComment($student['ptap_comment_review'] ?? '') ?>
</span>
</td>
<?php if ($semester === 'fall'): ?>
<td class="text-center score-col <?= $scoreClass($midtermVal) ?>" data-score-check="1" data-field-label="Midterm" data-field-value="<?= esc($midtermVal) ?>">
<?= $displayScore($midtermVal) ?>
</td>
<td class="midterm-comment-cell comment-col">
<div class="comment-check" data-field-label="Midterm Comment">
<textarea
name="comments[<?= $studentId ?>][midterm]"
class="form-control auto-expand comment-textarea <?= $preserveCommentClass($studentId, 'midterm', $student['midterm_comment'] ?? '') ?>"
data-first-name="<?= esc($student['firstname']) ?>"
minlength="100"
maxlength="350"
placeholder="Midterm Comment"
rows="1"
style="min-width: 100px; overflow:hidden; resize: none;"
spellcheck="true"
autocorrect="on"
autocapitalize="sentences"
lang="en" <?= $lockAttr ?>><?= $preserveCommentValue($studentId, 'midterm', $student['midterm_comment'] ?? '') ?></textarea>
<?php $midtermMissingOk = !empty($missingOk['midterm_comment'][0]); ?>
<label class="missing-check mt-1 <?= $midtermCommentRaw === '' ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= $studentId ?>][midterm_comment]"
value="1"
class="missing-comment-checkbox"
data-field-label="Midterm Comment" <?= $lockAttr ?> <?= $midtermMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</div>
<div class="char-count-wrapper" style="font-size: 0.75rem; text-align: right; color: #6c757d;">
<span class="char-count">0</span> / 350
</div>
<div class="comment-error text-danger small" role="alert" aria-live="polite"></div>
</td>
<td class="comment-review midterm-comment-review-cell comment-col">
<span class="<?= $commentClass($student['midterm_comment_review'] ?? '') ?>">
<?= $displayComment($student['midterm_comment_review'] ?? '') ?>
</span>
</td>
<?php endif; ?>
<?php if ($semester === 'spring'): ?>
<td class="text-center score-col <?= $scoreClass($finalVal) ?>" data-score-check="1" data-field-label="Final Exam" data-field-value="<?= esc($finalVal) ?>">
<?= $displayScore($finalVal) ?>
</td>
<td class="comment-col">
<div class="comment-check" data-field-label="Final Comment">
<textarea
name="comments[<?= $studentId ?>][final]"
class="form-control auto-expand comment-textarea <?= $preserveCommentClass($studentId, 'final', $student['final_comment'] ?? '') ?>"
data-first-name="<?= esc($student['firstname']) ?>"
minlength="100"
maxlength="350"
placeholder="Final Comment"
rows="1"
style="min-width: 100px; overflow:hidden; resize: none;"
spellcheck="true"
autocorrect="on"
autocapitalize="sentences"
lang="en" <?= $lockAttr ?>><?= $preserveCommentValue($studentId, 'final', $student['final_comment'] ?? '') ?></textarea>
<?php $finalMissingOk = !empty($missingOk['final_comment'][0]); ?>
<label class="missing-check mt-1 <?= $finalCommentRaw === '' ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= $studentId ?>][final_comment]"
value="1"
class="missing-comment-checkbox"
data-field-label="Final Comment" <?= $lockAttr ?> <?= $finalMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</div>
<div class="char-count-wrapper" style="font-size: 0.75rem; text-align: right; color: #6c757d; margin-top: 2px;">
<span class="char-count">0</span> / 350
</div>
<div class="comment-error text-danger small" role="alert" aria-live="polite"></div>
</td>
<td class="comment-review comment-col">
<span class="<?= $commentClass($student['final_comment_review'] ?? '') ?>">
<?= $displayComment($student['final_comment_review'] ?? '') ?>
</span>
</td>
<?php endif; ?>
<td class="text-center score-col <?= $scoreClass($student['semester_score'] ?? null) ?>"><?= $displayScore($student['semester_score'] ?? null) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="scores-action-bar d-flex flex-wrap gap-2 mt-3">
<a href="<?= base_url('/teacher/addHomework?class_section_id=' . $classSectionId) ?>" class="btn btn-secondary">Add Homework</a>
<a href="<?= base_url('/teacher/addQuiz?class_section_id=' . $classSectionId) ?>" class="btn btn-secondary">Add Quiz</a>
<a href="<?= base_url('/teacher/addProject?class_section_id=' . $classSectionId) ?>" class="btn btn-secondary">Add Project</a>
<a href="<?= base_url('/teacher/addParticipation?class_section_id=' . $classSectionId) ?>" class="btn btn-secondary">Add Participation</a>
<?php if ($semester === 'fall'): ?>
<a href="<?= base_url('/teacher/addMidtermExam?class_section_id=' . $classSectionId) ?>" class="btn btn-secondary">Add Midterm</a>
<?php elseif ($semester === 'spring'): ?>
<a href="<?= base_url('/teacher/addFinalExam?class_section_id=' . $classSectionId) ?>" class="btn btn-secondary">Add Final</a>
<?php endif; ?>
<button type="submit" class="btn btn-info" <?= $lockAttr ?>>Save Comments</button>
<button
type="submit"
class="btn btn-outline-danger"
id="submitScoresLockBtn"
formaction="<?= base_url('/teacher/submit-scores-lock') ?>"
formmethod="post"
<?= $scoresLocked ? 'disabled' : '' ?>>
<?= $scoresLocked ? 'Scores Locked' : 'Submit Semester Scores' ?>
</button>
</div>
<div id="missingWarning" class="alert alert-danger mt-3 d-none" role="alert" aria-live="polite">
<div class="fw-semibold mb-2">Please fix the missing items before submitting:</div>
<pre class="missing-warning-body mb-0"></pre>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script src="<?= base_url('assets/js/validateScores.js') ?>"></script>
<script>
document.addEventListener('input', function(event) {
if (event.target.classList.contains('auto-expand')) {
event.target.style.height = 'auto';
event.target.style.height = Math.max(event.target.scrollHeight, 26) + 'px';
}
});
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.auto-expand').forEach(function(textarea) {
textarea.style.height = 'auto';
textarea.style.height = Math.max(textarea.scrollHeight, 26) + 'px';
});
const form = document.getElementById('gradesForm');
if (form) {
const csrfInput = form.querySelector('input[type="hidden"][name*="csrf"]');
const csrfCookieName = '<?= esc(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>';
const getCookie = function(name) {
try {
return document.cookie
.split(';')
.map(function(value) { return value.trim(); })
.find(function(value) { return value.indexOf(name + '=') === 0; })
?.split('=')[1] || '';
} catch (e) {
return '';
}
};
const syncCsrfToken = function() {
if (!csrfInput) return;
const fresh = decodeURIComponent(getCookie(csrfCookieName) || '');
if (fresh) {
csrfInput.value = fresh;
}
};
const escapeRegex = function(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
const getErrorElement = function(field) {
return field && field.parentElement ? field.parentElement.querySelector('.comment-error') : null;
};
const validateStartsWithName = function(field) {
const errorEl = getErrorElement(field);
if (!errorEl) return true;
const value = field.value.trim();
const firstName = field.dataset.firstName || '';
if (!value || !firstName) {
errorEl.textContent = '';
return true;
}
const startsWithName = new RegExp(`^${escapeRegex(firstName)}\\b`, 'i');
if (!startsWithName.test(value)) {
errorEl.textContent = `Comment must start with ${firstName}'s first name.`;
return false;
}
errorEl.textContent = '';
return true;
};
const shouldAutoSave = function(field) {
const value = field.value.trim();
if (!value) return false;
if (!validateStartsWithName(field)) return false;
if (value.length < 100 || value.length > 350) return false;
const lastSaved = field.getAttribute('data-last-saved') || '';
return value !== lastSaved;
};
const buildAutoSavePayload = function(field) {
const data = new FormData();
if (csrfInput) {
const csrfValue = decodeURIComponent(getCookie(csrfCookieName) || '') || csrfInput.value;
data.append(csrfInput.name, csrfValue);
}
['selected_semester', 'school_year', 'class_section_id'].forEach(function(name) {
const input = form.querySelector('[name="' + name + '"]');
if (input) {
data.append(name, input.value);
}
});
data.append(field.name, field.value);
return data;
};
const autoSaveField = function(field) {
if (!shouldAutoSave(field)) return;
const payload = buildAutoSavePayload(field);
fetch(form.action, {
method: 'POST',
body: payload,
credentials: 'same-origin'
})
.then(function() {
field.setAttribute('data-last-saved', field.value.trim());
syncCsrfToken();
})
.catch(function() {});
};
form.addEventListener('submit', function(event) {
syncCsrfToken();
const errors = [];
form.querySelectorAll('textarea[data-first-name]').forEach(function(field) {
const value = field.value.trim();
if (value === '') return;
const min = 100;
const max = 350;
const firstName = field.dataset.firstName || '';
if (value.length < min || value.length > max) {
errors.push(`Comment for ${firstName || 'student'} must be between ${min} and ${max} characters.`);
return;
}
if (firstName) {
const startsWithName = new RegExp(`^${escapeRegex(firstName)}\\b`, 'i');
if (!startsWithName.test(value)) {
errors.push(`Comment for ${firstName} must start with the student's first name.`);
}
}
});
if (errors.length > 0) {
event.preventDefault();
alert(errors.join('\\n'));
}
});
form.querySelectorAll('textarea.comment-textarea').forEach(function(field) {
field.setAttribute('data-last-saved', field.value.trim());
field.addEventListener('input', function() {
validateStartsWithName(this);
});
field.addEventListener('blur', function() {
validateStartsWithName(this);
autoSaveField(this);
});
});
}
<?php if (!empty($focusTarget)): ?>
(function scrollForFocus() {
const focusKey = '<?= esc($focusTarget) ?>';
const focusElement = focusKey === 'comments'
? document.querySelector('.comment-textarea')
: document.getElementById('myTable');
if (!focusElement) return;
focusElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
if (focusKey === 'comments') {
focusElement.focus();
}
})();
<?php endif; ?>
});
</script>
<style>
.comment-review {
background-color: #f8f9fa;
padding: 2px 4px;
border-radius: 4px;
min-width: 120px;
max-width: 200px;
white-space: pre-wrap;
vertical-align: top;
}
/* Compact table cells */
.scores-table td,
.scores-table th {
padding: 0.2rem 0.35rem;
vertical-align: top !important;
white-space: normal;
}
.score-col {
width: 80px !important;
/* Fixed width for score columns */
white-space: nowrap;
}
.comment-col {
min-width: 200px !important;
/* Minimum width for comment columns */
}
/* Ensure room above footer on this page and neutralize global 100vh flex */
main.container-fluid {
display: block !important;
min-height: auto !important;
/* override global min-height: 100vh */
flex: 1 0 auto !important;
/* fill remaining height so footer sits at bottom */
padding-bottom: 80px;
/* room so sticky bar doesn't cover footer */
}
/* Keep action buttons visible and above footer */
.scores-action-bar {
position: sticky;
bottom: 0;
background: #fff;
padding: .75rem .5rem;
border-top: 1px solid rgba(0, 0, 0, .1);
z-index: 10;
/* above table, below modals/nav */
}
.score-empty {
background-color: #fff3cd !important;
box-shadow: inset 0 0 0 1px #ffe8a1;
}
.comment-review .score-empty {
display: inline-block;
width: 100%;
padding: 2px 4px;
}
.missing-check {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.75rem;
color: #6c757d;
margin-top: 4px;
}
/* Compact textareas and comment cells; grow only when content expands */
.scores-table textarea.form-control.auto-expand {
min-height: 26px;
height: 26px;
line-height: 1.25;
padding: 4px 6px;
resize: none;
margin: 0;
display: block;
width: 100%;
vertical-align: top !important;
}
.ptap-comment-cell,
.ptap-comment-review-cell {
vertical-align: top !important;
padding-top: 2px;
}
.ptap-comment-cell textarea {
display: block;
width: 100%;
margin: 0;
vertical-align: top !important;
}
.ptap-comment-review-cell span {
display: block;
vertical-align: top !important;
width: 100%;
}
.midterm-comment-cell,
.midterm-comment-review-cell {
vertical-align: top !important;
padding-top: 2px;
}
.midterm-comment-cell textarea {
display: block;
width: 100%;
margin: 0;
vertical-align: top !important;
}
.midterm-comment-review-cell span {
display: block;
vertical-align: top !important;
width: 100%;
}
.scores-table {
table-layout: auto;
width: 100%;
}
#missingWarning {
font-size: 1rem;
}
#missingWarning .missing-warning-body {
font-size: 1rem;
line-height: 1.4;
white-space: pre-wrap;
}
</style>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.10/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.10/css/dataTables.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.10/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.10/js/dataTables.bootstrap5.min.js"></script>
<script>
(function ($) {
// Order plug-in: treat cell text as number, empty => -Infinity so blanks drop to bottom on desc
if ($ && $.fn && $.fn.dataTable && !$.fn.dataTable.ext.order['dom-text-num']) {
$.fn.dataTable.ext.order['dom-text-num'] = function (settings, col) {
return this.api()
.column(col, { order: 'index' })
.nodes()
.map(function (td) {
const raw = (td.textContent || '').trim();
const num = parseFloat(raw);
return Number.isFinite(num) ? num : -Infinity;
});
};
}
$(document).ready(function () {
if (!($ && $.fn && $.fn.DataTable)) return;
const numericCols = [
2, // Attendance
3, // Homework Avg
4, // Project Avg
5, // Test/Quiz Avg
6, // Participation
7, // PTAP
<?php if ($semester === 'fall'): ?>
10, // Midterm
13 // Semester Score
<?php elseif ($semester === 'spring'): ?>
10, // Final Exam
13 // Semester Score
<?php else: ?>
10 // Semester Score (no midterm/final columns)
<?php endif; ?>
];
// Comment + review columns should stay unsortable
const commentCols = [
8, 9
<?php if ($semester === 'fall'): ?>,11,12<?php endif; ?>
<?php if ($semester === 'spring'): ?>,11,12<?php endif; ?>
];
$('#myTable').DataTable({
autoWidth: false,
scrollX: true,
paging: false,
info: false,
order: [[0, 'asc'], [1, 'asc']],
columnDefs: [
{ targets: numericCols, orderDataType: 'dom-text-num', type: 'num', orderSequence: ['desc', 'asc'] },
{ targets: commentCols, orderable: false, searchable: true },
]
});
});
})(jQuery);
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const textareas = document.querySelectorAll('.comment-textarea');
/**
* Updates the counter for a specific textarea
* @param {HTMLElement} el - The textarea element
*/
function updateCharCount(el) {
// Find the counter span within the same table cell
const wrapper = el.parentElement.querySelector('.char-count');
if (!wrapper) return;
const currentLength = el.value.trim().length;
wrapper.textContent = currentLength;
// Visual feedback for the 100-character minimum
if (currentLength > 0 && currentLength < 100) {
wrapper.style.color = '#dc3545'; // Bootstrap Red
wrapper.style.fontWeight = 'bold';
} else if (currentLength >= 100) {
wrapper.style.color = '#198754'; // Bootstrap Green
wrapper.style.fontWeight = 'normal';
} else {
wrapper.style.color = '#6c757d'; // Default Gray
}
}
// Initialize and attach listeners
textareas.forEach(textarea => {
// Run once on load to show current character counts
updateCharCount(textarea);
// Run on every input change
textarea.addEventListener('input', function() {
updateCharCount(this);
});
});
});
</script>
<?= $this->endSection() ?>