recreate project
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
<?= $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() ?>
|
||||
Reference in New Issue
Block a user