recreate project
This commit is contained in:
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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 100–350 characters and begin with the student’s 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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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 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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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">
|
||||
© <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>
|
||||
@@ -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">×</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() ?>
|
||||
@@ -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>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
Reference in New Issue
Block a user