apply the school year concept
Tests / PHPUnit (push) Failing after 1m19s

This commit is contained in:
root
2026-07-14 00:59:00 -04:00
parent 504c3bc9f9
commit feb1b29a32
73 changed files with 4288 additions and 620 deletions
+383 -11
View File
@@ -1,3 +1,35 @@
Yesterday 11:12 PM
write sql querry to update school_year = 2025-2026
UPDATE your_table_name
SET school_year = '2025-2026'
WHERE your_condition;
To update every row:
UPDATE your_table_name
SET school_year = '2025-2026';
Use the second version only if you genuinely intend to rewrite the entire table, because databases are remarkably obedient when humans forget WHERE.
Today 12:49 AM
Screenshot 2026-07-14 at 12.49.07AM.png
Pasted code(6).php
File
remove the image content from the view
Removed the School year / Semester filter from the view by deleting:
<?= $this->include('partials/academic_filter') ?>
Pasted_code_6_without_academic_filter.php
Library
/
Pasted_code_6_without_academic_filter.php
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
@@ -28,8 +60,6 @@ $formatRole = function (?string $role): string {
<h2 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Generate Staff Badges</h2>
</div>
<?= $this->include('partials/academic_filter') ?>
<div class="d-flex flex-column flex-md-row justify-content-md-end align-items-md-center gap-2">
<!-- Submit button associates to form below -->
<button type="submit" class="btn btn-success mt-3" form="badgeForm">Generate Badges</button>
@@ -38,10 +68,7 @@ $formatRole = function (?string $role): string {
<!-- Staff selection -->
<form method="post" action="<?= base_url('badge') ?>" target="_blank" id="badgeForm">
<?= csrf_field() ?>
<input type="hidden" name="school_year" value="<?= esc($selectedYear ?? '') ?>">
<!-- hidden container to preserve selections and their data (guaranteed inside form) -->
<div id="selectedHiddenInputs" style="display:none"></div>
<table id="staffTable" class="table table-bordered table-striped align-middle w-100">
<thead class="table-dark">
<tr>
@@ -278,11 +305,7 @@ $formatRole = function (?string $role): string {
async function refreshCsrf() {
try {
const params = new URLSearchParams();
if (selectedYear) params.append('school_year', selectedYear);
const resp = await fetch('<?= base_url('api/printables/badges/status') ?>' + (params.toString() ? ('?' + params.toString()) : ''), {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (!resp.ok) return;
const json = await resp.json();
if (json && json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
@@ -351,3 +374,352 @@ $formatRole = function (?string $role): string {
});
</script>
<?= $this->endSection() ?>
Library
/
Pasted_code_6_without_academic_filter.php
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
// Role formatter for display + posting
$formatRole = function (?string $role): string {
$role = (string)$role;
$role = str_replace(['-', '_'], ' ', $role);
$role = preg_replace('/\s+/', ' ', trim($role));
if ($role === '') return '';
$out = [];
foreach (explode(' ', $role) as $w) {
if ($w === '') continue;
if (preg_match('/^[A-Za-z]{1,3}$/', $w)) {
$out[] = strtoupper($w); // TA, PTA, HR, KG...
} else {
$out[] = ucfirst(strtolower($w)); // Teacher, Assistant, Admin...
}
}
return implode(' ', $out);
};
?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<div class="text-center mx-auto mb-5 wow" data-wow-delay="0.1s" style="max-width: 600px;">
<br>
<h2 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Generate Staff Badges</h2>
</div>
<div class="d-flex flex-column flex-md-row justify-content-md-end align-items-md-center gap-2">
<!-- Submit button associates to form below -->
<button type="submit" class="btn btn-success mt-3" form="badgeForm">Generate Badges</button>
<br>
</div>
<!-- Staff selection -->
<form method="post" action="<?= base_url('badge') ?>" target="_blank" id="badgeForm">
<?= csrf_field() ?>
<table id="staffTable" class="table table-bordered table-striped align-middle w-100">
<thead class="table-dark">
<tr>
<th style="width: 70px;">#</th>
<th scope="col">Firstname</th>
<th scope="col">Lastname</th>
<th scope="col">Role</th>
<th scope="col">Teacher Class Section</th>
<th scope="col" style="width: 140px;">Badge Prints</th>
<th scope="col" style="width: 150px;">
<div class="form-check m-0">
<input class="form-check-input" type="checkbox" id="select-all">
<label class="form-check-label" for="select-all">Select All (page)</label>
</div>
</th>
</tr>
</thead>
<tbody>
<?php if (!empty($users)): ?>
<?php $order = 1; ?>
<?php foreach ($users as $user): ?>
<?php
// Pick a representative role (first of CSV or role_name/active_role)
$roleRaw = $user['active_role'] ?? $user['role_name'] ?? ($user['roles'] ?? '');
if (strpos((string)$roleRaw, ',') !== false) {
$parts = array_filter(array_map('trim', explode(',', (string)$roleRaw)));
$roleRaw = $parts[0] ?? '';
}
//$roleLabel = $roleRaw !== '' ? $formatRole($roleRaw) : '-';
$roleLabel = $user['role_name'] ?? ($user['roles'] ?? '-'); // both are pre-formatted by $formatRole
// Normalize user id key for checkbox
$uid = $user['user_id'] ?? $user['id'] ?? ($user['users.id'] ?? null);
// Class section name (optional)
$className = !empty($user['class_section_name']) ? (string)$user['class_section_name'] : '';
?>
<tr
data-user-id="<?= esc($uid ?? '') ?>"
data-role-label="<?= esc($roleLabel) ?>"
data-class-name="<?= esc($className) ?>">
<td style="text-align: center;"><?= esc($order++) ?></td>
<td><?= esc($user['firstname'] ?? '') ?></td>
<td><?= esc($user['lastname'] ?? '') ?></td>
<td><?= esc($roleLabel) ?></td>
<td><?= $className !== '' ? esc($className) : '-' ?></td>
<td><span class="badge bg-secondary prints-badge" data-user-id="<?= esc($uid ?? '') ?>">—</span></td>
<td>
<?php if ($uid !== null): ?>
<div class="form-check m-0">
<input type="checkbox" name="user_ids[]" value="<?= esc($uid) ?>" class="form-check-input user-checkbox" id="chk-<?= esc($uid) ?>">
<label class="form-check-label" for="chk-<?= esc($uid) ?>">Select</label>
</div>
<?php else: ?>
<span class="text-muted">N/A</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7" class="text-center text-muted">No staff found for the selected year.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</form>
<br>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track selected user IDs across paging/filtering/sorting
const selected = new Set();
const formEl = document.getElementById('badgeForm');
let clearTimerId = null; // pending auto-clear timer, if any
let hiddenContainer = document.getElementById('selectedHiddenInputs');
// Ensure hidden container exists inside the form (some earlier layouts had it outside)
if (!hiddenContainer || !formEl.contains(hiddenContainer)) {
hiddenContainer = document.createElement('div');
hiddenContainer.id = 'selectedHiddenInputs';
hiddenContainer.style.display = 'none';
formEl.appendChild(hiddenContainer);
}
const selectAll = document.getElementById('select-all');
const csrfName = <?= json_encode(csrf_token()) ?>;
const csrfInput = document.querySelector(`input[name='${csrfName}']`);
// Build a meta map (userId -> { role, className }) from the DOM BEFORE DataTables paginates
const rowMeta = {};
document.querySelectorAll('#staffTable tbody tr').forEach(tr => {
const id = tr.getAttribute('data-user-id');
if (!id) return;
rowMeta[id] = {
role: tr.getAttribute('data-role-label') || '',
className: tr.getAttribute('data-class-name') || ''
};
});
const table = $('#staffTable').DataTable({
stateSave: true,
pageLength: 100,
lengthMenu: [10, 25, 50, 100],
order: [
[1, 'asc'],
[2, 'asc']
], // Firstname, Lastname
columnDefs: [{
targets: 0,
searchable: false
}, // row #
{
targets: 6,
orderable: false,
searchable: false
} // checkbox column
]
});
// Helper: keep hidden inputs in sync so selections submit even if not on current page
function syncHiddenInputs() {
hiddenContainer.innerHTML = '';
selected.forEach(function(id) {
const meta = rowMeta[id] || {
role: '',
className: ''
};
// user_ids[]
const i1 = document.createElement('input');
i1.type = 'hidden';
i1.name = 'user_ids[]';
i1.value = id;
hiddenContainer.appendChild(i1);
// roles[ID]
const i2 = document.createElement('input');
i2.type = 'hidden';
i2.name = `roles[${id}]`;
i2.value = meta.role;
hiddenContainer.appendChild(i2);
// classes[ID]
const i3 = document.createElement('input');
i3.type = 'hidden';
i3.name = `classes[${id}]`;
i3.value = meta.className;
hiddenContainer.appendChild(i3);
});
}
// Helper: refresh checkboxes on current page based on Set
function syncPageCheckboxes() {
const pageNodes = table.rows({
page: 'current'
}).nodes().to$();
let allChecked = true;
let anyChecked = false;
pageNodes.each(function() {
const row = this;
const userId = row.getAttribute('data-user-id');
const cb = row.querySelector('.user-checkbox');
if (!cb || !userId) return;
cb.checked = selected.has(userId);
if (!cb.checked) allChecked = false;
if (cb.checked) anyChecked = true;
});
// Update header select-all for current page
selectAll.checked = allChecked && pageNodes.length > 0;
selectAll.indeterminate = !allChecked && anyChecked;
}
// Helper: clear all selections and reset the current page UI
function clearSelectionsNow() {
selected.clear();
// Uncheck visible checkboxes on current page
table.rows({ page: 'current' }).every(function () {
const row = this.node();
const cb = row.querySelector('.user-checkbox');
if (cb) cb.checked = false;
});
// Reset select-all and hidden inputs
selectAll.checked = false;
selectAll.indeterminate = false;
syncHiddenInputs();
syncPageCheckboxes();
}
// On draw (paging/sort/search), just refresh checkbox UI states
table.on('draw', function() {
syncPageCheckboxes();
});
// Delegate checkbox change handling once at the table level (works across redraws)
document.getElementById('staffTable').addEventListener('change', function (e) {
const target = e.target;
if (!target || !target.classList.contains('user-checkbox')) return;
const tr = target.closest('tr[data-user-id]');
const userId = tr ? tr.getAttribute('data-user-id') : null;
if (!userId) return;
if (target.checked) selected.add(userId); else selected.delete(userId);
syncHiddenInputs();
syncPageCheckboxes();
// If user interacts, cancel any pending auto-clear to avoid wiping new selections
if (clearTimerId !== null) { clearTimeout(clearTimerId); clearTimerId = null; }
});
// Initial sync on first load
table.draw(false);
// Header "Select All (page)" toggler
selectAll.addEventListener('change', function() {
const check = this.checked;
table.rows({
page: 'current'
}).every(function() {
const row = this.node();
const userId = row.getAttribute('data-user-id');
const cb = row.querySelector('.user-checkbox');
if (!cb || !userId) return;
cb.checked = check;
if (check) selected.add(userId);
else selected.delete(userId);
});
syncHiddenInputs();
syncPageCheckboxes();
if (clearTimerId !== null) { clearTimeout(clearTimerId); clearTimerId = null; }
});
// Before submit, ensure CSRF is fresh and hidden inputs include all selections
const form = document.getElementById('badgeForm');
async function refreshCsrf() {
try {
const params = new URLSearchParams();
if (!resp.ok) return;
const json = await resp.json();
if (json && json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
csrfInput.value = json.csrf_hash;
}
} catch (e) {
// No-op: if refresh fails we'll still attempt submit; server may accept existing token
}
}
form.addEventListener('submit', async function(e) {
e.preventDefault();
if (selected.size === 0) {
alert('Please select at least one staff member to generate badges.');
return;
}
syncHiddenInputs();
// CSRF is excluded for this endpoint now, but keeping refresh is safe
try { await refreshCsrf(); } catch (_) {}
// Native submit so browser handles PDF in a new tab
form.submit();
// Auto-clear selections 5 seconds after generating badges
clearTimerId = setTimeout(function() { clearSelectionsNow(); clearTimerId = null; }, 5000);
});
// --- Fetch and render print status ---
const selectedYear = <?= json_encode($selectedYear ?? '') ?>;
function fetchPrintStatus() {
const ids = Object.keys(rowMeta);
if (ids.length === 0) return;
const params = new URLSearchParams();
ids.forEach(id => params.append('user_ids[]', id));
if (selectedYear) params.append('school_year', selectedYear);
fetch('<?= base_url('api/printables/badges/status') ?>?' + params.toString(), {
method: 'GET',
headers: {
'Accept': 'application/json'
}
})
.then(r => r.ok ? r.json() : Promise.reject())
.then(json => {
if (!json || !json.ok || !json.data) return;
const data = json.data;
Object.keys(rowMeta).forEach(id => {
const info = data[id];
const badge = document.querySelector(`.prints-badge[data-user-id="${id}"]`);
const tr = document.querySelector(`#staffTable tbody tr[data-user-id="${id}"]`);
if (!badge || !tr) return;
const count = info ? (info.count || 0) : 0;
badge.textContent = count > 0 ? `Printed ${count}` : '—';
badge.className = 'badge prints-badge ' + (count > 0 ? 'bg-success' : 'bg-secondary');
tr.classList.toggle('table-success', count > 0);
});
// Refresh CSRF for subsequent submissions (so no page reload is needed)
if (json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
csrfInput.value = json.csrf_hash;
}
})
.catch(() => {});
}
fetchPrintStatus();
window.addEventListener('focus', fetchPrintStatus);
});
</script>
<?= $this->endSection() ?>