248 lines
9.1 KiB
PHP
248 lines
9.1 KiB
PHP
<?= $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() ?>
|