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

317 lines
14 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') ?>
<div class="container-fluid py-5">
<div class="container-fluid">
<div class="text-center mx-auto mb-5 mt-4" style="max-width: 600px;">
<h2 class="text-dark" style="font-family: Arial, sans-serif;">Update Project 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="projectForm" action="<?= base_url('/teacher/updateProject') ?>" 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="projectTable" class="table table-bordered mt-4 w-100">
<thead>
<tr>
<th>#</th>
<th class="text-left">Student Name</th>
<?php foreach ($projectHeaders as $projectIndex): ?>
<th class="text-center" data-index="<?= esc($projectIndex) ?>"><?= esc("Project " . $projectIndex) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr data-student-id="<?= $student['student_id'] ?>">
<td><?= $index + 1 ?></td>
<td class="text-left">
<?= esc($student['firstname'] . ' ' . $student['lastname']) ?>
<input type="hidden" class="student-id" value="<?= $student['student_id'] ?>">
</td>
<?php foreach ($projectHeaders as $projectIndex): ?>
<td>
<input type="number" name="scores[<?= $student['student_id'] ?>][<?= $projectIndex ?>]"
value="<?= $formatScore($student['scores'][$projectIndex] ?? null) ?>"
class="form-control text-center <?= ($student['scores'][$projectIndex] ?? null) === null || ($student['scores'][$projectIndex] ?? null) === '' ? 'score-empty' : '' ?>"
min="0" max="100" step="0.01">
<?php
$isEmptyScore = (($student['scores'][$projectIndex] ?? null) === null || ($student['scores'][$projectIndex] ?? null) === '');
$studentId = (int) ($student['student_id'] ?? 0);
$isMissingOk = !empty($missingOkMap[$studentId][$projectIndex]);
?>
<label class="missing-check mt-1 <?= $isEmptyScore ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= intval($student['student_id']) ?>][<?= intval($projectIndex) ?>]"
value="1"
class="missing-score-checkbox"
data-field-label="Project <?= esc($projectIndex) ?>" <?= $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') ?>
<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('#projectTable') : 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 (!/^Project\\s+\\d+$/i.test(text)) {
th.textContent = "Project " + idx;
}
});
}
function initProjectTable() {
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();
}
initProjectTable();
}
function destroyDataTable() {
if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable && $table && $table.length)) return;
if (jQuery.fn.DataTable.isDataTable($table)) {
$table.DataTable().destroy();
}
}
syncHeaderTitles();
initProjectTable();
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 Project index
let projectCounter = getMaxProjectIndex() + 1;
function getMaxProjectIndex() {
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(/^Project\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 = projectCounter++;
const headerRow = document.querySelector('thead tr');
const newTh = document.createElement('th');
newTh.textContent = "Project " + newIndex;
newTh.classList.add(`project-col-${newIndex}`, 'project-header', 'dynamic-col', 'text-center');
newTh.dataset.index = newIndex;
headerRow.appendChild(newTh);
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
// ✅ Get student ID safely from the hidden input
const studentIdInput = row.querySelector('.student-id');
const studentId = studentIdInput ? studentIdInput.value : null;
if (!studentId) {
console.warn("Missing student ID in row:", row);
const newTd = document.createElement('td');
newTd.classList.add(`project-col-${newIndex}`, 'dynamic-col');
row.appendChild(newTd);
return;
}
const newTd = document.createElement('td');
newTd.classList.add(`project-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="Project ${newIndex}">
Missing ok
</label>`;
row.appendChild(newTd);
});
attachInputListeners();
initProjectTable();
});
// ❌ 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(`.project-col-${index}.dynamic-col`);
if (cell) {
cell.remove();
}
});
// ✅ Sync the counter with the current highest index
projectCounter = getMaxProjectIndex() + 1;
initProjectTable();
});
});
</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() ?>