add change password per user

This commit is contained in:
mahdahar 2025-12-10 16:37:51 +07:00
parent 43c8823803
commit d3534e62a1
9 changed files with 117 additions and 31 deletions

View File

@ -72,11 +72,11 @@ $routes->group('v2', function($routes) {
$routes->get('login', 'V2::loginPage');
$routes->post('login', 'V2::login');
$routes->get('logout', 'V2::logout');
$routes->patch('setPassword', 'V2::setPassword');
// Admin
$routes->group('admin', ['filter' => 'role:1'], function($routes) {
$routes->get('', 'V2\Admin::index');
$routes->get('users', 'V2\Admin::users');
$routes->post('setPassword', 'V2\Users::setPassword');
// Users
$routes->get('api/users', 'V2\Users::index');
$routes->post('api/users', 'V2\Users::create');
@ -84,22 +84,21 @@ $routes->group('v2', function($routes) {
$routes->delete('api/users/(:any)', 'V2\Users::delete/$1');
// Request
$routes->get('api/requests', 'V2\Requests::index');
$routes->post('api/requests/validate/(:any)', 'V2\Requests::validate/$1');
$routes->delete('api/requests/validate/(:any)', 'V2\Requests::unvalidate/$1');
$routes->post('api/requests/validate/(:any)', 'V2\Requests::val/$1');
$routes->delete('api/requests/validate/(:any)', 'V2\Requests::unval/$1');
// Samples
$routes->post('api/samples/collect/(:any)', 'V2\Samples::collect/$1');
$routes->delete('api/samples/collect/(:any)', 'V2\Samples::uncollect/$1');
$routes->delete('api/samples/receive/(:any)', 'V2\Samples::unreceive/$1');
$routes->get('api/samples/(:any)', 'V2\Samples::show/$1');
});
// Analyst and doctor
// Lab
$routes->group('lab', ['filter' => 'role:2'], function($routes) {
$routes->get('', 'V2\Lab::index');
$routes->post('setPassword', 'V2\Lab::setPassword');
// Request
$routes->get('api/requests', 'V2\Requests::index');
$routes->post('api/requests/validate/(:any)', 'V2\Requests::validate/$1');
$routes->delete('api/requests/validate/(:any)', 'V2\Requests::unvalidate/$1');
$routes->post('api/requests/validate/(:any)', 'V2\Requests::val/$1');
$routes->delete('api/requests/validate/(:any)', 'V2\Requests::unval/$1');
// Samples
$routes->post('api/samples/collect/(:any)', 'V2\Samples::collect/$1');
$routes->get('api/samples/(:any)', 'V2\Samples::show/$1');

View File

@ -97,4 +97,16 @@ class V2 extends BaseController {
$session->destroy();
return redirect()->to('v2/login');
}
public function setPassword() {
$input = $this->request->getJSON(true);
$userid = $input['userid'];
$password = $input['password'];
$password = password_hash($password, PASSWORD_DEFAULT);
$db = db_connect();
$sql = "update GDC_CMOD.dbo.USERS set PASSWORD='$password' where USERID='$userid'";
$db->query($sql);
$data = ['status' => 'success', 'message' => 'Password updated successfully', 'data' => "$userid" ];
return $this->response->setJSON($data);
}
}

View File

@ -177,7 +177,7 @@
let param = new URLSearchParams(this.filter).toString();
// reset counters before processing
for (let k in this.counters) { this.counters[k] = 0; }
fetch(`${BASEURL}/api/requests?${param}`, {
fetch(`${BASEURL}/admin/api/requests?${param}`, {
method: 'GET',
headers: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(data => {
@ -250,7 +250,7 @@
fetchItem(accessnumber){
this.item = [];
fetch(`${BASEURL}/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}})
fetch(`${BASEURL}/admin/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}})
.then(res => res.json()).then(data => {
this.item = data.data ?? {};
if (!Array.isArray(this.item.samples)) this.item.samples = [];
@ -258,7 +258,7 @@
},
collect(sampcode, accessnumber) {
fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
})
@ -269,7 +269,7 @@
uncollect(sampcode, accessnumber) {
if(!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, {
method: 'DELETE', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
})
@ -280,7 +280,7 @@
unreceive(sampcode, accessnumber) {
if(!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/api/samples/unreceive/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/samples/unreceive/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid : '<?= session('userid'); ?>'})
})
@ -333,7 +333,7 @@
this.isDialogValOpen = false;
},
validate(accessnumber, userid) {
fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` })
@ -357,7 +357,7 @@
},
unvalidate(accessnumber, userid) {
if(!confirm(`Unvalidate request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, {
method: "DELETE",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}`, comment: this.unvalReason.trim() })

View File

@ -23,7 +23,7 @@
}
</style>
</head>
<body class="bg-base-200 min-h-screen flex flex-col">
<body class="bg-base-200 min-h-screen flex flex-col" x-data="main">
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
<div class='flex-1'>
@ -41,9 +41,9 @@
<span class="text-xl"><i class="fa fa-user"></i></span>
</div>
<ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-xl border border-base-200 mt-2">
<li class="menu-title px-4 py-2">Account</li>
<li><a class="active:bg-primary" href="<?=base_url('v2/admin') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
<li><a class="active:bg-primary" href="<?=base_url('v2/admin/users') ?>"><i class="fa fa-users mr-2"></i> Users </a></li>
<li><a @click.prevent="openDialogSetPassword()" class="active:bg-primary"><i class="fa fa-key mr-2"></i> Change Password</a></li>
<li class="divider my-1"></li>
<li><a href="<?=base_url('v2/logout')?>" class="text-error hover:bg-error/10"><i class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
</ul>
@ -52,11 +52,10 @@
</nav>
<?=$this->renderSection('content');?>
<?=$this->include('v2/dialog_setPassword');?>
<footer class='bg-base-100 p-1'>&copy; <?=date('Y');?> - 5Panda</footer>
<script>
window.BASEURL = "<?=base_url("v2/admin");?>";
window.BASEURL = "<?=base_url("v2");?>";
</script>
<?=$this->renderSection('script');?>
</body>

View File

@ -165,7 +165,7 @@
},
fetchUsers() {
fetch(`${BASEURL}/api/users`)
fetch(`${BASEURL}/admin/api/users`)
.then(res => res.json())
.then(data => {
this.list = data.data ?? [];
@ -213,13 +213,13 @@
try {
let res;
if(this.mode == 'create') {
res = await fetch(`${BASEURL}/api/users`, {
res = await fetch(`${BASEURL}/admin/api/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form)
});
} else {
res = await fetch(`${BASEURL}/api/users/${this.form.userid}`, {
res = await fetch(`${BASEURL}/admin/api/users/${this.form.userid}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form)

View File

@ -0,0 +1,35 @@
<dialog class="modal" :class="{ 'modal-open': isDialogSetPasswordOpen }">
<div class="modal-box">
<form method="dialog">
<button type="button" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" @click="closeDialogSetPassword()"></button>
</form>
<h3 class="font-bold text-lg mb-4 text-base-content"><i class="fa fa-key text-primary mr-2"></i>Change Password</h3>
<form @submit.prevent="savePassword('<?= session('userid'); ?>')">
<div class="form-control w-full mb-3">
<label class="input w-full mb-3">
<span class="label-text font-medium">Password</span>
<input type="password" placeholder="••••••••" x-model="password" />
</label>
<label class="input w-full mb-3">
<span class="label-text font-medium">Confirm</span>
<input type="password" placeholder="••••••••" x-model="confirm_password" />
</label>
<label class="label p-1 mt-1" x-show="error" x-transition>
<span class="label-text-alt text-error flex items-center gap-1"><i class="fa-solid fa-circle-exclamation"></i> <span x-text="error"></span></span>
</label>
</div>
<div class="modal-action border-t border-base-200 mt-2 pt-4">
<button type="button" class="btn btn-ghost" @click="closeDialogSetPassword()">Cancel</button>
<button type="submit" class="btn btn-primary px-6" :disabled="isLoading">
<span x-show="isLoading" class="loading loading-spinner loading-xs"></span>
Save
</button>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop">
<button type="button" @click="closeDialogSetPassword()">close</button>
</form>
</dialog>

View File

@ -177,7 +177,7 @@
let param = new URLSearchParams(this.filter).toString();
// reset counters before processing
for (let k in this.counters) { this.counters[k] = 0; }
fetch(`${BASEURL}/api/requests?${param}`, {
fetch(`${BASEURL}/admin/api/requests?${param}`, {
method: 'GET',
headers: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(data => {
@ -250,7 +250,7 @@
fetchItem(accessnumber){
this.item = [];
fetch(`${BASEURL}/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}})
fetch(`${BASEURL}/admin/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}})
.then(res => res.json()).then(data => {
this.item = data.data ?? {};
if (!Array.isArray(this.item.samples)) this.item.samples = [];
@ -258,7 +258,7 @@
},
collect(sampcode, accessnumber) {
fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
})
@ -269,7 +269,7 @@
uncollect(sampcode, accessnumber) {
if(!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, {
method: 'DELETE', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
})
@ -280,7 +280,7 @@
unreceive(sampcode, accessnumber) {
if(!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/api/samples/unreceive/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/samples/unreceive/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid : '<?= session('userid'); ?>'})
})
@ -333,7 +333,7 @@
this.isDialogValOpen = false;
},
validate(accessnumber, userid) {
fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` })
@ -357,7 +357,7 @@
},
unvalidate(accessnumber, userid) {
if(!confirm(`Unvalidate request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, {
method: "DELETE",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}`, comment: this.unvalReason.trim() })

View File

@ -41,8 +41,8 @@
<span class="text-xl"><i class="fa fa-user"></i></span>
</div>
<ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-xl border border-base-200 mt-2">
<li class="menu-title px-4 py-2">Account</li>
<li><a class="active:bg-primary" href="<?=base_url('v2/lab') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
<li><a class="active:bg-primary" href="<?=base_url('v2/setPassword') ?>"><i class="fa fa-key mr-2"></i> Set Password</a></li>
<li class="divider my-1"></li>
<li><a href="<?=base_url('v2/logout')?>" class="text-error hover:bg-error/10"><i class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
</ul>

View File

@ -5,7 +5,48 @@ Alpine.plugin(persist)
window.Alpine = Alpine
Alpine.data("main", () => ({
/*
passwordDialog
*/
isDialogSetPasswordOpen: false,
password: '',
confirm_password: '',
isLoading: false,
error: '',
openDialogSetPassword(){
this.isDialogSetPasswordOpen = true;
},
closeDialogSetPassword(){
this.isDialogSetPasswordOpen = false;
},
savePassword(userid) {
this.error = '';
if(!this.password) { this.error = "Password is required"; return; }
if(this.password !== this.confirm_password) { this.error = "Passwords do not match"; return; }
this.isLoading = true;
fetch(BASEURL + '/setPassword', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userid: userid, password: this.password })
})
.then(res => res.json())
.then(data => {
this.isLoading = false;
if(data.status === 'success') {
alert('Password changed successfully');
this.password = '';
this.confirm_password = '';
this.closeDialogSetPassword();
} else {
this.error = data.message || 'Unknown error occurred';
}
})
.catch(err => {
this.isLoading = false;
this.error = 'Network error occurred. Please try again.';
console.error(err);
});
}
}));
export default Alpine;