This commit is contained in:
@@ -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>
|
||||
`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user