Files
root 611a5c8e4b
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 49s
Tests / PHPUnit (push) Successful in 1m23s
fix test and add invocie to parent enrollment
2026-08-29 15:25:17 -04:00

298 lines
13 KiB
PHP

<?= $this->extend('layout/main_layout') ?>
<?= $this->section('styles') ?>
<style>
.homework-table-scroll {
overflow-x: auto;
max-width: 100%;
-webkit-overflow-scrolling: touch;
}
.homework-sticky-table {
border-collapse: separate;
border-spacing: 0;
min-width: max-content;
width: auto;
}
.homework-sticky-table th,
.homework-sticky-table td {
box-sizing: border-box;
vertical-align: middle;
background-clip: padding-box;
}
.homework-sticky-table th.homework-rownum-col,
.homework-sticky-table td.homework-rownum-col {
position: sticky;
left: 0;
z-index: 4;
min-width: 56px;
width: 56px;
text-align: center;
white-space: nowrap;
background: #fff;
}
.homework-sticky-table th.homework-student-name-col,
.homework-sticky-table td.homework-student-name-col {
position: sticky;
left: 56px;
z-index: 4;
min-width: var(--homework-name-col-width, 18ch);
width: var(--homework-name-col-width, 18ch);
max-width: var(--homework-name-col-width, 18ch);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: #fff;
box-shadow: 1px 0 0 rgba(0, 0, 0, 0.12);
}
.homework-sticky-table thead th.homework-rownum-col,
.homework-sticky-table thead th.homework-student-name-col {
z-index: 6;
background: #f8f9fa;
}
.homework-sticky-table th.homework-score-col,
.homework-sticky-table td.homework-score-col,
.homework-sticky-table th.dynamic-col,
.homework-sticky-table td.dynamic-col {
min-width: 180px;
width: 180px;
}
.homework-sticky-table .form-control {
min-width: 0;
width: 100%;
}
.score-empty {
background-color: #fff3cd;
}
.missing-check {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.75rem;
color: #6c757d;
white-space: nowrap;
}
</style>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="container-fluid py-5">
<div class="container-fluid">
<div class="text-center mx-auto mb-5 mt-4" style="max-width: 600px;">
<h1 class="mb-3">Update Homework Scores</h1>
</div>
<?php if (session()->get('status')): ?>
<div class="alert alert-success">
<?= session()->get('status') ?>
</div>
<?php endif; ?>
<?php
$formatScore = static function ($value) {
return ($value === null || $value === '') ? '' : esc($value);
};
$missingOkMap = $missingOkMap ?? [];
$studentNameWidthCh = 18;
foreach ($students as $student) {
$fullName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
$studentNameWidthCh = max($studentNameWidthCh, strlen($fullName) + 3);
}
$studentNameWidthCh = min($studentNameWidthCh, 42);
?>
<form id="homeworkForm" action="<?= base_url('/teacher/updateHomework') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
<div class="homework-table-scroll">
<table id="homeworkTable" class="table table-bordered mt-4 homework-sticky-table homework-sticky-table--teacher" style="--homework-name-col-width: <?= (int) $studentNameWidthCh ?>ch;">
<thead>
<tr>
<th class="homework-rownum-col">#</th>
<th class="homework-student-name-col" style="text-align: left;">Student Name</th>
<?php foreach ($homeworkHeaders as $homeworkIndex): ?>
<th class="text-center homework-score-col" data-index="<?= esc($homeworkIndex) ?>"><?= esc("Homework " . $homeworkIndex) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr>
<td class="homework-rownum-col"><?= $index + 1 ?></td>
<td class="homework-student-name-col" style="text-align: left;">
<?= esc($student['firstname'] . ' ' . $student['lastname']) ?>
<input type="hidden" class="student-id" value="<?= $student['student_id'] ?>">
</td>
<?php foreach ($homeworkHeaders as $homeworkIndex): ?>
<td class="homework-score-col">
<?php
$rawScore = $student['scores'][$homeworkIndex] ?? null;
$isEmptyScore = ($rawScore === null || $rawScore === '');
$studentId = (int) ($student['student_id'] ?? 0);
$isMissingOk = !empty($missingOkMap[$studentId][$homeworkIndex]);
?>
<input type="number"
name="scores[<?= intval($student['student_id']) ?>][<?= intval($homeworkIndex) ?>]"
value="<?= $formatScore($rawScore) ?>"
class="form-control text-center <?= $isEmptyScore ? 'score-empty' : '' ?>"
min="0" max="100" step="0.01">
<label class="missing-check mt-1 <?= $isEmptyScore ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= intval($student['student_id']) ?>][<?= intval($homeworkIndex) ?>]"
value="1"
class="missing-score-checkbox"
data-field-label="Homework <?= esc($homeworkIndex) ?>" <?= $isMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="d-flex justify-content-between mt-3">
<button type="submit" class="btn btn-success">Save Changes</button>
<button type="button" id="addColumnBtn" class="btn btn-warning">Add New Column</button>
<button type="button" id="removeColumnBtn" class="btn btn-danger">Remove Empty Column</button>
<a href="<?= base_url('/teacher/scores'); ?>" class="btn btn-info">
<i class="bi bi-arrow-left-circle"></i> Back to Scores
</a>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('homeworkTable');
if (!table) return;
function syncHeaderTitles() {
table.querySelectorAll('thead th[data-index]').forEach((th) => {
const idx = th.dataset.index;
if (!idx) return;
const text = th.textContent.trim();
if (!/^Homework\s+\d+$/i.test(text)) {
th.textContent = 'Homework ' + idx;
}
});
}
const toggleEmptyClass = (input) => {
input.classList.toggle('score-empty', input.value === '' || input.value === null);
};
const toggleMissingCheck = (input) => {
const label = input.parentElement ? input.parentElement.querySelector('.missing-check') : null;
if (!label) return;
const empty = input.value === '' || input.value === null;
label.classList.toggle('d-none', !empty);
if (!empty) {
const checkbox = label.querySelector('input[type="checkbox"]');
if (checkbox) checkbox.checked = false;
}
};
const attachInputListeners = () => {
table.querySelectorAll('input[type="number"]').forEach((input) => {
toggleEmptyClass(input);
toggleMissingCheck(input);
if (!input.dataset.listenerAttached) {
input.addEventListener('input', () => {
toggleEmptyClass(input);
toggleMissingCheck(input);
});
input.dataset.listenerAttached = 'true';
}
});
};
function getMaxHomeworkIndex() {
let maxIndex = 0;
table.querySelectorAll('thead th').forEach(th => {
const dataIndex = th.dataset.index;
if (dataIndex && !Number.isNaN(parseInt(dataIndex, 10))) {
maxIndex = Math.max(maxIndex, parseInt(dataIndex, 10));
return;
}
const match = th.textContent.trim().match(/^Homework\s+(\d+)$/i);
if (match) {
maxIndex = Math.max(maxIndex, parseInt(match[1], 10));
}
});
return maxIndex;
}
syncHeaderTitles();
attachInputListeners();
let homeworkCounter = getMaxHomeworkIndex() + 1;
const addBtn = document.getElementById('addColumnBtn');
const removeBtn = document.getElementById('removeColumnBtn');
addBtn?.addEventListener('click', function(e) {
e.preventDefault();
const newIndex = homeworkCounter++;
const headerRow = table.querySelector('thead tr');
const newTh = document.createElement('th');
newTh.textContent = 'Homework ' + newIndex;
newTh.classList.add(`homework-col-${newIndex}`, 'homework-header', 'dynamic-col', 'text-center');
newTh.dataset.index = newIndex;
headerRow.appendChild(newTh);
table.querySelectorAll('tbody tr').forEach(row => {
const studentIdInput = row.querySelector('.student-id');
const studentId = studentIdInput ? studentIdInput.value : null;
const newTd = document.createElement('td');
newTd.classList.add(`homework-col-${newIndex}`, 'dynamic-col');
if (!studentId) {
row.appendChild(newTd);
return;
}
newTd.innerHTML = `
<input type="number"
name="scores[${studentId}][${newIndex}]"
class="form-control text-center score-empty"
min="0" max="100" step="0.01">
<label class="missing-check mt-1">
<input type="checkbox"
name="missing_ok[${studentId}][${newIndex}]"
value="1"
class="missing-score-checkbox"
data-field-label="Homework ${newIndex}">
Missing ok
</label>`;
row.appendChild(newTd);
});
attachInputListeners();
});
removeBtn?.addEventListener('click', function(e) {
e.preventDefault();
const dynamicHeaders = table.querySelectorAll('th.dynamic-col');
if (dynamicHeaders.length === 0) {
alert('No dynamic columns to remove.');
return;
}
const lastHeader = dynamicHeaders[dynamicHeaders.length - 1];
const index = lastHeader.dataset.index;
lastHeader.remove();
table.querySelectorAll('tbody tr').forEach(row => {
row.querySelector(`.homework-col-${index}.dynamic-col`)?.remove();
});
homeworkCounter = getMaxHomeworkIndex() + 1;
});
});
</script>
<?= $this->endSection() ?>