298 lines
13 KiB
PHP
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() ?>
|