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:
mahdahar 2026-01-21 17:00:05 +07:00
parent 13591da5b4
commit 02762bb355
24 changed files with 849 additions and 1033 deletions

120
CLAUDE.md Normal file
View 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']));
```

View File

@ -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_DATABASE') || define('EXIT_DATABASE', 8); // database error
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code 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 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',
]);

View File

@ -2,7 +2,7 @@
use CodeIgniter\Router\RouteCollection; use CodeIgniter\Router\RouteCollection;
$routes->set404Override(function() { $routes->set404Override(function () {
$response = service('response'); $response = service('response');
$response->setStatusCode(404); $response->setStatusCode(404);
echo view('errors/notfound'); echo view('errors/notfound');
@ -11,72 +11,74 @@ $routes->get('/unauthorized', 'ErrorPage::unauthorized');
$routes->get('/', 'Home::index'); $routes->get('/', 'Home::index');
$routes->get('/login', 'Auth::loginPage', ['filter' => 'guest']); $routes->get('/login', 'AuthController::loginPage', ['filter' => 'guest']);
$routes->post('/login', 'Auth::login', ['filter' => 'guest']); $routes->post('/login', 'AuthController::login', ['filter' => 'guest']);
$routes->get('/logout', 'Auth::logout'); $routes->get('/logout', 'AuthController::logout');
$routes->patch('/setPassword', 'Auth::setPassword'); $routes->patch('/setPassword', 'AuthController::setPassword');
$routes->get('label/coll/(:any)', 'Label::coll/$1'); $routes->get('label/coll/(:any)', 'LabelController::coll/$1');
$routes->get('label/dispatch/(:any)/(:any)', 'Label::dispatch/$1/$2'); $routes->get('label/dispatch/(:any)/(:any)', 'LabelController::dispatch/$1/$2');
$routes->get('label/all/(:any)', 'Label::print_all/$1'); $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'); // --- Page Routes ---
$routes->get('users', 'Admin::users');
$routes->get('api/users', 'Users::index'); $routes->group('superuser', ['filter' => 'role:0'], function ($routes) {
$routes->post('api/users', 'Users::create'); $routes->get('', 'Pages\SuperuserController::index');
$routes->patch('api/users/(:any)', 'Users::update/$1'); $routes->get('users', 'Pages\SuperuserController::users');
$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('lab', ['filter' => 'role:2'], function($routes) { $routes->group('admin', ['filter' => 'role:1'], function ($routes) {
$routes->get('', 'Lab::index'); $routes->get('', 'Pages\AdminController::index');
$routes->get('api/requests', 'Requests::index'); $routes->get('users', 'Pages\AdminController::users');
$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->group('lab', ['filter' => 'role:2'], function ($routes) {
$routes->get('', 'Phlebotomist::index'); $routes->get('', 'Pages\LabController::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('cs', ['filter' => 'role:4'], function($routes) { $routes->group('phlebo', ['filter' => 'role:3'], function ($routes) {
$routes->get('', 'Cs::index'); $routes->get('', 'Pages\PhlebotomistController::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->group('cs', ['filter' => 'role:4'], function ($routes) {
$routes->post('api/samples/collect/(:any)', 'Samples::collect/$1'); $routes->get('', 'Pages\CsController::index');
$routes->get('api/samples/(:any)', 'Samples::show/$1');
}); });
$routes->get('/dummypage', 'Home::dummyPage'); $routes->get('/dummypage', 'Home::dummyPage');

View File

@ -4,13 +4,16 @@ namespace App\Controllers;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Auth extends BaseController { class AuthController extends BaseController
{
public function loginPage() { public function loginPage()
{
return view("login"); return view("login");
} }
public function login() { public function login()
{
helper(['form', 'url']); helper(['form', 'url']);
$session = session(); $session = session();
$db = \Config\Database::connect(); $db = \Config\Database::connect();
@ -23,35 +26,17 @@ class Auth extends BaseController {
if ($user && !empty($user['PASSWORD']) && password_verify($password, $user['PASSWORD'])) { if ($user && !empty($user['PASSWORD']) && password_verify($password, $user['PASSWORD'])) {
switch ((int)$user['USERROLEID']) { $roleId = (int) $user['USERROLEID'];
case 0: $role = ROLE_NAMES[$roleId] ?? '';
$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;
}
$session->set([ $session->set([
'isLoggedIn' => true, 'isLoggedIn' => true,
'userid' => (string) $user['USERID'], 'userid' => (string) $user['USERID'],
'userroleid' => (int) $user['USERROLEID'], 'userroleid' => (int) $user['USERROLEID'],
'userrole' => (string) $role, 'userrole' => (string) $role,
]); ]);
switch ((int)$user['USERROLEID']) { switch ((int) $user['USERROLEID']) {
case 0: case 0:
return redirect()->to('superuser'); return redirect()->to('superuser');
case 1: case 1:
@ -71,13 +56,15 @@ class Auth extends BaseController {
} }
} }
public function logout() { public function logout()
{
$session = session(); $session = session();
$session->destroy(); $session->destroy();
return redirect()->to('login'); return redirect()->to('login');
} }
public function setPassword() { public function setPassword()
{
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$userid = $input['userid']; $userid = $input['userid'];
$password = $input['password']; $password = $input['password'];
@ -85,7 +72,7 @@ class Auth extends BaseController {
$db = db_connect(); $db = db_connect();
$sql = "update GDC_CMOD.dbo.USERS set PASSWORD='$password' where USERID='$userid'"; $sql = "update GDC_CMOD.dbo.USERS set PASSWORD='$password' where USERID='$userid'";
$db->query($sql); $db->query($sql);
$data = ['status' => 'success', 'message' => 'Password updated successfully', 'data' => "$userid" ]; $data = ['status' => 'success', 'message' => 'Password updated successfully', 'data' => "$userid"];
return $this->response->setJSON($data); return $this->response->setJSON($data);
} }
} }

View File

@ -1,8 +1,10 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers;
class Label extends BaseController { class LabelController extends BaseController
public function coll($reqnum) { {
public function coll($reqnum)
{
$db = \Config\Database::connect(); $db = \Config\Database::connect();
//$reqnum = str_pad($reqnum, 10, 0, STR_PAD_LEFT); //$reqnum = str_pad($reqnum, 10, 0, STR_PAD_LEFT);
$sql = "select p.PATNUMBER, $sql = "select p.PATNUMBER,
@ -34,7 +36,7 @@ class Label extends BaseController {
//print_r($rows); //print_r($rows);
$row = $rows[0]; $row = $rows[0];
$patnum = $row['PATNUMBER']; $patnum = $row['PATNUMBER'];
$patnum = substr($patnum,14); $patnum = substr($patnum, 14);
//$patnum = str_pad(substr($row[0],5),17," "); //$patnum = str_pad(substr($row[0],5),17," ");
$patname = $row['Name']; $patname = $row['Name'];
$dob = $row['dob']; $dob = $row['dob'];
@ -58,14 +60,15 @@ A10,195,0,1,1,1,N,\"HIS : $hospnum\"
A190,190,0,2,1,1,N,\"$date\" A190,190,0,2,1,1,N,\"$date\"
P1\n]"; P1\n]";
$handle = fopen("./file.txt","a+"); $handle = fopen("./file.txt", "a+");
fwrite($handle,$bar); fwrite($handle, $bar);
fclose($handle); fclose($handle);
/*exec($command);*/ /*exec($command);*/
} }
public function dispatch($reqnum, $samid) { public function dispatch($reqnum, $samid)
{
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$sql = "select p.PATNUMBER, $sql = "select p.PATNUMBER,
[Name] = case [Name] = case
@ -92,11 +95,11 @@ P1\n]";
left join PATIENTS p on spr.PATID=p.PATID left join PATIENTS p on spr.PATID=p.PATID
left join DICT_TEXTS tx on tx.TEXTID=p.TITLEID left join DICT_TEXTS tx on tx.TEXTID=p.TITLEID
where spr.SP_ACCESSNUMBER='$reqnum'"; where spr.SP_ACCESSNUMBER='$reqnum'";
$rows = $db->query($sql)->getResultArray(); $rows = $db->query($sql)->getResultArray();
$row = $rows[0]; $row = $rows[0];
$patnum = $row['PATNUMBER']; $patnum = $row['PATNUMBER'];
$patnum = substr($patnum,14); $patnum = substr($patnum, 14);
$patname = $row['Name']; $patname = $row['Name'];
$age = $row['age']; $age = $row['age'];
$sex = $row['Gender']; $sex = $row['Gender'];
@ -109,11 +112,13 @@ P1\n]";
$samptext = $row['SHORTTEXT']; $samptext = $row['SHORTTEXT'];
$tests = $row['TESTS']; $tests = $row['TESTS'];
$tests1 = $row['TESTS1']; $tests1 = $row['TESTS1'];
if($tests == '') {$tests = $tests1;} if ($tests == '') {
$tubeid = $sampcode.substr("$reqnum",5,5); $tests = $tests1;
}
$tubeid = $sampcode . substr("$reqnum", 5, 5);
$date = date("d/M/Y H:i"); $date = date("d/M/Y H:i");
$bar = "[ $bar = "[
N N
OD OD
q400 q400
@ -132,19 +137,20 @@ A190,190,0,2,1,1,N,\"$date\"
P1 P1
]"; ]";
$handle = fopen("./file.txt","a+"); $handle = fopen("./file.txt", "a+");
fwrite($handle,$bar); fwrite($handle, $bar);
fclose($handle); fclose($handle);
//exec($command); //exec($command);
} }
public function print_all($accessnumber) { public function print_all($accessnumber)
{
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$this->coll($accessnumber); $this->coll($accessnumber);
$sql = "select SAMPCODE from GDC_CMOD.dbo.v_sp_reqtube where SP_ACCESSNUMBER='$accessnumber'"; $sql = "select SAMPCODE from GDC_CMOD.dbo.v_sp_reqtube where SP_ACCESSNUMBER='$accessnumber'";
$rows = $db->query($sql)->getResultArray(); $rows = $db->query($sql)->getResultArray();
foreach($rows as $row) { foreach ($rows as $row) {
$sampcode = $row['SAMPCODE']; $sampcode = $row['SAMPCODE'];
$this->dispatch($accessnumber, $sampcode); $this->dispatch($accessnumber, $sampcode);
} }

View File

@ -1,10 +1,10 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers\Pages;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Admin extends BaseController { class AdminController extends BaseController {
public function __construct() { public function __construct() {
helper(['url', 'form', 'text']); helper(['url', 'form', 'text']);

View File

@ -1,11 +1,11 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers\Pages;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Cs extends BaseController { class CsController extends BaseController {
public function __construct() { public function __construct() {
helper(['url', 'form', 'text']); helper(['url', 'form', 'text']);
} }

View File

@ -1,11 +1,11 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers\Pages;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Lab extends BaseController { class LabController extends BaseController {
public function __construct() { public function __construct() {
helper(['url', 'form', 'text']); helper(['url', 'form', 'text']);
} }

View File

@ -1,10 +1,10 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers\Pages;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Phlebotomist extends BaseController { class PhlebotomistController extends BaseController {
public function __construct() { public function __construct() {
helper(['url', 'form', 'text']); helper(['url', 'form', 'text']);
@ -13,5 +13,5 @@ class Phlebotomist extends BaseController {
public function index() { public function index() {
return view('phlebo/index'); return view('phlebo/index');
} }
} }

View File

@ -1,10 +1,10 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers\Pages;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Superuser extends BaseController { class SuperuserController extends BaseController {
public function __construct() { public function __construct() {
helper(['url', 'form', 'text']); helper(['url', 'form', 'text']);

View File

@ -3,32 +3,35 @@ namespace App\Controllers;
use CodeIgniter\API\ResponseTrait; use CodeIgniter\API\ResponseTrait;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Requests extends BaseController { class RequestsController extends BaseController
{
use ResponseTrait; use ResponseTrait;
public function index() { public function index()
$db = \Config\Database::connect(); {
$date1 = $this->request->getGet('date1'); $db = \Config\Database::connect();
$date2 = $this->request->getGet('date2'); $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' $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'"; 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) { foreach ($rows as &$row) {
$row['COLLECTIONDATE'] = date('Y-m-d H:i', strtotime($row['COLLECTIONDATE'])); $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['ODR_DDATE'] = date('Y-m-d H:i', strtotime($row['ODR_DDATE']));
$row['REQDATE'] = date('Y-m-d H:i', strtotime($row['REQDATE'])); $row['REQDATE'] = date('Y-m-d H:i', strtotime($row['REQDATE']));
} }
$data['data'] = $rows; $data['data'] = $rows;
return $this->response->setJSON($data); return $this->response->setJSON($data);
} }
public function show($accessnumber) { public function show($accessnumber)
{
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$data['accessnumber'] = $accessnumber; $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 left join GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER=d.SP_ACCESSNUMBER
WHERE d.SP_ACCESSNUMBER='$accessnumber'"; WHERE d.SP_ACCESSNUMBER='$accessnumber'";
$result = $db $result = $db
@ -38,62 +41,68 @@ class Requests extends BaseController {
$data['val1user'] = $result[0]['VAL1USER']; $data['val1user'] = $result[0]['VAL1USER'];
$data['val2'] = $result[0]['ISVAL2']; $data['val2'] = $result[0]['ISVAL2'];
$data['val2user'] = $result[0]['VAL2USER']; $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; $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); $input = $this->request->getJSON(true);
$userid = $input['userid']; // Securely get userid from session
$userid = session('userid');
$comment = $input['comment']; $comment = $input['comment'];
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=null, VAL1USER=null, VAL1DATE=null, ISVAL2=null, VAL2USER=null, VAL2DATE=null, $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'"; ISPENDING=1, PENDINGTEXT='$comment', PENDINGUSER='$userid', PENDINGDATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
$db->query($sql); $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); return $this->response->setJSON($data);
} }
public function val($accessnumber) { public function val($accessnumber)
{
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$userid = $input['userid']; // Securely get userid from session
$userid = session('userid');
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$sql = "select * from GDC_CMOD.dbo.CM_REQUESTS where ACCESSNUMBER='$accessnumber'"; $sql = "select * from GDC_CMOD.dbo.CM_REQUESTS where ACCESSNUMBER='$accessnumber'";
$result = $db->query($sql)->getResultArray(); $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())"; $sql = "insert into GDC_CMOD.dbo.CM_REQUESTS(ACCESSNUMBER, ISVAL1, VAL1USER, VAL1DATE) VALUES ('$accessnumber', 1, '$userid', GETDATE())";
$db->query($sql); $db->query($sql);
$data['val'] = 1; $data['val'] = 1;
$data['userid'] = $userid; $data['userid'] = $userid;
} else { } else {
$row = $result[0]; $row = $result[0];
$isval1 = $row['ISVAL1']; $isval1 = $row['ISVAL1'];
$isval2 = $row['ISVAL2']; $isval2 = $row['ISVAL2'];
$val1user = $row['VAL1USER']; $val1user = $row['VAL1USER'];
if( $isval1 == 1 ) { if ($isval1 == 1) {
if ( $isval2 == 1 ) { return $this->response->setJSON(['message'=> 'validation done, not updating anything']); } if ($isval2 == 1) {
else { return $this->response->setJSON(['message' => 'validation done, not updating anything']);
if($val1user != $userid) { } else {
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'"; if ($val1user != $userid) {
$data['val'] = 2; $sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
$data['userid'] = $userid; $data['val'] = 2;
} else { $data['userid'] = $userid;
} else {
$this->response->setStatusCode(500); $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 { } else {
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=1, VAL1USER='$userid', VAL1DATE=GETDATE() where ACCESSNUMBER='$accessnumber'"; $sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=1, VAL1USER='$userid', VAL1DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
$data['val'] = 1; $data['val'] = 1;
$data['userid'] = $userid; $data['userid'] = $userid;
} }
$db->query($sql); $db->query($sql);
} }
return $this->response->setJSON($data); return $this->response->setJSON($data);
} }

View File

@ -3,13 +3,15 @@ namespace App\Controllers;
use CodeIgniter\API\ResponseTrait; use CodeIgniter\API\ResponseTrait;
use App\Controllers\BaseController; use App\Controllers\BaseController;
class Samples extends BaseController { class SamplesController extends BaseController
{
use ResponseTrait; use ResponseTrait;
public function show($accessnumber) { public function show($accessnumber)
$db = \Config\Database::connect(); {
$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) 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 , else FLOOR(DATEDIFF(DAY, p.BIRTHDATE, spr.COLLECTIONDATE) / 365.25) end ,
[Gender] = case [Gender] = case
@ -17,108 +19,113 @@ class Samples extends BaseController {
when p.SEX = 2 then 'F' when p.SEX = 2 then 'F'
else '' else ''
end, spr.REQDATE, spo.COMMENTTEXT, dmg.DMG_CKTPNO, dmg.DMG_CPLACEOFBIRTH 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 PATIENTS p on p.PATID=spr.PATID
left join SP_REQUESTS_OCOM spo on spr.SP_ACCESSNUMBER=spo.SP_ACCESSNUMBER 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) 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'"; where spr.PATID=p.PATID and spr.SP_ACCESSNUMBER='$accessnumber'";
$query = $db->query($sql); $query = $db->query($sql);
$results = $query->getRowArray(); $results = $query->getRowArray();
$data = [ $data = [
'patnumber' => $results["patnumber"], 'patnumber' => $results["patnumber"],
'age' => $results[""], 'age' => $results[""],
'patname' => $results['Name'] ?? '', 'patname' => $results['Name'] ?? '',
'reqdate' => $results['REQDATE'] ?? '', 'reqdate' => $results['REQDATE'] ?? '',
'gender' => $results['Gender'] ?? '', 'gender' => $results['Gender'] ?? '',
'placeofbirth' => $results['DMG_CPLACEOFBIRTH'] ?? '', 'placeofbirth' => $results['DMG_CPLACEOFBIRTH'] ?? '',
'ktp' => $results['DMG_CKTPNO'] ?? '', 'ktp' => $results['DMG_CKTPNO'] ?? '',
'comment' => $results['COMMENTTEXT'] ?? '', 'comment' => $results['COMMENTTEXT'] ?? '',
'accessnumber' => $accessnumber, 'accessnumber' => $accessnumber,
]; ];
$samples = []; $samples = [];
$sql = "SELECT req.SAMPTYPEID, req.SAMPCODE, req.SHORTTEXT, tu.STATUS, st.TUBESTATUS $sql = "SELECT req.SAMPTYPEID, req.SAMPCODE, req.SHORTTEXT, tu.STATUS, st.TUBESTATUS
from GDC_CMOD.dbo.v_sp_reqtube req 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 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 left join glendb.dbo.SP_TUBES st on st.SP_ACCESSNUMBER=req.SP_ACCESSNUMBER and req.SAMPCODE=st.SAMPLETYPE
where req.SP_ACCESSNUMBER='$accessnumber'"; where req.SP_ACCESSNUMBER='$accessnumber'";
$query = $db->query($sql); $query = $db->query($sql);
$results = $query->getResultArray(); $results = $query->getResultArray();
foreach ($results as $row) { foreach ($results as $row) {
$samples[] = [ $samples[] = [
'samptypeid' => $row['SAMPTYPEID'] ?? null, 'samptypeid' => $row['SAMPTYPEID'] ?? null,
'sampcode' => $row['SAMPCODE'] ?? null, 'sampcode' => $row['SAMPCODE'] ?? null,
'name' => $row['SHORTTEXT'] ?? '', 'name' => $row['SHORTTEXT'] ?? '',
'colstatus' => $row['STATUS'] ?? '', 'colstatus' => $row['STATUS'] ?? '',
'tubestatus' => $row['TUBESTATUS'] ?? '', 'tubestatus' => $row['TUBESTATUS'] ?? '',
]; ];
} }
$data['samples'] = $samples; $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(); $db = \Config\Database::connect();
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$samplenumber = $input['samplenumber']; $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'"; $sql = "update GDC_CMOD.dbo.TUBES set USERID='$userid',STATUS='1', COLLECTIONDATE=getdate() where ACCESSNUMBER='$accessnumber' and TUBENUMBER='$samplenumber'";
$db->query($sql); $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())"; VALUES ('$accessnumber', '$samplenumber', '$userid', '1', getdate())";
$db->query($sql); $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(); $db = \Config\Database::connect();
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$samplenumber = $input['samplenumber']; $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'"; $sql = "update GDC_CMOD.dbo.TUBES set USERID='$userid',STATUS='0', COLLECTIONDATE=getdate() where ACCESSNUMBER='$accessnumber' and TUBENUMBER='$samplenumber'";
$db->query($sql); $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())"; VALUES ('$accessnumber', '$samplenumber', '$userid', '0', getdate())";
$db->query($sql); $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(); $db = \Config\Database::connect();
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$samplenumber = $input['samplenumber']; $samplenumber = $input['samplenumber'];
$sql = "select r.EXTERNALORDERNUMBER, dt.TESTCODE, do.HISCODE from glendb.dbo.TESTS t $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.DICT_TESTS dt on dt.TESTID=t.TESTID
left join glendb.dbo.REQUESTS r on r.REQUESTID=t.REQUESTID 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_TEST_SAMPLES dts on dts.TESTID=t.TESTID
left join glendb.dbo.DICT_SAMPLES_TYPES ds on ds.SAMPTYPEID=dts.SAMPTYPEID 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 left join GDC_CMOD.dbo.DICT_TESTS_ORDER do on do.TESTCODE=dt.TESTCODE
where t.DEPTH=0 where t.DEPTH=0
and r.ACCESSNUMBER='$accessnumber' and ds.SAMPCODE='$samplenumber'"; and r.ACCESSNUMBER='$accessnumber' and ds.SAMPCODE='$samplenumber'";
$rows = $db->query($sql)->getResultArray(); $rows = $db->query($sql)->getResultArray();
$his_test = ''; $his_test = '';
$lis_test = ''; $lis_test = '';
foreach( $rows as $row ) { foreach ($rows as $row) {
$hon = $row['EXTERNALORDERNUMBER']; $hon = $row['EXTERNALORDERNUMBER'];
$testcode = $row['TESTCODE']; $testcode = $row['TESTCODE'];
$hiscode = $row['HISCODE']; $hiscode = $row['HISCODE'];
$his_test .= "'$hiscode',"; $his_test .= "'$hiscode',";
$lis_test .= "'$testcode',"; $lis_test .= "'$testcode',";
} }
$his_test = rtrim($his_test,','); $his_test = rtrim($his_test, ',');
$lis_test = rtrim($lis_test,','); $lis_test = rtrim($lis_test, ',');
$conn = odbc_connect('GLENEAGLES','',''); $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)"; $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); $rs = odbc_exec($conn, $sql);
if (!$rs) {exit("Error in Update FB");} if (!$rs) {
exit("Error in Update FB");
}
$sql = "update SP_TUBES set TUBESTATUS=0 where SP_ACCESSNUMBER='$accessnumber' and SAMPLETYPE='$samplenumber' "; $sql = "update SP_TUBES set TUBESTATUS=0 where SP_ACCESSNUMBER='$accessnumber' and SAMPLETYPE='$samplenumber' ";
$db->query($sql); $db->query($sql);
$sql = "update SP_TESTS set SP_TESTSTATUS=NULL where SP_ACCESSNUMBER='$accessnumber' and SP_TESTCODE in ($lis_test)"; $sql = "update SP_TESTS set SP_TESTSTATUS=NULL where SP_ACCESSNUMBER='$accessnumber' and SP_TESTCODE in ($lis_test)";
$db->query($sql); $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);
} }
} }

View File

@ -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!']);
}
}

View 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']);
}
}

View File

@ -28,68 +28,45 @@
</head> </head>
<body class="bg-base-200 min-h-screen" x-data="main"> <body class="bg-base-200 min-h-screen" x-data="main">
<div class="drawer"> <div class="flex flex-col min-h-screen">
<input id="main-drawer" type="checkbox" class="drawer-toggle" /> <!-- Navbar -->
<div class="drawer-content flex flex-col min-h-screen"> <nav class="navbar bg-base-100 shadow-md px-6 z-20">
<!-- Navbar --> <div class='flex-1'>
<nav class="navbar bg-base-100 shadow-md px-6 z-20"> <a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
<div class="flex-none"> <i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost"> Admin Dashboard</span>
<i class="fa fa-bars"></i> </a>
</label> </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>
<div class='flex-1'> <div class="dropdown dropdown-end">
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'> <div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
<i class="fa fa-cube"></i> CMOD <span <span class="text-xl"><i class="fa fa-bars"></i></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> </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> </div>
</nav> </div>
</nav>
<!-- Page Content --> <!-- Page Content -->
<?= $this->renderSection('content'); ?> <?= $this->renderSection('content'); ?>
<?= $this->include('admin/dialog_setPassword'); ?> <?= $this->include('admin/dialog_setPassword'); ?>
<footer class='bg-base-100 p-1 mt-auto'>&copy; <?= date('Y'); ?> - 5Panda</footer> <footer class='bg-base-100 p-1 mt-auto'>&copy; <?= 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>
</div> </div>
<script> <script>
window.BASEURL = "<?= base_url("admin"); ?>"; window.BASEURL = "<?= base_url(); ?>";
</script> </script>
<?= $this->renderSection('script'); ?> <?= $this->renderSection('script'); ?>
</body> </body>

View File

@ -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(); ?>

View File

@ -28,67 +28,45 @@
</head> </head>
<body class="bg-base-200 min-h-screen" x-data="main"> <body class="bg-base-200 min-h-screen" x-data="main">
<div class="drawer"> <div class="flex flex-col min-h-screen">
<input id="main-drawer" type="checkbox" class="drawer-toggle" /> <!-- Navbar -->
<div class="drawer-content flex flex-col min-h-screen"> <nav class="navbar bg-base-100 shadow-md px-6 z-20">
<!-- Navbar --> <div class='flex-1'>
<nav class="navbar bg-base-100 shadow-md px-6 z-20"> <a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
<div class="flex-none"> <i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost"> Customer Service Dashboard</span>
<i class="fa fa-bars"></i> </a>
</label> </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>
<div class='flex-1'> <div class="dropdown dropdown-end">
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'> <div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
<i class="fa fa-cube"></i> CMOD <span <span class="text-xl"><i class="fa fa-bars"></i></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> </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> </div>
</nav> </div>
</nav>
<!-- Page Content --> <!-- Page Content -->
<?= $this->renderSection('content'); ?> <?= $this->renderSection('content'); ?>
<?= $this->include('cs/dialog_setPassword'); ?> <?= $this->include('cs/dialog_setPassword'); ?>
<footer class='bg-base-100 p-1 mt-auto'>&copy; <?= date('Y'); ?> - 5Panda</footer> <footer class='bg-base-100 p-1 mt-auto'>&copy; <?= 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>
</div> </div>
<script> <script>
window.BASEURL = "<?= base_url("cs"); ?>"; window.BASEURL = "<?= base_url(); ?>";
</script> </script>
<?= $this->renderSection('script'); ?> <?= $this->renderSection('script'); ?>
</body> </body>

View File

@ -28,67 +28,45 @@
</head> </head>
<body class="bg-base-200 min-h-screen" x-data="main"> <body class="bg-base-200 min-h-screen" x-data="main">
<div class="drawer"> <div class="flex flex-col min-h-screen">
<input id="main-drawer" type="checkbox" class="drawer-toggle" /> <!-- Navbar -->
<div class="drawer-content flex flex-col min-h-screen"> <nav class="navbar bg-base-100 shadow-md px-6 z-20">
<!-- Navbar --> <div class='flex-1'>
<nav class="navbar bg-base-100 shadow-md px-6 z-20"> <a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
<div class="flex-none"> <i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost"> Lab Analyst Dashboard</span>
<i class="fa fa-bars"></i> </a>
</label> </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>
<div class='flex-1'> <div class="dropdown dropdown-end">
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'> <div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
<i class="fa fa-cube"></i> CMOD <span <span class="text-xl"><i class="fa fa-bars"></i></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> </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> </div>
</nav> </div>
</nav>
<!-- Page Content --> <!-- Page Content -->
<?= $this->renderSection('content'); ?> <?= $this->renderSection('content'); ?>
<?= $this->include('lab/dialog_setPassword'); ?> <?= $this->include('lab/dialog_setPassword'); ?>
<footer class='bg-base-100 p-1 mt-auto'>&copy; <?= date('Y'); ?> - 5Panda</footer> <footer class='bg-base-100 p-1 mt-auto'>&copy; <?= 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>
</div> </div>
<script> <script>
window.BASEURL = "<?= base_url("lab"); ?>"; window.BASEURL = "<?= base_url(); ?>";
</script> </script>
<?= $this->renderSection('script'); ?> <?= $this->renderSection('script'); ?>
</body> </body>

View File

@ -28,67 +28,45 @@
</head> </head>
<body class="bg-base-200 min-h-screen" x-data="main"> <body class="bg-base-200 min-h-screen" x-data="main">
<div class="drawer"> <div class="flex flex-col min-h-screen">
<input id="main-drawer" type="checkbox" class="drawer-toggle" /> <!-- Navbar -->
<div class="drawer-content flex flex-col min-h-screen"> <nav class="navbar bg-base-100 shadow-md px-6 z-20">
<!-- Navbar --> <div class='flex-1'>
<nav class="navbar bg-base-100 shadow-md px-6 z-20"> <a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
<div class="flex-none"> <i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost"> Phlebotomist Dashboard</span>
<i class="fa fa-bars"></i> </a>
</label> </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>
<div class='flex-1'> <div class="dropdown dropdown-end">
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'> <div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
<i class="fa fa-cube"></i> CMOD <span <span class="text-xl"><i class="fa fa-bars"></i></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> </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> </div>
</nav> </div>
</nav>
<!-- Page Content --> <!-- Page Content -->
<?= $this->renderSection('content'); ?> <?= $this->renderSection('content'); ?>
<?= $this->include('phlebo/dialog_setPassword'); ?> <?= $this->include('phlebo/dialog_setPassword'); ?>
<footer class='bg-base-100 p-1 mt-auto'>&copy; <?= date('Y'); ?> - 5Panda</footer> <footer class='bg-base-100 p-1 mt-auto'>&copy; <?= 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>
</div> </div>
<script> <script>
window.BASEURL = "<?= base_url("phlebo"); ?>"; window.BASEURL = "<?= base_url(); ?>";
</script> </script>
<?= $this->renderSection('script'); ?> <?= $this->renderSection('script'); ?>
</body> </body>

View File

@ -28,68 +28,46 @@
</head> </head>
<body class="bg-base-200 min-h-screen" x-data="main"> <body class="bg-base-200 min-h-screen" x-data="main">
<div class="drawer"> <div class="flex flex-col min-h-screen">
<input id="main-drawer" type="checkbox" class="drawer-toggle" /> <!-- Navbar -->
<div class="drawer-content flex flex-col min-h-screen"> <nav class="navbar bg-base-100 shadow-md px-6 z-20">
<!-- Navbar --> <div class='flex-1'>
<nav class="navbar bg-base-100 shadow-md px-6 z-20"> <a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'>
<div class="flex-none"> <i class="fa fa-cube"></i> CMOD <span class="text-base-content/40 font-light text-sm hidden sm:inline-block">|
<label for="main-drawer" aria-label="open sidebar" class="btn btn-square btn-ghost"> Superuser Dashboard</span>
<i class="fa fa-bars"></i> </a>
</label> </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>
<div class='flex-1'> <div class="dropdown dropdown-end">
<a class='text-xl text-primary font-bold tracking-wide flex items-center gap-2 ml-2'> <div tabindex="0" role="button" class="btn btn-ghost avatar placeholder px-2">
<i class="fa fa-cube"></i> CMOD <span <span class="text-xl"><i class="fa fa-bars"></i></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> </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> </div>
</nav> </div>
</nav>
<!-- Page Content --> <!-- Page Content -->
<?= $this->renderSection('content'); ?> <?= $this->renderSection('content'); ?>
<?= $this->include('superuser/dialog_setPassword'); ?> <?= $this->include('superuser/dialog_setPassword'); ?>
<footer class='bg-base-100 p-1 mt-auto'>&copy; <?= date('Y'); ?> - 5Panda</footer> <footer class='bg-base-100 p-1 mt-auto'>&copy; <?= 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>
</div> </div>
<script> <script>
window.BASEURL = "<?= base_url("superuser"); ?>"; window.BASEURL = "<?= base_url(); ?>";
</script> </script>
<?= $this->renderSection('script'); ?> <?= $this->renderSection('script'); ?>
</body> </body>

View File

@ -1,175 +1,198 @@
<?= $this->extend('superuser/main'); ?> <?= $this->extend('superuser/main'); ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<div x-data="users" class="contents"> <div x-data="users" class="contents">
<main class="p-4 flex-1 flex flex-col gap-2 max-w-6xl w-full mx-auto"> <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 bg-base-100 shadow-xl border border-base-200">
<div class="card-body p-4"> <div class="card-body p-4">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content"> <h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
<i class="fa fa-users text-primary"></i> User Management <i class="fa fa-users text-primary"></i> User Management
</h2> </h2>
<button class="btn btn-primary btn-sm" @click="openUserModal('create')"> <button class="btn btn-primary btn-sm" @click="openUserModal('create')">
<i class="fa fa-plus"></i> Add User <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> </button>
</div> </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> </div>
<form method="dialog" class="modal-backdrop"> </div>
<button @click="closeModal()">close</button> </main>
</form>
</dialog> <!-- User Modal -->
</div> <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->endSection(); ?>
<?= $this->section('script') ?> <?= $this->section('script') ?>
<script type="module"> <script type="module">
import Alpine from '<?=base_url("js/app.js");?>'; import Alpine from '<?= base_url("js/app.js"); ?>';
document.addEventListener('alpine:init', () => { document.addEventListener('alpine:init', () => {
Alpine.data("users", () => ({ Alpine.data("users", () => ({
@ -179,7 +202,8 @@
errorMsg: '', errorMsg: '',
form: { form: {
userid: '', userid: '',
userlevel: '', username: '',
userroleid: '',
password: '', password: '',
password_2: '' password_2: ''
}, },
@ -200,7 +224,7 @@
}, },
getRoleName(level) { getRoleName(level) {
const map = { 1: 'Administrator', 2: 'Lab', 3: 'Phlebotomist', 4: 'Customer Service' }; const map = <?= json_encode(ROLE_NAMES) ?>;
return map[level] || 'Unknown (' + level + ')'; return map[level] || 'Unknown (' + level + ')';
}, },
@ -212,16 +236,17 @@
openUserModal(targetMode, user = null) { openUserModal(targetMode, user = null) {
this.mode = targetMode; this.mode = targetMode;
this.errorMsg = ''; this.errorMsg = '';
if (targetMode === 'edit' && user) { if (targetMode === 'edit' && user) {
this.form = { this.form = {
userid: user.USERID, userid: user.USERID,
userlevel: user.USERLEVEL, username: user.USERNAME,
userroleid: user.USERROLEID,
password: '', password: '',
password_2: '' password_2: ''
}; };
} else { } else {
this.form = { userid: '', userlevel: '', password: '', password_2: '' }; this.form = { userid: '', username: '', userroleid: '', password: '', password_2: '' };
} }
document.getElementById('user_modal').showModal(); document.getElementById('user_modal').showModal();
}, },
@ -236,7 +261,7 @@
try { try {
let res; let res;
if(this.mode == 'create') { if (this.mode == 'create') {
res = await fetch(`${BASEURL}/api/users`, { res = await fetch(`${BASEURL}/api/users`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -265,5 +290,5 @@
}); });
Alpine.start(); Alpine.start();
</script> </script>
<?= $this->endSection(); ?> <?= $this->endSection(); ?>