Files
2026-05-16 13:44:12 -04:00

248 lines
9.1 KiB
PHP
Executable File

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
helper('url');
$endpoint = $deletedNotificationsEndpoint ?? site_url('api/notifications/deleted');
$restoreBaseUrl = $restoreEndpoint ?? site_url('notifications/restore');
$csrfTokenName = $csrfTokenName ?? csrf_token();
$csrfTokenValue = $csrfTokenValue ?? csrf_hash();
?>
<div class="container-fluid px-0 mt-4"
data-notifications-endpoint="<?= esc($endpoint) ?>"
data-restore-base="<?= esc($restoreBaseUrl) ?>"
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>
<?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<div id="deletedNotificationsAlert" class="alert alert-danger d-none" role="alert"></div>
<div class="table-responsive">
<table id="deletedNotificationsTable" class="table table-striped table-bordered align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Title</th>
<th>Message</th>
<th>Priority</th>
<th>Scheduled At</th>
<th>Expires At</th>
<th>Deleted At</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="deletedNotificationsBody">
<tr>
<td colspan="8" class="text-center text-muted">Loading deleted notifications…</td>
</tr>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-notifications-endpoint]');
if (!container) {
return;
}
const endpoint = container.dataset.notificationsEndpoint;
const restoreBase = container.dataset.restoreBase;
const csrfName = container.dataset.csrfName;
const csrfValue = container.dataset.csrfValue;
const CSRF_COOKIE_NAME = <?= json_encode(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>;
function getCookie(n){
return document.cookie.split(';').map(v=>v.trim()).find(v=>v.startsWith(n+'='))?.split('=')[1] || '';
}
const alertBox = document.getElementById('deletedNotificationsAlert');
const tableBody = document.getElementById('deletedNotificationsBody');
const tableSelector = '#deletedNotificationsTable';
let rows = [];
const showError = (message) => {
if (!alertBox) {
return;
}
alertBox.textContent = message;
alertBox.classList.remove('d-none');
};
const clearError = () => {
if (!alertBox) {
return;
}
alertBox.textContent = '';
alertBox.classList.add('d-none');
};
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;
}
window.jQuery(tableSelector).DataTable({
pageLength: 25,
responsive: true,
order: [[6, 'desc']],
columnDefs: [
{
targets: [2, 7],
orderable: false,
},
],
});
};
const formatDateTime = (value) => {
if (!value) {
return '—';
}
const date = new Date(value.replace(' ', 'T'));
if (Number.isNaN(date.valueOf())) {
return value;
}
const pad = (num) => String(num).padStart(2, '0');
const datePart = `${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${date.getFullYear()}`;
const hasTime = /[T ]\d{2}:\d{2}/.test(String(value));
if (!hasTime) {
return datePart;
}
const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
return `${datePart} ${timePart}`;
};
const renderTable = () => {
destroyDataTable();
tableBody.innerHTML = '';
if (!Array.isArray(rows) || rows.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 deleted notifications found.';
row.appendChild(cell);
tableBody.appendChild(row);
return;
}
rows.forEach((entry, index) => {
const tr = document.createElement('tr');
const cells = [
index + 1,
entry.title ?? '—',
entry.message ?? '—',
entry.priority ?? '—',
formatDateTime(entry.scheduled_at),
entry.expires_at ? formatDateTime(entry.expires_at) : 'No expiry',
formatDateTime(entry.deleted_at),
];
cells.forEach((text) => {
const td = document.createElement('td');
td.textContent = text;
tr.appendChild(td);
});
const actionCell = document.createElement('td');
if (entry.id && restoreBase) {
const form = document.createElement('form');
form.method = 'post';
form.action = `${restoreBase}/${entry.id}`;
form.addEventListener('submit', function(){
try {
const fresh = decodeURIComponent(getCookie(CSRF_COOKIE_NAME) || '');
if (fresh) {
const hidden = form.querySelector('input[name="' + csrfName + '"]');
if (hidden) hidden.value = fresh;
}
} catch(_) {}
});
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = csrfName;
csrfInput.value = csrfValue;
form.appendChild(csrfInput);
const submitBtn = document.createElement('button');
submitBtn.type = 'submit';
submitBtn.className = 'btn btn-sm btn-success';
submitBtn.textContent = 'Restore';
form.appendChild(submitBtn);
actionCell.appendChild(form);
} else {
actionCell.textContent = '—';
}
tr.appendChild(actionCell);
tableBody.appendChild(tr);
});
initDataTable();
};
const fetchNotifications = () => {
clearError();
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted">Loading deleted notifications…</td>
</tr>
`;
return fetch(endpoint, {
headers: {
'Accept': 'application/json',
},
credentials: 'same-origin',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
})
.then((payload) => {
rows = Array.isArray(payload?.notifications) ? payload.notifications : [];
renderTable();
})
.catch((error) => {
showError(error.message || 'Unable to load deleted notifications.');
destroyDataTable();
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-danger">Failed to load deleted notifications.</td>
</tr>
`;
});
};
fetchNotifications();
});
</script>
<?= $this->endSection() ?>