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
+315
View File
@@ -0,0 +1,315 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid py-5">
<div class="container-fluid">
<div class="text-center mx-auto mb-5 wow fadeInUp" data-wow-delay="0.1s" style="max-width: 600px;">
<br>
<h2 class="text-dark" style="font-family: Arial, sans-serif;">Update Quiz Scores</h2>
</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 ?? [];
?>
<form id="quizForm" action="<?= base_url('/teacher/updateQuizScores') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
<div class="table-responsive">
<table id="quizTable" class="table table-bordered mt-4 w-100">
<thead>
<tr>
<th>#</th>
<th>Student Name</th>
<?php foreach ($quizHeaders as $quizLabel): ?>
<th class="text-center" data-index="<?= esc($quizLabel) ?>"><?= esc("Quiz " . $quizLabel) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr>
<td><?= $index + 1 ?></td>
<td>
<?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?>
<input type="hidden" class="student-id" value="<?= $student['student_id'] ?>">
</td>
<?php foreach ($quizHeaders as $quizLabel): ?>
<?php
$rawScore = $student['scores'][$quizLabel] ?? null;
$isEmptyScore = ($rawScore === null || $rawScore === '');
$studentId = (int) ($student['student_id'] ?? 0);
$isMissingOk = !empty($missingOkMap[$studentId][$quizLabel]);
?>
<td>
<input type="number"
name="scores[<?= intval($student['student_id']) ?>][<?= intval($quizLabel) ?>]"
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($quizLabel) ?>]"
value="1"
class="missing-score-checkbox"
data-field-label="Quiz <?= esc($quizLabel) ?>" <?= $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>
<br>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<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>
document.addEventListener('DOMContentLoaded', function() {
const $table = window.jQuery ? window.jQuery('#quizTable') : null;
// Numeric ordering based on input values
if (window.jQuery && jQuery.fn && jQuery.fn.dataTable && !jQuery.fn.dataTable.ext.order['dom-num-input']) {
jQuery.fn.dataTable.ext.order['dom-num-input'] = function (settings, col) {
return this.api()
.column(col, { order: 'index' })
.nodes()
.map(function (td) {
const val = jQuery('input', td).val();
const num = parseFloat(val);
return Number.isFinite(num) ? num : -Infinity;
});
};
}
function syncHeaderTitles() {
document.querySelectorAll('thead th[data-index]').forEach((th) => {
const idx = th.dataset.index;
if (!idx) return;
const text = th.textContent.trim();
if (!/^Quiz\\s+\\d+$/i.test(text)) {
th.textContent = "Quiz " + idx;
}
});
}
function initQuizTable() {
if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable && $table && $table.length)) return;
const totalCols = $table.find('thead th').length;
const scoreCols = [];
for (let i = 2; i < totalCols; i++) scoreCols.push(i);
$table.DataTable({
paging: false,
info: false,
searching: false,
autoWidth: false,
order: [[1, 'asc']],
columnDefs: [
{ targets: 0, orderable: false, searchable: false },
...(scoreCols.length ? [{ targets: scoreCols, orderDataType: 'dom-num-input', orderSequence: ['desc', 'asc'], type: 'num' }] : []),
],
drawCallback: function () {
const api = this.api();
api.column(0, { search: 'applied', order: 'applied' }).nodes().each(function (cell, i) {
cell.textContent = i + 1;
});
},
});
syncHeaderTitles();
}
function refreshDataTable() {
if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable && $table && $table.length)) return;
if (jQuery.fn.DataTable.isDataTable($table)) {
$table.DataTable().destroy();
}
initQuizTable();
}
function destroyDataTable() {
if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable && $table && $table.length)) return;
if (jQuery.fn.DataTable.isDataTable($table)) {
$table.DataTable().destroy();
}
}
syncHeaderTitles();
initQuizTable();
const toggleEmptyClass = (input) => {
if (input.value === '' || input.value === null) {
input.classList.add('score-empty');
} else {
input.classList.remove('score-empty');
}
};
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 = () => {
document.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';
}
});
};
attachInputListeners();
// Initialize counter from current highest Quiz index
let quizCounter = getMaxQuizIndex() + 1;
function getMaxQuizIndex() {
const headers = document.querySelectorAll('thead th');
let maxIndex = 0;
headers.forEach(th => {
const dataIndex = th.dataset.index;
if (dataIndex && !isNaN(parseInt(dataIndex, 10))) {
maxIndex = Math.max(maxIndex, parseInt(dataIndex, 10));
return;
}
const text = th.textContent.trim();
const match = text.match(/^Quiz\s+(\d+)$/i);
if (match) {
const index = parseInt(match[1], 10);
if (!isNaN(index)) {
maxIndex = Math.max(maxIndex, index);
}
}
});
return maxIndex;
}
const addBtn = document.getElementById('addColumnBtn');
const removeBtn = document.getElementById('removeColumnBtn');
// Add Column
addBtn.addEventListener('click', function(e) {
e.preventDefault();
destroyDataTable();
const newIndex = quizCounter++;
// Add new header
const headerRow = document.querySelector('thead tr');
const newTh = document.createElement('th');
newTh.textContent = "Quiz " + newIndex;
newTh.classList.add(`quiz-col-${newIndex}`, 'quiz-header', 'dynamic-col', 'text-center');
newTh.dataset.index = newIndex;
headerRow.appendChild(newTh);
// Add column for each student row
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
const studentIdInput = row.querySelector('.student-id');
const studentId = studentIdInput ? studentIdInput.value : null;
if (!studentId) {
console.warn("Missing student ID input for row:", row);
const newTd = document.createElement('td');
newTd.classList.add(`quiz-col-${newIndex}`, 'dynamic-col');
row.appendChild(newTd);
return;
}
const newTd = document.createElement('td');
newTd.classList.add(`quiz-col-${newIndex}`, 'dynamic-col');
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=\"Quiz ${newIndex}\">
Missing ok
</label>`;
row.appendChild(newTd);
});
attachInputListeners();
initQuizTable();
});
// ❌ Remove Last Column
removeBtn.addEventListener('click', function(e) {
e.preventDefault();
destroyDataTable();
const dynamicHeaders = document.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();
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
const cell = row.querySelector(`.quiz-col-${index}.dynamic-col`);
if (cell) {
cell.remove();
}
});
// Sync the counter with remaining columns
quizCounter = getMaxQuizIndex() + 1;
initQuizTable();
});
});
</script>
<style>
.score-empty {
background-color: #fff3cd;
}
.missing-check {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.75rem;
color: #6c757d;
}
</style>
<?= $this->endSection() ?>