refactor: streamline user permission management
Move per-user permissions into row actions, add contextual module permission guides, show selected-user details, and relocate online sessions to the profile menu.
This commit is contained in:
parent
c263b9e4ac
commit
8f25904a13
|
|
@ -11,14 +11,6 @@
|
|||
<i class="bi bi-person-badge"></i>
|
||||
Role Master
|
||||
</button>
|
||||
<button class="btn btn-outline-primary" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/permissions') ?>','maindiv','Additional Permissions');">
|
||||
<i class="bi bi-shield-lock"></i>
|
||||
Additional Permissions
|
||||
</button>
|
||||
<button class="btn btn-outline-success" type="button" onclick="load_form_div('<?= base_url('setting/admin/user-management/sessions') ?>','maindiv','Active Sessions');">
|
||||
<i class="bi bi-person-check"></i>
|
||||
Who is Online
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
@ -106,10 +98,19 @@
|
|||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="load_form_div('<?= base_url('setting/admin/user-management/edit/' . (int) ($user->id ?? 0)) ?>','maindiv','Edit User');">Edit</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning" onclick="load_form_div('<?= base_url('setting/admin/user-management/reset-password/' . (int) ($user->id ?? 0)) ?>','maindiv','Reset Password');">Reset Password</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" title="Edit user" aria-label="Edit user" onclick="load_form_div('<?= base_url('setting/admin/user-management/edit/' . (int) ($user->id ?? 0)) ?>','maindiv','Edit User');">
|
||||
<i class="bi bi-pencil-square" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning" title="Reset password" aria-label="Reset password" onclick="load_form_div('<?= base_url('setting/admin/user-management/reset-password/' . (int) ($user->id ?? 0)) ?>','maindiv','Reset Password');">
|
||||
<i class="bi bi-key" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-info" title="Additional permissions" aria-label="Additional permissions" onclick="load_form_div('<?= base_url('setting/admin/user-management/permissions?user_id=' . (int) ($user->id ?? 0)) ?>','maindiv','Additional Permissions');">
|
||||
<i class="bi bi-shield-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
<?php if ((int) ($user->id ?? 0) !== (int) (auth()->user()->id ?? 0)) : ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-delete-user" data-user-id="<?= (int) ($user->id ?? 0) ?>" data-username="<?= esc((string) ($user->username ?? ''), 'attr') ?>">Delete</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-delete-user" title="Delete user" aria-label="Delete user" data-user-id="<?= (int) ($user->id ?? 0) ?>" data-username="<?= esc((string) ($user->username ?? ''), 'attr') ?>">
|
||||
<i class="bi bi-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -24,32 +24,49 @@
|
|||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php
|
||||
$selectedEmail = '';
|
||||
$selectedPersonName = '';
|
||||
$selectedPhoneNo = '';
|
||||
if (! empty($selectedUser)) {
|
||||
$selectedIdentity = $selectedUser->getEmailIdentity();
|
||||
$selectedEmail = trim((string) ($selectedIdentity->secret ?? ''));
|
||||
$selectedExtra = $selectedIdentity->extra ?? [];
|
||||
if (is_string($selectedExtra) && trim($selectedExtra) !== '') {
|
||||
$decodedExtra = json_decode($selectedExtra, true);
|
||||
$selectedExtra = is_array($decodedExtra) ? $decodedExtra : [];
|
||||
}
|
||||
if (is_array($selectedExtra)) {
|
||||
$selectedPersonName = trim((string) ($selectedExtra['full_name'] ?? ''));
|
||||
$selectedPhoneNo = trim((string) ($selectedExtra['phone_no'] ?? ''));
|
||||
}
|
||||
}
|
||||
?>
|
||||
<form class="needs-validation" novalidate action="<?= base_url('setting/admin/user-management/permissions') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="user_id">User</label>
|
||||
<select class="form-select" id="user_id" name="user_id" required>
|
||||
<option value="">Select user</option>
|
||||
<?php if (! empty($users)) : ?>
|
||||
<?php foreach ($users as $user) : ?>
|
||||
<?php
|
||||
$emailIdentity = $user->getEmailIdentity();
|
||||
$email = $emailIdentity ? $emailIdentity->secret : '';
|
||||
$label = trim(($user->username ?? '') . ($email ? ' (' . $email . ')' : ''));
|
||||
?>
|
||||
<option value="<?= esc($user->id) ?>" <?= ! empty($selectedUser) && (int) $selectedUser->id === (int) $user->id ? 'selected' : '' ?>>
|
||||
<?= esc($label) ?>
|
||||
</option>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
</select>
|
||||
<div class="invalid-feedback">User is required.</div>
|
||||
<input type="hidden" name="user_id" value="<?= ! empty($selectedUser) ? (int) $selectedUser->id : '' ?>">
|
||||
|
||||
<?php if (! empty($selectedUser)) : ?>
|
||||
<div class="border rounded p-3 mb-3 bg-light">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<i class="bi bi-person-badge fs-4 text-primary"></i>
|
||||
<div>
|
||||
<div class="fw-bold"><?= esc($selectedPersonName !== '' ? $selectedPersonName : (string) ($selectedUser->username ?? 'User')) ?></div>
|
||||
<div class="text-muted small">Selected user for additional permissions</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-2 small">
|
||||
<div class="col-md-4"><strong>Login ID:</strong> <?= esc((string) ($selectedUser->username ?? '-')) ?></div>
|
||||
<div class="col-md-4"><strong>User ID:</strong> <?= (int) $selectedUser->id ?></div>
|
||||
<div class="col-md-4"><strong>Status:</strong> <?= ! empty($selectedUser->active) ? 'Active' : 'Inactive' ?></div>
|
||||
<div class="col-md-4"><strong>Email:</strong> <?= esc($selectedEmail !== '' ? $selectedEmail : '-') ?></div>
|
||||
<div class="col-md-4"><strong>Phone:</strong> <?= esc($selectedPhoneNo !== '' ? $selectedPhoneNo : '-') ?></div>
|
||||
<div class="col-md-4"><strong>Role:</strong> <?= esc(! empty($selectedRoleTitles) ? implode(', ', $selectedRoleTitles) : 'No role') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-end">
|
||||
<button class="btn btn-outline-secondary" type="button" id="loadPermissions">Load Permissions</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="alert alert-warning">No user was selected. Return to the user list and choose the permissions action for a user.</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="mt-4">
|
||||
<?php if (! empty($selectedUser)) : ?>
|
||||
|
|
@ -61,9 +78,6 @@
|
|||
<?php endif ?>
|
||||
<label class="form-label">Direct grants</label>
|
||||
<div class="text-muted small mb-2">Checked boxes are additional permissions saved specifically for this user. Unchecking one does not remove access inherited from the role.</div>
|
||||
<div class="alert alert-info py-2 mb-2">
|
||||
Pharmacy module access is controlled by <strong>pharmacy.access</strong>. Editing past Walk-in/Registered pharmacy invoices requires <strong>pharmacy.invoice.edit-old</strong> (or <strong>pharmacy.invoice.admin</strong>). Discount caps can be granted with <strong>pharmacy.invoice.discount.10</strong>, <strong>pharmacy.invoice.discount.20</strong>, <strong>pharmacy.invoice.discount.30</strong>, or full override using <strong>pharmacy.invoice.admin</strong>. Base user discount can be set in hospital setting key <strong>PHARMACY_NORMAL_MAX_DISCOUNT_PERCENT</strong>.
|
||||
</div>
|
||||
<div class="border rounded p-3 bg-light">
|
||||
<?php
|
||||
$selectedPermissions = [];
|
||||
|
|
@ -85,8 +99,19 @@
|
|||
'billing.opd' => 'OPD',
|
||||
'billing.charges' => 'Charges',
|
||||
'billing.ipd' => 'IPD Billing',
|
||||
'billing' => 'Billing',
|
||||
'billing.access' => 'Billing Access',
|
||||
'billing.patient' => 'Patient Billing',
|
||||
'billing.payment_request' => 'Payment Requests',
|
||||
'billing.refund' => 'Refunds',
|
||||
'billing.items' => 'Billing Items',
|
||||
'billing.packages' => 'Billing Packages',
|
||||
'abdm' => 'ABDM',
|
||||
'diagnosis' => 'Diagnosis',
|
||||
'doctor_work' => 'Doctor Work',
|
||||
'hospital_stock' => 'Hospital Stock',
|
||||
'media' => 'Media',
|
||||
'reports' => 'Reports',
|
||||
'settings' => 'Settings',
|
||||
'other' => 'Other',
|
||||
];
|
||||
foreach ($permissions as $permissionKey => $permissionLabel) {
|
||||
|
|
@ -134,13 +159,45 @@
|
|||
$groupOrder[] = $detectedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
$groupHelp = [
|
||||
'admin' => 'Controls access to the administrative area and core application settings. Grant only to trusted system administrators.',
|
||||
'users' => 'Controls user-account lifecycle actions such as creating, editing, deleting, and managing privileged users.',
|
||||
'template' => 'Controls access to clinical, diagnostic, discharge, document, and print-template configuration areas.',
|
||||
'pharmacy' => 'Controls pharmacy access, invoice administration, old-invoice editing, purchase status changes, and user discount limits. The base discount is configured by hospital setting PHARMACY_NORMAL_MAX_DISCOUNT_PERCENT.',
|
||||
'finance' => 'Controls Accounts and Finance workflows including cash submission, verification, bank reconciliation, compliance, vendors, purchase orders, GRNs, invoices, and payouts.',
|
||||
'opd' => 'Controls access to the OPD doctor panel used for clinical work on outpatient visits.',
|
||||
'billing.access' => 'Provides the main entry permission for billing workflows. More specific billing permissions below restrict individual operations.',
|
||||
'billing.patient' => 'Controls exceptional patient billing actions, including editing a patient name after the normal time restriction.',
|
||||
'billing.opd' => 'Controls OPD registration editing and payment confirmation actions.',
|
||||
'billing.charges' => 'Controls charges invoice viewing, editing, date changes, payment, cancellation, and correction actions.',
|
||||
'billing.ipd' => 'Controls IPD billing access, admissions, invoices, cash reports, bill printing, discharge edits, status management, and exports.',
|
||||
'billing.payment_request' => 'Controls viewing and processing of organization payment requests.',
|
||||
'billing.refund' => 'Controls viewing and processing of billing refund requests.',
|
||||
'billing.items' => 'Controls access to billing item masters and permission to maintain them.',
|
||||
'billing.packages' => 'Controls access to billing package masters and permission to maintain them.',
|
||||
'abdm' => 'Controls ABDM access, ABHA operations, task board, gateway actions, and Bridge communication logs.',
|
||||
'diagnosis' => 'Controls entry to the diagnosis module and access to diagnosis reports.',
|
||||
'doctor_work' => 'Controls doctor workflows including appointments, Rx groups, medicines, advice, clinical templates, and immunization records.',
|
||||
'hospital_stock' => 'Controls hospital stock masters, indents, approvals, issues, purchases, receiving, reports, and alerts.',
|
||||
'media' => 'Controls optional image preparation actions such as rotating or cropping before upload.',
|
||||
'reports' => 'Controls access to operational, collection, insurance, NABH audit, billing, and document issue reports.',
|
||||
'settings' => 'Controls access to operational settings such as bed management and charges configuration.',
|
||||
'beta' => 'Provides access to features designated for beta testing before general availability.',
|
||||
'other' => 'Contains permissions that do not yet belong to a dedicated module heading.',
|
||||
];
|
||||
?>
|
||||
<?php if (! empty($permissions)) : ?>
|
||||
<div class="row g-3">
|
||||
<?php foreach ($groupOrder as $groupKey) : ?>
|
||||
<?php if (empty($groupedPermissions[$groupKey])) { continue; } ?>
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="fw-bold mb-2"><?= esc($groupTitles[$groupKey] ?? $groupKey) ?></div>
|
||||
<div class="d-flex align-items-center gap-1 mb-2">
|
||||
<div class="fw-bold"><?= esc($groupTitles[$groupKey] ?? $groupKey) ?></div>
|
||||
<button type="button" class="btn btn-sm p-0 border-0 text-primary permission-help" data-bs-toggle="modal" data-bs-target="#permissionHelpModal" data-group-key="<?= esc($groupKey, 'attr') ?>" title="About <?= esc($groupTitles[$groupKey] ?? $groupKey, 'attr') ?> permissions" aria-label="About <?= esc($groupTitles[$groupKey] ?? $groupKey, 'attr') ?> permissions">
|
||||
<i class="bi bi-info-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php foreach ($groupedPermissions[$groupKey] as $permissionKey => $permissionLabel) : ?>
|
||||
<div class="form-check mb-1">
|
||||
<input class="form-check-input" type="checkbox" id="perm_<?= esc($permissionKey) ?>" name="permissions[]" value="<?= esc($permissionKey) ?>" <?= in_array($permissionKey, $selectedPermissions, true) ? 'checked' : '' ?>>
|
||||
|
|
@ -169,23 +226,61 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="permissionHelpModal" tabindex="-1" aria-labelledby="permissionHelpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="permissionHelpModalLabel">Permission Guide</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="permissionHelpSummary" class="mb-3"></p>
|
||||
<h6>Permissions in this section</h6>
|
||||
<div id="permissionHelpList" class="list-group list-group-flush"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var loadButton = document.getElementById('loadPermissions');
|
||||
var userSelect = document.getElementById('user_id');
|
||||
var form = document.querySelector('form[action="<?= base_url('setting/admin/user-management/permissions') ?>"]');
|
||||
var moduleGuides = <?= json_encode(array_reduce(array_keys($groupedPermissions), static function (array $guides, string $groupKey) use ($groupedPermissions, $groupTitles, $groupHelp): array {
|
||||
$permissionRows = [];
|
||||
foreach ($groupedPermissions[$groupKey] as $permissionKey => $permissionLabel) {
|
||||
$permissionRows[] = ['key' => $permissionKey, 'label' => $permissionLabel];
|
||||
}
|
||||
$guides[$groupKey] = [
|
||||
'title' => ($groupTitles[$groupKey] ?? $groupKey) . ' Permission Guide',
|
||||
'summary' => $groupHelp[$groupKey] ?? 'Review these permissions before granting additional access for this module.',
|
||||
'permissions' => $permissionRows,
|
||||
];
|
||||
return $guides;
|
||||
}, []), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
|
||||
|
||||
if (!loadButton || !userSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(document).off('click.userPermissionsLoad', '#loadPermissions').on('click.userPermissionsLoad', '#loadPermissions', function() {
|
||||
var userId = userSelect.value;
|
||||
if (!userId) {
|
||||
$(document).off('show.bs.modal.permissionHelp', '#permissionHelpModal').on('show.bs.modal.permissionHelp', '#permissionHelpModal', function(event) {
|
||||
var button = event.relatedTarget;
|
||||
var groupKey = button ? button.getAttribute('data-group-key') : '';
|
||||
var guide = moduleGuides[groupKey];
|
||||
if (!guide) {
|
||||
return;
|
||||
}
|
||||
var url = '<?= base_url('setting/admin/user-management/permissions') ?>' + '?user_id=' + encodeURIComponent(userId);
|
||||
load_form_div(url, 'maindiv', 'Give Permission');
|
||||
|
||||
$('#permissionHelpModalLabel').text(guide.title);
|
||||
$('#permissionHelpSummary').text(guide.summary);
|
||||
var list = document.getElementById('permissionHelpList');
|
||||
list.replaceChildren();
|
||||
guide.permissions.forEach(function(permission) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'list-group-item px-0';
|
||||
var key = document.createElement('code');
|
||||
key.className = 'd-block mb-1';
|
||||
key.textContent = permission.key;
|
||||
var label = document.createElement('div');
|
||||
label.textContent = permission.label;
|
||||
item.append(key, label);
|
||||
list.appendChild(item);
|
||||
});
|
||||
});
|
||||
|
||||
if (!form || !window.jQuery) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
$hdrCanIpdBilling = false;
|
||||
$hdrCanDoctorWork = false;
|
||||
$hdrCanPharmacy = false;
|
||||
$hdrCanManageSessions = false;
|
||||
if ($authUser && method_exists($authUser, 'can')) {
|
||||
$hdrCanBilling = $authUser->can('billing.access')
|
||||
|| $authUser->can('billing.opd.edit')
|
||||
|
|
@ -32,6 +33,9 @@
|
|||
|| $authUser->can('doctor_work.*');
|
||||
$hdrCanPharmacy = $authUser->can('pharmacy.access')
|
||||
|| $authUser->can('billing.access');
|
||||
$hdrCanManageSessions = $authUser->can('users.edit')
|
||||
|| $authUser->can('users.manage-admins')
|
||||
|| $authUser->can('users.*');
|
||||
}
|
||||
if ($authUser && method_exists($authUser, 'inGroup')) {
|
||||
$inAdminGroup = $authUser->inGroup('superadmin', 'admin', 'developer');
|
||||
|
|
@ -39,6 +43,7 @@
|
|||
if (! $hdrCanIpdBilling) { $hdrCanIpdBilling = $inAdminGroup; }
|
||||
if (! $hdrCanDoctorWork) { $hdrCanDoctorWork = $inAdminGroup; }
|
||||
if (! $hdrCanPharmacy) { $hdrCanPharmacy = $inAdminGroup; }
|
||||
if (! $hdrCanManageSessions) { $hdrCanManageSessions = $inAdminGroup; }
|
||||
}
|
||||
|
||||
$loginId = trim((string) ($authUser->username ?? ''));
|
||||
|
|
@ -166,6 +171,14 @@
|
|||
<span>My Profile</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php if ($hdrCanManageSessions) { ?>
|
||||
<li>
|
||||
<a class="dropdown-item d-flex align-items-center" href="javascript:load_form('<?= base_url('setting/admin/user-management/sessions') ?>','Who is Online');">
|
||||
<i class="bi bi-person-check"></i>
|
||||
<span>Who is Online</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user