recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
+272
View File
@@ -0,0 +1,272 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
helper('url');
$endpoint = $loginActivityEndpoint ?? site_url('api/login-activity');
$defaultPerPage = isset($defaultPerPage) && (int)$defaultPerPage > 0 ? (int)$defaultPerPage : 25;
?>
<div class="container-fluid"
data-login-activity-endpoint="<?= esc($endpoint) ?>"
data-default-per-page="<?= esc($defaultPerPage) ?>">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Login Activity</h2>
<?= $this->include('partials/academic_filter') ?>
<div id="loginActivityAlert" class="alert alert-danger d-none" role="alert"></div>
<div class="d-flex justify-content-between align-items-center flex-wrap gap-3 mb-3">
<div>
<label for="perPageSelect" class="form-label mb-0 me-2">Rows per page:</label>
<select class="form-select d-inline-block w-auto" id="perPageSelect">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-secondary" id="prevPageBtn">Previous</button>
<span id="paginationInfo" class="text-muted small">Page 1 of 1</span>
<button type="button" class="btn btn-outline-secondary" id="nextPageBtn">Next</button>
</div>
</div>
<div class="table-responsive">
<table id="loginActivityTable" class="table table-striped table-bordered align-middle mb-0">
<thead class="table-dark">
<tr>
<th>User ID</th>
<th>Email</th>
<th>Login Time</th>
<th>Logout Time</th>
<th>IP Address</th>
<th>User Agent</th>
</tr>
</thead>
<tbody id="loginActivityBody">
<tr>
<td colspan="6" class="text-center text-muted">Loading login activity…</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-login-activity-endpoint]');
if (!container) {
return;
}
const endpoint = container.dataset.loginActivityEndpoint;
const alertBox = document.getElementById('loginActivityAlert');
const tableBody = document.getElementById('loginActivityBody');
const perPageSelect = document.getElementById('perPageSelect');
const paginationInfo = document.getElementById('paginationInfo');
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
const tableSelector = '#loginActivityTable';
let currentPage = 1;
let perPage = parseInt(container.dataset.defaultPerPage, 10) || 25;
let pageCount = 1;
const setPerPageSelect = () => {
const options = Array.from(perPageSelect.options);
const found = options.some((opt) => parseInt(opt.value, 10) === perPage);
if (!found) {
const option = document.createElement('option');
option.value = String(perPage);
option.textContent = perPage;
perPageSelect.appendChild(option);
}
perPageSelect.value = String(perPage);
};
setPerPageSelect();
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({
paging: false,
searching: false,
info: false,
order: [[2, 'desc']],
});
};
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 = (activities) => {
destroyDataTable();
tableBody.innerHTML = '';
if (!Array.isArray(activities) || activities.length === 0) {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 6;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No login activities found.';
row.appendChild(cell);
tableBody.appendChild(row);
initDataTable();
return;
}
activities.forEach((activity) => {
const row = document.createElement('tr');
const cells = [
activity.user_id ?? '—',
activity.email ?? '—',
formatDateTime(activity.login_time ?? null),
formatDateTime(activity.logout_time ?? null),
activity.ip_address ?? '—',
activity.user_agent ?? '—',
];
cells.forEach((text) => {
const cell = document.createElement('td');
cell.textContent = text;
row.appendChild(cell);
});
tableBody.appendChild(row);
});
initDataTable();
};
const updatePaginationControls = () => {
paginationInfo.textContent = `Page ${currentPage} of ${pageCount}`;
prevBtn.disabled = currentPage <= 1;
nextBtn.disabled = currentPage >= pageCount;
};
const buildUrl = () => {
try {
const url = new URL(endpoint, window.location.origin);
url.searchParams.set('page', currentPage);
url.searchParams.set('per_page', perPage);
return url.toString();
} catch (error) {
const separator = endpoint.includes('?') ? '&' : '?';
return `${endpoint}${separator}page=${encodeURIComponent(currentPage)}&per_page=${encodeURIComponent(perPage)}`;
}
};
const fetchActivities = () => {
clearError();
const url = buildUrl();
return fetch(url, {
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) => {
const activities = Array.isArray(payload?.activities) ? payload.activities : [];
const pagination = payload?.pagination ?? {};
currentPage = Number.isFinite(pagination.currentPage) ? pagination.currentPage : currentPage;
pageCount = Number.isFinite(pagination.pageCount) ? pagination.pageCount : pageCount;
renderTable(activities);
updatePaginationControls();
})
.catch((error) => {
showError(error.message || 'Unable to load login activity.');
destroyDataTable();
tableBody.innerHTML = '';
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 6;
cell.classList.add('text-center', 'text-danger');
cell.textContent = 'Failed to load login activity.';
row.appendChild(cell);
tableBody.appendChild(row);
updatePaginationControls();
});
};
perPageSelect.addEventListener('change', () => {
perPage = parseInt(perPageSelect.value, 10) || 25;
currentPage = 1;
fetchActivities();
});
prevBtn.addEventListener('click', () => {
if (currentPage > 1) {
currentPage -= 1;
fetchActivities();
}
});
nextBtn.addEventListener('click', () => {
if (currentPage < pageCount) {
currentPage += 1;
fetchActivities();
}
});
fetchActivities();
});
</script>
<?= $this->endSection() ?>