fix pages and add distribution system
Tests / PHPUnit (push) Failing after 1m13s

This commit is contained in:
root
2026-07-15 23:03:51 -04:00
parent 7f3b24e47f
commit ba87598d3a
38 changed files with 1362 additions and 658 deletions
+56 -99
View File
@@ -11,7 +11,6 @@
data-notifications-endpoint="<?= esc($endpoint) ?>"
data-default-target-group="<?= esc($defaultTarget) ?>">
<h2 class="text-center mt-4 mb-3">Active Notifications</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
@@ -61,53 +60,6 @@
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- FixedHeader helpers / assets ---
function getFixedHeaderOffset() {
let total = 0;
const stack = [];
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top');
if (header) stack.push(header);
const mgmt = document.getElementById('navbarManagement');
if (mgmt && (mgmt.classList.contains('sticky-top') || mgmt.classList.contains('fixed-top'))) stack.push(mgmt);
document.querySelectorAll('.navbar.sticky-top, .navbar.fixed-top').forEach(el => { if (!stack.includes(el)) stack.push(el); });
stack.forEach(el => { const h = el.offsetHeight || el.getBoundingClientRect().height || 0; total += Math.max(0, Math.round(h)); });
return total;
}
function loadScript(src, id) {
return new Promise((resolve, reject) => {
if (id && document.getElementById(id)) return resolve();
const s = document.createElement('script');
if (id) s.id = id;
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
function loadCss(href, id) {
return new Promise((resolve) => {
if (id && document.getElementById(id)) return resolve();
const l = document.createElement('link');
if (id) l.id = id;
l.rel = 'stylesheet';
l.href = href;
l.onload = resolve;
document.head.appendChild(l);
});
}
function ensureFixedHeaderAssets() {
const hasFH = !!(window.jQuery && window.jQuery.fn && window.jQuery.fn.dataTable && window.jQuery.fn.dataTable.FixedHeader);
if (hasFH) return Promise.resolve();
return Promise.all([
loadScript('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader@3.4.0/js/dataTables.fixedHeader.min.js', 'dt-fixedheader'),
loadCss('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader-bs5@3.4.0/css/fixedHeader.bootstrap5.min.css', 'dt-fixedheader-css')
]).catch(() => {});
}
// Start fetching FixedHeader in parallel
ensureFixedHeaderAssets();
const container = document.querySelector('[data-notifications-endpoint]');
if (!container) {
return;
@@ -123,6 +75,7 @@
const resetBtn = document.getElementById('resetFilterBtn');
let lastPayload = [];
let dataTable = null;
const showError = (message) => {
if (!alertBox) {
@@ -140,23 +93,16 @@
alertBox.textContent = '';
};
const destroyDataTable = () => {
if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.DataTable) {
return;
}
if (window.jQuery.fn.DataTable.isDataTable(tableSelector)) {
window.jQuery(tableSelector).DataTable().clear().destroy();
}
};
const initDataTable = () => {
if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.DataTable) {
return;
return null;
}
const opts = {
if (dataTable) {
return dataTable;
}
dataTable = window.jQuery(tableSelector).DataTable({
pageLength: 25,
responsive: true,
// Sort by Scheduled At desc, then Created At desc by default
order: [[5, 'desc'], [7, 'desc']],
columnDefs: [
{
@@ -164,22 +110,9 @@
orderable: false,
},
],
};
});
if (window.jQuery.fn.dataTable && window.jQuery.fn.dataTable.FixedHeader) {
opts.fixedHeader = { header: true, headerOffset: getFixedHeaderOffset() };
}
const dt = window.jQuery(tableSelector).DataTable(opts);
// Attach FixedHeader if it loads slightly later
if (!opts.fixedHeader) {
ensureFixedHeaderAssets().then(() => {
if (window.jQuery.fn.dataTable && window.jQuery.fn.dataTable.FixedHeader) {
try { new window.jQuery.fn.dataTable.FixedHeader(dt, { header: true, headerOffset: getFixedHeaderOffset() }); } catch (_) {}
}
});
}
return dataTable;
};
const formatDateTime = (value) => {
@@ -208,21 +141,32 @@
};
const renderTable = () => {
destroyDataTable();
tableBody.innerHTML = '';
if (!dataTable && window.jQuery && window.jQuery.fn && window.jQuery.fn.DataTable) {
tableBody.innerHTML = '';
}
const dt = initDataTable();
if (dt) {
dt.clear();
} else {
tableBody.innerHTML = '';
}
if (!Array.isArray(lastPayload) || lastPayload.length === 0) {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 8;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No active notifications found.';
row.appendChild(cell);
tableBody.appendChild(row);
initDataTable();
if (dt) {
dt.draw();
} else {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 8;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No active notifications found.';
row.appendChild(cell);
tableBody.appendChild(row);
}
return;
}
const rows = [];
lastPayload.forEach((entry, index) => {
const row = document.createElement('tr');
if (entry.isExpired) {
@@ -265,10 +209,15 @@
row.appendChild(cell);
});
tableBody.appendChild(row);
if (!dt) {
tableBody.appendChild(row);
}
rows.push(row);
});
initDataTable();
if (dt) {
dt.rows.add(rows).draw();
}
};
const buildUrl = (target) => {
@@ -290,11 +239,15 @@
clearError();
const url = buildUrl(targetGroup);
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted">Loading notifications…</td>
</tr>
`;
if (dataTable) {
dataTable.clear().draw();
} else {
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted">Loading notifications…</td>
</tr>
`;
}
return fetch(url, {
headers: {
@@ -314,12 +267,16 @@
})
.catch((error) => {
showError(error.message || 'Unable to load notifications.');
destroyDataTable();
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-danger">Failed to load notifications.</td>
</tr>
`;
const dt = initDataTable();
if (dt) {
dt.clear().draw();
} else {
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-danger">Failed to load notifications.</td>
</tr>
`;
}
});
};
-1
View File
@@ -15,7 +15,6 @@
data-csrf-name="<?= esc($csrfTokenName) ?>"
data-csrf-value="<?= esc($csrfTokenValue) ?>">
<h2 class="text-center mt-4 mb-3">Deleted Notifications</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>