Files
alrahma_sunday_school/app/Views/teacher/scores.php
T
2026-02-10 22:11:06 -05:00

728 lines
34 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?= $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() ?>