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
+169
View File
@@ -0,0 +1,169 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-4" style="max-width:700px;">
<h4 class="text-dark" style="font-family:Arial,sans-serif;">TimeOff Request</h4>
<div class="text-muted">Logged in as: <strong><?= esc($teacher_name ?? 'Teacher') ?></strong></div>
<?php if (!empty($semester) && !empty($schoolYear)): ?>
<div class="small text-muted">Term: <?= esc($semester) ?> — <?= esc($schoolYear) ?></div>
<?php endif; ?>
</div>
<?php if (session()->getFlashdata('message')): ?>
<?php $status = session()->getFlashdata('status') ?? 'info';
$cls = $status === 'success' ? 'alert-success' : ($status === 'error' ? 'alert-danger' : 'alert-info'); ?>
<div class="alert <?= $cls ?> text-center" role="alert">
<?= esc(session()->getFlashdata('message')) ?>
</div>
<?php endif; ?>
<div class="card shadow-sm mb-4">
<div class="card-header bg-light"><strong>Submit Upcoming Absences</strong></div>
<div class="card-body">
<form action="<?= site_url('/teacher/absence') ?>" method="post" id="absenceForm">
<?= csrf_field() ?>
<?php
$formatDateLabel = function ($value) {
if (!$value) return '';
$value = (string)$value;
if (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})/', $value, $m)) {
return $m[2] . '-' . $m[3] . '-' . $m[1];
}
try {
return local_date($value, 'm-d-Y');
} catch (Throwable $e) {
return $value;
}
};
?>
<?php $hasDates = !empty($availableDates) && is_array($availableDates); ?>
<div class="mb-3">
<label class="form-label">Select Sunday Date(s)</label>
<div id="datesContainer">
<div class="d-flex align-items-center gap-2 mb-2 date-row">
<select name="dates[]" class="form-select" style="max-width:260px;" <?= $hasDates ? '' : 'disabled' ?> required>
<option value="" disabled selected>Select Sunday date</option>
<?php foreach (($availableDates ?? []) as $d): ?>
<option value="<?= esc($d) ?>"><?= esc($formatDateLabel($d)) ?></option>
<?php endforeach; ?>
</select>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addDateRow()" <?= $hasDates ? '' : 'disabled' ?>>
<i class="bi bi-plus-lg"></i> Add another
</button>
</div>
</div>
<div class="form-text">Future Sundays from September to May are available only.</div>
<?php if (!$hasDates): ?>
<div class="alert alert-info mt-2">No future Sundays are available in the current term.</div>
<?php endif; ?>
</div>
<div class="row g-3 mb-3">
<div class="col-sm-6">
<label class="form-label" for="reason_type">Reason Type</label>
<select id="reason_type" name="reason_type" class="form-select">
<option value="vacation">Vacation</option>
<option value="sick">Sick</option>
<option value="personal">Personal</option>
<option value="other">Other</option>
</select>
</div>
<div class="col-sm-6">
<label class="form-label" for="reason">Reason <span class="text-danger">*</span></label>
<input type="text" id="reason" name="reason" class="form-control" placeholder="Provide a reason" required>
</div>
</div>
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-primary px-5" <?= $hasDates ? '' : 'disabled' ?> >
Submit Absence
</button>
</div>
</form>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-light"><strong>Your Reported Status (This Term)</strong></div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-bordered table-striped m-0 align-middle">
<thead class="table-light">
<tr>
<th class="text-center" style="width:80px;">#</th>
<th>Date</th>
<th>Status</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<?php if (!empty($existing)): ?>
<?php $i=1; foreach ($existing as $row): ?>
<tr>
<td class="text-center"><?= $i++ ?></td>
<td><?= esc(!empty($row['date']) ? $formatDateLabel($row['date']) : '') ?></td>
<td>
<?php $st = strtolower((string)($row['status'] ?? ''));
$badge = $st==='absent'?'danger':($st==='late'?'warning text-dark':'success'); ?>
<span class="badge bg-<?= $badge ?>"><?= ucfirst($st ?: 'n/a') ?></span>
</td>
<td><?= esc($row['reason'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="4" class="text-center">No reported entries yet.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
const AVAILABLE_DATES = <?= json_encode(array_values($availableDates ?? [])) ?>;
function addDateRow() {
if (!AVAILABLE_DATES || AVAILABLE_DATES.length === 0) return;
const container = document.getElementById('datesContainer');
const row = document.createElement('div');
row.className = 'd-flex align-items-center gap-2 mb-2 date-row';
const select = document.createElement('select');
select.name = 'dates[]';
select.className = 'form-select';
select.style.maxWidth = '260px';
select.required = true;
const def = document.createElement('option');
def.value = '';
def.disabled = true;
def.selected = true;
def.textContent = 'Select Sunday date';
select.appendChild(def);
AVAILABLE_DATES.forEach(function(d) {
const opt = document.createElement('option');
opt.value = d;
opt.textContent = d;
select.appendChild(opt);
});
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-outline-danger btn-sm';
btn.innerHTML = '<i class="bi bi-x-lg"></i> Remove';
btn.addEventListener('click', function(){ row.remove(); });
row.appendChild(select);
row.appendChild(btn);
container.appendChild(row);
}
</script>
<?= $this->endSection() ?>
+111
View File
@@ -0,0 +1,111 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<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;">Final Exam 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 action="<?= base_url('/teacher/updateFinalExamScores') ?>" method="post"
onsubmit="return validateForm()">
<?= csrf_field() ?>
<table class="table table-bordered mt-4">
<thead>
<tr>
<th>#</th>
<th>Student Name</th>
<th class="text-center">Final Exam Score</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr>
<td><?= $index + 1 ?></td>
<td><?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?></td>
<td>
<?php
$rawScore = $student['score'] ?? null;
$isEmptyScore = ($rawScore === null || $rawScore === '');
$studentId = (int) ($student['student_id'] ?? 0);
$isMissingOk = !empty($missingOkMap[$studentId][0]);
?>
<input type="number" name="final_score[<?= esc($student['student_id']) ?>][score]"
value="<?= $formatScore($rawScore) ?>" class="form-control text-center <?= $isEmptyScore ? 'score-empty' : '' ?>" min="0" max="100" step="0.01"
placeholder="Enter score">
<label class="missing-check mt-1 <?= $isEmptyScore ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= esc($student['student_id']) ?>][final_exam]"
value="1"
class="missing-score-checkbox"
data-field-label="Final Exam" <?= $isMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<button type="submit" class="btn btn-success">Save Changes</button>
<a href="<?= base_url('/teacher/scores'); ?>" class="btn btn-info">
<i class="bi bi-arrow-left-circle"></i> Back to Scores
</a>
</form>
<br>
</div>
<style>
.score-empty {
background-color: #fff3cd;
}
.missing-check {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.75rem;
color: #6c757d;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
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;
}
};
document.querySelectorAll('input[type="number"]').forEach((input) => {
toggleEmptyClass(input);
toggleMissingCheck(input);
input.addEventListener('input', () => {
toggleEmptyClass(input);
toggleMissingCheck(input);
});
});
});
</script>
<?= $this->endSection() ?>
+316
View File
@@ -0,0 +1,316 @@
<?= $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;">
<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 ?? [];
?>
<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="table-responsive">
<table id="homeworkTable" class="table table-bordered mt-4 w-100">
<thead>
<tr>
<th>#</th>
<th style="text-align: left;">Student Name</th>
<?php foreach ($homeworkHeaders as $homeworkIndex): ?>
<th class="text-center" data-index="<?= esc($homeworkIndex) ?>"><?= esc("Homework " . $homeworkIndex) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr>
<td><?= $index + 1 ?></td>
<td 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>
<?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') ?>
<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('#homeworkTable') : 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 (!/^Homework\\s+\\d+$/i.test(text)) {
th.textContent = "Homework " + idx;
}
});
}
function initHomeworkTable() {
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();
}
initHomeworkTable();
}
function destroyDataTable() {
if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable && $table && $table.length)) return;
if (jQuery.fn.DataTable.isDataTable($table)) {
$table.DataTable().destroy();
}
}
syncHeaderTitles();
initHomeworkTable();
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 Homework index
let homeworkCounter = getMaxHomeworkIndex() + 1;
function getMaxHomeworkIndex() {
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(/^Homework\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 = homeworkCounter++;
const headerRow = document.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);
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 in row", row);
const newTd = document.createElement('td');
newTd.classList.add(`homework-col-${newIndex}`, 'dynamic-col');
row.appendChild(newTd);
return;
}
const newTd = document.createElement('td');
newTd.classList.add(`homework-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="Homework ${newIndex}">
Missing ok
</label>`;
row.appendChild(newTd);
});
attachInputListeners();
initHomeworkTable();
});
// ❌ 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(`.homework-col-${index}.dynamic-col`);
if (cell) {
cell.remove();
}
});
// ✅ Sync the counter with the current highest index
homeworkCounter = getMaxHomeworkIndex() + 1;
initHomeworkTable();
});
});
</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() ?>
+148
View File
@@ -0,0 +1,148 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<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;">Add Midterm Exam 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 action="<?= base_url('/teacher/updateMidtermExamScores') ?>" method="post"
onsubmit="return validateForm()">
<?= csrf_field() ?>
<table class="table table-bordered mt-4" id="midtermExamTable">
<thead>
<tr>
<th>#</th>
<th>Student Name</th>
<th class="text-center">Midterm Exam Score</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr>
<td><?= $index + 1 ?></td>
<td><?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?></td>
<td>
<?php
$rawScore = $student['score'] ?? null;
$isEmptyScore = ($rawScore === null || $rawScore === '');
$studentId = (int) ($student['student_id'] ?? 0);
$isMissingOk = !empty($missingOkMap[$studentId][0]);
?>
<input type="number" name="final_score[<?= esc($student['student_id']) ?>][score]"
value="<?= $formatScore($rawScore) ?>" class="form-control text-center <?= $isEmptyScore ? 'score-empty' : '' ?>" min="0" max="100" step="0.01"
placeholder="Enter score">
<label class="missing-check mt-1 <?= $isEmptyScore ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= esc($student['student_id']) ?>][midterm]"
value="1"
class="missing-score-checkbox"
data-field-label="Midterm Exam" <?= $isMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<button type="submit" class="btn btn-success">Save Changes</button>
<a href="<?= base_url('/teacher/scores'); ?>" class="btn btn-info">
<i class="bi bi-arrow-left-circle"></i> Back to Scores
</a>
</form>
<br>
</div>
<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() ?>
<?= $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 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;
}
};
document.querySelectorAll('input[type="number"]').forEach((input) => {
toggleEmptyClass(input);
toggleMissingCheck(input);
input.addEventListener('input', () => {
toggleEmptyClass(input);
toggleMissingCheck(input);
});
});
});
</script>
<script>
(function($) {
function initMidtermExamTable() {
if (!($ && $.fn && $.fn.DataTable)) return;
const $table = $('#midtermExamTable');
if (!$table.length) return;
$table.DataTable({
paging: false,
info: false,
searching: false,
autoWidth: false,
order: [[1, 'asc']],
columnDefs: [
{ targets: 0, orderable: false, searchable: false },
{ targets: 2, orderable: false, searchable: false },
],
drawCallback: function() {
const api = this.api();
api.column(0, { search: 'applied', order: 'applied' }).nodes().each(function(cell, i) {
cell.textContent = i + 1;
});
},
});
}
$(document).ready(initMidtermExamTable);
})(jQuery);
</script>
<?= $this->endSection() ?>
+148
View File
@@ -0,0 +1,148 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<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;">Add Participation 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 action="<?= base_url('/teacher/updateParticipationScore') ?>" method="post"
onsubmit="return validateForm()">
<?= csrf_field() ?>
<table class="table table-bordered mt-4" id="participationTable">
<thead>
<tr>
<th>#</th>
<th>Student Name</th>
<th class="text-center">Participation Score</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<tr>
<td><?= $index + 1 ?></td>
<td><?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?></td>
<td>
<?php
$rawScore = $student['score'] ?? null;
$isEmptyScore = ($rawScore === null || $rawScore === '');
$studentId = (int) ($student['student_id'] ?? 0);
$isMissingOk = !empty($missingOkMap[$studentId][0]);
?>
<input type="number" name="final_score[<?= esc($student['student_id']) ?>][score]"
value="<?= $formatScore($rawScore) ?>" class="form-control text-center <?= $isEmptyScore ? 'score-empty' : '' ?>"
min="0" max="100" step="0.01" placeholder="Enter score">
<label class="missing-check mt-1 <?= $isEmptyScore ? '' : 'd-none' ?>">
<input type="checkbox"
name="missing_ok[<?= esc($student['student_id']) ?>][participation]"
value="1"
class="missing-score-checkbox"
data-field-label="Participation" <?= $isMissingOk ? 'checked' : '' ?>>
Missing ok
</label>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<button type="submit" class="btn btn-success">Save Changes</button>
<a href="<?= base_url('/teacher/scores'); ?>" class="btn btn-info">
<i class="bi bi-arrow-left-circle"></i> Back to Scores
</a>
</form>
<br>
</div>
<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() ?>
<?= $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 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;
}
};
document.querySelectorAll('input[type="number"]').forEach((input) => {
toggleEmptyClass(input);
toggleMissingCheck(input);
input.addEventListener('input', () => {
toggleEmptyClass(input);
toggleMissingCheck(input);
});
});
});
</script>
<script>
(function($) {
function initParticipationTable() {
if (!($ && $.fn && $.fn.DataTable)) return;
const $table = $('#participationTable');
if (!$table.length) return;
$table.DataTable({
paging: false,
info: false,
searching: false,
autoWidth: false,
order: [[1, 'asc']],
columnDefs: [
{ targets: 0, orderable: false, searchable: false },
{ targets: 2, orderable: false, searchable: false },
],
drawCallback: function() {
const api = this.api();
api.column(0, { search: 'applied', order: 'applied' }).nodes().each(function(cell, i) {
cell.textContent = i + 1;
});
},
});
}
$(document).ready(initParticipationTable);
})(jQuery);
</script>
<?= $this->endSection() ?>
+316
View File
@@ -0,0 +1,316 @@
<?= $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() ?>
+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() ?>
@@ -0,0 +1,17 @@
<?= $this->extend('layout/main') ?>
<?= $this->section('content') ?>
<div class="container mt-5">
<h2>Add New Quiz Column</h2>
<form method="post" action="<?= base_url('/teacher/addQuizColumn') ?>">
<?= csrf_field(); ?>
<div class="mb-3">
<label for="quiz_index" class="form-label">Quiz Number</label>
<input type="text" name="quiz_index" class="form-control" id="quiz_index" placeholder="e.g. Quiz 4"
required>
</div>
<button type="submit" class="btn btn-primary">Add Column</button>
<a href="<?= base_url('/teacher/addQuiz') ?>" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?= $this->endSection() ?>
+73
View File
@@ -0,0 +1,73 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="d-flex align-items-center gap-3 mb-3">
<strong>Legend:</strong>
<span class="d-inline-flex align-items-center"><span style="display:inline-block;width:12px;height:12px;border-radius:3px;background:#3788d8;border:1px solid rgba(0,0,0,.2);margin-right:6px"></span>General Event</span>
<span class="d-inline-flex align-items-center"><span style="display:inline-block;width:12px;height:12px;border-radius:3px;background:#28a745;border:1px solid rgba(0,0,0,.2);margin-right:6px"></span>Teacher-only Event</span>
<span class="d-inline-flex align-items-center"><span style="display:inline-block;width:12px;height:12px;border-radius:3px;background:#ffc107;border:1px solid rgba(0,0,0,.2);margin-right:6px"></span>No School Day</span>
</div>
<div class="main-content">
<div id="calendar-container">
<div id="calendar"></div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const calendarEl = document.getElementById('calendar');
const isMobile = window.innerWidth <= 576;
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: isMobile ? 'listWeek' : 'dayGridMonth',
height: 'auto',
contentHeight: 'auto',
expandRows: true,
events: <?= json_encode($events) ?>,
eventDidMount: function(info) {
if (info.event.extendedProps.description) {
$(info.el).tooltip({
title: info.event.extendedProps.description,
placement: 'top',
trigger: 'hover',
container: 'body'
});
}
},
eventContent: function(arg) {
const event = arg.event;
const bg = event.extendedProps.backgroundColor || '#3788d8';
return {
html: `
<div style="
background-color: ${bg};
padding: 4px;
border-radius: 4px;
color: #fff;
font-size: 0.8rem;
text-align: center;">
${event.title}
</div>`
};
},
windowResize: function(view) {
if (window.innerWidth <= 576 && calendar.view.type !== 'listWeek') {
calendar.changeView('listWeek');
} else if (window.innerWidth > 576 && calendar.view.type !== 'dayGridMonth') {
calendar.changeView('dayGridMonth');
}
calendar.updateSize();
}
});
calendar.render();
window.calendar = calendar;
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,98 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container py-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h3 class="mb-0">My Progress Reports</h3>
<div class="text-muted">Review weekly submissions and compare Islamic Studies with Quran/Arabic.</div>
</div>
<a href="<?= base_url('teacher/progress/submit') ?>" class="btn btn-outline-secondary">Submit New Report</a>
</div>
<?php
$reportGroups = $reportGroups ?? [];
$subjectSections = $subjectSections ?? [];
$classSectionOptions = $classSectionOptions ?? [];
$selectedSectionId = $selectedSectionId ?? null;
?>
<?php if (!empty($classSectionOptions)): ?>
<form method="get" class="mb-3 row g-2 align-items-center">
<div class="col-auto">
<label class="form-label mb-0 small">Class / Section</label>
</div>
<div class="col-auto">
<select name="class_section_id" class="form-select form-select-sm" onchange="this.form.submit()">
<?php foreach ($classSectionOptions as $id => $label): ?>
<option value="<?= esc($id) ?>" <?= $selectedSectionId == $id ? 'selected' : '' ?>><?= esc($label ?: 'Unknown Section') ?></option>
<?php endforeach; ?>
</select>
</div>
</form>
<?php endif; ?>
<?php if (empty($reportGroups)): ?>
<div class="alert alert-secondary">No reports submitted yet.</div>
<?php else: ?>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Week</th>
<th>Subjects</th>
<th class="text-end">Details</th>
</tr>
</thead>
<tbody>
<?php foreach ($reportGroups as $group): ?>
<?php
$start = $group['week_start'] ?? '';
$end = $group['week_end'] ?? '';
$weekLabel = $start ? date('M d, Y', strtotime($start)) : '-';
if ($end) {
$weekLabel .= ' ' . date('M d, Y', strtotime($end));
}
$reports = $group['reports'] ?? [];
$exampleReport = $reports ? reset($reports) : null;
?>
<tr>
<td>
<div class="fw-semibold"><?= esc($weekLabel) ?></div>
<?php if (!empty($group['class_section_name'])): ?>
<div class="text-muted small">Class: <?= esc($group['class_section_name']) ?></div>
<?php endif; ?>
</td>
<td>
<div class="d-flex flex-column gap-2">
<?php foreach ($subjectSections as $slug => $section): ?>
<?php
$subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
$report = $reports[$subjectName] ?? null;
$statusBadge = $report ? ($report['status_label'] ?? 'Unknown') : 'No submission';
$badgeClass = $report ? 'bg-secondary' : 'bg-light text-muted';
?>
<div class="border rounded-3 p-2">
<div class="d-flex justify-content-between align-items-center">
<strong class="small mb-0"><?= esc($section['label'] ?? $subjectName) ?></strong>
<span class="badge <?= $badgeClass ?>"><?= esc($statusBadge) ?></span>
</div>
<div class="small text-muted">
<?= $report ? esc($report['unit_title'] ?: '-') : 'No submission' ?>
</div>
</div>
<?php endforeach; ?>
</div>
</td>
<td class="text-end">
<?php if ($exampleReport): ?>
<a href="<?= base_url('teacher/progress/view/' . $exampleReport['id']) ?>" class="btn btn-sm btn-outline-primary">View Weekly Details</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+358
View File
@@ -0,0 +1,358 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<?php
$hasClass = !empty($classSectionId);
$assignedClassName = $classSectionName ?? '';
$sundayOptions = $sundayOptions ?? [];
$defaultWeekStart = $defaultWeekStart ?? ($sundayOptions[0] ?? '');
$weekStartSelected = set_value('week_start', $defaultWeekStart);
$weekEndValue = set_value('week_end');
if (!$weekEndValue && $weekStartSelected) {
try {
$dt = new \DateTime($weekStartSelected);
$dt->modify('+6 days');
$weekEndValue = $dt->format('Y-m-d');
} catch (\Exception $e) {
$weekEndValue = '';
}
}
$weekEndLabel = '';
if ($weekEndValue) {
try {
$weekEndLabel = (new \DateTime($weekEndValue))->format('M d, Y');
} catch (\Exception $e) {
$weekEndLabel = 'Week end';
}
} else {
$weekEndLabel = 'Select a week';
}
?>
<div class="py-4">
<div class="d-flex flex-wrap align-items-center justify-content-between mb-3">
<div>
<h3 class="mb-0">
<?= esc($classSectionName ? "Class {$classSectionName} Progress Submission" : 'Class Progress Submission') ?>
</h3>
<div class="text-muted">Submit weekly progress for a single subject</div>
</div>
<a href="<?= base_url('teacher/progress/history') ?>" class="btn btn-outline-secondary">My Submissions</a>
</div>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('errors')): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach (session()->getFlashdata('errors') as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form action="<?= base_url('teacher/progress/store') ?>" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
<?= csrf_field() ?>
<input type="hidden" name="class_section_id" value="<?= esc($classSectionId ?? '') ?>">
<div class="row g-3">
<div class="col">
<div class="card shadow-sm mb-3">
<div class="card-header bg-white d-flex flex-wrap align-items-center justify-content-between gap-3">
<strong class="mb-0">Date Selection</strong>
<div class="d-flex align-items-center gap-2">
<select id="weekStartSelect" name="week_start" class="form-select form-select-sm" required>
<option value="">Select week</option>
<?php foreach ($sundayOptions as $sunday): ?>
<?php
try {
$startDt = new \DateTime($sunday);
$displayStart = $startDt->format('M d, Y');
} catch (\Exception $e) {
$displayStart = $sunday;
}
?>
<option value="<?= esc($sunday) ?>" <?= $sunday === $weekStartSelected ? 'selected' : '' ?>>
<?= esc($displayStart) ?>
</option>
<?php endforeach; ?>
</select>
<div class="invalid-feedback">Week start is required.</div>
</div>
</div>
<div class="card-body">
<input type="hidden" name="week_end" id="weekEndInput" value="<?= esc($weekEndValue) ?>" required>
</div>
</div>
</div>
</div>
<div class="row g-3 mt-1">
<?php
$subjectSections = $subjectSections ?? [];
$subjectCurriculum = $subjectCurriculum ?? [];
?>
<?php foreach ($subjectSections as $slug => $section): ?>
<?php
$unitValues = old("unit_$slug") ?? [];
$chapterValues = old("chapter_$slug") ?? [];
$rowsCount = max(count($unitValues), count($chapterValues));
?>
<div class="col-lg-6">
<div class="border border-dark rounded-3 p-3 h-100 subject-section">
<div class="d-flex align-items-center justify-content-between mb-3">
<h4 class="mb-0"><?= esc($section['label'] ?? $slug) ?></h4>
</div>
<?php $curriculumOptions = $subjectCurriculum[$slug] ?? []; ?>
<?php
$preparedEntries = array_values(array_filter(array_map(function ($option) {
$chapterName = trim($option['chapter_name'] ?? '');
if ($chapterName === '') {
return null;
}
return [
'unit_number' => $option['unit_number'] ?? '',
'unit_title' => trim($option['unit_title'] ?? ''),
'chapter_name' => $chapterName,
];
}, $curriculumOptions)));
?>
<div class="mb-2 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-2">
<div class="position-relative">
<button type="button"
class="btn btn-sm btn-outline-dark"
data-add-unit-chapter
data-subject="<?= esc($slug) ?>">
<?= $slug === 'quran' ? '+ Add Surah, Arabic Subject' : '+ Add Unit, Chapter' ?>
</button>
<div class="subject-curriculum-list position-absolute d-none border rounded shadow-sm bg-white mt-1" id="curriculumList-<?= esc($slug) ?>" style="min-width: 220px; z-index: 5;">
<?php if (empty($preparedEntries)): ?>
<div class="p-2 text-muted small">No saved entries.</div>
<?php else: ?>
<?php foreach ($preparedEntries as $entry): ?>
<?php
$unitNumber = $entry['unit_number'] ?? '';
$unitTitle = $entry['unit_title'] ?? '';
$chapterName = $entry['chapter_name'] ?? '';
$displayParts = [];
if ($slug === 'islamic') {
if ($unitNumber) $displayParts[] = 'Unit ' . $unitNumber;
if ($unitTitle !== '') $displayParts[] = $unitTitle;
$prefix = $displayParts ? implode(' ', $displayParts) . ' / ' : '';
$display = $prefix . $chapterName;
} else {
$display = 'Surah: ' . $chapterName;
}
?>
<button type="button"
class="dropdown-item text-start"
data-curriculum-entry
data-subject="<?= esc($slug) ?>"
data-unit-number="<?= esc($unitNumber) ?>"
data-unit-title="<?= esc($unitTitle) ?>"
data-chapter="<?= esc($chapterName) ?>">
<?= esc($display) ?>
</button>
<?php endforeach; ?>
<?php endif; ?>
<?php if ($slug === 'quran'): ?>
<div class="px-2 py-2 border-top">
<div class="input-group input-group-sm">
<input type="text" class="form-control" placeholder="Custom Surah" data-custom-input data-subject="<?= esc($slug) ?>">
<button type="button" class="btn btn-outline-secondary" data-custom-entry data-subject="<?= esc($slug) ?>">Add</button>
</div>
<div class="form-text small text-muted">Add a custom Surah or Arabic target.</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="unit-chapter-rows mb-3" id="unitChapterRows-<?= esc($slug) ?>">
<?php for ($i = 0; $i < $rowsCount; $i++): ?>
<div class="row g-2 align-items-end mb-2 unit-chapter-entry">
<div class="col">
<input type="text" name="unit_<?= esc($slug) ?>[]" class="form-control form-control-sm" placeholder="Unit name" value="<?= esc($unitValues[$i] ?? '') ?>">
</div>
<div class="col">
<input type="text" name="chapter_<?= esc($slug) ?>[]" class="form-control form-control-sm" placeholder="Chapter" value="<?= esc($chapterValues[$i] ?? '') ?>">
</div>
</div>
<?php endfor; ?>
</div>
<div class="mb-3">
<label class="form-label mb-1">What has been covered?</label>
<textarea name="covered_<?= esc($slug) ?>" class="form-control" rows="4" required placeholder="What was taught? Key topics, activities, memorization, etc."><?= esc(old("covered_$slug")) ?></textarea>
</div>
<div class="mb-3">
<label class="form-label mb-1">Assigned homework:</label>
<textarea name="homework_<?= esc($slug) ?>" class="form-control" rows="3" placeholder="Homework, practice quizzes, review pages"><?= esc(old("homework_$slug")) ?></textarea>
</div>
<div class="mb-3">
<label class="form-label mb-1">Attachment (optional):</label>
<input type="file" name="attachment_<?= esc($slug) ?>[]" class="form-control" accept=".pdf,.jpg,.jpeg,.png" multiple>
<div class="form-text small">Upload worksheets, quizzes, or reference files for <?= esc(strtolower($section['label'] ?? 'this subject')) ?>. You can select multiple files.</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="row mt-3">
<div class="col">
<div class="card shadow-sm">
<div class="card-body d-flex flex-column">
<button class="btn btn-primary w-100 mt-auto" type="submit" <?= $hasClass ? '' : 'disabled' ?>>Submit Progress</button>
<?php if (! $hasClass): ?>
<div class="text-muted small mt-2">
You are not assigned to a class. Contact the administrator to submit progress.
</div>
<?php else: ?>
<div class="text-muted small mt-2">Make sure week dates and subject information are accurate before submitting.</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
(() => {
'use strict';
const forms = document.querySelectorAll('.needs-validation');
Array.from(forms).forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
}, false);
});
const weekStartSelect = document.getElementById('weekStartSelect');
const weekEndInput = document.getElementById('weekEndInput');
const updateWeekEnd = (value) => {
if (!weekEndInput) return;
if (!value) {
weekEndInput.value = '';
return;
}
const startDate = new Date(value);
if (Number.isNaN(startDate.valueOf())) {
weekEndInput.value = '';
return;
}
const endDate = new Date(startDate);
endDate.setDate(endDate.getDate() + 6);
weekEndInput.value = endDate.toISOString().slice(0, 10);
};
if (weekStartSelect) {
weekStartSelect.addEventListener('change', () => updateWeekEnd(weekStartSelect.value));
updateWeekEnd(weekStartSelect.value);
}
const buildRow = (slug, unitValue = '', chapterValue = '') => {
const row = document.createElement('div');
row.className = 'row g-2 align-items-end mb-2 unit-chapter-entry';
row.innerHTML = `
<div class="col">
<input type="text" name="unit_${slug}[]" class="form-control form-control-sm" placeholder="Unit name" />
</div>
<div class="col">
<input type="text" name="chapter_${slug}[]" class="form-control form-control-sm" placeholder="Chapter" />
</div>
`;
const inputs = row.querySelectorAll('input');
if (inputs[0]) {
inputs[0].value = unitValue;
}
if (inputs[1]) {
inputs[1].value = chapterValue;
}
return row;
};
const appendUnitChapterRow = (slug, unitValue = '', chapterValue = '') => {
const container = document.getElementById(`unitChapterRows-${slug}`);
if (!container) return;
container.appendChild(buildRow(slug, unitValue, chapterValue));
};
const buildUnitDisplay = (subject, unitNumber, unitTitle, { isQuran = false, isCustom = false } = {}) => {
const parts = [];
if (unitNumber) {
parts.push(`Unit ${unitNumber}`);
}
if (unitTitle) {
parts.push(unitTitle);
}
if (isQuran) {
return isCustom ? 'Custom' : 'Surah';
}
return parts.join(' ');
};
const hideMenus = () => {
document.querySelectorAll('.subject-curriculum-list').forEach(menu => menu.classList.add('d-none'));
};
document.addEventListener('click', () => hideMenus());
document.querySelectorAll('[data-add-unit-chapter]').forEach(button => {
const subject = button.dataset.subject;
if (!subject) return;
const menu = document.getElementById(`curriculumList-${subject}`);
if (menu) {
menu.addEventListener('click', event => event.stopPropagation());
}
button.addEventListener('click', event => {
event.stopPropagation();
if (!menu) return;
const shouldShow = menu.classList.contains('d-none');
hideMenus();
if (shouldShow) {
menu.classList.remove('d-none');
activeMenus.add(menu);
}
});
});
document.querySelectorAll('[data-curriculum-entry]').forEach(entry => {
entry.addEventListener('click', event => {
event.stopPropagation();
const subject = entry.dataset.subject;
const unitNumber = entry.dataset.unitNumber || '';
const unitTitle = entry.dataset.unitTitle || '';
const chapterValue = entry.dataset.chapter || '';
if (!subject) return;
const unitValue = buildUnitDisplay(subject, unitNumber, unitTitle, { isQuran: subject === 'quran' });
appendUnitChapterRow(subject, unitValue, chapterValue);
hideMenus();
});
});
document.querySelectorAll('[data-custom-entry]').forEach(button => {
button.addEventListener('click', event => {
event.stopPropagation();
const subject = button.dataset.subject;
if (!subject) return;
const input = document.querySelector(`[data-custom-input][data-subject="${subject}"]`);
if (!input) return;
const customValue = input.value.trim();
if (customValue === '') return;
const unitValue = buildUnitDisplay(subject, '', '', { isQuran: subject === 'quran', isCustom: true });
appendUnitChapterRow(subject, unitValue, customValue);
input.value = '';
hideMenus();
});
});
})();
</script>
<?= $this->endSection() ?>
+71
View File
@@ -0,0 +1,71 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container py-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h3 class="mb-0">Progress Report</h3>
<div class="text-muted">
<?= esc($row['class_section_name'] ?? '-') ?> • <?= esc($row['week_start']) ?>
</div>
</div>
<a href="<?= base_url('teacher/progress/history') ?>" class="btn btn-outline-secondary">Back</a>
</div>
<div class="row g-4">
<?php
$weeklyReports = $weeklyReports ?? [];
$subjectSections = $subjectSections ?? [];
$reportsBySubject = [];
foreach ($weeklyReports as $report) {
$reportsBySubject[$report['subject']] = $report;
}
?>
<?php foreach ($subjectSections as $slug => $section): ?>
<?php
$subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
$isQuran = $subjectName === 'Quran/Arabic';
$unitLabel = $isQuran ? 'Surah / Custom Arabic' : 'Unit / Chapter';
$homeworkLabel = $isQuran ? 'Arabic Practice / Homework' : 'Assigned Homework';
$report = $reportsBySubject[$subjectName] ?? null;
?>
<div class="col-md-6">
<div class="card shadow-sm mb-3 h-100">
<div class="card-header bg-white">
<strong><?= esc($section['label'] ?? $subjectName) ?></strong>
</div>
<div class="card-body">
<?php if (! $report): ?>
<div class="text-muted">No entry submitted for this subject this week.</div>
<?php else: ?>
<div class="mb-2"><strong><?= $unitLabel ?>:</strong> <?= esc($report['unit_title'] ?: '-') ?></div>
<div class="mb-3"><strong>Activities</strong><div class="mt-1"><?= nl2br(esc($report['covered'] ?? '-')) ?></div></div>
<div class="mb-3">
<strong><?= $homeworkLabel ?>:</strong>
<div class="mt-1"><?= nl2br(esc($report['homework'] ?: '-')) ?></div>
</div>
<?php if (!empty($report['attachments'])): ?>
<div class="mt-3">
<strong>Attachments:</strong>
<div class="d-flex flex-column gap-2 mt-2">
<?php foreach ($report['attachments'] as $attachment): ?>
<?php
$linkId = $attachment['id'] ?? $report['id'];
$link = !empty($attachment['legacy'])
? base_url('teacher/progress/attachment/' . $linkId)
: base_url('teacher/progress/attachment-file/' . $linkId);
?>
<a class="btn btn-sm btn-outline-secondary text-start" href="<?= $link ?>" target="_blank" rel="noreferrer">
<?= esc($attachment['name'] ?? 'Attachment') ?>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?= $this->endSection() ?>
+177
View File
@@ -0,0 +1,177 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<style>
/* Keep header centered and wide */
.class-header {
max-width: 800px;
}
/* Make the table span the page width and avoid unexpected shrinking */
.fullwidth-wrap {
width: 100%;
}
/* DataTables: keep width and allow horizontal scroll area to use full width */
table.dataTable {
width: 100% !important;
white-space: nowrap; /* prevent wrapping that makes table look narrow */
}
.dataTables_wrapper .dataTables_scrollHeadInner,
.dataTables_wrapper .dataTables_scrollHeadInner table {
width: 100% !important;
}
th, td {
vertical-align: middle !important;
font-family: Arial, sans-serif;
}
.table thead th {
background-color: #f8f9fa;
font-weight: bold;
}
</style>
<div class="container-fluid py-5">
<?php $activeClassId = (int)($active_class_id ?? $class_section_id ?? 0); ?>
<?php if (!empty($classes) && is_array($classes)): ?>
<?php foreach ($classes as $index => $class): ?>
<?php
$sectionId = $class['class_section_id'];
$mains = $assignedNames[$sectionId]['mains'] ?? [];
$tas = $assignedNames[$sectionId]['tas'] ?? [];
?>
<!-- Full-width table area (title + table) -->
<div class="fullwidth-wrap px-3 px-md-4 mb-5 class-block <?= ((int)$sectionId === $activeClassId) ? '' : 'd-none'; ?>"
data-class-section-id="<?= (int)$sectionId; ?>">
<div class="class-header text-center mx-auto mb-4">
<h4 class="text-dark" style="font-family: Arial, sans-serif;">Students Class List</h4>
<?php if (!empty($mains)): ?>
<h5 class="text-dark" style="font-family: Arial, sans-serif; margin-bottom: 5px;">
Teachers: <?= esc(implode(', ', $mains)); ?>
</h5>
<?php endif; ?>
<?php if (!empty($tas)): ?>
<h5 class="text-dark" style="font-family: Arial, sans-serif; margin-bottom: 5px;">
Teacher Assistants: <?= esc(implode(', ', $tas)); ?>
</h5>
<?php endif; ?>
<h5 class="text-dark" style="font-family: Arial, sans-serif; margin-bottom: 5px;">
Grade-Section: <?= esc($class['class_section_name']); ?>
</h5>
</div>
<div class="table-responsive"> <!-- gives horizontal scroll on small screens -->
<table id="classTable<?= $index ?>" class="table table-bordered table-striped w-100">
<thead class="table-light">
<tr>
<th class="text-center">#</th>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th class="text-center">Age</th>
<th class="text-center">New Student</th>
<th class="text-center">Take Photo</th>
<th class="text-center">Allergies</th>
<th class="text-center">Medical Conditions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($studentsData[$sectionId])): ?>
<?php $order = 1; ?>
<?php foreach ($studentsData[$sectionId] as $student): ?>
<tr>
<td class="text-center"><?= esc($order++); ?></td>
<td><?= esc($student['school_id']); ?></td>
<td>
<a href="#"
class="text-decoration-none"
data-family-student-id="<?= (int) ($student['student_id'] ?? 0); ?>">
<?= esc($student['firstname']); ?>
</a>
</td>
<td>
<a href="#"
class="text-decoration-none"
data-family-student-id="<?= (int) ($student['student_id'] ?? 0); ?>">
<?= esc($student['lastname']); ?>
</a>
</td>
<td class="text-center"><?= esc($student['age']); ?></td>
<td class="text-center">
<?php if ((int)($student['is_new'] ?? 0) === 1): ?>
<span class="badge bg-warning text-dark">Yes</span>
<?php else: ?>
<span class="badge bg-success">No</span>
<?php endif; ?>
</td>
<td class="text-center">
<?php if ((int)($student['photo_consent'] ?? 0) === 1): ?>
<span class="badge bg-success">Yes</span>
<?php else: ?>
<span class="badge bg-danger">No</span>
<?php endif; ?>
</td>
<td class="text-center">
<span class="badge <?= (!empty($student['allergies_text']) && strtolower($student['allergies_text']) !== 'none')
? 'bg-warning text-dark'
: 'bg-success'; ?>">
<?= esc($student['allergies_text'] ?: 'None'); ?>
</span>
</td>
<td class="text-center">
<span class="badge <?= (!empty($student['medical_conditions_text']) && strtolower($student['medical_conditions_text']) !== 'none')
? 'bg-warning text-dark'
: 'bg-success'; ?>">
<?= esc($student['medical_conditions_text'] ?: 'None'); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="9" class="text-center">No students found in this class.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="container">
<div class="alert alert-warning" role="alert">
No classes found for this teacher.
</div>
</div>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
<?php if (!empty($classes)): ?>
<?php foreach ($classes as $index => $class): ?>
$('#classTable<?= $index ?>').DataTable({
// Keep columns visible and avoid squeezing
responsive: false, // prevent column hiding that can make table look small
autoWidth: true, // let DT calculate widths, we'll still force 100% via CSS
scrollX: true, // allow horizontal scroll when needed
pageLength: 25,
lengthChange: false,
columnDefs: [
{ targets: '_all', className: 'text-nowrap' } // avoid wrapping labels
]
})
.columns.adjust(); // finalize widths after init
<?php endforeach; ?>
<?php endif; ?>
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,105 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="w-100 py-3 px-3">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3">
<div>
<h4 class="mb-1" style="font-family: Arial, sans-serif;">Competition Scores</h4>
<div class="text-muted">Enter scores for your class competitions.</div>
</div>
<div class="text-end">
<?php if (!empty($activeClassName)): ?>
<div class="fw-semibold">Class: <?= esc($activeClassName) ?></div>
<?php endif; ?>
<?php if (!empty($schoolYear)): ?>
<div class="text-muted small"><?= esc($schoolYear) ?> <?= esc($semester ?? '') ?></div>
<?php endif; ?>
</div>
</div>
<?php if (session('success')): ?>
<div class="alert alert-success"><?= esc(session('success')) ?></div>
<?php endif; ?>
<?php if (session('error')): ?>
<div class="alert alert-danger"><?= esc(session('error')) ?></div>
<?php endif; ?>
<?php if (empty($hasClasses)): ?>
<div class="alert alert-warning">No classes assigned to you for the current term.</div>
<?php elseif (empty($competitions)): ?>
<div class="alert alert-info">No competitions available for this class right now.</div>
<?php else: ?>
<?php
$formatDateLabel = function ($value) {
if (!$value) return '';
$value = (string)$value;
if (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})/', $value, $m)) {
return $m[2] . '-' . $m[3] . '-' . $m[1];
}
try {
return local_date($value, 'm-d-Y');
} catch (Throwable $e) {
return $value;
}
};
?>
<div class="table-responsive">
<table class="table table-bordered table-sm align-middle">
<thead class="table-light">
<tr>
<th>Title</th>
<th>Questions</th>
<th>Scores Entered</th>
<th>Dates</th>
<th>Locked</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($competitions as $competition): ?>
<?php
$compId = (int) ($competition['id'] ?? 0);
$sectionId = (int) ($competition['class_section_id'] ?? 0);
$sectionName = $sectionId > 0 ? ($sectionMap[$sectionId] ?? $sectionId) : 'All Classes';
$questionCount = $questionCounts[$compId] ?? null;
$scoreCount = $scoreCounts[$compId] ?? 0;
$scoreLabel = $studentTotal > 0 ? $scoreCount . ' / ' . $studentTotal : (string) $scoreCount;
$isLocked = !empty($competition['is_locked']);
$startDate = $competition['start_date'] ?? null;
$endDate = $competition['end_date'] ?? null;
$startLabel = $startDate ? $formatDateLabel($startDate) : '';
$endLabel = $endDate ? $formatDateLabel($endDate) : '';
if ($startLabel && $endLabel) {
$dateLabel = $startLabel . ' - ' . $endLabel;
} elseif ($startLabel) {
$dateLabel = $startLabel;
} elseif ($endLabel) {
$dateLabel = $endLabel;
} else {
$dateLabel = '—';
}
?>
<tr>
<td><?= esc($competition['title'] ?? 'Competition') ?></td>
<td><?= $questionCount !== null ? esc($questionCount) : '—' ?></td>
<td><?= esc($scoreLabel) ?></td>
<td><?= esc($dateLabel) ?></td>
<td><?= $isLocked ? 'Yes' : 'No' ?></td>
<td>
<?php if ($isLocked): ?>
<button class="btn btn-sm btn-outline-secondary" type="button" disabled>Locked</button>
<?php else: ?>
<a class="btn btn-sm btn-outline-primary" href="<?= base_url('/teacher/competition-scores/' . $compId) ?>">
Enter Scores
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,96 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="w-100 py-3 px-3">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-2">
<div>
<h4 class="mb-1" style="font-family: Arial, sans-serif;">Enter Competition Scores</h4>
<div class="text-muted"><?= esc($competition['title'] ?? 'Competition') ?></div>
</div>
<a class="btn btn-outline-secondary" href="<?= base_url('/teacher/competition-scores') ?>">Back</a>
</div>
<?php $isLocked = !empty($isLocked); ?>
<?php $lockedAttr = $isLocked ? 'disabled' : ''; ?>
<?php if ($isLocked): ?>
<div class="alert alert-warning">This competition is locked. Scores are read-only.</div>
<?php endif; ?>
<div class="mb-3">
<?php if (!empty($classSectionName)): ?>
<div><strong>Class Section:</strong> <?= esc($classSectionName) ?></div>
<?php endif; ?>
<div>
<strong>Students:</strong> <?= esc($classStudentCount ?? 0) ?>
<?php if ($questionCount !== null): ?>
| <strong>Questions:</strong> <?= esc($questionCount) ?>
<?php endif; ?>
</div>
<?php if (!empty($classSelectionLocked)): ?>
<div class="text-muted small">This competition is locked to a specific class.</div>
<?php endif; ?>
</div>
<?php if (session('success')): ?>
<div class="alert alert-success"><?= esc(session('success')) ?></div>
<?php endif; ?>
<?php if (session('error')): ?>
<div class="alert alert-danger"><?= esc(session('error')) ?></div>
<?php endif; ?>
<form method="post" action="<?= base_url('/teacher/competition-scores/' . ($competition['id'] ?? 0)) ?>">
<?= csrf_field() ?>
<input type="hidden" name="class_section_id" value="<?= esc($classSectionId ?? '') ?>">
<?php if (empty($classSectionId)): ?>
<div class="alert alert-warning">Select a class section to load students.</div>
<?php else: ?>
<?php $scoreMax = ($questionCount !== null && (int) $questionCount > 0) ? (int) $questionCount : null; ?>
<div class="table-responsive">
<table class="table table-bordered table-sm" id="competitionScoresTable">
<thead class="table-light">
<tr>
<th style="width:140px;">School ID</th>
<th>Student</th>
<th style="width:180px;">
Score<?= $scoreMax !== null ? ' (max ' . esc($scoreMax) . ')' : '' ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<?php
$name = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''));
$studentId = $student['id'] ?? $student['student_id'] ?? null;
$schoolId = $student['school_id'] ?? null;
$val = $studentId !== null ? ($scoreMap[$studentId] ?? '') : '';
if (is_numeric($val)) {
$val = (string) (int) $val;
}
?>
<tr>
<td><?= esc($schoolId ?: $studentId) ?></td>
<td><?= esc($name !== '' ? $name : ('Student #' . $studentId)) ?></td>
<td>
<input class="form-control form-control-sm"
name="scores[<?= esc($studentId) ?>]"
type="number" step="1" min="0" inputmode="numeric" pattern="[0-9]*"
value="<?= esc($val) ?>"
<?= $scoreMax !== null ? 'max="' . esc($scoreMax) . '"' : '' ?>
<?= $lockedAttr ?>>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<div class="d-flex gap-2">
<button class="btn btn-primary" <?= $lockedAttr ?>>Save Scores</button>
<a class="btn btn-outline-secondary" href="<?= base_url('/teacher/competition-scores') ?>">Back to list</a>
</div>
</form>
</div>
<?= $this->endSection() ?>
+34
View File
@@ -0,0 +1,34 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<h1>Drafts</h1>
<?php if (!empty($draftMessages)): ?>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Subject</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($draftMessages as $message): ?>
<tr>
<td><?= esc($message['subject']) ?></td>
<td><?= esc(!empty($message['created_at']) ? local_datetime($message['created_at'], 'm-d-Y H:i') : '') ?></td>
<td>
<a href="<?= base_url('messages/edit/' . $message['id']) ?>">Edit</a> |
<a href="<?= base_url('messages/delete/' . $message['id']) ?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<p>No drafts found.</p>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+218
View File
@@ -0,0 +1,218 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4 gap-3">
<h1 class="h3 mb-0">Exam Drafts</h1>
<?php if (!empty($semester) || !empty($schoolYear)): ?>
<div class="text-muted small">
<?= esc($semester ?: 'Semester') ?> <?= esc($schoolYear ?: '') ?>
</div>
<?php endif; ?>
</div>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success">
<?= esc(session()->getFlashdata('success')) ?>
</div>
<?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-danger">
<?= esc(session()->getFlashdata('error')) ?>
</div>
<?php endif; ?>
<?php if (!empty($validation) && $validation->getErrors()): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($validation->getErrors() as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<ul class="nav nav-pills mb-3" id="teacherExamTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="new-tab" data-bs-toggle="pill" data-bs-target="#new" type="button" role="tab" aria-controls="new" aria-selected="true">
New Exams
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="legacy-tab" data-bs-toggle="pill" data-bs-target="#legacy" type="button" role="tab" aria-controls="legacy" aria-selected="false">
Previous Exams
</button>
</li>
</ul>
<div class="tab-content" id="teacherExamTabsContent">
<div class="tab-pane fade show active" id="new" role="tabpanel" aria-labelledby="new-tab">
<div class="card mb-4">
<div class="card-header">
Submit a new draft
</div>
<div class="card-body">
<p class="text-muted small mb-3">
Max upload size: <?= esc(number_format($maxUploadBytes / (1024 * 1024))) ?> MB.
Allowed file types: Word documents only (.doc/.docx).
</p>
<form method="post" action="<?= base_url('/teacher/exam-drafts') ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<div class="row g-3">
<input type="hidden" name="class_section_id" value="<?= esc($selectedClassSection ?? 0) ?>">
<div class="col-lg-6">
<label class="form-label">Exam type</label>
<select name="exam_type" class="form-select">
<option value="">Select type (optional)</option>
<?php $selectedType = old('exam_type'); ?>
<?php foreach ($examTypes as $type): ?>
<option value="<?= esc($type) ?>" <?= $type === $selectedType ? 'selected' : '' ?>>
<?= esc($type) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-lg-6">
<label class="form-label">Draft file</label>
<input
type="file"
name="draft_file"
class="form-control"
<?= empty($assignments) ? 'disabled' : '' ?>
/>
</div>
<div class="col-12">
<label class="form-label">Notes / instructions</label>
<textarea
name="description"
rows="3"
class="form-control"
placeholder="Share context for the reviewer..."
<?= empty($assignments) ? 'disabled' : '' ?>><?= esc(old('description')) ?></textarea>
</div>
</div>
<div class="text-end mt-3">
<button type="submit" class="btn btn-primary" <?= empty($assignments) ? 'disabled' : '' ?>>
Send for review
</button>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
Previous submissions
</div>
<div class="card-body">
<?php if (empty($drafts)): ?>
<p class="text-muted">You have not submitted any exam drafts yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-borderless align-middle">
<thead>
<tr>
<th>Type</th>
<th>Version</th>
<th>Submitted</th>
<th>Status</th>
<th>Files & Review</th>
</tr>
</thead>
<tbody>
<?php foreach ($drafts as $draft): ?>
<?php
$statusInfo = $statusBadges[$draft['status'] ?? 'pending'] ?? ['label' => 'Unknown', 'class' => 'bg-secondary text-white'];
?>
<tr>
<td><?= esc($draft['exam_type'] ?? 'N/A') ?></td>
<td class="text-nowrap">v<?= esc($draft['version'] ?? 1) ?></td>
<td class="text-nowrap"><?= esc((!empty($draft['created_at']) ? local_datetime($draft['created_at'], 'M j, Y g:i A') : '—')) ?></td>
<td>
<span class="badge <?= esc($statusInfo['class']) ?>">
<?= esc($statusInfo['label']) ?>
</span>
</td>
<td>
<?php if (!empty($draft['teacher_file'])): ?>
<div>
<a href="<?= base_url('exam-drafts/files/teacher/' . $draft['teacher_file']) ?>" target="_blank">
<?= esc($draft['teacher_filename'] ?? 'Download submitted file') ?>
</a>
</div>
<?php else: ?>
<div class="text-muted small">No file uploaded</div>
<?php endif; ?>
<?php if (!empty($draft['admin_comments'])): ?>
<div class="mt-2">
<strong class="small">Review notes:</strong>
<div class="small text-secondary"><?= esc($draft['admin_comments']) ?></div>
</div>
<?php endif; ?>
<?php if (!empty($draft['final_file'])): ?>
<div class="mt-2">
<a href="<?= base_url('exam-drafts/files/final/' . $draft['final_file']) ?>" target="_blank" class="link-success small">
<?= esc($draft['final_filename'] ?? 'Download final draft') ?>
</a>
</div>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="tab-pane fade" id="legacy" role="tabpanel" aria-labelledby="legacy-tab">
<div class="card">
<div class="card-header">
Previous exams by class section
</div>
<div class="card-body">
<?php if (empty($legacyExams)): ?>
<p class="text-muted mb-0">No previous exams available for your classes yet.</p>
<?php else: ?>
<?php
$grouped = [];
foreach ($legacyExams as $item) {
$cid = $item['class_section_id'] ?? 0;
$grouped[$cid]['name'] = $item['class_section_name'] ?? ('Class ' . $cid);
$grouped[$cid]['items'][] = $item;
}
?>
<?php foreach ($grouped as $cid => $group): ?>
<div class="mb-3">
<h6 class="mb-2"><?= esc($group['name']) ?></h6>
<div class="list-group">
<?php foreach ($group['items'] as $item): ?>
<div class="list-group-item d-flex justify-content-between align-items-start">
<div class="me-3">
<div class="fw-semibold"><?= esc($item['draft_title'] ?? 'Legacy Exam') ?></div>
<div class="small text-muted">
<?= esc($item['exam_type'] ?? 'N/A') ?> •
<?= esc($item['semester'] ?? '') ?> <?= esc($item['school_year'] ?? '') ?>
</div>
</div>
<div class="text-end">
<?php if (!empty($item['final_file'])): ?>
<a href="<?= base_url('exam-drafts/files/final/' . $item['final_file']) ?>" target="_blank" class="btn btn-sm btn-outline-primary">
Download
</a>
<?php else: ?>
<span class="text-muted small">File missing</span>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
+50
View File
@@ -0,0 +1,50 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-5 wow fadeInUp" data-wow-delay="0.1s" style="max-width: 600px;">
<br>
<h1 class="mb3">Homework Scores</h1>
</div>
<?php if (session()->get('status')): ?>
<div class="alert alert-success">
<?= session()->get('status') ?>
</div>
<?php endif; ?>
<form action="<?= route_to('updateHomeworkScores') ?>" method="post" onsubmit="return validateForm()">
<input type="hidden" name="class_section_id" value="<?= $classSectionId ?>">
<h2>Homework List for Class Section <?= $classSectionId; ?></h2>
<h1>Homework List for Class Section ID: <?= esc($classSectionId) ?></h1>
<?php if (!empty($students)): ?>
<table class="table">
<thead>
<tr>
<th>Student Name</th>
<th>Homework</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<tr>
<td><?= esc($student['firstname']) . ' ' . esc($student['lastname']) ?></td>
<td>
<?php if (isset($homeworkData[$student['id']])): ?>
<?= esc($homeworkData[$student['id']]['score']) ?>
<?php else: ?>
No homework data available
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>No students found for this class section.</p>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+36
View File
@@ -0,0 +1,36 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<h1>Inbox</h1>
<?php if (!empty($receivedMessages)): ?>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>From</th>
<th>Subject</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($receivedMessages as $message): ?>
<tr>
<td><?= esc($message['sender_id']) ?></td>
<td><?= esc($message['subject']) ?></td>
<td><?= esc(!empty($message['created_at']) ? local_datetime($message['created_at'], 'm-d-Y H:i') : '') ?></td>
<td>
<a href="<?= base_url('messages/view/' . $message['id']) ?>">View</a> |
<a href="<?= base_url('messages/delete/' . $message['id']) ?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<p>No messages found.</p>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+10
View File
@@ -0,0 +1,10 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-5 wow fadeInUp" data-wow-delay="0.1s" style="max-width: 600px;">
<!-- no_classes.php -->
<div class="alert alert-info mb-3 d-inline-block">
<?= session('message') ?>
</div>
<?= $this->endSection() ?>
+727
View File
@@ -0,0 +1,727 @@
<?= $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() ?>
+26
View File
@@ -0,0 +1,26 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm border-0">
<div class="card-body text-center">
<h3 class="card-title mb-3">Choose a semester</h3>
<p class="text-muted mb-4">
Before viewing scores, pick the semester you want to work with.
</p>
<?php if (!empty($invalidChoice)): ?>
<div class="alert alert-warning">
<strong>Note:</strong> "<?= esc($invalidChoice) ?>" is not a valid semester selection.
</div>
<?php endif; ?>
<div class="d-flex flex-wrap justify-content-center gap-3">
<a href="<?= esc($fallUrl) ?>" class="btn btn-primary btn-lg px-4">Fall</a>
<a href="<?= esc($springUrl) ?>" class="btn btn-outline-primary btn-lg px-4">Spring</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
+36
View File
@@ -0,0 +1,36 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<h1>Sent Messages</h1>
<?php if (!empty($sentMessages)): ?>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>To</th>
<th>Subject</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($sentMessages as $message): ?>
<tr>
<td><?= esc($message['recipient_id']) ?></td>
<td><?= esc($message['subject']) ?></td>
<td><?= esc(!empty($message['created_at']) ? local_datetime($message['created_at'], 'm-d-Y H:i') : '') ?></td>
<td>
<a href="<?= base_url('messages/view/' . $message['id']) ?>">View</a> |
<a href="<?= base_url('messages/delete/' . $message['id']) ?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<p>No messages found.</p>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+331
View File
@@ -0,0 +1,331 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-5" style="max-width:600px;">
<h4 class="text-dark" style="font-family:Arial,sans-serif;">Class Attendance</h4>
</div>
<?php
$canEdit = isset($canEdit) ? (bool)$canEdit : false;
$sundayDates = isset($sunday_dates) && is_array($sunday_dates) ? array_values($sunday_dates) : [];
$currentSunday = isset($current_sunday) ? (string)$current_sunday : '';
if ($currentSunday === '' && count($sundayDates) > 0) {
$currentSunday = $sundayDates[count($sundayDates) - 1];
}
$historyDates = array_slice($sundayDates, 0, max(0, count($sundayDates) - 1));
$historyDates = array_pad($historyDates, 2, '');
$no_school_days = isset($no_school_days) && is_array($no_school_days) ? $no_school_days : [];
$isNoSchoolToday = $currentSunday !== '' && !empty($no_school_days[$currentSunday]);
$canEditToday = $canEdit && !$isNoSchoolToday;
$btnDisabledAttr = $canEditToday ? '' : ' disabled="disabled"';
$lockedByStudent = isset($locked_by_student) && is_array($locked_by_student) ? $locked_by_student : [];
// Derive a header label showing the actual recent dates (instead of generic text)
$formatDateLabel = function ($value) {
if (!$value) return '—';
$value = (string)$value;
if (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})/', $value, $m)) {
return $m[2] . '-' . $m[3] . '-' . $m[1];
}
try {
return local_date($value, 'm-d-Y');
} catch (Throwable $e) {
return $value;
}
};
$recentHeaderLabel = $currentSunday ? $formatDateLabel($currentSunday) : 'Last Sunday';
// Teacher columns follow the computed Sunday dates (oldest → newest)
$teacherDateColumns = $sundayDates;
// unified badge rendering
function renderStatusBadge($status)
{
$status = strtolower(trim((string)$status));
if ($status === 'present') return '<span class="badge bg-success">Present</span>';
if ($status === 'late') return '<span class="badge bg-warning text-dark">Late</span>';
if ($status === 'absent') return '<span class="badge bg-danger">Absent</span>';
if ($status === 'no school') return '<span class="badge bg-info text-dark">No&nbsp;School</span>';
return '';
}
// blue tint for entire column
$noSchoolClass = fn($date) => $date && !empty($no_school_days[$date]) ? ' style="background-color:#cff4fc;"' : '';
?>
<?php if ($isNoSchoolToday): ?>
<div class="alert alert-info text-center fw-bold" role="alert">
Today is marked as <strong>No School</strong>. Attendance inputs are disabled.
</div>
<?php endif; ?>
<form action="<?= site_url('/teacher/update_attendance') ?>" method="post" id="attendanceForm">
<?= csrf_field() ?>
<input type="hidden" name="class_section_id" value="<?= esc($class_section_id) ?>">
<input type="hidden" name="class_id" value="<?= esc($class_id ?? '') ?>">
<!-- 🔹 TEACHER ATTENDANCE -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light"><strong>Teacher Attendance</strong></div>
<div class="card-body p-0">
<div class="table-responsive">
<table id="teachersTable" class="table table-bordered table-striped m-0 align-middle">
<thead class="table-light">
<tr>
<th class="text-center" style="width:56px;">#</th>
<th>Name</th>
<th class="text-center" style="width:120px;">Role</th>
<?php foreach ($teacherDateColumns as $d): ?>
<th class="text-center" style="width:130px;"><?= esc($formatDateLabel($d)) ?></th>
<?php endforeach; ?>
<th class="text-center" style="width:160px;">Today</th>
<th style="width:240px;">Reason</th>
</tr>
</thead>
<tbody>
<?php if (!empty($teachers)): ?>
<?php $i=1; foreach($teachers as $t): $todayVal=strtolower($t['today']??''); ?>
<tr>
<td class="text-center"><?= $i++ ?></td>
<td><?= esc($t['name']); ?></td>
<td class="text-center"><?= esc($t['position']==='main'?'Main Teacher':'Teacher Assistant'); ?></td>
<!-- Past Sundays (aligned to computed dates) -->
<?php foreach ($teacherDateColumns as $d): ?>
<td class="text-center"<?= $noSchoolClass($d) ?>>
<?php if ($d && !empty($no_school_days[$d])): ?>
<?= renderStatusBadge('no school') ?>
<?php else: ?>
<div class="d-flex flex-column align-items-center">
<?= renderStatusBadge($t['sunday_statuses'][$d] ?? '') ?>
</div>
<?php endif; ?>
</td>
<?php endforeach; ?>
<!-- Today -->
<td class="text-center"<?= $noSchoolClass($currentSunday) ?>>
<?php if ($isNoSchoolToday): ?>
<?= renderStatusBadge('no school') ?>
<?php else: ?>
<input type="hidden" name="teachers[<?= (int)$t['teacher_id'] ?>][teacher_id]" value="<?= (int)$t['teacher_id'] ?>">
<input type="hidden" name="teachers[<?= (int)$t['teacher_id'] ?>][position]" value="<?= esc($t['position']) ?>">
<select name="teachers[<?= (int)$t['teacher_id'] ?>][status]"
<?= $canEditToday?'':'disabled' ?> data-required="1" <?= $canEditToday?'required':'' ?>
style="font-size:.9rem; padding:2px 4px; width:120px;">
<option value="" disabled <?= empty($t['today'])?'selected':'' ?>>Select</option>
<option value="Present" <?= $todayVal==='present'?'selected':'' ?>>Present</option>
<option value="Absent" <?= $todayVal==='absent'?'selected':'' ?>>Absent</option>
<option value="Late" <?= $todayVal==='late'?'selected':'' ?>>Late</option>
</select>
<?php endif; ?>
</td>
<td>
<input type="text"
name="teachers[<?= (int)$t['teacher_id'] ?>][reason]"
placeholder="<?= $isNoSchoolToday?'No School':'Reason (if absent/late)' ?>"
<?= $canEditToday?'':'disabled' ?>
style="font-size:.9rem; padding:4px 6px; width:100%;">
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="8" class="text-center">No teachers assigned.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- 🔹 STUDENT ATTENDANCE -->
<div class="card mb-4 shadow-sm">
<div class="card-header bg-light"><strong>Students Attendance</strong></div>
<div class="card-body p-0">
<div class="table-responsive">
<table id="studentsTable" class="table table-bordered table-striped m-0 align-middle">
<thead class="table-light">
<tr>
<th class="text-center">#</th>
<th style="min-width:160px;">School ID</th>
<th>First Name</th>
<th>Last Name</th>
<?php foreach ($historyDates as $d): ?>
<th class="text-center"><?= esc($formatDateLabel($d)) ?></th>
<?php endforeach; ?>
<th class="text-center"><?= esc($recentHeaderLabel) ?></th>
<th class="text-center">Today</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<?php if (!empty($students)): ?>
<?php $n=1; foreach($students as $a): ?>
<?php
$studentId = (int)($a['student']['id'] ?? 0);
$lockInfo = $lockedByStudent[$studentId] ?? ['locked'=>false,'source'=>null,'reason'=>null,'status'=>$a['today'] ?? null];
$isLocked = !empty($lockInfo['locked']);
$lockSource = $lockInfo['source'] ?? '';
$lockLabel = $lockSource === 'parent' ? 'Parent submitted' : ($lockSource === 'admin' ? 'Admin submitted' : 'Locked');
$todayStatus = $lockInfo['status'] ?? ($a['today'] ?? null);
?>
<tr>
<input type="hidden" name="attendance[<?= (int)$a['student']['id'] ?>][school_id]" value="<?= esc($a['student']['school_id']) ?>">
<input type="hidden" name="attendance[<?= (int)$a['student']['id'] ?>][student_id]" value="<?= (int)$a['student']['id'] ?>">
<td class="text-center"><?= $n++ ?></td>
<td style="min-width:160px;"><?= esc($a['student']['school_id']); ?></td>
<td><?= esc($a['student']['firstname']); ?></td>
<td><?= esc($a['student']['lastname']); ?></td>
<?php foreach ($historyDates as $d): ?>
<td class="text-center"<?= $noSchoolClass($d) ?>>
<?php if (!empty($no_school_days[$d])): ?>
<?= renderStatusBadge('no school') ?>
<?php else: ?>
<?= renderStatusBadge($a['sunday_statuses'][$d] ?? '') ?>
<?php endif; ?>
</td>
<?php endforeach; ?>
<td class="text-center">
<?php
$currentStatus = $a['sunday_statuses'][$currentSunday] ?? '';
$normalizedStatus = strtolower(trim((string)$currentStatus));
?>
<?php if ($normalizedStatus === '' || $normalizedStatus === 'n/a'): ?>
<span class="text-muted small">No data</span>
<?php else: ?>
<div class="d-flex flex-column gap-1 align-items-start align-items-md-center">
<span class="d-flex gap-2 align-items-center">
<?= renderStatusBadge($currentStatus) ?>
</span>
</div>
<?php endif; ?>
</td>
<td class="text-center"<?= $noSchoolClass($currentSunday) ?>>
<?php if ($isNoSchoolToday): ?>
<?= renderStatusBadge('no school') ?>
<?php elseif ($isLocked): ?>
<input type="hidden" name="attendance[<?= (int)$a['student']['id'] ?>][status]" value="<?= esc(ucfirst(strtolower((string)$todayStatus))) ?>">
<div class="d-flex flex-column align-items-center gap-1">
<?= renderStatusBadge($todayStatus ?? '') ?>
<span class="badge bg-secondary" title="This attendance was submitted externally and cannot be edited.">
<?= esc($lockLabel) ?>
</span>
</div>
<?php else: ?>
<select name="attendance[<?= (int)$a['student']['id'] ?>][status]"
<?= $canEditToday?'':'disabled' ?> data-required="1" <?= $canEditToday?'required':'' ?>
style="font-size:.875rem; padding:2px 4px; width:110px;">
<option value="" disabled <?= empty($a['today'])?'selected':'' ?>>Select</option>
<option value="Present" <?= (strtolower($a['today']??'')==='present')?'selected':'' ?>>Present</option>
<option value="Absent" <?= (strtolower($a['today']??'')==='absent')?'selected':'' ?>>Absent</option>
<option value="Late" <?= (strtolower($a['today']??'')==='late')?'selected':'' ?>>Late</option>
</select>
<?php endif; ?>
</td>
<td>
<?php if ($isLocked): ?>
<input type="hidden" name="attendance[<?= (int)$a['student']['id'] ?>][reason]" value="<?= esc($lockInfo['reason'] ?? '') ?>">
<div class="text-muted small">
<?= esc($lockInfo['reason'] ?? 'Submitted externally.') ?>
</div>
<?php else: ?>
<input type="text"
name="attendance[<?= (int)$a['student']['id'] ?>][reason]"
placeholder="<?= $isNoSchoolToday?'No School':'Reason if absent/late' ?>"
<?= $canEditToday?'':'disabled' ?>
style="font-size:.875rem; padding:4px 6px; width:100%;">
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="8" class="text-center">No students found</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="d-flex justify-content-center mt-4 mb-4">
<button type="submit" class="btn btn-success btn-lg px-5 shadow" id="submitAttendanceBtn" <?= $btnDisabledAttr ?>>
Submit Attendance
</button>
</div>
</form>
</div>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="confirmSubmitModal" tabindex="-1" aria-labelledby="confirmSubmitLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-info text-dark">
<h5 class="modal-title" id="confirmSubmitLabel">Confirm Submission</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
Are you sure you want to submit?<br><strong>Once submitted you cannot make changes.</strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" id="cancelSubmitBtn" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" id="confirmSubmitBtn">Yes, Submit</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener("DOMContentLoaded",()=>{
const form=document.getElementById("attendanceForm");
const submitBtn=document.getElementById("submitAttendanceBtn");
const modal=new bootstrap.Modal(document.getElementById("confirmSubmitModal"));
const confirmBtn=document.getElementById("confirmSubmitBtn");
const cancelBtn=document.getElementById("cancelSubmitBtn");
const isNoSchoolToday=<?= $isNoSchoolToday?'true':'false' ?>;
function allRequiredFilled(){
const selects=form.querySelectorAll('select[data-required="1"]:not([disabled])');
for(const s of selects){if(!s.value){s.classList.add('is-invalid');s.focus();return false;}s.classList.remove('is-invalid');}
return true;
}
form.addEventListener("submit",e=>{
e.preventDefault();
if(isNoSchoolToday){alert("Today is marked as No School. Attendance submission is disabled.");return;}
if(!allRequiredFilled()){alert("Please fill all required fields.");return;}
setTimeout(()=>cancelBtn.focus(),150);
modal.show();
});
confirmBtn.addEventListener("click",()=>{
confirmBtn.disabled=true;
submitBtn.disabled=true;
modal.hide();
form.submit();
});
});
</script>
<script>
$(function(){
$('#teachersTable').DataTable({
pageLength:100,
order:[],
columnDefs:[{targets:[0,3,4,5,6],className:'text-center'}]
});
$('#studentsTable').DataTable({
pageLength:100,
order:[],
columnDefs:[{targets:[0,4,5,6,7],className:'text-center'}]
});
});
</script>
<?= $this->endSection() ?>
+14
View File
@@ -0,0 +1,14 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="row">
<main class="col-12 px-md-4">
<h1 class="h2">Assignments</h1>
<h5 class="h5 mt-2">Coming soon,
this page will provide feature to generate Quizes and Exams</h5>
</main>
</div>
</div>
<?= $this->endSection() ?>
+282
View File
@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="" name="keywords">
<meta content="" name="description">
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap" rel="stylesheet">
<!-- Icon Font Stylesheet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Libraries Stylesheet -->
<link href="<?= base_url('lib/animate/animate.min.css') ?>" rel="stylesheet">
<link href="<?= base_url('lib/owlcarousel/assets/owl.carousel.min.css') ?>" rel="stylesheet">
<!-- Customized Bootstrap Stylesheet -->
<link href="<?= base_url('css/bootstrap.min.css') ?>" rel="stylesheet">
<!-- Template Stylesheet -->
<link href="<?= base_url('css/style.css') ?>" rel="stylesheet">
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div id="spinner" class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="<?= base_url('/about') ?>" class="nav-item nav-link">About Us</a>
<a href="<?= base_url('/classes') ?>" class="nav-item nav-link">Classes</a>
<a href="<?= base_url('/contact') ?>" class="nav-item nav-link">Contact Us</a>
</div>
<div class="d-flex">
<a href="/user/login" class="btn btn-primary rounded-pill px-3">Login<i class="fa fa-arrow-right ms-3"></i></a>
<a href="/register" class="btn btn-primary rounded-pill px-3 me-2">Register<i class="fa fa-arrow-right ms-3"></i></a>
</div>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header Start -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">Contact Us</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item text-green"><a href="/">Home</a></li>
<li class="breadcrumb-item text-green"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-green active" aria-current="page">Contact Us</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- Contact Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-5 wow fadeInUp" data-wow-delay="0.1s" style="max-width: 600px;">
<h1 class="mb-3">Get In Touch</h1>
<p>Get in touch with us today to learn more about our programs and how you can get involved.</p>
</div>
<div class="row g-4 mb-5">
<div class="col-md-6 col-lg-4 text-center wow fadeInUp" data-wow-delay="0.1s">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center mb-4"
style="width: 75px; height: 75px;">
<i class="fa fa-map-marker-alt fa-2x text-primary"></i>
</div>
<h6>5 Courthouse Lane, Chelmsford, MA 01824</h6>
</div>
<div class="col-md-6 col-lg-4 text-center wow fadeInUp" data-wow-delay="0.3s">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center mb-4"
style="width: 75px; height: 75px;">
<i class="fa fa-envelope-open fa-2x text-primary"></i>
</div>
<h6>alrahma.isgl@gmail.com</h6>
</div>
<div class="col-md-6 col-lg-4 text-center wow fadeInUp" data-wow-delay="0.5s">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center mb-4"
style="width: 75px; height: 75px;">
<i class="fa fa-phone-alt fa-2x text-primary"></i>
</div>
<h6>+1 978-364-0219</h6>
</div>
</div>
<div class="bg-light rounded">
<div class="row g-0">
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.1s">
<div class="h-100 d-flex flex-column justify-content-center p-5">
<form id="contactForm">
<div class="row g-3">
<div class="col-sm-6">
<div class="form-floating">
<input type="text" class="form-control border-0" id="name" name="name" placeholder="Your Name">
<label for="name">Your Name</label>
</div>
</div>
<div class="col-sm-6">
<div class="form-floating">
<input type="email" class="form-control border-0" id="email" name="email" placeholder="Your Email">
<label for="email">Your Email</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<input type="text" class="form-control border-0" id="subject" name="subject" placeholder="Subject">
<label for="subject">Subject</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<textarea class="form-control border-0" placeholder="Leave a message here" id="message" name="message" style="height: 100px"></textarea>
<label for="message">Message</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary w-100 py-3" type="submit">Send Message</button>
</div>
<div id="formResponse" class="mt-3"></div>
</div>
</form>
</div>
</div>
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.5s" style="min-height: 400px;">
<div class="position-relative h-100">
<iframe class="position-relative rounded w-100 h-100"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2949.902891223497!2d-71.3606721845427!3d42.59682637917086!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89e3a489bb344a85%3A0xeaba1b29330726cb!2s5%20Courthouse%20Ln%2C%20Chelmsford%2C%20MA%2001824%2C%20USA!5e0!3m2!1sen!2sbd!4v1627993075161!5m2!1sen!2sbd"
frameborder="0" style="min-height: 400px; border:0;" allowfullscreen=""
aria-hidden="false" tabindex="0"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Contact End -->
<!-- Footer Start -->
<div class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn" data-wow-delay="0.1s">
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2"><i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane, Chelmsford, MA 01824
</p>
<p class="mb-2"><i class="fa fa-phone-alt me-3"></i>+1 978-364-0219
</p>
<p class="mb-2"><i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com
</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-youtube"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="<?= base_url('/about') ?>">About Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/contact') ?>">Contact Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/services') ?>">Our Services</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/privacy') ?>">Privacy Policy</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/terms') ?>">Terms & Condition</a>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-1.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-2.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-3.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-4.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-5.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-6.jpg') ?>" alt="">
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>Our newsletter is your gateway to staying informed and connected with our Sunday school community. </p>
<div class="position-relative mx-auto" style="max-width: 400px;">
<input class="form-control bg-transparent w-100 py-3 ps-4 pe-5" type="text" placeholder="Your email">
<button type="button" class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2">SignUp</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy; <a class="border-bottom" href="#">Al Rahma Sunday School by ISGL</a>, All Right Reserved.
Designed By <a class="border-bottom" href="https://htmlcodex.com">HTML Codex</a>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="#">Home</a>
<a href="#">Cookies</a>
<a href="#">Help</a>
<a href="#">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"><i class="bi bi-arrow-up"></i></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="<?= base_url('lib/wow/wow.min.js') ?>"></script>
<script src="<?= base_url('lib/easing/easing.min.js') ?>"></script>
<script src="<?= base_url('lib/waypoints/waypoints.min.js') ?>"></script>
<script src="<?= base_url('lib/owlcarousel/owl.carousel.min.js') ?>"></script>
<!-- Template Javascript -->
<script src="<?= base_url('js/main.js') ?>"></script>
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
e.preventDefault();
let formData = new FormData(this);
fetch('contact_process.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
document.getElementById('formResponse').innerHTML = data;
document.getElementById('contactForm').reset();
})
.catch(error => console.error('Error:', error));
});
</script>
</body>
</html>
+156
View File
@@ -0,0 +1,156 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="sidebar">
<a href="<?= base_url('messages/inbox') ?>" class="active">Inbox</a>
<a href="<?= base_url('messages/sent') ?>">Sent</a>
<a href="<?= base_url('messages/drafts') ?>">Drafts</a>
<a href="<?= base_url('messages/trash') ?>">Trash</a>
</div>
<div class="main-content">
<div class="dashboard-title">
<h1 class="h2">Messages</h1>
<h5 class="h5 mt-2">Manage your messages.</h5>
</div>
<!-- Button to trigger the modal -->
<div class="text-center mb-3">
<button type="button" class="btn btn-info" id="composeMessageBtn">Compose Message</button>
</div>
<!-- Section to display received messages -->
<div class="message-list">
<h3>Inbox</h3>
<?php if (!empty($receivedMessages)): ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col"><input type="checkbox" id="select-all"></th>
<th scope="col">From</th>
<th scope="col">Subject</th>
<th scope="col">Date</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<?php foreach ($receivedMessages as $message): ?>
<tr>
<td><input type="checkbox" name="message_select[]"></td>
<td><?= esc($message['sender_name']) ?></td>
<td><a
href="<?= base_url('messages/view/' . $message['id']) ?>"><?= esc($message['subject']) ?></a>
</td>
<td><?= esc(!empty($message['sent_datetime']) ? local_datetime($message['sent_datetime'], 'm-d-Y H:i') : '') ?></td>
<td>
<a href="<?= base_url('messages/delete/' . $message['id']) ?>"
class="text-danger">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<p>No messages found.</p>
<?php endif; ?>
</div>
<!-- Modal for Compose Message -->
<div class="modal fade" id="composeMessageModal" tabindex="-1" role="dialog"
aria-labelledby="composeMessageModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="composeMessageModalLabel">Compose Message</h5>
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="<?= base_url('messages/send') ?>" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="recipient-type">Recipient Type</label>
<select class="form-control" id="recipient-type" name="recipient_type" required>
<option value="" disabled selected>Select recipient type</option>
<option value="administration">Administration</option>
<option value="teacher">Teacher</option>
<option value="parent">Parent</option>
</select>
</div>
<div class="form-group">
<label for="recipient">Recipient</label>
<select class="form-control" id="recipient" name="recipient_id" required>
<!-- This will be populated dynamically -->
</select>
</div>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" class="form-control" id="subject" name="subject" required>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea class="form-control" id="message" name="message" rows="4"
required></textarea>
</div>
<div class="form-group">
<label for="priority">Priority</label>
<select class="form-control" id="priority" name="priority">
<option value="low">Low</option>
<option value="normal" selected>Normal</option>
<option value="high">High</option>
</select>
</div>
<div class="form-group">
<label for="attachment">Attachment</label>
<input type="file" class="form-control" id="attachment" name="attachment">
</div>
<button type="submit" class="btn btn-primary btn-block">Send Message</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('recipient-type').addEventListener('change', function() {
var recipientType = this.value;
var recipientSelect = document.getElementById('recipient');
// Clear previous options
recipientSelect.innerHTML = '<option value="" disabled selected>Select recipient</option>';
if (recipientType) {
fetch(`<?= base_url('messages/getRecipients') ?>/${recipientType}`)
.then(response => response.json())
.then(data => {
data.forEach(recipient => {
var option = document.createElement('option');
option.value = recipient.id;
option.textContent = recipient.name;
recipientSelect.appendChild(option);
});
})
.catch(error => console.error('Error fetching recipients:', error));
}
});
document.getElementById('select-all').addEventListener('change', function() {
var checkboxes = document.querySelectorAll('input[name="message_select[]"]');
checkboxes.forEach(checkbox => checkbox.checked = this.checked);
});
// Show the message composer modal when the Compose Message button is clicked
$('#composeMessageBtn').click(function() {
$('#composeMessageModal').modal('show');
});
});
</script>
<?= $this->endSection() ?>
+109
View File
@@ -0,0 +1,109 @@
<nav class="navbar navbar-expand-lg navbar-teacher">
<div class="container-fluid px-2">
<!-- Navbar brand -->
<a class="navbar-brand text-white" href="<?= base_url('/teacher_dashboard'); ?>"
data-bs-toggle="tooltip" data-bs-placement="bottom" title="Go to teacher dashboard">
<i class="bi bi-house-door"></i> Dashboard
</a>
<!-- Hamburger menu for small screens -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#teacherNavbar"
aria-controls="teacherNavbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Navigation links -->
<div class="collapse navbar-collapse" id="teacherNavbar">
<ul class="navbar-nav ms-auto flex-lg-row flex-column">
<li class="nav-item mx-lg-2 my-1">
<a class="nav-link text-white" href="<?= base_url('/teacher/class_view'); ?>"
data-bs-toggle="tooltip" data-bs-placement="bottom" title="View your class list">
<i class="bi bi-pencil-square"></i> Class
</a>
</li>
<li class="nav-item mx-lg-2 my-1">
<?php $class_section_id = session()->get('class_section_id'); ?>
<a class="nav-link text-white"
href="<?= base_url('/teacher/showupdate_attendance?class_section_id=' . $class_section_id); ?>"
data-bs-toggle="tooltip" data-bs-placement="bottom" title="Mark or review attendance">
<i class="bi bi-calendar-check"></i> Attendance
</a>
</li>
<li class="nav-item mx-lg-2 my-1">
<a class="nav-link text-white" href="<?= base_url('/teacher/scores'); ?>" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="Enter or view student scores">
<i class="bi bi-clipboard-data"></i> Scores
</a>
</li>
<li class="nav-item mx-lg-2 my-1">
<a class="nav-link text-white" href="<?= base_url('books/distribute'); ?>" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="Enter number of given book to students">
<i class="bi bi-clipboard-data"></i> Books
</a>
</li>
<li class="nav-item mx-lg-2 my-1">
<a class="nav-link text-white" href="<?= base_url('/teacher/calendar'); ?>" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="View class calendar">
<i class="bi bi-calendar"></i> Calendar
</a>
</li>
<li class="nav-item mx-lg-2 my-1">
<a class="nav-link text-white" href="<?= base_url('teacher/progress/submit'); ?>" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="Submit weekly progress">
<i class="bi bi-clipboard-check"></i> Progress
</a>
</li>
<?php
$userId = session()->get('user_id');
$activeClassId = (int)(session()->get('class_section_id') ?? 0);
$classOptions = [];
if ($userId) {
$configModel = new \App\Models\ConfigurationModel();
$teacherClassModel = new \App\Models\TeacherClassModel();
$sy = $configModel->getConfig('school_year');
$sem = $configModel->getConfig('semester');
$classOptions = $teacherClassModel->getClassAssignmentsByUserId((int)$userId, (string)$sy, (string)$sem);
}
?>
<?php if (!empty($classOptions) && count($classOptions) > 1): ?>
<li class="nav-item mx-lg-2 my-1">
<?php
$qs = $_SERVER['QUERY_STRING'] ?? '';
$redirectTo = current_url() . ($qs ? '?' . $qs : '');
?>
<form class="d-flex align-items-center gap-2" method="post" action="<?= base_url('/teacher/switch-class'); ?>">
<?= csrf_field(); ?>
<input type="hidden" name="redirect_to" value="<?= esc($redirectTo) ?>">
<label for="teacherClassSwitch" class="text-white small mb-0">Class:</label>
<select name="class_section_id" id="teacherClassSwitch" class="form-select form-select-sm"
onchange="this.form.submit();">
<?php foreach ($classOptions as $opt): ?>
<?php $cid = (int)($opt['class_section_id'] ?? 0); ?>
<option value="<?= $cid ?>" <?= $cid === $activeClassId ? 'selected' : '' ?>>
<?= esc($opt['class_section_name'] ?? 'Class'); ?>
</option>
<?php endforeach; ?>
</select>
</form>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<!-- Tooltip activation script (include once near bottom of page) -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach(function(tooltipTriggerEl) {
new bootstrap.Tooltip(tooltipTriggerEl);
});
});
</script>
+45
View File
@@ -0,0 +1,45 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="row">
<main class="col-12 px-md-4">
<div class="pt-3 pb-2 mb-3 dashboard-title">
<h1 class="h2">Support</h1>
<h5 class="h5 mt-2">Submit your support request</h5>
</div>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success">
<?= session()->getFlashdata('success') ?>
</div>
<?php endif; ?>
<?php if (isset($validation)): ?>
<div class="alert alert-danger">
<?= $validation->listErrors() ?>
</div>
<?php endif; ?>
<?php helper(['form']); // Load form helper
?>
<form action="<?= base_url('support/submit') ?>" method="post" class="support-form">
<?= csrf_field() ?>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" class="form-control" id="subject" name="subject"
value="<?= set_value('subject') ?>" required>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea class="form-control" id="message" name="message"
required><?= set_value('message') ?></textarea>
</div>
<button type="submit" class="btn btn-primary mt-3">Submit</button>
</form>
</main>
</div>
</div>
<?= $this->endSection() ?>
+34
View File
@@ -0,0 +1,34 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<h1>Trash</h1>
<?php if (!empty($trashedMessages)): ?>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Subject</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($trashedMessages as $message): ?>
<tr>
<td><?= esc($message['subject']) ?></td>
<td><?= esc(!empty($message['created_at']) ? local_datetime($message['created_at'], 'm-d-Y H:i') : '') ?></td>
<td>
<a href="<?= base_url('messages/restore/' . $message['id']) ?>">Restore</a> |
<a href="<?= base_url('messages/deletePermanent/' . $message['id']) ?>">Delete Permanently</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<p>No messages found in trash.</p>
<?php endif; ?>
</div>
<?= $this->endSection() ?>