Files
root d2cb159451
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 49s
Tests / PHPUnit (push) Failing after 1m20s
fix enrollment and registration email send
2026-08-28 16:16:59 -04:00

349 lines
13 KiB
PHP

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
helper('url');
$assignRoleEndpoint = $assignRoleEndpoint ?? site_url('api/rolepermission/users');
$rolesEndpoint = $rolesEndpoint ?? site_url('api/rolepermission/roles');
?>
<div class="container-fluid"
data-users-endpoint="<?= esc($assignRoleEndpoint) ?>"
data-roles-endpoint="<?= esc($rolesEndpoint) ?>">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Assign Role to Users</h2>
<?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="assignRoleAlert" class="alert alert-danger d-none" role="alert"></div>
<div class="table-responsive">
<table id="usersTable" class="display table table-bordered table-striped align-middle no-dt-fixedheader" data-no-dt-fixedheader>
<thead class="table-dark">
<tr>
<th>User ID</th>
<th>Account Number</th>
<th>Name</th>
<th>Email</th>
<th>Current Roles</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="6" class="text-center text-muted">Loading users…</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade" id="assignRoleModal" tabindex="-1" aria-labelledby="assignRoleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="<?= site_url('rolepermission/save_role'); ?>" id="assignRoleForm">
<?= csrf_field(); ?>
<div class="modal-header">
<h5 class="modal-title" id="assignRoleModalLabel">Assign Roles</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="user_id" id="modal_user_id">
<div class="mb-3">
<label class="form-label">User</label>
<div id="modal_user_info" class="fw-semibold"></div>
</div>
<div class="form-group" id="modal_roles_container">
<label class="form-label">Select Roles</label>
<div id="modal_roles_list" class="d-flex flex-column gap-1"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Roles</button>
</div>
</form>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-users-endpoint]');
if (!container) {
return;
}
const usersEndpoint = container.dataset.usersEndpoint;
const rolesEndpoint = container.dataset.rolesEndpoint;
const alertBox = document.getElementById('assignRoleAlert');
const tableBody = document.querySelector('#usersTable tbody');
const modalElement = document.getElementById('assignRoleModal');
const modalUserInfo = document.getElementById('modal_user_info');
const modalUserId = document.getElementById('modal_user_id');
const modalRolesList = document.getElementById('modal_roles_list');
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
let users = [];
let usersById = new Map();
let roles = [];
const hasDataTables = () =>
!!(window.jQuery && window.jQuery.fn && window.jQuery.fn.DataTable);
const destroyDataTable = () => {
if (!hasDataTables()) return;
const table = document.getElementById('usersTable');
if (!table || !table.parentNode) return;
if (window.jQuery.fn.DataTable.isDataTable(table)) {
try {
const dt = window.jQuery(table).DataTable();
if (dt.fixedHeader && typeof dt.fixedHeader.destroy === 'function') {
dt.fixedHeader.destroy();
}
dt.clear().destroy();
} catch (error) {
console.warn('Unable to destroy users DataTable cleanly.', error);
}
}
};
const initDataTable = () => {
if (!hasDataTables()) return;
const opts = {
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
columnDefs: [
{ orderable: false, targets: [5] },
],
};
window.jQuery('#usersTable').DataTable(opts);
};
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 buildRolesMarkup = (selectedIds = []) => {
modalRolesList.innerHTML = '';
const selectedSet = new Set(selectedIds.map((id) => Number(id)));
if (!Array.isArray(roles) || roles.length === 0) {
modalRolesList.innerHTML = '<div class="text-muted">No roles available.</div>';
return;
}
roles.forEach((role) => {
const checkboxId = `roleCheck_${role.id}`;
const wrapper = document.createElement('div');
wrapper.classList.add('form-check');
const input = document.createElement('input');
input.type = 'checkbox';
input.className = 'form-check-input';
input.name = 'role_ids[]';
input.value = role.id;
input.id = checkboxId;
if (selectedSet.has(Number(role.id))) {
input.checked = true;
}
const label = document.createElement('label');
label.className = 'form-check-label';
label.htmlFor = checkboxId;
label.textContent = role.name ?? '';
wrapper.appendChild(input);
wrapper.appendChild(label);
modalRolesList.appendChild(wrapper);
});
};
const openModalForUser = (userId) => {
const user = usersById.get(userId);
if (!user || !modalInstance) return;
modalUserId.value = user.id ?? '';
const fullName = `${user.firstname ?? ''} ${user.lastname ?? ''}`.trim();
modalUserInfo.textContent = `${fullName} (${user.email ?? ''})`;
buildRolesMarkup([]);
const selectedRoleIds = [];
if (Array.isArray(user.roles) && user.roles.length > 0) {
const lookup = new Map(roles.map((role) => [role.name, role.id]));
user.roles.forEach((roleName) => {
if (lookup.has(roleName)) {
selectedRoleIds.push(lookup.get(roleName));
}
});
}
buildRolesMarkup(selectedRoleIds);
modalInstance.show();
};
const renderNameCell = (user) => {
const td = document.createElement('td');
const fullName = `${user?.firstname ?? ''} ${user?.lastname ?? ''}`.trim();
const label = fullName !== '' ? fullName : '—';
const roleList = Array.isArray(user?.roles)
? user.roles.map((role) => (role || '').toString().toLowerCase())
: [];
const isParent = roleList.includes('parent');
const uid = Number(user?.id || 0);
if (isParent && uid > 0) {
const link = document.createElement('a');
link.href = '#';
link.className = 'text-decoration-none';
link.setAttribute('data-family-guardian-id', String(uid));
link.textContent = label;
td.appendChild(link);
return td;
}
td.textContent = label;
return td;
};
const renderTable = () => {
if (!tableBody) return;
destroyDataTable();
tableBody.innerHTML = '';
if (!Array.isArray(users) || users.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 users found.';
row.appendChild(cell);
tableBody.appendChild(row);
return;
}
users.forEach((user) => {
usersById.set(user.id, user);
const row = document.createElement('tr');
const userIdCell = document.createElement('td');
userIdCell.textContent = user.id ?? '';
row.appendChild(userIdCell);
const accountCell = document.createElement('td');
accountCell.textContent = user.account_id ?? '';
row.appendChild(accountCell);
row.appendChild(renderNameCell(user));
const emailCell = document.createElement('td');
emailCell.textContent = user.email ?? '';
row.appendChild(emailCell);
const rolesCell = document.createElement('td');
if (Array.isArray(user.roles) && user.roles.length > 0) {
rolesCell.innerHTML = user.roles
.map((role) => `<span class="badge bg-primary me-1 mb-1">${role}</span>`)
.join('');
} else {
rolesCell.innerHTML = '<span class="text-muted">No role assigned</span>';
}
row.appendChild(rolesCell);
const actionsCell = document.createElement('td');
const button = document.createElement('button');
button.type = 'button';
button.className = 'btn btn-primary btn-sm';
button.textContent = 'Assign / Update Roles';
button.addEventListener('click', () => openModalForUser(user.id));
actionsCell.appendChild(button);
row.appendChild(actionsCell);
tableBody.appendChild(row);
});
initDataTable();
};
const fetchRoles = () => {
if (!rolesEndpoint) return Promise.resolve();
return fetch(rolesEndpoint, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Roles request failed with status ${response.status}`);
}
return response.json();
})
.then((payload) => {
roles = Array.isArray(payload?.roles) ? payload.roles : [];
});
};
const fetchUsers = () => {
if (!usersEndpoint) return;
tableBody.innerHTML = `
<tr>
<td colspan="6" class="text-center text-muted">Loading users…</td>
</tr>
`;
return fetch(usersEndpoint, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Users request failed with status ${response.status}`);
}
return response.json();
})
.then((payload) => {
users = Array.isArray(payload?.users) ? payload.users : [];
clearError();
renderTable();
})
.catch((error) => {
console.error(error);
showError('Unable to load users.');
destroyDataTable();
tableBody.innerHTML = `
<tr>
<td colspan="6" class="text-center text-danger">Failed to load users.</td>
</tr>
`;
});
};
fetchRoles()
.then(fetchUsers)
.catch((error) => {
console.error(error);
showError('Unable to load roles.');
});
});
</script>
<?= $this->endSection() ?>