Fix Superuser User Management and Refactor Dashboard Layouts
- Refactor 'app/Views/superuser/users.php' to fix user creation/editing logic using Alpine.js. - Ensure efficient form state management (userid, username, password handling) in user modal. - Standardize dashboard layouts and script initialization (window.BASEURL) across 'admin', 'cs', 'lab', 'phlebo', and 'superuser' main views. - Remove redundant 'app/Views/admin/users.php' to consolidate user management.
This commit is contained in:
parent
13591da5b4
commit
02762bb355
120
CLAUDE.md
Normal file
120
CLAUDE.md
Normal file
@ -0,0 +1,120 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a **CodeIgniter 4** PHP application for laboratory management (GDC CMOD - Laboratory Request Management System). It handles specimen collection tracking, request validation, and result management with role-based access control.
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Run PHP built-in server (from project root)
|
||||
php spark serve
|
||||
|
||||
# Run tests
|
||||
composer test
|
||||
```
|
||||
|
||||
**Note:** This is a Windows-based deployment using IIS/XAMPP. For production, configure a virtual host pointing to the `public/` folder.
|
||||
|
||||
## Database Configuration
|
||||
|
||||
- **Primary DB:** SQL Server (`GDC_CMOD.dbo`) via Microsoft ODBC Driver (MSOLEDBSQL)
|
||||
- **Legacy DB:** Firebird/InterBase (`GLENEAGLES` via ODBC) for patient data
|
||||
- **Connection:** `\Config\Database::connect()` returns MySQLi connection
|
||||
- **No CI4 Models** - uses raw SQL queries via `Database::connect()`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
| Role ID | Name | Route Prefix | Permissions |
|
||||
|---------|------|--------------|-------------|
|
||||
| 0 | Superuser | `/superuser` | Full access + Users CRUD |
|
||||
| 1 | Admin | `/admin` | Full access + Users CRUD |
|
||||
| 2 | Lab | `/lab` | Request validation, Sample collection |
|
||||
| 3 | Phlebo | `/phlebo` | Request validation, Sample collection |
|
||||
| 4 | CS | `/cs` | Request validation, Sample collection |
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. `Auth::login()` - Verifies credentials against `GDC_CMOD.dbo.USERS`, sets session
|
||||
2. `RoleFilter` - Checks `session()->get('isLoggedIn')` and role ID
|
||||
3. `GuestFilter` - Redirects logged-in users to role-based dashboard
|
||||
|
||||
### Key Database Tables
|
||||
|
||||
- `GDC_CMOD.dbo.USERS` - Users with `USERID`, `USERROLEID`, `PASSWORD`
|
||||
- `GDC_CMOD.dbo.CM_REQUESTS` - Validation tracking (`ISVAL1`, `ISVAL2`, validation users/dates)
|
||||
- `GDC_CMOD.dbo.TUBES` - Sample collection status
|
||||
- `GDC_CMOD.dbo.V_DASHBOARD_DEV` - Dashboard data view
|
||||
- `glendb.dbo.*` - Legacy Firebird patient data
|
||||
|
||||
### Request Validation (Dual-Level)
|
||||
|
||||
Validation requires 2 different users to validate the same request:
|
||||
1. First validation sets `ISVAL1=1`, `VAL1USER`, `VAL1DATE`
|
||||
2. Second validation (different user) sets `ISVAL2=1`, `VAL2USER`, `VAL2DATE`
|
||||
|
||||
## Code Conventions
|
||||
|
||||
### Controllers
|
||||
- All extend `BaseController` (which extends `CodeIgniter\Controller`)
|
||||
- Use `ResponseTrait` for JSON APIs
|
||||
- Raw SQL queries via `\Config\Database::connect()->query()`
|
||||
|
||||
### Routing Pattern
|
||||
|
||||
```php
|
||||
$routes->group('prefix', ['filter' => 'role:N'], function($routes) {
|
||||
$routes->get('', 'Controller::index');
|
||||
$routes->get('api/resource', 'Controller::method');
|
||||
});
|
||||
```
|
||||
|
||||
### Session Structure
|
||||
|
||||
```php
|
||||
session()->set([
|
||||
'isLoggedIn' => true,
|
||||
'userid' => (string) $user['USERID'],
|
||||
'userroleid' => (int) $user['USERROLEID'],
|
||||
'userrole' => (string) $role,
|
||||
]);
|
||||
```
|
||||
|
||||
## Important Routes
|
||||
|
||||
| Route | Purpose |
|
||||
|-------|---------|
|
||||
| `/login`, `/logout` | Authentication |
|
||||
| `/label/coll/:accessnumber` | Zebra printer label (public) |
|
||||
| `/api/requests` | Dashboard data (date-filtered) |
|
||||
| `/api/requests/validate/:accessnumber` | Dual-level validation |
|
||||
| `/api/samples/collect/:accessnumber` | Mark sample collected |
|
||||
| `/api/samples/receive/:accessnumber` | Mark sample received (Admin/Superuser only) |
|
||||
|
||||
## Frontend Stack
|
||||
|
||||
- TailwindCSS + DaisyUI 5 (CDN)
|
||||
- Alpine.js for reactivity
|
||||
- Font Awesome 7 for icons
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### JSON API Response
|
||||
```php
|
||||
return $this->response->setJSON(['status' => 'success', 'data' => $result]);
|
||||
```
|
||||
|
||||
### Database Query
|
||||
```php
|
||||
$db = \Config\Database::connect();
|
||||
$result = $db->query("SELECT * FROM table WHERE col = ?", [$value])->getResultArray();
|
||||
```
|
||||
|
||||
### Date Formatting from SQL Server
|
||||
```php
|
||||
$row['DATE_COLUMN'] = date('Y-m-d H:i', strtotime($row['DATE_COLUMN']));
|
||||
```
|
||||
@ -77,3 +77,18 @@ defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7); // invalid u
|
||||
defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database error
|
||||
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
|
||||
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Role Names
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Mapping of role IDs to role names for the application.
|
||||
*/
|
||||
defined('ROLE_NAMES') || define('ROLE_NAMES', [
|
||||
0 => 'Superuser',
|
||||
1 => 'Admin',
|
||||
2 => 'Lab Analyst',
|
||||
3 => 'Phlebotomist',
|
||||
4 => 'Customer Service',
|
||||
]);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use CodeIgniter\Router\RouteCollection;
|
||||
|
||||
$routes->set404Override(function() {
|
||||
$routes->set404Override(function () {
|
||||
$response = service('response');
|
||||
$response->setStatusCode(404);
|
||||
echo view('errors/notfound');
|
||||
@ -11,72 +11,74 @@ $routes->get('/unauthorized', 'ErrorPage::unauthorized');
|
||||
|
||||
|
||||
$routes->get('/', 'Home::index');
|
||||
$routes->get('/login', 'Auth::loginPage', ['filter' => 'guest']);
|
||||
$routes->post('/login', 'Auth::login', ['filter' => 'guest']);
|
||||
$routes->get('/logout', 'Auth::logout');
|
||||
$routes->patch('/setPassword', 'Auth::setPassword');
|
||||
$routes->get('/login', 'AuthController::loginPage', ['filter' => 'guest']);
|
||||
$routes->post('/login', 'AuthController::login', ['filter' => 'guest']);
|
||||
$routes->get('/logout', 'AuthController::logout');
|
||||
$routes->patch('/setPassword', 'AuthController::setPassword');
|
||||
|
||||
$routes->get('label/coll/(:any)', 'Label::coll/$1');
|
||||
$routes->get('label/dispatch/(:any)/(:any)', 'Label::dispatch/$1/$2');
|
||||
$routes->get('label/all/(:any)', 'Label::print_all/$1');
|
||||
$routes->get('label/coll/(:any)', 'LabelController::coll/$1');
|
||||
$routes->get('label/dispatch/(:any)/(:any)', 'LabelController::dispatch/$1/$2');
|
||||
$routes->get('label/all/(:any)', 'LabelController::print_all/$1');
|
||||
|
||||
|
||||
// --- API Group ---
|
||||
$routes->group('api', function ($routes) {
|
||||
|
||||
// Users Management - Only Superuser (0) and Admin (1)
|
||||
$routes->group('users', ['filter' => 'role:0,1'], function ($routes) {
|
||||
$routes->get('', 'UsersController::index');
|
||||
$routes->post('', 'UsersController::create');
|
||||
$routes->patch('(:any)', 'UsersController::update/$1');
|
||||
$routes->delete('(:any)', 'UsersController::delete/$1');
|
||||
});
|
||||
|
||||
// Requests - All Roles (0,1,2,3,4)
|
||||
$routes->group('requests', ['filter' => 'role:0,1,2,3,4'], function ($routes) {
|
||||
$routes->get('', 'RequestsController::index');
|
||||
$routes->post('validate/(:any)', 'RequestsController::val/$1');
|
||||
$routes->delete('validate/(:any)', 'RequestsController::unval/$1');
|
||||
});
|
||||
|
||||
// Samples
|
||||
$routes->group('samples', function ($routes) {
|
||||
// Collect & Show - All Roles
|
||||
$routes->group('', ['filter' => 'role:0,1,2,3,4'], function ($routes) {
|
||||
$routes->post('collect/(:any)', 'SamplesController::collect/$1');
|
||||
$routes->get('(:any)', 'SamplesController::show/$1');
|
||||
});
|
||||
|
||||
// Uncollect & Unreceive - Only Superuser (0) and Admin (1)
|
||||
$routes->group('', ['filter' => 'role:0,1'], function ($routes) {
|
||||
$routes->delete('collect/(:any)', 'SamplesController::uncollect/$1');
|
||||
$routes->delete('receive/(:any)', 'SamplesController::unreceive/$1');
|
||||
});
|
||||
});
|
||||
|
||||
$routes->group('superuser', ['filter' => 'role:0'], function($routes) {
|
||||
$routes->get('', 'Superuser::index');
|
||||
$routes->get('users', 'Superuser::users');
|
||||
$routes->get('api/users', 'Users::index');
|
||||
$routes->post('api/users', 'Users::create');
|
||||
$routes->patch('api/users/(:any)', 'Users::update/$1');
|
||||
$routes->delete('api/users/(:any)', 'Users::delete/$1');
|
||||
$routes->get('api/requests', 'Requests::index');
|
||||
$routes->post('api/requests/validate/(:any)', 'Requests::val/$1');
|
||||
$routes->delete('api/requests/validate/(:any)', 'Requests::unval/$1');
|
||||
$routes->post('api/samples/collect/(:any)', 'Samples::collect/$1');
|
||||
$routes->delete('api/samples/collect/(:any)', 'Samples::uncollect/$1');
|
||||
$routes->delete('api/samples/receive/(:any)', 'Samples::unreceive/$1');
|
||||
$routes->get('api/samples/(:any)', 'Samples::show/$1');
|
||||
});
|
||||
|
||||
$routes->group('admin', ['filter' => 'role:1'], function($routes) {
|
||||
$routes->get('', 'Admin::index');
|
||||
$routes->get('users', 'Admin::users');
|
||||
$routes->get('api/users', 'Users::index');
|
||||
$routes->post('api/users', 'Users::create');
|
||||
$routes->patch('api/users/(:any)', 'Users::update/$1');
|
||||
$routes->delete('api/users/(:any)', 'Users::delete/$1');
|
||||
$routes->get('api/requests', 'Requests::index');
|
||||
$routes->post('api/requests/validate/(:any)', 'Requests::val/$1');
|
||||
$routes->delete('api/requests/validate/(:any)', 'Requests::unval/$1');
|
||||
$routes->post('api/samples/collect/(:any)', 'Samples::collect/$1');
|
||||
$routes->delete('api/samples/collect/(:any)', 'Samples::uncollect/$1');
|
||||
$routes->delete('api/samples/receive/(:any)', 'Samples::unreceive/$1');
|
||||
$routes->get('api/samples/(:any)', 'Samples::show/$1');
|
||||
|
||||
// --- Page Routes ---
|
||||
|
||||
$routes->group('superuser', ['filter' => 'role:0'], function ($routes) {
|
||||
$routes->get('', 'Pages\SuperuserController::index');
|
||||
$routes->get('users', 'Pages\SuperuserController::users');
|
||||
});
|
||||
|
||||
$routes->group('lab', ['filter' => 'role:2'], function($routes) {
|
||||
$routes->get('', 'Lab::index');
|
||||
$routes->get('api/requests', 'Requests::index');
|
||||
$routes->post('api/requests/validate/(:any)', 'Requests::val/$1');
|
||||
$routes->delete('api/requests/validate/(:any)', 'Requests::unval/$1');
|
||||
$routes->post('api/samples/collect/(:any)', 'Samples::collect/$1');
|
||||
$routes->get('api/samples/(:any)', 'Samples::show/$1');
|
||||
$routes->group('admin', ['filter' => 'role:1'], function ($routes) {
|
||||
$routes->get('', 'Pages\AdminController::index');
|
||||
$routes->get('users', 'Pages\AdminController::users');
|
||||
});
|
||||
|
||||
$routes->group('phlebo', ['filter' => 'role:3'], function($routes) {
|
||||
$routes->get('', 'Phlebotomist::index');
|
||||
$routes->get('api/requests', 'Requests::index');
|
||||
$routes->post('api/requests/validate/(:any)', 'Requests::val/$1');
|
||||
$routes->delete('api/requests/validate/(:any)', 'Requests::unval/$1');
|
||||
$routes->post('api/samples/collect/(:any)', 'Samples::collect/$1');
|
||||
$routes->get('api/samples/(:any)', 'Samples::show/$1');
|
||||
$routes->group('lab', ['filter' => 'role:2'], function ($routes) {
|
||||
$routes->get('', 'Pages\LabController::index');
|
||||
});
|
||||
|
||||
$routes->group('cs', ['filter' => 'role:4'], function($routes) {
|
||||
$routes->get('', 'Cs::index');
|
||||
$routes->get('api/requests', 'Requests::index');
|
||||
$routes->post('api/requests/validate/(:any)', 'Requests::val/$1');
|
||||
$routes->delete('api/requests/validate/(:any)', 'Requests::unval/$1');
|
||||
$routes->post('api/samples/collect/(:any)', 'Samples::collect/$1');
|
||||
$routes->get('api/samples/(:any)', 'Samples::show/$1');
|
||||
$routes->group('phlebo', ['filter' => 'role:3'], function ($routes) {
|
||||
$routes->get('', 'Pages\PhlebotomistController::index');
|
||||
});
|
||||
|
||||
$routes->group('cs', ['filter' => 'role:4'], function ($routes) {
|
||||
$routes->get('', 'Pages\CsController::index');
|
||||
});
|
||||
|
||||
$routes->get('/dummypage', 'Home::dummyPage');
|
||||
|
||||
@ -4,13 +4,16 @@ namespace App\Controllers;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Auth extends BaseController {
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
|
||||
public function loginPage() {
|
||||
public function loginPage()
|
||||
{
|
||||
return view("login");
|
||||
}
|
||||
|
||||
public function login() {
|
||||
public function login()
|
||||
{
|
||||
helper(['form', 'url']);
|
||||
$session = session();
|
||||
$db = \Config\Database::connect();
|
||||
@ -23,35 +26,17 @@ class Auth extends BaseController {
|
||||
|
||||
if ($user && !empty($user['PASSWORD']) && password_verify($password, $user['PASSWORD'])) {
|
||||
|
||||
switch ((int)$user['USERROLEID']) {
|
||||
case 0:
|
||||
$role = 'Superuser';
|
||||
break;
|
||||
case 1:
|
||||
$role = 'Admin';
|
||||
break;
|
||||
case 2:
|
||||
$role = 'Lab Analyst';
|
||||
break;
|
||||
case 3:
|
||||
$role = 'Phlebotomist';
|
||||
break;
|
||||
case 4:
|
||||
$role = 'Customer Service';
|
||||
break;
|
||||
default:
|
||||
$role = '';
|
||||
break;
|
||||
}
|
||||
$roleId = (int) $user['USERROLEID'];
|
||||
$role = ROLE_NAMES[$roleId] ?? '';
|
||||
|
||||
$session->set([
|
||||
'isLoggedIn' => true,
|
||||
'userid' => (string) $user['USERID'],
|
||||
'userid' => (string) $user['USERID'],
|
||||
'userroleid' => (int) $user['USERROLEID'],
|
||||
'userrole' => (string) $role,
|
||||
'userrole' => (string) $role,
|
||||
]);
|
||||
|
||||
switch ((int)$user['USERROLEID']) {
|
||||
switch ((int) $user['USERROLEID']) {
|
||||
case 0:
|
||||
return redirect()->to('superuser');
|
||||
case 1:
|
||||
@ -71,13 +56,15 @@ class Auth extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
public function logout() {
|
||||
public function logout()
|
||||
{
|
||||
$session = session();
|
||||
$session->destroy();
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
public function setPassword() {
|
||||
public function setPassword()
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
$password = $input['password'];
|
||||
@ -85,7 +72,7 @@ class Auth extends BaseController {
|
||||
$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);
|
||||
$data = ['status' => 'success', 'message' => 'Password updated successfully', 'data' => "$userid"];
|
||||
return $this->response->setJSON($data);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
class Label extends BaseController {
|
||||
public function coll($reqnum) {
|
||||
class LabelController extends BaseController
|
||||
{
|
||||
public function coll($reqnum)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
//$reqnum = str_pad($reqnum, 10, 0, STR_PAD_LEFT);
|
||||
$sql = "select p.PATNUMBER,
|
||||
@ -34,7 +36,7 @@ class Label extends BaseController {
|
||||
//print_r($rows);
|
||||
$row = $rows[0];
|
||||
$patnum = $row['PATNUMBER'];
|
||||
$patnum = substr($patnum,14);
|
||||
$patnum = substr($patnum, 14);
|
||||
//$patnum = str_pad(substr($row[0],5),17," ");
|
||||
$patname = $row['Name'];
|
||||
$dob = $row['dob'];
|
||||
@ -58,14 +60,15 @@ A10,195,0,1,1,1,N,\"HIS : $hospnum\"
|
||||
A190,190,0,2,1,1,N,\"$date\"
|
||||
P1\n]";
|
||||
|
||||
$handle = fopen("./file.txt","a+");
|
||||
fwrite($handle,$bar);
|
||||
$handle = fopen("./file.txt", "a+");
|
||||
fwrite($handle, $bar);
|
||||
fclose($handle);
|
||||
/*exec($command);*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function dispatch($reqnum, $samid) {
|
||||
public function dispatch($reqnum, $samid)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$sql = "select p.PATNUMBER,
|
||||
[Name] = case
|
||||
@ -92,11 +95,11 @@ P1\n]";
|
||||
left join PATIENTS p on spr.PATID=p.PATID
|
||||
left join DICT_TEXTS tx on tx.TEXTID=p.TITLEID
|
||||
where spr.SP_ACCESSNUMBER='$reqnum'";
|
||||
|
||||
|
||||
$rows = $db->query($sql)->getResultArray();
|
||||
$row = $rows[0];
|
||||
$patnum = $row['PATNUMBER'];
|
||||
$patnum = substr($patnum,14);
|
||||
$patnum = substr($patnum, 14);
|
||||
$patname = $row['Name'];
|
||||
$age = $row['age'];
|
||||
$sex = $row['Gender'];
|
||||
@ -109,11 +112,13 @@ P1\n]";
|
||||
$samptext = $row['SHORTTEXT'];
|
||||
$tests = $row['TESTS'];
|
||||
$tests1 = $row['TESTS1'];
|
||||
if($tests == '') {$tests = $tests1;}
|
||||
$tubeid = $sampcode.substr("$reqnum",5,5);
|
||||
if ($tests == '') {
|
||||
$tests = $tests1;
|
||||
}
|
||||
$tubeid = $sampcode . substr("$reqnum", 5, 5);
|
||||
$date = date("d/M/Y H:i");
|
||||
|
||||
$bar = "[
|
||||
$bar = "[
|
||||
N
|
||||
OD
|
||||
q400
|
||||
@ -132,19 +137,20 @@ A190,190,0,2,1,1,N,\"$date\"
|
||||
P1
|
||||
]";
|
||||
|
||||
$handle = fopen("./file.txt","a+");
|
||||
fwrite($handle,$bar);
|
||||
$handle = fopen("./file.txt", "a+");
|
||||
fwrite($handle, $bar);
|
||||
fclose($handle);
|
||||
//exec($command);
|
||||
|
||||
}
|
||||
|
||||
public function print_all($accessnumber) {
|
||||
public function print_all($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$this->coll($accessnumber);
|
||||
$sql = "select SAMPCODE from GDC_CMOD.dbo.v_sp_reqtube where SP_ACCESSNUMBER='$accessnumber'";
|
||||
$rows = $db->query($sql)->getResultArray();
|
||||
foreach($rows as $row) {
|
||||
foreach ($rows as $row) {
|
||||
$sampcode = $row['SAMPCODE'];
|
||||
$this->dispatch($accessnumber, $sampcode);
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Pages;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Admin extends BaseController {
|
||||
class AdminController extends BaseController {
|
||||
|
||||
public function __construct() {
|
||||
helper(['url', 'form', 'text']);
|
||||
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Pages;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Cs extends BaseController {
|
||||
|
||||
class CsController extends BaseController {
|
||||
|
||||
public function __construct() {
|
||||
helper(['url', 'form', 'text']);
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Pages;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Lab extends BaseController {
|
||||
|
||||
class LabController extends BaseController {
|
||||
|
||||
public function __construct() {
|
||||
helper(['url', 'form', 'text']);
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Pages;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Phlebotomist extends BaseController {
|
||||
class PhlebotomistController extends BaseController {
|
||||
|
||||
public function __construct() {
|
||||
helper(['url', 'form', 'text']);
|
||||
@ -13,5 +13,5 @@ class Phlebotomist extends BaseController {
|
||||
public function index() {
|
||||
return view('phlebo/index');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
namespace App\Controllers\Pages;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Superuser extends BaseController {
|
||||
class SuperuserController extends BaseController {
|
||||
|
||||
public function __construct() {
|
||||
helper(['url', 'form', 'text']);
|
||||
@ -3,32 +3,35 @@ namespace App\Controllers;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Requests extends BaseController {
|
||||
class RequestsController extends BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
public function index() {
|
||||
$db = \Config\Database::connect();
|
||||
$date1 = $this->request->getGet('date1');
|
||||
$date2 = $this->request->getGet('date2');
|
||||
|
||||
$sql = "SELECT * from GDC_CMOD.dbo.V_DASHBOARD_DEV where
|
||||
COLLECTIONDATE between '$date1 00:00' and '$date2 23:59'
|
||||
public function index()
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$date1 = $this->request->getGet('date1');
|
||||
$date2 = $this->request->getGet('date2');
|
||||
|
||||
$sql = "SELECT * from GDC_CMOD.dbo.V_DASHBOARD_DEV where
|
||||
COLLECTIONDATE between '$date1 00:00' and '$date2 23:59'
|
||||
and ODR_DDATE between '$date1 00:00' and '$date2 23:59'";
|
||||
$rows = $db->query($sql)->getResultArray();
|
||||
$rows = $db->query($sql)->getResultArray();
|
||||
foreach ($rows as &$row) {
|
||||
$row['COLLECTIONDATE'] = date('Y-m-d H:i', strtotime($row['COLLECTIONDATE']));
|
||||
$row['ODR_DDATE'] = date('Y-m-d H:i', strtotime($row['ODR_DDATE']));
|
||||
$row['REQDATE'] = date('Y-m-d H:i', strtotime($row['REQDATE']));
|
||||
}
|
||||
$data['data'] = $rows;
|
||||
$data['data'] = $rows;
|
||||
return $this->response->setJSON($data);
|
||||
}
|
||||
|
||||
|
||||
public function show($accessnumber) {
|
||||
public function show($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$data['accessnumber'] = $accessnumber;
|
||||
$sql = "SELECT d.STATS, r.* FROM GDC_CMOD.dbo.V_DASHBOARD_DEV d
|
||||
$sql = "SELECT d.STATS, r.* FROM GDC_CMOD.dbo.V_DASHBOARD_DEV d
|
||||
left join GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER=d.SP_ACCESSNUMBER
|
||||
WHERE d.SP_ACCESSNUMBER='$accessnumber'";
|
||||
$result = $db
|
||||
@ -38,62 +41,68 @@ class Requests extends BaseController {
|
||||
$data['val1user'] = $result[0]['VAL1USER'];
|
||||
$data['val2'] = $result[0]['ISVAL2'];
|
||||
$data['val2user'] = $result[0]['VAL2USER'];
|
||||
return view('admin/modal_request',$data);
|
||||
return view('admin/modal_request', $data);
|
||||
}
|
||||
|
||||
public function showUnval($accessnumber) {
|
||||
public function showUnval($accessnumber)
|
||||
{
|
||||
$data['accessnumber'] = $accessnumber;
|
||||
return view('admin/modal_unvalidate',$data);
|
||||
return view('admin/modal_unvalidate', $data);
|
||||
}
|
||||
|
||||
public function unval($accessnumber) {
|
||||
public function unval($accessnumber)
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
// Securely get userid from session
|
||||
$userid = session('userid');
|
||||
$comment = $input['comment'];
|
||||
$db = \Config\Database::connect();
|
||||
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=null, VAL1USER=null, VAL1DATE=null, ISVAL2=null, VAL2USER=null, VAL2DATE=null,
|
||||
ISPENDING=1, PENDINGTEXT='$comment', PENDINGUSER='$userid', PENDINGDATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
|
||||
$db->query($sql);
|
||||
$data = ['status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber" ];
|
||||
$data = ['status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber"];
|
||||
|
||||
return $this->response->setJSON($data);
|
||||
}
|
||||
|
||||
public function val($accessnumber) {
|
||||
public function val($accessnumber)
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
// Securely get userid from session
|
||||
$userid = session('userid');
|
||||
$db = \Config\Database::connect();
|
||||
$sql = "select * from GDC_CMOD.dbo.CM_REQUESTS where ACCESSNUMBER='$accessnumber'";
|
||||
$result = $db->query($sql)->getResultArray();
|
||||
if(!isset($result[0])) {
|
||||
if (!isset($result[0])) {
|
||||
$sql = "insert into GDC_CMOD.dbo.CM_REQUESTS(ACCESSNUMBER, ISVAL1, VAL1USER, VAL1DATE) VALUES ('$accessnumber', 1, '$userid', GETDATE())";
|
||||
$db->query($sql);
|
||||
$data['val'] = 1;
|
||||
$data['userid'] = $userid;
|
||||
$data['val'] = 1;
|
||||
$data['userid'] = $userid;
|
||||
} else {
|
||||
$row = $result[0];
|
||||
$isval1 = $row['ISVAL1'];
|
||||
$isval2 = $row['ISVAL2'];
|
||||
$val1user = $row['VAL1USER'];
|
||||
if( $isval1 == 1 ) {
|
||||
if ( $isval2 == 1 ) { return $this->response->setJSON(['message'=> 'validation done, not updating anything']); }
|
||||
else {
|
||||
if($val1user != $userid) {
|
||||
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
|
||||
$data['val'] = 2;
|
||||
$data['userid'] = $userid;
|
||||
} else {
|
||||
if ($isval1 == 1) {
|
||||
if ($isval2 == 1) {
|
||||
return $this->response->setJSON(['message' => 'validation done, not updating anything']);
|
||||
} else {
|
||||
if ($val1user != $userid) {
|
||||
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
|
||||
$data['val'] = 2;
|
||||
$data['userid'] = $userid;
|
||||
} else {
|
||||
$this->response->setStatusCode(500);
|
||||
return $this->response->setJSON([ 'message'=> 'user already validate this request' ]);
|
||||
return $this->response->setJSON(['message' => 'user already validate this request']);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=1, VAL1USER='$userid', VAL1DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
|
||||
$data['val'] = 1;
|
||||
$data['userid'] = $userid;
|
||||
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=1, VAL1USER='$userid', VAL1DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
|
||||
$data['val'] = 1;
|
||||
$data['userid'] = $userid;
|
||||
}
|
||||
$db->query($sql);
|
||||
}
|
||||
$db->query($sql);
|
||||
}
|
||||
|
||||
return $this->response->setJSON($data);
|
||||
}
|
||||
@ -3,13 +3,15 @@ namespace App\Controllers;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
class Samples extends BaseController {
|
||||
class SamplesController extends BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
public function show($accessnumber) {
|
||||
$db = \Config\Database::connect();
|
||||
public function show($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
$sql = "SELECT right(p.PATNUMBER,16) as [patnumber], ISNULL(p.FIRSTNAME,'') + ' ' + ISNULL(p.NAME,'') as [Name],
|
||||
$sql = "SELECT right(p.PATNUMBER,16) as [patnumber], ISNULL(p.FIRSTNAME,'') + ' ' + ISNULL(p.NAME,'') as [Name],
|
||||
case when format(p.BIRTHDATE,'MMdd')=format(spr.COLLECTIONDATE,'MMdd') then DATEDIFF(YEAR,p.BIRTHDATE, spr.COLLECTIONDATE)
|
||||
else FLOOR(DATEDIFF(DAY, p.BIRTHDATE, spr.COLLECTIONDATE) / 365.25) end ,
|
||||
[Gender] = case
|
||||
@ -17,108 +19,113 @@ class Samples extends BaseController {
|
||||
when p.SEX = 2 then 'F'
|
||||
else ''
|
||||
end, spr.REQDATE, spo.COMMENTTEXT, dmg.DMG_CKTPNO, dmg.DMG_CPLACEOFBIRTH
|
||||
from SP_REQUESTS spr
|
||||
from SP_REQUESTS spr
|
||||
left join PATIENTS p on p.PATID=spr.PATID
|
||||
left join SP_REQUESTS_OCOM spo on spr.SP_ACCESSNUMBER=spo.SP_ACCESSNUMBER
|
||||
left join GDC_CMOD.dbo.TDL_DEMOGRAPHIC dmg on right(dmg.DMG_CPATNUMBER,15)=right(p.PATNUMBER,15)
|
||||
where spr.PATID=p.PATID and spr.SP_ACCESSNUMBER='$accessnumber'";
|
||||
$query = $db->query($sql);
|
||||
$results = $query->getRowArray();
|
||||
$data = [
|
||||
'patnumber' => $results["patnumber"],
|
||||
'age' => $results[""],
|
||||
'patname' => $results['Name'] ?? '',
|
||||
'reqdate' => $results['REQDATE'] ?? '',
|
||||
'gender' => $results['Gender'] ?? '',
|
||||
'placeofbirth' => $results['DMG_CPLACEOFBIRTH'] ?? '',
|
||||
'ktp' => $results['DMG_CKTPNO'] ?? '',
|
||||
'comment' => $results['COMMENTTEXT'] ?? '',
|
||||
'accessnumber' => $accessnumber,
|
||||
];
|
||||
$query = $db->query($sql);
|
||||
$results = $query->getRowArray();
|
||||
$data = [
|
||||
'patnumber' => $results["patnumber"],
|
||||
'age' => $results[""],
|
||||
'patname' => $results['Name'] ?? '',
|
||||
'reqdate' => $results['REQDATE'] ?? '',
|
||||
'gender' => $results['Gender'] ?? '',
|
||||
'placeofbirth' => $results['DMG_CPLACEOFBIRTH'] ?? '',
|
||||
'ktp' => $results['DMG_CKTPNO'] ?? '',
|
||||
'comment' => $results['COMMENTTEXT'] ?? '',
|
||||
'accessnumber' => $accessnumber,
|
||||
];
|
||||
|
||||
$samples = [];
|
||||
$sql = "SELECT req.SAMPTYPEID, req.SAMPCODE, req.SHORTTEXT, tu.STATUS, st.TUBESTATUS
|
||||
$samples = [];
|
||||
$sql = "SELECT req.SAMPTYPEID, req.SAMPCODE, req.SHORTTEXT, tu.STATUS, st.TUBESTATUS
|
||||
from GDC_CMOD.dbo.v_sp_reqtube req
|
||||
left join GDC_CMOD.dbo.TUBES tu on req.SP_ACCESSNUMBER=tu.ACCESSNUMBER and req.SAMPCODE=tu.TUBENUMBER
|
||||
left join glendb.dbo.SP_TUBES st on st.SP_ACCESSNUMBER=req.SP_ACCESSNUMBER and req.SAMPCODE=st.SAMPLETYPE
|
||||
where req.SP_ACCESSNUMBER='$accessnumber'";
|
||||
$query = $db->query($sql);
|
||||
$results = $query->getResultArray();
|
||||
foreach ($results as $row) {
|
||||
$samples[] = [
|
||||
'samptypeid' => $row['SAMPTYPEID'] ?? null,
|
||||
'sampcode' => $row['SAMPCODE'] ?? null,
|
||||
'name' => $row['SHORTTEXT'] ?? '',
|
||||
'colstatus' => $row['STATUS'] ?? '',
|
||||
'tubestatus' => $row['TUBESTATUS'] ?? '',
|
||||
];
|
||||
}
|
||||
$data['samples'] = $samples;
|
||||
$query = $db->query($sql);
|
||||
$results = $query->getResultArray();
|
||||
foreach ($results as $row) {
|
||||
$samples[] = [
|
||||
'samptypeid' => $row['SAMPTYPEID'] ?? null,
|
||||
'sampcode' => $row['SAMPCODE'] ?? null,
|
||||
'name' => $row['SHORTTEXT'] ?? '',
|
||||
'colstatus' => $row['STATUS'] ?? '',
|
||||
'tubestatus' => $row['TUBESTATUS'] ?? '',
|
||||
];
|
||||
}
|
||||
$data['samples'] = $samples;
|
||||
|
||||
$resp = [ 'data' => $data ];
|
||||
$resp = ['data' => $data];
|
||||
|
||||
return $this->response->setJSON($resp);
|
||||
return $this->response->setJSON($resp);
|
||||
}
|
||||
|
||||
public function collect($accessnumber) {
|
||||
|
||||
public function collect($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$input = $this->request->getJSON(true);
|
||||
$samplenumber = $input['samplenumber'];
|
||||
$userid = $input['userid'];
|
||||
$userid = session('userid');
|
||||
$sql = "update GDC_CMOD.dbo.TUBES set USERID='$userid',STATUS='1', COLLECTIONDATE=getdate() where ACCESSNUMBER='$accessnumber' and TUBENUMBER='$samplenumber'";
|
||||
$db->query($sql);
|
||||
$sql = "INSERT INTO GDC_CMOD.dbo.AUDIT_TUBES(ACCESSNUMBER, TUBENUMBER, USERID, STATUS, LOGDATE)
|
||||
$sql = "INSERT INTO GDC_CMOD.dbo.AUDIT_TUBES(ACCESSNUMBER, TUBENUMBER, USERID, STATUS, LOGDATE)
|
||||
VALUES ('$accessnumber', '$samplenumber', '$userid', '1', getdate())";
|
||||
$db->query($sql);
|
||||
return $this->respondCreated([ 'status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber-$samplenumber" ], 201);
|
||||
return $this->respondCreated(['status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber-$samplenumber"], 201);
|
||||
}
|
||||
|
||||
public function uncollect($accessnumber) {
|
||||
public function uncollect($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$input = $this->request->getJSON(true);
|
||||
$samplenumber = $input['samplenumber'];
|
||||
$userid = $input['userid'];
|
||||
$userid = session('userid');
|
||||
$sql = "update GDC_CMOD.dbo.TUBES set USERID='$userid',STATUS='0', COLLECTIONDATE=getdate() where ACCESSNUMBER='$accessnumber' and TUBENUMBER='$samplenumber'";
|
||||
$db->query($sql);
|
||||
$sql = "INSERT INTO GDC_CMOD.dbo.AUDIT_TUBES(ACCESSNUMBER, TUBENUMBER, USERID, STATUS, LOGDATE)
|
||||
$sql = "INSERT INTO GDC_CMOD.dbo.AUDIT_TUBES(ACCESSNUMBER, TUBENUMBER, USERID, STATUS, LOGDATE)
|
||||
VALUES ('$accessnumber', '$samplenumber', '$userid', '0', getdate())";
|
||||
$db->query($sql);
|
||||
return $this->respondCreated([ 'status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber-$samplenumber" ], 201);
|
||||
return $this->respondCreated(['status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber-$samplenumber"], 201);
|
||||
}
|
||||
|
||||
public function unreceive($accessnumber) {
|
||||
public function unreceive($accessnumber)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$input = $this->request->getJSON(true);
|
||||
$samplenumber = $input['samplenumber'];
|
||||
$sql = "select r.EXTERNALORDERNUMBER, dt.TESTCODE, do.HISCODE from glendb.dbo.TESTS t
|
||||
left join glendb.dbo.DICT_TESTS dt on dt.TESTID=t.TESTID
|
||||
left join glendb.dbo.REQUESTS r on r.REQUESTID=t.REQUESTID
|
||||
left join glendb.dbo.DICT_TEST_SAMPLES dts on dts.TESTID=t.TESTID
|
||||
left join glendb.dbo.DICT_SAMPLES_TYPES ds on ds.SAMPTYPEID=dts.SAMPTYPEID
|
||||
left join GDC_CMOD.dbo.DICT_TESTS_ORDER do on do.TESTCODE=dt.TESTCODE
|
||||
where t.DEPTH=0
|
||||
$sql = "select r.EXTERNALORDERNUMBER, dt.TESTCODE, do.HISCODE from glendb.dbo.TESTS t
|
||||
left join glendb.dbo.DICT_TESTS dt on dt.TESTID=t.TESTID
|
||||
left join glendb.dbo.REQUESTS r on r.REQUESTID=t.REQUESTID
|
||||
left join glendb.dbo.DICT_TEST_SAMPLES dts on dts.TESTID=t.TESTID
|
||||
left join glendb.dbo.DICT_SAMPLES_TYPES ds on ds.SAMPTYPEID=dts.SAMPTYPEID
|
||||
left join GDC_CMOD.dbo.DICT_TESTS_ORDER do on do.TESTCODE=dt.TESTCODE
|
||||
where t.DEPTH=0
|
||||
and r.ACCESSNUMBER='$accessnumber' and ds.SAMPCODE='$samplenumber'";
|
||||
$rows = $db->query($sql)->getResultArray();
|
||||
$his_test = '';
|
||||
$lis_test = '';
|
||||
foreach( $rows as $row ) {
|
||||
foreach ($rows as $row) {
|
||||
$hon = $row['EXTERNALORDERNUMBER'];
|
||||
$testcode = $row['TESTCODE'];
|
||||
$hiscode = $row['HISCODE'];
|
||||
$his_test .= "'$hiscode',";
|
||||
$lis_test .= "'$testcode',";
|
||||
}
|
||||
$his_test = rtrim($his_test,',');
|
||||
$lis_test = rtrim($lis_test,',');
|
||||
$conn = odbc_connect('GLENEAGLES','','');
|
||||
$his_test = rtrim($his_test, ',');
|
||||
$lis_test = rtrim($lis_test, ',');
|
||||
$conn = odbc_connect('GLENEAGLES', '', '');
|
||||
$sql = "UPDATE TDL_ORDERDT SET ODD_NRECEIVED=NULL , ODD_DTRECEIVE=NULL WHERE ODR_CNOLAB='$hon' and ODD_CPRODUCTCODE IN ($his_test)";
|
||||
$rs = odbc_exec($conn,$sql);
|
||||
if (!$rs) {exit("Error in Update FB");}
|
||||
$rs = odbc_exec($conn, $sql);
|
||||
if (!$rs) {
|
||||
exit("Error in Update FB");
|
||||
}
|
||||
|
||||
$sql = "update SP_TUBES set TUBESTATUS=0 where SP_ACCESSNUMBER='$accessnumber' and SAMPLETYPE='$samplenumber' ";
|
||||
$db->query($sql);
|
||||
$sql = "update SP_TESTS set SP_TESTSTATUS=NULL where SP_ACCESSNUMBER='$accessnumber' and SP_TESTCODE in ($lis_test)";
|
||||
$db->query($sql);
|
||||
return $this->respondCreated([ 'status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber-$samplenumber" ], 201);
|
||||
return $this->respondCreated(['status' => 'success', 'message' => 'Data updated successfully', 'data' => "$accessnumber-$samplenumber"], 201);
|
||||
}
|
||||
}
|
||||
@ -1,115 +0,0 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
// Users Management
|
||||
class Users extends BaseController {
|
||||
use ResponseTrait;
|
||||
protected $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
public function index() {
|
||||
$sql = "select u.USERID, u.USERLEVEL from GDC_CMOD.dbo.USERS u
|
||||
left join glendb.dbo.USERS u1 on u1.USERID=u.USERID
|
||||
where u1.LOCKEDACCOUNT is null";
|
||||
$query = $this->db->query($sql);
|
||||
$results = $query->getResultArray();
|
||||
$data['data'] = $results;
|
||||
|
||||
return $this->respond(['data' => $results]);
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
$userlevel = $input['userlevel'];
|
||||
$password = $input['password'];
|
||||
$password_2 = $input['password_2'];
|
||||
|
||||
if ($password != $password_2) {
|
||||
return $this->response->setJSON(['message'=> 'Password not the same']);
|
||||
}
|
||||
if ( strlen($password) < 3 ) {
|
||||
return $this->response->setJSON(['message'=> 'Password must be more than 2 characters']);
|
||||
}
|
||||
|
||||
$sql = $this->db->query("SELECT USERID FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]);
|
||||
$query = $sql->getRowArray();
|
||||
if ($query != null) {
|
||||
return $this->response->setJSON(['message'=> 'Userid already exists']);
|
||||
}
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
$sqlInsert = "
|
||||
INSERT INTO gdc_cmod.dbo.USERS (USERID, USERLEVEL, PASSWORD)
|
||||
VALUES (?, ?, ?)
|
||||
";
|
||||
$this->db->query($sqlInsert, [$userid, $userlevel, $hashedPassword]);
|
||||
|
||||
$this->db->transCommit();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setJSON(['message'=> 'Server error']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['message'=> 'User '.$userid.' Berhasil ditambahkan!']);
|
||||
}
|
||||
|
||||
public function update($id = null) {
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
$userlevel = $input['userlevel'];
|
||||
$password = $input['password'];
|
||||
$password_2 = $input['password_2'];
|
||||
|
||||
if ( $password != '' || $password_2 != '') {
|
||||
if ($password != $password_2) {
|
||||
return $this->response->setJSON(['message'=> 'Password not the same']);
|
||||
}
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$sqlUpdate ="
|
||||
UPDATE gdc_cmod.dbo.USERS
|
||||
SET USERLEVEL = ?,
|
||||
PASSWORD = ?
|
||||
WHERE USERID = ?
|
||||
";
|
||||
$fullUpdate = true;
|
||||
|
||||
} else {
|
||||
$sqlUpdate ="
|
||||
UPDATE gdc_cmod.dbo.USERS
|
||||
SET USERLEVEL = ?
|
||||
WHERE USERID = ?
|
||||
";
|
||||
$fullUpdate = false;
|
||||
}
|
||||
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
|
||||
if ($fullUpdate) {
|
||||
$this->db->query($sqlUpdate, [$userlevel, $hashedPassword, $userid]);
|
||||
} else {
|
||||
$this->db->query($sqlUpdate, [$userlevel, $userid]);
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setJSON(['message'=> 'Terjadi kesalahan pada server.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['message'=> 'User '.$userid.' Berhasil Diupdate!']);
|
||||
}
|
||||
|
||||
}
|
||||
140
app/Controllers/UsersController.php
Normal file
140
app/Controllers/UsersController.php
Normal file
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
use App\Controllers\BaseController;
|
||||
|
||||
// Users Management
|
||||
class UsersController extends BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
protected $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$sql = "select u.USERID, u.USERROLEID, u.USERNAME from GDC_CMOD.dbo.USERS u
|
||||
left join glendb.dbo.USERS u1 on u1.USERID=u.USERID
|
||||
where u1.LOCKEDACCOUNT is null";
|
||||
$query = $this->db->query($sql);
|
||||
$results = $query->getResultArray();
|
||||
$data['data'] = $results;
|
||||
|
||||
return $this->respond(['data' => $results]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
$userroleid = $input['userroleid'];
|
||||
$password = $input['password'];
|
||||
$password_2 = $input['password_2'];
|
||||
|
||||
if ($password != $password_2) {
|
||||
return $this->response->setJSON(['message' => 'Password not the same']);
|
||||
}
|
||||
if (strlen($password) < 3) {
|
||||
return $this->response->setJSON(['message' => 'Password must be more than 2 characters']);
|
||||
}
|
||||
|
||||
$sql = $this->db->query("SELECT USERID FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]);
|
||||
$query = $sql->getRowArray();
|
||||
if ($query != null) {
|
||||
return $this->response->setJSON(['message' => 'Userid already exists']);
|
||||
}
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
$sqlInsert = "
|
||||
INSERT INTO gdc_cmod.dbo.USERS (USERID, USERROLEID, PASSWORD)
|
||||
VALUES (?, ?, ?)
|
||||
";
|
||||
$this->db->query($sqlInsert, [$userid, $userroleid, $hashedPassword]);
|
||||
|
||||
$this->db->transCommit();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setJSON(['message' => 'Server error']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['message' => 'User ' . $userid . ' Berhasil ditambahkan!']);
|
||||
}
|
||||
|
||||
public function update($id = null)
|
||||
{
|
||||
$input = $this->request->getJSON(true);
|
||||
$userid = $input['userid'];
|
||||
$userroleid = $input['userroleid'];
|
||||
$password = $input['password'];
|
||||
$password_2 = $input['password_2'];
|
||||
|
||||
if ($password != '' || $password_2 != '') {
|
||||
if ($password != $password_2) {
|
||||
return $this->response->setJSON(['message' => 'Password not the same']);
|
||||
}
|
||||
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$sqlUpdate = "
|
||||
UPDATE gdc_cmod.dbo.USERS
|
||||
SET USERROLEID = ?,
|
||||
PASSWORD = ?
|
||||
WHERE USERID = ?
|
||||
";
|
||||
$fullUpdate = true;
|
||||
|
||||
} else {
|
||||
$sqlUpdate = "
|
||||
UPDATE gdc_cmod.dbo.USERS
|
||||
SET USERROLEID = ?
|
||||
WHERE USERID = ?
|
||||
";
|
||||
$fullUpdate = false;
|
||||
}
|
||||
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
|
||||
if ($fullUpdate) {
|
||||
$this->db->query($sqlUpdate, [$userroleid, $hashedPassword, $userid]);
|
||||
} else {
|
||||
$this->db->query($sqlUpdate, [$userroleid, $userid]);
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setJSON(['message' => 'Terjadi kesalahan pada server.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['message' => 'User ' . $userid . ' Berhasil Diupdate!']);
|
||||
}
|
||||
|
||||
public function delete($id = null)
|
||||
{
|
||||
$this->db->transBegin();
|
||||
try {
|
||||
$sql = "DELETE FROM gdc_cmod.dbo.USERS WHERE USERID = ?";
|
||||
$this->db->query($sql, [$id]);
|
||||
|
||||
if ($this->db->affectedRows() == 0) {
|
||||
throw new \Exception('User not found or already deleted');
|
||||
}
|
||||
|
||||
$this->db->transCommit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setStatusCode(500)->setJSON(['message' => 'Error deleting user: ' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['message' => 'User ' . $id . ' deleted successfully']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -28,68 +28,45 @@
|
||||
</head>
|
||||
|
||||
<body class="bg-base-200 min-h-screen" x-data="main">
|
||||
<div class="drawer">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class="flex-none">
|
||||
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
|
||||
Admin Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span
|
||||
class="text-base-content/40 font-light text-sm hidden sm:inline-block">| Admin Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-user"></i></span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-bars"></i></span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow-lg border border-base-300">
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<div class="divider my-1"></div>
|
||||
<li><a href="<?= base_url('admin') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('admin/dialog_setPassword'); ?>
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('admin/dialog_setPassword'); ?>
|
||||
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="drawer-side z-50">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-100 text-base-content min-h-full w-80 p-4 flex flex-col">
|
||||
<!-- Sidebar content here -->
|
||||
<li class="mb-4">
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2'>
|
||||
<i class="fa fa-cube"></i> CMOD
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li><a href="<?= base_url('admin') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
<li><a href="<?= base_url('admin/users') ?>"><i class="fa fa-users mr-2"></i> Users </a></li>
|
||||
|
||||
<div class="mt-auto">
|
||||
<li class="menu-title">Account</li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.BASEURL = "<?= base_url("admin"); ?>";
|
||||
window.BASEURL = "<?= base_url(); ?>";
|
||||
</script>
|
||||
<?= $this->renderSection('script'); ?>
|
||||
</body>
|
||||
|
||||
@ -1,269 +0,0 @@
|
||||
<?= $this->extend('admin/main'); ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div x-data="users" class="contents">
|
||||
<main class="p-4 flex-1 flex flex-col gap-2 max-w-6xl w-full mx-auto">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
|
||||
<i class="fa fa-users text-primary"></i> User Management
|
||||
</h2>
|
||||
<button class="btn btn-primary btn-sm" @click="openUserModal('create')">
|
||||
<i class="fa fa-plus"></i> Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<template x-if="isLoading">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><div class="skeleton h-4 w-24"></div></th>
|
||||
<th><div class="skeleton h-4 w-24"></div></th>
|
||||
<th class="text-right"><div class="skeleton h-4 w-16 ml-auto"></div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="i in 5" :key="i">
|
||||
<tr>
|
||||
<td><div class="skeleton h-4 w-20"></div></td>
|
||||
<td><div class="skeleton h-4 w-24"></div></td>
|
||||
<td class="text-right"><div class="skeleton h-4 w-16 ml-auto"></div></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template x-if="!isLoading && list.length">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Role/Level</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="user in list" :key="user.USERID">
|
||||
<tr>
|
||||
<td class="font-bold" x-text="user.USERID"></td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
:class="getRoleClass(user.USERLEVEL)"
|
||||
x-text="getRoleName(user.USERLEVEL)"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button class="btn btn-square btn-ghost btn-xs text-info" @click="openUserModal('edit', user)">
|
||||
<i class="fa fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-square btn-ghost btn-xs text-error" @click="deleteUser(user.USERID)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template x-if="!isLoading && !list.length">
|
||||
<div class="text-center py-10">
|
||||
<i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
|
||||
<p>No users found</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- User Modal -->
|
||||
<dialog id="user_modal" class="modal">
|
||||
<div class="modal-box p-0 overflow-hidden w-11/12 max-w-lg bg-base-100 shadow-2xl">
|
||||
<div class="p-6 flex flex-col gap-4">
|
||||
<div class="alert alert-info shadow-sm py-2 text-sm" x-show="mode === 'edit'">
|
||||
<i class="fa fa-info-circle"></i> Editing user: <span class="font-bold font-mono" x-text="form.userid"></span>
|
||||
</div>
|
||||
|
||||
<!-- User ID & Level -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">User ID</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-id-badge text-base-content/40"></i>
|
||||
<input type="text" class="grow font-mono" x-model="form.userid" :disabled="mode === 'edit'" placeholder="e.g. USER001" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Role / Level</span>
|
||||
</label>
|
||||
<select class="select select-bordered w-full focus:select-primary transition-all" x-model="form.userlevel">
|
||||
<option value="" disabled>Select Level</option>
|
||||
<option value="1">Admin</option>
|
||||
<option value="2">Lab</option>
|
||||
<option value="3">Phlebotomist</option>
|
||||
<option value="4">Customer Service</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider text-xs text-base-content/30 my-0">Security</div>
|
||||
|
||||
<!-- Passwords -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Password</span>
|
||||
<span class="label-text-alt text-xs opacity-50" x-show="mode === 'edit'">(Optional)</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-lock text-base-content/40"></i>
|
||||
<input type="password" class="grow" x-model="form.password" placeholder="••••••" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Confirm</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-lock text-base-content/40"></i>
|
||||
<input type="password" class="grow" x-model="form.password_2" placeholder="••••••" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div x-show="errorMsg"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform scale-95"
|
||||
x-transition:enter-end="opacity-100 transform scale-100"
|
||||
class="alert alert-error text-sm shadow-md">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span x-text="errorMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="modal-action bg-base-200/50 p-6 m-0 flex justify-between items-center border-t border-base-200">
|
||||
<button class="btn btn-ghost hover:bg-base-200 text-base-content/70" @click="closeModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary px-8 shadow-lg shadow-primary/30 min-w-[120px]" @click="saveUser()" :disabled="isLoading">
|
||||
<span x-show="isLoading" class="loading loading-spinner loading-xs"></span>
|
||||
<span x-show="!isLoading" x-text="mode === 'create' ? 'Create User' : 'Save Changes'"></span>
|
||||
<i x-show="!isLoading" class="fa fa-save ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button @click="closeModal()">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<?= $this->endSection(); ?>
|
||||
|
||||
<?= $this->section('script') ?>
|
||||
<script type="module">
|
||||
import Alpine from '<?=base_url("js/app.js");?>';
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data("users", () => ({
|
||||
list: [],
|
||||
mode: 'create',
|
||||
isLoading: false,
|
||||
errorMsg: '',
|
||||
form: {
|
||||
userid: '',
|
||||
userlevel: '',
|
||||
password: '',
|
||||
password_2: ''
|
||||
},
|
||||
|
||||
init() {
|
||||
this.fetchUsers();
|
||||
},
|
||||
|
||||
fetchUsers() {
|
||||
this.isLoading = true;
|
||||
fetch(`${BASEURL}/api/users`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
this.list = data.data ?? [];
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
|
||||
getRoleName(level) {
|
||||
const map = { 1: 'Administrator', 2: 'Lab', 3: 'Phlebotomist', 4: 'Customer Service' };
|
||||
return map[level] || 'Unknown (' + level + ')';
|
||||
},
|
||||
|
||||
getRoleClass(level) {
|
||||
const map = { 1: 'badge-primary', 2: 'badge-secondary', 3: 'badge-accent', 4: 'badge-neutral' };
|
||||
return map[level] || 'badge-ghost';
|
||||
},
|
||||
|
||||
openUserModal(targetMode, user = null) {
|
||||
this.mode = targetMode;
|
||||
this.errorMsg = '';
|
||||
|
||||
if (targetMode === 'edit' && user) {
|
||||
this.form = {
|
||||
userid: user.USERID,
|
||||
userlevel: user.USERLEVEL,
|
||||
password: '',
|
||||
password_2: ''
|
||||
};
|
||||
} else {
|
||||
this.form = { userid: '', userlevel: '', password: '', password_2: '' };
|
||||
}
|
||||
document.getElementById('user_modal').showModal();
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
document.getElementById('user_modal').close();
|
||||
},
|
||||
|
||||
async saveUser() {
|
||||
this.errorMsg = '';
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
let res;
|
||||
if(this.mode == 'create') {
|
||||
res = await fetch(`${BASEURL}/api/users`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.form)
|
||||
});
|
||||
} else {
|
||||
res = await fetch(`${BASEURL}/api/users/${this.form.userid}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.form)
|
||||
});
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.messages?.error || data.message || 'Error saving user');
|
||||
return data;
|
||||
} catch (err) {
|
||||
this.errorMsg = err.message;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
this.closeModal();
|
||||
this.fetchUsers();
|
||||
}
|
||||
},
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
Alpine.start();
|
||||
</script>
|
||||
<?= $this->endSection(); ?>
|
||||
@ -28,67 +28,45 @@
|
||||
</head>
|
||||
|
||||
<body class="bg-base-200 min-h-screen" x-data="main">
|
||||
<div class="drawer">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class="flex-none">
|
||||
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
|
||||
Customer Service Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span
|
||||
class="text-base-content/40 font-light text-sm hidden sm:inline-block">| Customer Service Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-user"></i></span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-bars"></i></span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow-lg border border-base-300">
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<div class="divider my-1"></div>
|
||||
<li><a href="<?= base_url('cs') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('cs/dialog_setPassword'); ?>
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('cs/dialog_setPassword'); ?>
|
||||
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="drawer-side z-50">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-100 text-base-content min-h-full w-80 p-4 flex flex-col">
|
||||
<!-- Sidebar content here -->
|
||||
<li class="mb-4">
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2'>
|
||||
<i class="fa fa-cube"></i> CMOD
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li><a href="<?= base_url('cs') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
|
||||
<div class="mt-auto">
|
||||
<li class="menu-title">Account</li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.BASEURL = "<?= base_url("cs"); ?>";
|
||||
window.BASEURL = "<?= base_url(); ?>";
|
||||
</script>
|
||||
<?= $this->renderSection('script'); ?>
|
||||
</body>
|
||||
|
||||
@ -28,67 +28,45 @@
|
||||
</head>
|
||||
|
||||
<body class="bg-base-200 min-h-screen" x-data="main">
|
||||
<div class="drawer">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class="flex-none">
|
||||
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
|
||||
Lab Analyst Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span
|
||||
class="text-base-content/40 font-light text-sm hidden sm:inline-block">| Lab Analyst Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-user"></i></span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-bars"></i></span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow-lg border border-base-300">
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<div class="divider my-1"></div>
|
||||
<li><a href="<?= base_url('lab') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('lab/dialog_setPassword'); ?>
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('lab/dialog_setPassword'); ?>
|
||||
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="drawer-side z-50">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-100 text-base-content min-h-full w-80 p-4 flex flex-col">
|
||||
<!-- Sidebar content here -->
|
||||
<li class="mb-4">
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2'>
|
||||
<i class="fa fa-cube"></i> CMOD
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li><a href="<?= base_url('lab') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
|
||||
<div class="mt-auto">
|
||||
<li class="menu-title">Account</li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.BASEURL = "<?= base_url("lab"); ?>";
|
||||
window.BASEURL = "<?= base_url(); ?>";
|
||||
</script>
|
||||
<?= $this->renderSection('script'); ?>
|
||||
</body>
|
||||
|
||||
@ -28,67 +28,45 @@
|
||||
</head>
|
||||
|
||||
<body class="bg-base-200 min-h-screen" x-data="main">
|
||||
<div class="drawer">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class="flex-none">
|
||||
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
|
||||
Phlebotomist Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span
|
||||
class="text-base-content/40 font-light text-sm hidden sm:inline-block">| Phlebotomist Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-user"></i></span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-bars"></i></span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow-lg border border-base-300">
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<div class="divider my-1"></div>
|
||||
<li><a href="<?= base_url('phlebo') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('phlebo/dialog_setPassword'); ?>
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('phlebo/dialog_setPassword'); ?>
|
||||
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="drawer-side z-50">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-100 text-base-content min-h-full w-80 p-4 flex flex-col">
|
||||
<!-- Sidebar content here -->
|
||||
<li class="mb-4">
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2'>
|
||||
<i class="fa fa-cube"></i> CMOD
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li><a href="<?= base_url('phlebo') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
|
||||
<div class="mt-auto">
|
||||
<li class="menu-title">Account</li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.BASEURL = "<?= base_url("phlebo"); ?>";
|
||||
window.BASEURL = "<?= base_url(); ?>";
|
||||
</script>
|
||||
<?= $this->renderSection('script'); ?>
|
||||
</body>
|
||||
|
||||
@ -28,68 +28,46 @@
|
||||
</head>
|
||||
|
||||
<body class="bg-base-200 min-h-screen" x-data="main">
|
||||
<div class="drawer">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class="flex-none">
|
||||
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<div class="flex flex-col min-h-screen">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-100 shadow-md px-6 z-20">
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
|
||||
Superuser Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class='flex-1'>
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
|
||||
<i class="fa fa-cube"></i> CMOD <span
|
||||
class="text-base-content/40 font-light text-sm hidden sm:inline-block">| Superuser Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="text-right hidden sm:block leading-tight">
|
||||
<div class="text-sm font-bold opacity-70">Hi, <?= session('userid'); ?></div>
|
||||
<div class="text-xs opacity-50"><?= session()->get('userrole') ?></div>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-user"></i></span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
|
||||
<span class="text-xl"><i class="fa fa-bars"></i></span>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow-lg border border-base-300">
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<div class="divider my-1"></div>
|
||||
<li><a href="<?= base_url('superuser') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
<li><a href="<?= base_url('superuser/users') ?>"><i class="fa fa-users mr-2"></i> Users</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('superuser/dialog_setPassword'); ?>
|
||||
<!-- Page Content -->
|
||||
<?= $this->renderSection('content'); ?>
|
||||
<?= $this->include('superuser/dialog_setPassword'); ?>
|
||||
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="drawer-side z-50">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-100 text-base-content min-h-full w-80 p-4 flex flex-col">
|
||||
<!-- Sidebar content here -->
|
||||
<li class="mb-4">
|
||||
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2'>
|
||||
<i class="fa fa-cube"></i> CMOD
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li><a href="<?= base_url('superuser') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
|
||||
<li><a href="<?= base_url('superuser/users') ?>"><i class="fa fa-users mr-2"></i> Users </a></li>
|
||||
|
||||
<div class="mt-auto">
|
||||
<li class="menu-title">Account</li>
|
||||
<li><a @click.prevent="openDialogSetPassword()"><i class="fa fa-key mr-2"></i> Change Password</a></li>
|
||||
<li><a href="<?= base_url('logout') ?>" class="text-error hover:bg-error/10"><i
|
||||
class="fa fa-sign-out-alt mr-2"></i> Logout</a></li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class='bg-base-100 p-1 mt-auto'>© <?= date('Y'); ?> - 5Panda</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.BASEURL = "<?= base_url("superuser"); ?>";
|
||||
window.BASEURL = "<?= base_url(); ?>";
|
||||
</script>
|
||||
<?= $this->renderSection('script'); ?>
|
||||
</body>
|
||||
|
||||
@ -1,175 +1,198 @@
|
||||
<?= $this->extend('superuser/main'); ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div x-data="users" class="contents">
|
||||
<main class="p-4 flex-1 flex flex-col gap-2 max-w-6xl w-full mx-auto">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
|
||||
<i class="fa fa-users text-primary"></i> User Management
|
||||
</h2>
|
||||
<button class="btn btn-primary btn-sm" @click="openUserModal('create')">
|
||||
<i class="fa fa-plus"></i> Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<template x-if="isLoading">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><div class="skeleton h-4 w-24"></div></th>
|
||||
<th><div class="skeleton h-4 w-24"></div></th>
|
||||
<th class="text-right"><div class="skeleton h-4 w-16 ml-auto"></div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="i in 5" :key="i">
|
||||
<tr>
|
||||
<td><div class="skeleton h-4 w-20"></div></td>
|
||||
<td><div class="skeleton h-4 w-24"></div></td>
|
||||
<td class="text-right"><div class="skeleton h-4 w-16 ml-auto"></div></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template x-if="!isLoading && list.length">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Role/Level</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="user in list" :key="user.USERID">
|
||||
<tr>
|
||||
<td class="font-bold" x-text="user.USERID"></td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
:class="getRoleClass(user.USERLEVEL)"
|
||||
x-text="getRoleName(user.USERLEVEL)"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button class="btn btn-square btn-ghost btn-xs text-info" @click="openUserModal('edit', user)">
|
||||
<i class="fa fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-square btn-ghost btn-xs text-error" @click="deleteUser(user.USERID)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template x-if="!isLoading && !list.length">
|
||||
<div class="text-center py-10">
|
||||
<i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
|
||||
<p>No users found</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- User Modal -->
|
||||
<dialog id="user_modal" class="modal">
|
||||
<div class="modal-box p-0 overflow-hidden w-11/12 max-w-lg bg-base-100 shadow-2xl">
|
||||
<div class="p-6 flex flex-col gap-4">
|
||||
<div class="alert alert-info shadow-sm py-2 text-sm" x-show="mode === 'edit'">
|
||||
<i class="fa fa-info-circle"></i> Editing user: <span class="font-bold font-mono" x-text="form.userid"></span>
|
||||
</div>
|
||||
|
||||
<!-- User ID & Level -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">User ID</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-id-badge text-base-content/40"></i>
|
||||
<input type="text" class="grow font-mono" x-model="form.userid" :disabled="mode === 'edit'" placeholder="e.g. USER001" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Role / Level</span>
|
||||
</label>
|
||||
<select class="select select-bordered w-full focus:select-primary transition-all" x-model="form.userlevel">
|
||||
<option value="" disabled>Select Level</option>
|
||||
<option value="1">Admin</option>
|
||||
<option value="2">Lab</option>
|
||||
<option value="3">Phlebotomist</option>
|
||||
<option value="4">Customer Service</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider text-xs text-base-content/30 my-0">Security</div>
|
||||
|
||||
<!-- Passwords -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Password</span>
|
||||
<span class="label-text-alt text-xs opacity-50" x-show="mode === 'edit'">(Optional)</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-lock text-base-content/40"></i>
|
||||
<input type="password" class="grow" x-model="form.password" placeholder="••••••" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Confirm</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-lock text-base-content/40"></i>
|
||||
<input type="password" class="grow" x-model="form.password_2" placeholder="••••••" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div x-show="errorMsg"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform scale-95"
|
||||
x-transition:enter-end="opacity-100 transform scale-100"
|
||||
class="alert alert-error text-sm shadow-md">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span x-text="errorMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="modal-action bg-base-200/50 p-6 m-0 flex justify-between items-center border-t border-base-200">
|
||||
<button class="btn btn-ghost hover:bg-base-200 text-base-content/70" @click="closeModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary px-8 shadow-lg shadow-primary/30 min-w-[120px]" @click="saveUser()" :disabled="isLoading">
|
||||
<span x-show="isLoading" class="loading loading-spinner loading-xs"></span>
|
||||
<span x-show="!isLoading" x-text="mode === 'create' ? 'Create User' : 'Save Changes'"></span>
|
||||
<i x-show="!isLoading" class="fa fa-save ml-2"></i>
|
||||
<div x-data="users" class="contents">
|
||||
<main class="p-4 flex-1 flex flex-col gap-2 max-w-6xl w-full mx-auto">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
|
||||
<i class="fa fa-users text-primary"></i> User Management
|
||||
</h2>
|
||||
<button class="btn btn-primary btn-sm" @click="openUserModal('create')">
|
||||
<i class="fa fa-plus"></i> Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<template x-if="isLoading">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
</th>
|
||||
<th class="text-right">
|
||||
<div class="skeleton h-4 w-16 ml-auto"></div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="i in 5" :key="i">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="skeleton h-4 w-20"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="skeleton h-4 w-16 ml-auto"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template x-if="!isLoading && list.length">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Username</th>
|
||||
<th>Role/Level</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="user in list" :key="user.USERID">
|
||||
<tr>
|
||||
<td class="font-bold" x-text="user.USERID"></td>
|
||||
<td class="font-bold" x-text="user.USERNAME"></td>
|
||||
<td>
|
||||
<span class="badge" :class="getRoleClass(user.USERROLEID)"
|
||||
x-text="getRoleName(user.USERROLEID)"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button class="btn btn-square btn-ghost btn-xs text-info" @click="openUserModal('edit', user)">
|
||||
<i class="fa fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-square btn-ghost btn-xs text-error" @click="deleteUser(user.USERID)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<template x-if="!isLoading && !list.length">
|
||||
<div class="text-center py-10">
|
||||
<i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
|
||||
<p>No users found</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button @click="closeModal()">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- User Modal -->
|
||||
<dialog id="user_modal" class="modal">
|
||||
<div class="modal-box p-0 overflow-hidden w-11/12 max-w-lg bg-base-100 shadow-2xl">
|
||||
<div class="p-6 flex flex-col gap-4">
|
||||
<div class="alert alert-info shadow-sm py-2 text-sm" x-show="mode === 'edit'">
|
||||
<i class="fa fa-info-circle"></i> Editing user: <span class="font-bold font-mono" x-text="form.userid"></span>
|
||||
</div>
|
||||
|
||||
<!-- User ID & Level -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">User ID</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-id-badge text-base-content/40"></i>
|
||||
<input type="text" class="grow font-mono" x-model="form.userid" :disabled="mode === 'edit'"
|
||||
placeholder="e.g. USER001" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Role / Level</span>
|
||||
</label>
|
||||
<select class="select select-bordered w-full focus:select-primary transition-all" x-model="form.userroleid">
|
||||
<option value="" disabled>Select Level</option>
|
||||
<?php foreach (ROLE_NAMES as $key => $role): ?>
|
||||
<option value="<?= $key ?>"><?= $role ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Username -->
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Username</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-user text-base-content/40"></i>
|
||||
<input type="text" class="grow font-mono" x-model="form.username" placeholder="e.g. john.doe" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="divider text-xs text-base-content/30 my-0">Security</div>
|
||||
|
||||
<!-- Passwords -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Password</span>
|
||||
<span class="label-text-alt text-xs opacity-50" x-show="mode === 'edit'">(Optional)</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-lock text-base-content/40"></i>
|
||||
<input type="password" class="grow" x-model="form.password" placeholder="••••••" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium text-base-content/70">Confirm</span>
|
||||
</label>
|
||||
<label class="input input-bordered flex items-center gap-2 focus-within:input-primary transition-all">
|
||||
<i class="fa fa-lock text-base-content/40"></i>
|
||||
<input type="password" class="grow" x-model="form.password_2" placeholder="••••••" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div x-show="errorMsg" x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform scale-95"
|
||||
x-transition:enter-end="opacity-100 transform scale-100" class="alert alert-error text-sm shadow-md">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span x-text="errorMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="modal-action bg-base-200/50 p-6 m-0 flex justify-between items-center border-t border-base-200">
|
||||
<button class="btn btn-ghost hover:bg-base-200 text-base-content/70" @click="closeModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary px-8 shadow-lg shadow-primary/30 min-w-[120px]" @click="saveUser()"
|
||||
:disabled="isLoading">
|
||||
<span x-show="isLoading" class="loading loading-spinner loading-xs"></span>
|
||||
<span x-show="!isLoading" x-text="mode === 'create' ? 'Create User' : 'Save Changes'"></span>
|
||||
<i x-show="!isLoading" class="fa fa-save ml-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button @click="closeModal()">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<?= $this->endSection(); ?>
|
||||
|
||||
<?= $this->section('script') ?>
|
||||
<script type="module">
|
||||
import Alpine from '<?=base_url("js/app.js");?>';
|
||||
<script type="module">
|
||||
import Alpine from '<?= base_url("js/app.js"); ?>';
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data("users", () => ({
|
||||
@ -179,7 +202,8 @@
|
||||
errorMsg: '',
|
||||
form: {
|
||||
userid: '',
|
||||
userlevel: '',
|
||||
username: '',
|
||||
userroleid: '',
|
||||
password: '',
|
||||
password_2: ''
|
||||
},
|
||||
@ -200,7 +224,7 @@
|
||||
},
|
||||
|
||||
getRoleName(level) {
|
||||
const map = { 1: 'Administrator', 2: 'Lab', 3: 'Phlebotomist', 4: 'Customer Service' };
|
||||
const map = <?= json_encode(ROLE_NAMES) ?>;
|
||||
return map[level] || 'Unknown (' + level + ')';
|
||||
},
|
||||
|
||||
@ -212,16 +236,17 @@
|
||||
openUserModal(targetMode, user = null) {
|
||||
this.mode = targetMode;
|
||||
this.errorMsg = '';
|
||||
|
||||
|
||||
if (targetMode === 'edit' && user) {
|
||||
this.form = {
|
||||
userid: user.USERID,
|
||||
userlevel: user.USERLEVEL,
|
||||
username: user.USERNAME,
|
||||
userroleid: user.USERROLEID,
|
||||
password: '',
|
||||
password_2: ''
|
||||
};
|
||||
} else {
|
||||
this.form = { userid: '', userlevel: '', password: '', password_2: '' };
|
||||
this.form = { userid: '', username: '', userroleid: '', password: '', password_2: '' };
|
||||
}
|
||||
document.getElementById('user_modal').showModal();
|
||||
},
|
||||
@ -236,7 +261,7 @@
|
||||
|
||||
try {
|
||||
let res;
|
||||
if(this.mode == 'create') {
|
||||
if (this.mode == 'create') {
|
||||
res = await fetch(`${BASEURL}/api/users`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@ -265,5 +290,5 @@
|
||||
});
|
||||
|
||||
Alpine.start();
|
||||
</script>
|
||||
<?= $this->endSection(); ?>
|
||||
</script>
|
||||
<?= $this->endSection(); ?>
|
||||
Loading…
x
Reference in New Issue
Block a user