diff --git a/.gitignore b/.gitignore index 364a1bc..8d85d65 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,4 @@ _modules/* /results/ /phpunit*.xml +.roo/ \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..88b9211 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,267 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Project Overview + +This is a CodeIgniter 4 PHP application for a laboratory management system (CMOD). It uses SQL Server database with role-based access control for different user types (admin, doctor/analyst, customer service). + +## Commands + +```bash +# Run all tests +./vendor/bin/phpunit +composer test + +# Run single test file +./vendor/bin/phpunit tests/unit/HealthTest.php + +# Run single test method +./vendor/bin/phpunit tests/unit/HealthTest.php --filter testIsDefinedAppPath + +# Development server (Linux/Mac) +php spark serve + +# List all routes +php spark list + +# Create controller +php spark make:controller Admin + +# Create model +php spark make:model User +``` + +## Code Style Guidelines + +### PHP Standards +- Use PHP 8.1+ features (typed properties, match expressions where appropriate) +- Always declare return types for public methods +- Use `strict_types=1` not required (CodeIgniter doesn't use it) +- No comments unless explaining complex logic (per project convention) + +### Naming Conventions +- **Classes**: PascalCase (e.g., `Admin`, `UserController`) +- **Methods**: camelCase (e.g., `index()`, `getUsers()`) +- **Variables**: camelCase (e.g., `$userId`, `$dataList`) +- **Constants**: UPPER_SNAKE_CASE (e.g., `DB_HOST`) +- **Database tables**: UPPER_SNAKE_CASE (e.g., `GDC_CMOD.dbo.USERS`) +- **Views**: lowercase with underscores (e.g., `admin/index.php`) + +### Controller Patterns + +#### Base Controllers +- All controllers extend `App\Controllers\BaseController` +- BaseController extends CodeIgniter\Controller + +```php +namespace App\Controllers; + +class Admin extends BaseController { + public function index() { + // Method body + } +} +``` + +#### API Controllers +- Controllers use `ResponseTrait` for API responses +- Use `$this->respond()` or `$this->response->setJSON()` for responses + +```php +namespace App\Controllers; + +use App\Controllers\BaseController; +use CodeIgniter\API\ResponseTrait; + +class Users extends BaseController { + use ResponseTrait; + + protected $db; + + public function __construct() { + $this->db = \Config\Database::connect(); + helper(['url', 'form', 'text']); + } + + public function index() { + $query = $this->db->query("SELECT * FROM table"); + return $this->respond(['data' => $query->getResultArray()]); + } +} +``` + +### Database Operations + +#### Connection Pattern +```php +$this->db = \Config\Database::connect(); +``` + +#### Query Methods +- `getRowArray()` - returns single row as associative array +- `getResultArray()` - returns multiple rows as array of arrays +- Use parameterized queries to prevent SQL injection + +```php +$query = $this->db->query("SELECT * FROM table WHERE id = ?", [$id]); +$row = $query->getRowArray(); +$results = $query->getResultArray(); +``` + +#### Transactions +```php +$this->db->transBegin(); +try { + $this->db->query("INSERT INTO ...", [$data]); + $this->db->transCommit(); +} catch (\Throwable $e) { + $this->db->transRollback(); + return $this->response->setJSON(['message' => 'Error']); +} +``` + +### Session Management + +#### Session Structure +```php +$session->set([ + 'isLoggedIn' => true, + 'userid' => (string) $user['USERID'], + 'userlevel' => (int) $user['USERLEVEL'], + 'userrole' => (string) $role, // 'admin', 'doctor', 'analyst', 'cs' +]); +``` + +#### Session Values +- `isLoggedIn`: bool +- `userid`: string +- `userlevel`: int +- `userrole`: string + +### Role-Based Access Control + +#### Role Values +- `1` = admin +- `2` = doctor (or lab/analyst) +- `3` = analyst +- `4` = cs (customer service) + +#### Route Filter Syntax +```php +// Single role +['filter' => 'role:1'] + +// Multiple roles +['filter' => 'role:1,2'] +``` + +### Request/Response Patterns + +#### Getting Input +```php +// POST data +$input = $this->request->getJSON(true); +$userid = $input['userid']; + +// Query parameters +$date1 = $this->request->getVar('date1') ?? date('Y-m-d'); +``` + +#### JSON Response (V2 API) +```php +return $this->respond(['data' => $results]); +return $this->response->setJSON(['message' => 'Success']); +``` + +#### View Response (Traditional) +```php +return view('admin/index', $data); +``` + +#### Redirect with Errors +```php +return redirect()->back()->with('errors', ['key' => 'message']); +``` + +### API Endpoint Patterns + +#### Validation Endpoints +- `POST /api/{resource}/validate/{id}` - validate a record +- `DELETE /api/{resource}/validate/{id}` - unvalidate a record + +#### Route Examples +```php +// Admin routes +$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'); +}); + +// Lab routes +$routes->group('lab', ['filter' => 'role:2'], function($routes) { + $routes->get('/', 'Lab::index'); + $routes->get('api/requests', 'Requests::index'); +}); +``` + +### Error Handling + +#### Database Operations +```php +try { + // DB operations +} catch (\Throwable $e) { + // Handle error + return $this->response->setJSON(['message' => 'Server error']); +} +``` + +#### Validation Errors +```php +if ($condition) { + return $this->response->setJSON(['message' => 'Error message']); +} +``` + +### Views + +#### View Pattern +- Views are plain PHP files in `app/Views/` +- Use `` syntax +- Pass data as associative array + +```php +// Controller +$data['dataList'] = $results; +return view('admin/index', $data); + +// View + + + +``` + +### Security + +- Always use parameterized queries (never interpolate directly) +- Use `esc()` when outputting user data in views +- Hash passwords with `password_hash()` and verify with `password_verify()` +- Validate and sanitize all input before use + +### Helper Functions + +Available helpers loaded via `helper(['name', 'name2'])`: +- `url` - URL helpers +- `form` - Form helpers +- `text` - Text formatting + +### Database Schema + +- Database: SQL Server +- Schema: `dbo` +- Main database: `GDC_CMOD` +- Reference database: `glendb` +- Table naming: `GDC_CMOD.dbo.TABLENAME` diff --git a/README.md b/README.md index d14b4c9..5da909d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,47 @@ to your `app` folder. The affected files can be copied or merged from Copy `env` to `.env` and tailor for your app, specifically the baseURL and any database settings. +## Role-Based Access Control + +This application uses role-based access control with four user roles. + +### User Roles + +| Role | Level | Access | +|------|-------|--------| +| Admin | 1 | All functions | +| Lab | 2 | All functions | +| Phlebo | 3 | Specimen collection, Dashboard | +| CS | 4 | Dashboard | + +### Feature Categories by Role + +#### Admin +- **Dashboard** - View all requests with status filters (Pend, Coll, Recv, Inc, Fin, Val) +- **User Management** - Create, edit, delete users; assign roles +- **Request Management** - View, validate, unvalidate all requests +- **Sample Management** - Collect, uncollect, receive, unreceive samples +- **Result Management** - Preview and print results + +#### Lab +- **Dashboard** - View requests with status filters +- **Request Validation** - Validate/unvalidate requests (2-level validation) +- **Sample Management** - Collect samples, mark received +- **Result Preview** - Preview and print results + +#### Phlebo +- **Dashboard** - View pending collections +- **Specimen Collection** - Log collected specimens + +#### CS (Customer Service) +- **Dashboard** - View-only request tracking +- **Status Monitoring** - Filter by request status +- **Patient Inquiry** - View request details + +### Route Prefixes +- Admin: `/admin` +- Lab: `/lab` + ## Important Change with index.php `index.php` is no longer in the root of the project! It has been moved inside the *public* folder, diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..2d05bc5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,129 @@ +# CMOD Project TODO + +## In Progress + +### V2 Namespace Removal +- [ ] Complete migration of Doctor role to new architecture +- [ ] Complete migration of Analyst role to new architecture +- [ ] Complete migration of CS (Customer Service) role to new architecture + +## Pending + +### Print Functionality +- [ ] Refactor print functionality from external URL (`http://glenlis/spooler_db/main_dev.php`) to internal solution +- [ ] Add print preview capability to Admin views +- [ ] Add print preview capability to Lab views +- [ ] Implement server-side PDF generation for print jobs + +### API Improvements +- [ ] Add pagination to Users API endpoint +- [ ] Add pagination to Requests API endpoint +- [ ] Add search/filter capability to list endpoints +- [ ] Add audit logging for critical operations (create/update/delete user, validate/unvalidate request) + +### Frontend Improvements +- [ ] Add loading states to all API calls +- [ ] Add toast notifications for success/error feedback +- [ ] Implement form validation with clear error messages +- [ ] Add confirmation dialogs for destructive actions (delete, unvalidate) + +### Role-Based Access Control +- [ ] Document current permission matrix for each role +- [ ] Add permission checks to API endpoints +- [ ] Create shared permission constants in a central location + +### Testing +- [ ] Set up automated tests for API endpoints +- [ ] Add unit tests for controller logic +- [ ] Add integration tests for critical workflows + +### Documentation +- [ ] Update README with current architecture overview +- [ ] Document API endpoints with examples +- [ ] Document database schema changes + +## Completed + +### V2 Namespace Removal +- [x] Created AGENTS.md with project conventions +- [x] Moved v2/admin views to views/admin +- [x] Moved v2/lab views to views/lab +- [x] Renamed V2.php controller to Auth.php +- [x] Renamed V2/Admin.php controller to Admin.php +- [x] Renamed V2/Lab.php controller to Lab.php +- [x] Renamed V2/Users.php controller to Users.php +- [x] Renamed V2/Samples.php controller to Samples.php +- [x] Renamed V2/Requests.php controller to Requests.php +- [x] Updated routes to remove v2 prefix +- [x] Updated view paths in all controllers +- [x] Fixed hardcoded date bug in views (changed to dynamic date) +- [x] Fixed status color mappings (added PartColl, PartRecv, partial statuses) +- [x] Fixed missing variables in Samples controller +- [x] Fixed duplicate db_connect() calls in Requests controller +- [x] Fixed id parameter in Users::update() +- [x] Cleaned up V2 namespace directory +- [x] Cleaned up old controller files (Admin.php, Doctor.php, Analyst.php, CustomerService.php) +- [x] Backed up old views to views/backup/ +- [x] Updated AGENTS.md with new code examples + +## Backlog + +### Features +- [ ] Add user profile page with activity history +- [ ] Add bulk import for users +- [ ] Add bulk operations for sample management +- [ ] Add export to CSV/Excel functionality +- [ ] Add dashboard analytics and statistics + +### Technical Debt +- [ ] Remove unused dependencies from composer.json +- [ ] Clean up unused view files in views/backup/ +- [ ] Add type hints to all controller methods +- [ ] Add return type declarations to all controller methods +- [ ] Consolidate duplicate code in dialog components + +### Security +- [ ] Add rate limiting to login endpoint +- [ ] Add CSRF protection to forms +- [ ] Implement password strength requirements +- [ ] Add session timeout configuration +- [ ] Audit all SQL queries for potential injection vulnerabilities + +## Notes + +### Print Functionality Current State +The current print implementation uses an external URL that opens a separate window: +```javascript +BASEURL + 'http://glenlis/spooler_db/main_dev.php?req_id=' + req_id +``` +This should be replaced with: +1. Server-side PDF generation using a library like TCPDF or Dompdf +2. Display PDF in an iframe for preview before print +3. Send directly to printer using browser print API or WebSocket to print server + +### Role Permissions +- **Admin (1)**: Full access to all features including user management +- **Doctor/Lab (2)**: Sample collection, validation +- **Analyst (3)**: Sample validation, report generation +- **CS (4)**: Read-only access to requests and samples + +### Database Tables +- `GDC_CMOD.dbo.USERS` - User accounts +- `GDC_CMOD.dbo.REQUESTS` - Test requests +- `GDC_CMOD.dbo.SAMPLES` - Sample records +- `glendb.dbo.*` - Reference data (tests, panels, etc.) + +### API Endpoints +All API endpoints return JSON responses and should follow consistent format: +```json +{ + "data": [...] // or single object for GET by ID +} +``` + +Error responses: +```json +{ + "message": "Error description" +} +``` diff --git a/app/Config/Routes.php b/app/Config/Routes.php index b1f81c4..68626c7 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -2,7 +2,6 @@ use CodeIgniter\Router\RouteCollection; -// ------------------------------------------------------For Error Handling------------------------------------------------------ // $routes->set404Override(function() { $response = service('response'); $response->setStatusCode(404); @@ -11,96 +10,39 @@ $routes->set404Override(function() { $routes->get('/unauthorized', 'ErrorPage::unauthorized'); -// ------------------------------------------------------Basic Page and Login/Logout------------------------------------------------------ // $routes->get('/', 'Home::index'); -$routes->match(['get','post'],'/login', 'Auth::login', ['filter' => 'guest']); +$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('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'); -// ------------------------------------------------------Page Based on Role------------------------------------------------------ // $routes->group('admin', ['filter' => 'role:1'], function($routes) { - $routes->get('/', 'Admin::index'); - $routes->get('modal_specimen', 'Admin::modal_specimen'); - $routes->get('user', 'User::index'); - $routes->post('user/create', 'User::create'); - $routes->post('user/update', 'User::update'); - $routes->post('user/delete', 'User::delete'); + $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'); }); -$routes->group('doctor', ['filter' => 'role:2'], function($routes) { - $routes->get('/', 'Doctor::index'); - $routes->get('modal_specimen', 'Doctor::modal_specimen'); +$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('analyst', ['filter' => 'role:3'], function($routes) { - $routes->get('/', 'Analyst::index'); - $routes->get('modal_specimen', 'Analyst::modal_specimen'); -}); - -$routes->group('cs', ['filter' => 'role:4'], function($routes) { - $routes->get('/', 'CustomerService::index'); -}); - -// dummy long page $routes->get('/dummypage', 'Home::dummyPage'); - -// ------------------------------------------------------For API------------------------------------------------------ // -// $routes->get('/api/dashboard', 'ApiDashboard::index'); -$routes->get('api/request/unvalidate/(:any)', 'Request::showUnval/$1'); -$routes->get('api/request/validate/(:any)', 'Request::show/$1'); - -$routes->post('api/request/validate/(:any)', 'Request::val/$1'); -$routes->delete('api/request/validate/(:any)', 'Request::unval/$1'); -$routes->get('api/request', 'Request::index'); -$routes->get('api/sample/(:any)', 'Sample::show/$1'); -$routes->post('api/sample/collect/(:any)', 'Sample::collect/$1'); -$routes->delete('api/sample/collect/(:any)', 'Sample::uncollect/$1'); -$routes->delete('api/sample/receive/(:any)', 'Sample::unreceive/$1'); - - -$routes->get('api/specimen/(:any)', 'Specimen::show/$1'); -$routes->post('api/specimen/collect/(:any)', 'Specimen::collect/$1'); -$routes->delete('api/specimen/receive/(:any)', 'Specimen::unreceive/$1'); - -/*- lets go alpine -*/ - -$routes->group('v2', function($routes) { - $routes->get('', 'V2::index'); - $routes->get('login', 'V2::loginPage'); - $routes->post('login', 'V2::login'); - $routes->get('logout', 'V2::logout'); - $routes->patch('setPassword', 'V2::setPassword'); - // Admin - $routes->group('admin', ['filter' => 'role:1'], function($routes) { - $routes->get('', 'V2\Admin::index'); - $routes->get('users', 'V2\Admin::users'); - // Users - $routes->get('api/users', 'V2\Users::index'); - $routes->post('api/users', 'V2\Users::create'); - $routes->patch('api/users/(:any)', 'V2\Users::update/$1'); - $routes->delete('api/users/(:any)', 'V2\Users::delete/$1'); - // Request - $routes->get('api/requests', 'V2\Requests::index'); - $routes->post('api/requests/validate/(:any)', 'V2\Requests::val/$1'); - $routes->delete('api/requests/validate/(:any)', 'V2\Requests::unval/$1'); - // Samples - $routes->post('api/samples/collect/(:any)', 'V2\Samples::collect/$1'); - $routes->delete('api/samples/collect/(:any)', 'V2\Samples::uncollect/$1'); - $routes->delete('api/samples/receive/(:any)', 'V2\Samples::unreceive/$1'); - $routes->get('api/samples/(:any)', 'V2\Samples::show/$1'); - }); - // Lab - $routes->group('lab', ['filter' => 'role:2'], function($routes) { - $routes->get('', 'V2\Lab::index'); - // Request - $routes->get('api/requests', 'V2\Requests::index'); - $routes->post('api/requests/validate/(:any)', 'V2\Requests::val/$1'); - $routes->delete('api/requests/validate/(:any)', 'V2\Requests::unval/$1'); - // Samples - $routes->post('api/samples/collect/(:any)', 'V2\Samples::collect/$1'); - $routes->get('api/samples/(:any)', 'V2\Samples::show/$1'); - }); -}); \ No newline at end of file diff --git a/app/Controllers/Admin.php b/app/Controllers/Admin.php index db43355..55ea447 100644 --- a/app/Controllers/Admin.php +++ b/app/Controllers/Admin.php @@ -1,116 +1,21 @@ request->getVar('date1') ?? $today; - $date2 = $this->request->getVar('date2') ?? $today; - - $db = \Config\Database::connect(); - $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'"; - $query = $db->query($sql); - $results = $query->getResultArray(); - - // === Variabel counter === - $CPending = 0; $CPColl = 0; $CColl = 0; $CPRecv = 0; - $CRecv = 0; $CInc = 0; $CPenV = 0; $CFin = 0; $CFinV = 0; - $CTotal = 0; - - $dataList = []; - - foreach ($results as $row) { - $reqdate = ''; - if (!empty($row['REQDATE'])) { - $reqdate = date('Y-m-d H:i', strtotime($row['REQDATE'])); - } - $patname = $row['Name'] ?? ''; - $sp_accessnumber = $row['SP_ACCESSNUMBER'] ?? ''; - $hostordernumber = $row['HOSTORDERNUMBER'] ?? ''; - $stats = $row['STATS'] ?? ''; - $tests = $row['TESTS'] ?? ''; - $isDelete = $row['ISDELETE'] ?? 0; - - // Bersihkan test string - $test = str_replace(['(', ')', ',', 'FA'], '', $tests); - - if (!is_numeric($test) && $isDelete == 0) { - switch ($stats) { - case 'Pend': $statscode = 1; $CPending++; break; - case 'PartColl':$statscode = 2; $CPColl++; break; - case 'Coll': $statscode = 3; $CColl++; break; - case 'PartRecv':$statscode = 4; $CPRecv++; break; - case 'Recv': $statscode = 5; $CRecv++; break; - case 'Inc': $statscode = 6; $CInc++; break; - case 'PenV': $statscode = 7; $CPenV++; break; - case 'Fin': $statscode = 8; $CFin++; break; - case 'FinV': $statscode = 9; $CFinV++; break; - default: $statscode = 0; break; - } - $CTotal++; - // Simpan ke array - $dataList[] = [ - 'statscode' => $statscode, - 'reqdate' => $reqdate, - 'patname' => $patname, - 'sp_accessnumber' => $sp_accessnumber, - 'hostordernumber' => $hostordernumber, - 'reff' => $row['REFF'] ?? '', - 'doc' => $row['DOC'] ?? '', - 'tests' => $row['TESTS'] ?? '', - 'stats' => $stats, - 'odr_cresult_to' => $row['ODR_CRESULT_TO'], - 'isprinted'=> $row['ODR_ISPRINTED'] ?? 0, - 'ispending'=> $row['ODR_ISPENDING'] ?? 0, - 'ishardcopy'=> $row['ODR_NFLAGHARDCOPY'] ?? 0, - 'isvaltd' => $row['ISVAL'] ?? 0, - // 'isval1' => $row['ISVAL1'] ?? 0, - // 'isval2' => $row['ISVAL2'] ?? 0, - 'isdelete' => $isDelete, - 'valcounter' => $row['VAL'] ?? 0, - 'val1user' => $row['VAL1USER'] ?? '-', - 'val2user' => $row['VAL2USER'] ?? '-', - ]; - - } - } - - // === Total counter === - $counter = [ - 'pending' => $CPending, - 'collect' => $CPColl+$CColl, - 'recv' => $CPRecv+$CRecv, - 'incomplete' => $CInc+$CPenV, - 'complete' => $CFin+$CFinV, - 'total' => $CTotal, - /* - 'partialCollected' => $CPColl, - 'collected' => $CColl, - 'partialReceived' => $CPRecv, - 'received' => $CRecv, - 'incomplete' => $CInc, - 'pendingValidation' => $CPenV, - 'final' => $CFin, - 'finalValidation' => $CFinV, - */ - ]; - - $data['dataList'] = $dataList; - $data['counter'] = $counter; - $data['date1'] = $date1; - $data['date2'] = $date2; - - // dd($results); - - - return view('admin/index', $data); + return view('admin/index'); } - public function modal_specimen() { - return view('admin/modal_specimen'); + public function users() { + return view('admin/users'); } - + } diff --git a/app/Controllers/Analyst.php b/app/Controllers/Analyst.php deleted file mode 100644 index 1966ab2..0000000 --- a/app/Controllers/Analyst.php +++ /dev/null @@ -1,115 +0,0 @@ -request->getVar('date1') ?? $today; - $date2 = $this->request->getVar('date2') ?? $today; - - $db = \Config\Database::connect(); - $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'"; - $query = $db->query($sql); - $results = $query->getResultArray(); - - // === Variabel counter === - $CPending = 0; $CPColl = 0; $CColl = 0; $CPRecv = 0; - $CRecv = 0; $CInc = 0; $CPenV = 0; $CFin = 0; $CFinV = 0; - $CTotal = 0; - - $dataList = []; - - foreach ($results as $row) { - $reqdate = ''; - if (!empty($row['REQDATE'])) { - $reqdate = date('Y-m-d H:i', strtotime($row['REQDATE'])); - } - $patname = $row['Name'] ?? ''; - $sp_accessnumber = $row['SP_ACCESSNUMBER'] ?? ''; - $hostordernumber = $row['HOSTORDERNUMBER'] ?? ''; - $stats = $row['STATS'] ?? ''; - $tests = $row['TESTS'] ?? ''; - $isDelete = $row['ISDELETE'] ?? 0; - - // Bersihkan test string - $test = str_replace(['(', ')', ',', 'FA'], '', $tests); - - if (!is_numeric($test) && $isDelete == 0) { - switch ($stats) { - case 'Pend': $statscode = 1; $CPending++; break; - case 'PartColl':$statscode = 2; $CPColl++; break; - case 'Coll': $statscode = 3; $CColl++; break; - case 'PartRecv':$statscode = 4; $CPRecv++; break; - case 'Recv': $statscode = 5; $CRecv++; break; - case 'Inc': $statscode = 6; $CInc++; break; - case 'PenV': $statscode = 7; $CPenV++; break; - case 'Fin': $statscode = 8; $CFin++; break; - case 'FinV': $statscode = 9; $CFinV++; break; - default: $statscode = 0; break; - } - $CTotal++; - // Simpan ke array - $dataList[] = [ - 'statscode' => $statscode, - 'reqdate' => $reqdate, - 'patname' => $patname, - 'sp_accessnumber' => $sp_accessnumber, - 'hostordernumber' => $hostordernumber, - 'reff' => $row['REFF'] ?? '', - 'doc' => $row['DOC'] ?? '', - 'tests' => $row['TESTS'] ?? '', - 'stats' => $stats, - 'odr_cresult_to' => $row['ODR_CRESULT_TO'], - 'isprinted'=> $row['ODR_ISPRINTED'] ?? 0, - 'ispending'=> $row['ODR_ISPENDING'] ?? 0, - 'ishardcopy'=> $row['ODR_NFLAGHARDCOPY'] ?? 0, - 'isvaltd' => $row['ISVAL'] ?? 0, - // 'isval1' => $row['ISVAL1'] ?? 0, - // 'isval2' => $row['ISVAL2'] ?? 0, - 'isdelete' => $isDelete, - 'valcounter' => $row['VAL'] ?? 0, - 'val1user' => $row['VAL1USER'] ?? '-', - 'val2user' => $row['VAL2USER'] ?? '-', - ]; - - } - } - - // === Total counter === - $counter = [ - 'pending' => $CPending, - 'collect' => $CPColl+$CColl, - 'recv' => $CPRecv+$CRecv, - 'incomplete' => $CInc+$CPenV, - 'complete' => $CFin+$CFinV, - 'total' => $CTotal, - /* - 'partialCollected' => $CPColl, - 'collected' => $CColl, - 'partialReceived' => $CPRecv, - 'received' => $CRecv, - 'incomplete' => $CInc, - 'pendingValidation' => $CPenV, - 'final' => $CFin, - 'finalValidation' => $CFinV, - */ - ]; - - $data['dataList'] = $dataList; - $data['counter'] = $counter; - $data['date1'] = $date1; - $data['date2'] = $date2; - - // dd($results); - - - return view('analyst/index', $data); - } - - public function modal_specimen() { - return view('analyst/modal_specimen'); - } - -} diff --git a/app/Controllers/Auth.php b/app/Controllers/Auth.php index 5aac2bf..988f550 100644 --- a/app/Controllers/Auth.php +++ b/app/Controllers/Auth.php @@ -2,60 +2,85 @@ namespace App\Controllers; +use App\Controllers\BaseController; + class Auth extends BaseController { + + public function loginPage() { + return view("login"); + } + public function login() { - if ($this->request->getMethod() === 'GET') { - return view('login'); - } else if ($this->request->getMethod() === 'POST') { - helper(['form', 'url']); - $session = session(); - $db = \Config\Database::connect(); + helper(['form', 'url']); + $session = session(); + $db = \Config\Database::connect(); - $userid = strtoupper(trim($this->request->getPost('userid'))); - $password = $this->request->getPost('password'); + $userid = strtoupper(trim($this->request->getPost('userid'))); + $password = $this->request->getPost('password'); - // Gunakan raw SQL sesuai kolom di tabel kamu - $query = $db->query("SELECT * FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]); - $user = $query->getRowArray(); + $query = $db->query("SELECT * FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]); + $user = $query->getRowArray(); - if ($user && !empty($user['PASSWORD']) && password_verify($password, $user['PASSWORD'])) { + if ($user && !empty($user['PASSWORD']) && password_verify($password, $user['PASSWORD'])) { - // Role untuk url - switch ((int)$user['USERLEVEL']) { - case 1: $role = 'admin'; break; - case 2: $role = 'doctor'; break; - case 3: $role = 'analyst'; break; - case 4: $role = 'cs'; break; - default: $role = ''; break; - } - - // Simpan session - $session->set([ - 'isLoggedIn' => true, - 'userid' => (string) $user['USERID'], - 'userlevel' => (int) $user['USERLEVEL'], - 'userrole' => (string) $role, - ]); - - // Redirect sesuai level dari data didatabase - switch ((int)$user['USERLEVEL']) { - case 1: return redirect()->to('/admin'); - case 2: return redirect()->to('/doctor'); - case 3: return redirect()->to('/analyst'); - case 4: return redirect()->to('/cs'); - default: return redirect()->to('/login'); - } - } else { - $session->setFlashdata('error', 'USERID atau PASSWORD salah.'); - return redirect()->back(); + switch ((int)$user['USERLEVEL']) { + case 1: + $role = 'admin'; + break; + case 2: + $role = 'analyst'; + break; + case 3: + $role = 'phlebotomist'; + break; + case 4: + $role = 'cs'; + break; + default: + $role = ''; + break; } + $session->set([ + 'isLoggedIn' => true, + 'userid' => (string) $user['USERID'], + 'userlevel' => (int) $user['USERLEVEL'], + 'userrole' => (string) $role, + ]); + + switch ((int)$user['USERLEVEL']) { + case 1: + return redirect()->to('admin'); + case 2: + return redirect()->to('lab'); + case 3: + return redirect()->to('analyst'); + case 4: + return redirect()->to('cs'); + default: + return redirect()->to('login'); + } + } else { + $session->setFlashdata('error', 'USERID atau PASSWORD salah.'); + return redirect()->back(); } - } public function logout() { - session()->destroy(); - return redirect()->to('/login'); + $session = session(); + $session->destroy(); + return redirect()->to('login'); + } + + public function setPassword() { + $input = $this->request->getJSON(true); + $userid = $input['userid']; + $password = $input['password']; + $password = password_hash($password, PASSWORD_DEFAULT); + $db = db_connect(); + $sql = "update GDC_CMOD.dbo.USERS set PASSWORD='$password' where USERID='$userid'"; + $db->query($sql); + $data = ['status' => 'success', 'message' => 'Password updated successfully', 'data' => "$userid" ]; + return $this->response->setJSON($data); } } diff --git a/app/Controllers/CustomerService.php b/app/Controllers/CustomerService.php deleted file mode 100644 index 21fba42..0000000 --- a/app/Controllers/CustomerService.php +++ /dev/null @@ -1,111 +0,0 @@ -request->getVar('date1') ?? $today; - $date2 = $this->request->getVar('date2') ?? $today; - - $db = \Config\Database::connect(); - $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'"; - $query = $db->query($sql); - $results = $query->getResultArray(); - - // === Variabel counter === - $CPending = 0; $CPColl = 0; $CColl = 0; $CPRecv = 0; - $CRecv = 0; $CInc = 0; $CPenV = 0; $CFin = 0; $CFinV = 0; - $CTotal = 0; - - $dataList = []; - - foreach ($results as $row) { - $reqdate = ''; - if (!empty($row['REQDATE'])) { - $reqdate = date('Y-m-d H:i', strtotime($row['REQDATE'])); - } - $patname = $row['Name'] ?? ''; - $sp_accessnumber = $row['SP_ACCESSNUMBER'] ?? ''; - $hostordernumber = $row['HOSTORDERNUMBER'] ?? ''; - $stats = $row['STATS'] ?? ''; - $tests = $row['TESTS'] ?? ''; - $isDelete = $row['ISDELETE'] ?? 0; - - // Bersihkan test string - $test = str_replace(['(', ')', ',', 'FA'], '', $tests); - - if (!is_numeric($test) && $isDelete == 0) { - switch ($stats) { - case 'Pend': $statscode = 1; $CPending++; break; - case 'PartColl':$statscode = 2; $CPColl++; break; - case 'Coll': $statscode = 3; $CColl++; break; - case 'PartRecv':$statscode = 4; $CPRecv++; break; - case 'Recv': $statscode = 5; $CRecv++; break; - case 'Inc': $statscode = 6; $CInc++; break; - case 'PenV': $statscode = 7; $CPenV++; break; - case 'Fin': $statscode = 8; $CFin++; break; - case 'FinV': $statscode = 9; $CFinV++; break; - default: $statscode = 0; break; - } - $CTotal++; - // Simpan ke array - $dataList[] = [ - 'statscode' => $statscode, - 'reqdate' => $reqdate, - 'patname' => $patname, - 'sp_accessnumber' => $sp_accessnumber, - 'hostordernumber' => $hostordernumber, - 'reff' => $row['REFF'] ?? '', - 'doc' => $row['DOC'] ?? '', - 'tests' => $row['TESTS'] ?? '', - 'stats' => $stats, - 'odr_cresult_to' => $row['ODR_CRESULT_TO'], - 'isprinted'=> $row['ODR_ISPRINTED'] ?? 0, - 'ispending'=> $row['ODR_ISPENDING'] ?? 0, - 'ishardcopy'=> $row['ODR_NFLAGHARDCOPY'] ?? 0, - 'isvaltd' => $row['ISVAL'] ?? 0, - // 'isval1' => $row['ISVAL1'] ?? 0, - // 'isval2' => $row['ISVAL2'] ?? 0, - 'isdelete' => $isDelete, - 'valcounter' => $row['VAL'] ?? 0, - 'val1user' => $row['VAL1USER'] ?? '-', - 'val2user' => $row['VAL2USER'] ?? '-', - ]; - - } - } - - // === Total counter === - $counter = [ - 'pending' => $CPending, - 'collect' => $CPColl+$CColl, - 'recv' => $CPRecv+$CRecv, - 'incomplete' => $CInc+$CPenV, - 'complete' => $CFin+$CFinV, - 'total' => $CTotal, - /* - 'partialCollected' => $CPColl, - 'collected' => $CColl, - 'partialReceived' => $CPRecv, - 'received' => $CRecv, - 'incomplete' => $CInc, - 'pendingValidation' => $CPenV, - 'final' => $CFin, - 'finalValidation' => $CFinV, - */ - ]; - - $data['dataList'] = $dataList; - $data['counter'] = $counter; - $data['date1'] = $date1; - $data['date2'] = $date2; - - // dd($results); - - - return view('cs/index', $data); - } - -} diff --git a/app/Controllers/Doctor.php b/app/Controllers/Doctor.php deleted file mode 100644 index 9896980..0000000 --- a/app/Controllers/Doctor.php +++ /dev/null @@ -1,115 +0,0 @@ -request->getVar('date1') ?? $today; - $date2 = $this->request->getVar('date2') ?? $today; - - $db = \Config\Database::connect(); - $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'"; - $query = $db->query($sql); - $results = $query->getResultArray(); - - // === Variabel counter === - $CPending = 0; $CPColl = 0; $CColl = 0; $CPRecv = 0; - $CRecv = 0; $CInc = 0; $CPenV = 0; $CFin = 0; $CFinV = 0; - $CTotal = 0; - - $dataList = []; - - foreach ($results as $row) { - $reqdate = ''; - if (!empty($row['REQDATE'])) { - $reqdate = date('Y-m-d H:i', strtotime($row['REQDATE'])); - } - $patname = $row['Name'] ?? ''; - $sp_accessnumber = $row['SP_ACCESSNUMBER'] ?? ''; - $hostordernumber = $row['HOSTORDERNUMBER'] ?? ''; - $stats = $row['STATS'] ?? ''; - $tests = $row['TESTS'] ?? ''; - $isDelete = $row['ISDELETE'] ?? 0; - - // Bersihkan test string - $test = str_replace(['(', ')', ',', 'FA'], '', $tests); - - if (!is_numeric($test) && $isDelete == 0) { - switch ($stats) { - case 'Pend': $statscode = 1; $CPending++; break; - case 'PartColl':$statscode = 2; $CPColl++; break; - case 'Coll': $statscode = 3; $CColl++; break; - case 'PartRecv':$statscode = 4; $CPRecv++; break; - case 'Recv': $statscode = 5; $CRecv++; break; - case 'Inc': $statscode = 6; $CInc++; break; - case 'PenV': $statscode = 7; $CPenV++; break; - case 'Fin': $statscode = 8; $CFin++; break; - case 'FinV': $statscode = 9; $CFinV++; break; - default: $statscode = 0; break; - } - $CTotal++; - // Simpan ke array - $dataList[] = [ - 'statscode' => $statscode, - 'reqdate' => $reqdate, - 'patname' => $patname, - 'sp_accessnumber' => $sp_accessnumber, - 'hostordernumber' => $hostordernumber, - 'reff' => $row['REFF'] ?? '', - 'doc' => $row['DOC'] ?? '', - 'tests' => $row['TESTS'] ?? '', - 'stats' => $stats, - 'odr_cresult_to' => $row['ODR_CRESULT_TO'], - 'isprinted'=> $row['ODR_ISPRINTED'] ?? 0, - 'ispending'=> $row['ODR_ISPENDING'] ?? 0, - 'ishardcopy'=> $row['ODR_NFLAGHARDCOPY'] ?? 0, - 'isvaltd' => $row['ISVAL'] ?? 0, - // 'isval1' => $row['ISVAL1'] ?? 0, - // 'isval2' => $row['ISVAL2'] ?? 0, - 'isdelete' => $isDelete, - 'valcounter' => $row['VAL'] ?? 0, - 'val1user' => $row['VAL1USER'] ?? '-', - 'val2user' => $row['VAL2USER'] ?? '-', - ]; - - } - } - - // === Total counter === - $counter = [ - 'pending' => $CPending, - 'collect' => $CPColl+$CColl, - 'recv' => $CPRecv+$CRecv, - 'incomplete' => $CInc+$CPenV, - 'complete' => $CFin+$CFinV, - 'total' => $CTotal, - /* - 'partialCollected' => $CPColl, - 'collected' => $CColl, - 'partialReceived' => $CPRecv, - 'received' => $CRecv, - 'incomplete' => $CInc, - 'pendingValidation' => $CPenV, - 'final' => $CFin, - 'finalValidation' => $CFinV, - */ - ]; - - $data['dataList'] = $dataList; - $data['counter'] = $counter; - $data['date1'] = $date1; - $data['date2'] = $date2; - - // dd($results); - - - return view('doctor/index', $data); - } - - public function modal_specimen() { - return view('doctor/modal_specimen'); - } - -} diff --git a/app/Controllers/Home.php b/app/Controllers/Home.php index c99740a..6e249ef 100644 --- a/app/Controllers/Home.php +++ b/app/Controllers/Home.php @@ -14,12 +14,12 @@ class Home extends BaseController { } // Jika sudah login, arahkan sesuai level - switch ($session->get('level')) { - case 1: return redirect()->to('/admin'); - case 2: return redirect()->to('/dokter'); - case 3: return redirect()->to('/analis'); - case 4: return redirect()->to('/cs'); - default: return redirect()->to('/login'); + switch ($session->get('userlevel')) { + case 1: return redirect()->to('admin'); + case 2: return redirect()->to('lab'); + case 3: return redirect()->to('analyst'); + case 4: return redirect()->to('cs'); + default: return redirect()->to('login'); } } diff --git a/app/Controllers/V2/Lab.php b/app/Controllers/Lab.php similarity index 75% rename from app/Controllers/V2/Lab.php rename to app/Controllers/Lab.php index f18d7c5..7f73f80 100644 --- a/app/Controllers/V2/Lab.php +++ b/app/Controllers/Lab.php @@ -1,5 +1,6 @@ request->getGet('date1'); $date2 = $this->request->getGet('date2'); - $db = \Config\Database::connect(); $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'"; @@ -27,7 +26,7 @@ class Requests extends BaseController { public function show($accessnumber) { - $db = db_connect(); + $db = \Config\Database::connect(); $data['accessnumber'] = $accessnumber; $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 @@ -51,7 +50,7 @@ class Requests extends BaseController { $input = $this->request->getJSON(true); $userid = $input['userid']; $comment = $input['comment']; - $db = db_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, ISPENDING=1, PENDINGTEXT='$comment', PENDINGUSER='$userid', PENDINGDATE=GETDATE() where ACCESSNUMBER='$accessnumber'"; $db->query($sql); @@ -63,11 +62,9 @@ class Requests extends BaseController { public function val($accessnumber) { $input = $this->request->getJSON(true); $userid = $input['userid']; - $db = db_connect(); - //cek val + $db = \Config\Database::connect(); $sql = "select * from GDC_CMOD.dbo.CM_REQUESTS where ACCESSNUMBER='$accessnumber'"; $result = $db->query($sql)->getResultArray(); - //$data['data'] = $result; if(!isset($result[0])) { $sql = "insert into GDC_CMOD.dbo.CM_REQUESTS(ACCESSNUMBER, ISVAL1, VAL1USER, VAL1DATE) VALUES ('$accessnumber', 1, '$userid', GETDATE())"; $db->query($sql); @@ -79,10 +76,8 @@ class Requests extends BaseController { $isval2 = $row['ISVAL2']; $val1user = $row['VAL1USER']; if( $isval1 == 1 ) { - // val done if ( $isval2 == 1 ) { return $this->response->setJSON(['message'=> 'validation done, not updating anything']); } else { - // val2 if user val1 != userid if($val1user != $userid) { $sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'"; $data['val'] = 2; @@ -93,7 +88,6 @@ class Requests extends BaseController { } } } else { - // val1 $sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL1=1, VAL1USER='$userid', VAL1DATE=GETDATE() where ACCESSNUMBER='$accessnumber'"; $data['val'] = 1; $data['userid'] = $userid; diff --git a/app/Controllers/V2/Samples.php b/app/Controllers/Samples.php similarity index 99% rename from app/Controllers/V2/Samples.php rename to app/Controllers/Samples.php index 87967e8..1128cd3 100644 --- a/app/Controllers/V2/Samples.php +++ b/app/Controllers/Samples.php @@ -1,5 +1,5 @@ response->setJSON($resp); } - + public function collect($accessnumber) { $db = \Config\Database::connect(); $input = $this->request->getJSON(true); @@ -90,7 +90,6 @@ class Samples extends BaseController { $db = \Config\Database::connect(); $input = $this->request->getJSON(true); $samplenumber = $input['samplenumber']; - // update firebird $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 @@ -101,6 +100,7 @@ class Samples extends BaseController { and r.ACCESSNUMBER='$accessnumber' and ds.SAMPCODE='$samplenumber'"; $rows = $db->query($sql)->getResultArray(); $his_test = ''; + $lis_test = ''; foreach( $rows as $row ) { $hon = $row['EXTERNALORDERNUMBER']; $testcode = $row['TESTCODE']; diff --git a/app/Controllers/V2/Users.php b/app/Controllers/Users.php similarity index 80% rename from app/Controllers/V2/Users.php rename to app/Controllers/Users.php index 17d7eec..62aa69f 100644 --- a/app/Controllers/V2/Users.php +++ b/app/Controllers/Users.php @@ -1,5 +1,5 @@ db = \Config\Database::connect(); } @@ -17,22 +16,20 @@ class Users extends BaseController { $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; + $query = $this->db->query($sql); + $results = $query->getResultArray(); + $data['data'] = $results; return $this->respond(['data' => $results]); } public function create() { - $input = $this->request->getJSON(true); - // ambil input + $input = $this->request->getJSON(true); $userid = $input['userid']; $userlevel = $input['userlevel']; $password = $input['password']; $password_2 = $input['password_2']; - // Cek Password Apakah Sama if ($password != $password_2) { return $this->response->setJSON(['message'=> 'Password not the same']); } @@ -40,17 +37,14 @@ class Users extends BaseController { return $this->response->setJSON(['message'=> 'Password must be more than 2 characters']); } - // Cek Apakah USERID Sama $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']); } - // Hash Password $hashedPassword = password_hash($password, PASSWORD_DEFAULT); - // Insert $this->db->transBegin(); try { $sqlInsert = " @@ -62,32 +56,25 @@ class Users extends BaseController { $this->db->transCommit(); } catch (\Throwable $e) { - // Kalau ada error, rollback semua perubahan $this->db->transRollback(); - - // (Opsional) tampilkan atau log error return $this->response->setJSON(['message'=> 'Server error']); } return $this->response->setJSON(['message'=> 'User '.$userid.' Berhasil ditambahkan!']); } - public function update() { + public function update($id = null) { $input = $this->request->getJSON(true); $userid = $input['userid']; $userlevel = $input['userlevel']; $password = $input['password']; $password_2 = $input['password_2']; - // Jika password tidak kosong - Lakukan Full Update if ( $password != '' || $password_2 != '') { - - // Cek Password Apakah Sama if ($password != $password_2) { return $this->response->setJSON(['message'=> 'Password not the same']); } - // Hash Password $hashedPassword = password_hash($password, PASSWORD_DEFAULT); $sqlUpdate =" UPDATE gdc_cmod.dbo.USERS @@ -97,7 +84,6 @@ class Users extends BaseController { "; $fullUpdate = true; - // Jika password kosong - Lakukan Partial Update Saja } else { $sqlUpdate =" UPDATE gdc_cmod.dbo.USERS @@ -107,7 +93,6 @@ class Users extends BaseController { $fullUpdate = false; } - // Insert $this->db->transBegin(); try { @@ -120,10 +105,7 @@ class Users extends BaseController { $this->db->transCommit(); } catch (\Throwable $e) { - // Kalau ada error, rollback semua perubahan $this->db->transRollback(); - - // (Opsional) tampilkan atau log error return $this->response->setJSON(['message'=> 'Terjadi kesalahan pada server.']); } diff --git a/app/Controllers/V2.php b/app/Controllers/V2.php deleted file mode 100644 index ef3fd02..0000000 --- a/app/Controllers/V2.php +++ /dev/null @@ -1,112 +0,0 @@ -get('isLoggedIn')) { - return redirect()->to('v2/login'); - } - - // Jika sudah login, arahkan sesuai level - switch ($session->get('level')) { - case 1: - return redirect()->to('v2/admin'); - case 2: - return redirect()->to('v2/analyst'); - case 3: - return redirect()->to('v2/phlebotomist'); - case 4: - return redirect()->to('v2/cs'); - default: - return redirect()->to('v2/login'); - } - } - - public function loginPage() { - return view("v2/login"); - } - - public function login() { - helper(['form', 'url']); - $session = session(); - $db = \Config\Database::connect(); - - $userid = strtoupper(trim($this->request->getPost('userid'))); - $password = $this->request->getPost('password'); - - // Gunakan raw SQL sesuai kolom di tabel kamu - $query = $db->query("SELECT * FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]); - $user = $query->getRowArray(); - - if ($user && !empty($user['PASSWORD']) && password_verify($password, $user['PASSWORD'])) { - - // Role untuk url - switch ((int)$user['USERLEVEL']) { - case 1: - $role = 'admin'; - break; - case 2: - $role = 'analyst'; - break; - case 3: - $role = 'phlebotomist'; - break; - case 4: - $role = 'cs'; - break; - default: - $role = ''; - break; - } - - // Simpan session - $session->set([ - 'isLoggedIn' => true, - 'userid' => (string) $user['USERID'], - 'userlevel' => (int) $user['USERLEVEL'], - 'userrole' => (string) $role, - ]); - - // Redirect sesuai level dari data didatabase - switch ((int)$user['USERLEVEL']) { - case 1: - return redirect()->to('v2/admin'); - case 2: - return redirect()->to('v2/lab'); - case 3: - return redirect()->to('v2/phlebotomist'); - case 4: - return redirect()->to('v2/cs'); - default: - return redirect()->to('v2/login'); - } - } else { - $session->setFlashdata('error', 'USERID atau PASSWORD salah.'); - return redirect()->back(); - } - } - - public function logout() { - $session = session(); - $session->destroy(); - return redirect()->to('v2/login'); - } - - public function setPassword() { - $input = $this->request->getJSON(true); - $userid = $input['userid']; - $password = $input['password']; - $password = password_hash($password, PASSWORD_DEFAULT); - $db = db_connect(); - $sql = "update GDC_CMOD.dbo.USERS set PASSWORD='$password' where USERID='$userid'"; - $db->query($sql); - $data = ['status' => 'success', 'message' => 'Password updated successfully', 'data' => "$userid" ]; - return $this->response->setJSON($data); - } -} diff --git a/app/Controllers/V2/Admin.php b/app/Controllers/V2/Admin.php deleted file mode 100644 index ab0138b..0000000 --- a/app/Controllers/V2/Admin.php +++ /dev/null @@ -1,24 +0,0 @@ -db = \Config\Database::connect(); - helper(['url', 'form', 'text']); - } - - public function index() { - return view('v2/admin/index'); - } - - public function users() { - return view('v2/admin/users'); - } -} diff --git a/app/Views/_layouts/main.php b/app/Views/_layouts/main.php deleted file mode 100644 index 812473f..0000000 --- a/app/Views/_layouts/main.php +++ /dev/null @@ -1,157 +0,0 @@ -getPath(); - $activeUrls = ['/admin', '/cs', '/analyst', '/doctor']; -?> - - - - - renderSection('title'); ?> - - - - - - renderSection('css'); ?> - - - - renderSection('content'); ?> - - - - - - - - - - - renderSection('script'); ?> - - - \ No newline at end of file diff --git a/app/Views/admin/dialog_preview.php b/app/Views/admin/dialog_preview.php new file mode 100644 index 0000000..d6f3e1f --- /dev/null +++ b/app/Views/admin/dialog_preview.php @@ -0,0 +1,45 @@ + + + diff --git a/app/Views/v2/lab/dialog_sample.php b/app/Views/admin/dialog_sample.php similarity index 84% rename from app/Views/v2/lab/dialog_sample.php rename to app/Views/admin/dialog_sample.php index 2f82f5b..2201541 100644 --- a/app/Views/v2/lab/dialog_sample.php +++ b/app/Views/admin/dialog_sample.php @@ -2,6 +2,15 @@ + diff --git a/app/Views/admin/dialog_setPassword.php b/app/Views/admin/dialog_setPassword.php new file mode 100644 index 0000000..623e8b2 --- /dev/null +++ b/app/Views/admin/dialog_setPassword.php @@ -0,0 +1,28 @@ + + + + diff --git a/app/Views/admin/dialog_unval.php b/app/Views/admin/dialog_unval.php new file mode 100644 index 0000000..d869e30 --- /dev/null +++ b/app/Views/admin/dialog_unval.php @@ -0,0 +1,20 @@ + + + diff --git a/app/Views/admin/index.php b/app/Views/admin/index.php index 159b651..e8efed8 100644 --- a/app/Views/admin/index.php +++ b/app/Views/admin/index.php @@ -1,287 +1,401 @@ -extend('_layouts/main.php') ?> - -section('title') ?> -Admin Glenlis -endSection() ?> +extend('admin/main'); ?> section('content') ?> -
-
-
Dashboard,
-
- Hi, - - -
-
+
+
+
+ + +
+
+
+

+ Requests Overview +

+
+ + +
+ + + + + + + +
+
-
-
-
'> -
-
- Date : -
+ +
+
+ +
+ + - + +
+
+ +
+ + +
-
- -
+ -
-
+
+ +
+
+
-
- -
+
+ + + +
-
- -
- -
-
- -
-
- - - - - - -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOrder DatetimePatient NameNo LabNo RegisterReffDoctorTestsResult ToValidationStatus
-
- - Printed
- Eng - -
- - - - - - - - - - get('userid'); - $val1 = $row['val1user']; - $val2 = $row['val2user']; - - $valButton = (($val1 === '-') || ($val2 === '-')) && ($user !== $val1 && $user !== $val2); - $unvalButton = ($val1 !== '-') || ($val2 !== '-'); - ?> - - - - - - - -
1 :
2 :
- - - - - - - -
-
-
-
- - - - - - - -
- -endSection() ?> +
+ + include('admin/dialog_sample'); ?> + include('admin/dialog_unval'); ?> + include('admin/dialog_preview'); ?> + + +endSection(); ?> section('script') ?> - - - -endSection() ?> \ No newline at end of file + isValidated (item) { + return item.ISVAL == 1 && item.ISPENDING != 1; + }, + get filtered() { + let filteredList = this.list; + if (this.filterKey === 'Validated') { + filteredList = filteredList.filter(item => this.isValidated(item)); + } else { + const validStatuses = this.statusMap[this.filterKey]; + if (validStatuses.length > 0) { + filteredList = filteredList.filter(item => validStatuses.includes(item.STATS)); + } + } + if (this.filterTable) { + const searchTerm = this.filterTable.toLowerCase(); + filteredList = filteredList.filter(item => + Object.values(item).some(value => + String(value).toLowerCase().includes(searchTerm) + ) + ); + } + return filteredList; + }, + get validatedCount() { + return this.list.filter(r => this.isValidated(r)).length; + }, + + /* + sample dialog + */ + item : '', + isDialogSampleOpen : false, + isSampleLoading: false, + + openSampleDialog (accessnumber) { + this.isDialogSampleOpen = true; + this.fetchItem(accessnumber) + }, + + closeSampleDialog () { + this.isDialogSampleOpen = false; + }, + + fetchItem(accessnumber){ + this.isSampleLoading = true; + this.item = []; + fetch(`${BASEURL}/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}}) + .then(res => res.json()).then(data => { + this.item = data.data ?? {}; + if (!Array.isArray(this.item.samples)) this.item.samples = []; + }).finally(() => { + this.isSampleLoading = false; + }); + }, + + collect(sampcode, accessnumber) { + fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, { + method: 'POST', headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({samplenumber: sampcode, userid: ''}) + }) + .then(res => res.json()).then(data => { + this.fetchItem(accessnumber); + }); + }, + + uncollect(sampcode, accessnumber) { + if(!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return ;} + fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, { + method: 'DELETE', headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({samplenumber: sampcode, userid: ''}) + }) + .then(res => res.json()).then(data => { + this.fetchItem(accessnumber); + }); + }, + + unreceive(sampcode, accessnumber) { + if(!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return ;} + fetch(`${BASEURL}/api/samples/unreceive/${accessnumber}`, { + method: 'POST', headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({samplenumber: sampcode, userid : ''}) + }) + .then(res => res.json()).then(data => { + this.fetchItem(accessnumber); + }); + }, + + /* + preview dialog + */ + isDialogPreviewOpen : false, + reviewed: false, + previewAccessnumber : null, + previewType : 'preview', + openPreviewDialog (accessnumber, type) { + this.previewAccessnumber = accessnumber; + this.previewType = type; + this.isDialogPreviewOpen = true; + this.reviewed = false; + }, + closePreviewDialog () { + this.isDialogPreviewOpen = false; + }, + setPreviewType(type) { + this.previewType = type; + }, + getPreviewUrl() { + let base = 'http://glenlis/spooler_db/main_dev.php'; + let url = `${base}?acc=${this.previewAccessnumber}`; + if (this.previewType === 'ind') url += '&lang=ID'; + if (this.previewType === 'eng') url += '&lang=EN'; + if (this.previewType === 'pdf') url += '&output=pdf'; + return url; + }, + validate(accessnumber, userid) { + fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ userid:`${userid}` }) + }).then(response => { + this.closePreviewDialog(); + this.fetchList(); + console.log('Validate clicked for', this.previewAccessnumber, 'by user', userid); + }); + }, + + /* + unvalidate dialog + */ + isDialogUnvalOpen : false, + unvalReason : '', + unvalAccessnumber : null, + openUnvalDialog (accessnumber) { + this.unvalReason = ''; + this.isDialogUnvalOpen = true; + this.unvalAccessnumber = accessnumber; + }, + unvalidate(accessnumber, userid) { + if(!confirm(`Unvalidate request ${accessnumber}?`)) { return ;} + fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { + method: "DELETE", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({ userid:`${userid}`, comment: this.unvalReason.trim() }) + }).then(response => { + this.closeUnvalDialog(); + this.fetchList(); + console.log(`Unvalidate clicked for ${accessnumber}, by user ${userid}`); + }); + }, + closeUnvalDialog () { + this.isDialogUnvalOpen = false; + }, + })); + }); + + Alpine.start(); + +endSection(); ?> diff --git a/app/Views/v2/admin/main.php b/app/Views/admin/main.php similarity index 82% rename from app/Views/v2/admin/main.php rename to app/Views/admin/main.php index bf788c4..e3605f0 100644 --- a/app/Views/v2/admin/main.php +++ b/app/Views/admin/main.php @@ -20,7 +20,7 @@ } .card-body { font-size: 0.71rem !important; - } + } @@ -41,22 +41,22 @@ renderSection('content');?> - include('v2/dialog_setPassword');?> + include('admin/dialog_setPassword');?> renderSection('script');?> - \ No newline at end of file + diff --git a/app/Views/admin/modal_request.php b/app/Views/admin/modal_request.php deleted file mode 100644 index c0bb9e9..0000000 --- a/app/Views/admin/modal_request.php +++ /dev/null @@ -1,40 +0,0 @@ -
-
val : -
-
- - -
-
- -
- -
- - \ No newline at end of file diff --git a/app/Views/admin/modal_specimen.php b/app/Views/admin/modal_specimen.php deleted file mode 100644 index faeef90..0000000 --- a/app/Views/admin/modal_specimen.php +++ /dev/null @@ -1,106 +0,0 @@ -
- -
-
-
Patient
-
: {{patientIdentity.name}}
-
-
-
Age
-
: {{patientIdentity.age}} years
-
-
-
Gender
-
: {{patientIdentity.gender}}
-
-
- -
-
-
Lab#
-
: {{accessnumber}}
-
-
-
MR#
-
: {{patientIdentity.rm}}
-
-
-
KTP
-
: {{patientIdentity.ktp}}
-
-
- -
- -
-
-
- Tube -
- - - - - - - - - - - - - - - - - - - - - - - {{#samples}} - - - - - - - - - {{/samples}} - - - - - - -
Sample CodeSample NameCollectedReceivedAction
Collection
All - - - - -
{{sampcode}}{{name}} - - - - - - - - - - -
-
- -
-
-
-
\ No newline at end of file diff --git a/app/Views/admin/modal_unvalidate.php b/app/Views/admin/modal_unvalidate.php deleted file mode 100644 index 790953c..0000000 --- a/app/Views/admin/modal_unvalidate.php +++ /dev/null @@ -1,16 +0,0 @@ -
-
-

Unvalidate

-
-
- - '> - -
- -
-
- -
-
-
\ No newline at end of file diff --git a/app/Views/v2/admin/users.php b/app/Views/admin/users.php similarity index 75% rename from app/Views/v2/admin/users.php rename to app/Views/admin/users.php index 39c7a50..9f63578 100644 --- a/app/Views/v2/admin/users.php +++ b/app/Views/admin/users.php @@ -1,4 +1,4 @@ -extend('v2/admin/main'); ?> +extend('admin/main'); ?> section('content') ?>
@@ -15,39 +15,63 @@
- - - - - - - - - - + + @@ -150,7 +174,7 @@ document.addEventListener('alpine:init', () => { Alpine.data("users", () => ({ list: [], - mode: 'create', // create | edit + mode: 'create', isLoading: false, errorMsg: '', form: { @@ -165,10 +189,13 @@ }, fetchUsers() { - fetch(`${BASEURL}/admin/api/users`) + this.isLoading = true; + fetch(`${BASEURL}/api/users`) .then(res => res.json()) .then(data => { this.list = data.data ?? []; + }).finally(() => { + this.isLoading = false; }); }, @@ -182,9 +209,6 @@ return map[level] || 'badge-ghost'; }, - /* - User Modal - */ openUserModal(targetMode, user = null) { this.mode = targetMode; this.errorMsg = ''; @@ -213,13 +237,13 @@ try { let res; if(this.mode == 'create') { - res = await fetch(`${BASEURL}/admin/api/users`, { + res = await fetch(`${BASEURL}/api/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.form) }); } else { - res = await fetch(`${BASEURL}/admin/api/users/${this.form.userid}`, { + res = await fetch(`${BASEURL}/api/users/${this.form.userid}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.form) diff --git a/app/Views/analyst/index.php b/app/Views/analyst/index.php deleted file mode 100644 index 2f75814..0000000 --- a/app/Views/analyst/index.php +++ /dev/null @@ -1,285 +0,0 @@ -extend('_layouts/main.php') ?> - -section('title') ?> -Analyst Glenlis -endSection() ?> - -section('content') ?> -
-
-
Dashboard,
-
- Hi, - - -
-
- -
-
-
'> -
-
- Date : -
- -
- -
- -
-
- -
- -
- -
- -
- -
-
- -
-
- - - - - - -
-
- -
-
- -
-
User IDRole/LevelActions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOrder DatetimePatient NameNo LabNo RegisterReffDoctorTestsResult ToValidationStatus
-
- - Printed
- Eng - -
- - - - - - - - - - get('userid'); - $val1 = $row['val1user']; - $val2 = $row['val2user']; - - $valButton = (($val1 === '-') || ($val2 === '-')) && ($user !== $val1 && $user !== $val2); - $unvalButton = ($val1 !== '-') || ($val2 !== '-'); - ?> - - - - - - - -
1 :
2 :
- - - - - - - -
-
-
- - - - - - - - - - -endSection() ?> - -section('script') ?> - - - -endSection() ?> \ No newline at end of file diff --git a/app/Views/analyst/modal_request.php b/app/Views/analyst/modal_request.php deleted file mode 100644 index c0bb9e9..0000000 --- a/app/Views/analyst/modal_request.php +++ /dev/null @@ -1,40 +0,0 @@ -
-
val : -
-
- - -
-
- -
- -
- - \ No newline at end of file diff --git a/app/Views/analyst/modal_specimen.php b/app/Views/analyst/modal_specimen.php deleted file mode 100644 index faeef90..0000000 --- a/app/Views/analyst/modal_specimen.php +++ /dev/null @@ -1,106 +0,0 @@ -
- -
-
-
Patient
-
: {{patientIdentity.name}}
-
-
-
Age
-
: {{patientIdentity.age}} years
-
-
-
Gender
-
: {{patientIdentity.gender}}
-
-
- -
-
-
Lab#
-
: {{accessnumber}}
-
-
-
MR#
-
: {{patientIdentity.rm}}
-
-
-
KTP
-
: {{patientIdentity.ktp}}
-
-
- -
- -
-
-
- Tube -
- - - - - - - - - - - - - - - - - - - - - - - {{#samples}} - - - - - - - - - {{/samples}} - - - - - - -
Sample CodeSample NameCollectedReceivedAction
Collection
All - - - - -
{{sampcode}}{{name}} - - - - - - - - - - -
-
- -
-
-
-
\ No newline at end of file diff --git a/app/Views/analyst/modal_unvalidate.php b/app/Views/analyst/modal_unvalidate.php deleted file mode 100644 index 790953c..0000000 --- a/app/Views/analyst/modal_unvalidate.php +++ /dev/null @@ -1,16 +0,0 @@ -
-
-

Unvalidate

-
-
- - '> - -
- -
-
- -
-
-
\ No newline at end of file diff --git a/app/Views/cs/index.php b/app/Views/cs/index.php deleted file mode 100644 index c0394bf..0000000 --- a/app/Views/cs/index.php +++ /dev/null @@ -1,261 +0,0 @@ -extend('_layouts/main.php') ?> - -section('title') ?> -Customer Service Glenlis -endSection() ?> - -section('content') ?> -
-
-
Dashboard,
-
- Hi, - - -
-
- -
-
-
'> -
-
- Date : -
- -
- -
- -
-
- -
- -
- -
- -
- -
-
-
-
-
- - - - - - -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOrder DatetimePatient NameNo LabNo RegisterReffDoctorTestsResult ToValidationStatus
-
- - Printed
- Eng - -
- - - - - - - - -
1 :
2 :
-
-
-
- - - - - - - - - -endSection() ?> - -section('script') ?> - - - -endSection() ?> \ No newline at end of file diff --git a/app/Views/cs/modal_request.php b/app/Views/cs/modal_request.php deleted file mode 100644 index c0bb9e9..0000000 --- a/app/Views/cs/modal_request.php +++ /dev/null @@ -1,40 +0,0 @@ -
-
val : -
-
- - -
-
- -
- -
- - \ No newline at end of file diff --git a/app/Views/cs/modal_specimen.php b/app/Views/cs/modal_specimen.php deleted file mode 100644 index faeef90..0000000 --- a/app/Views/cs/modal_specimen.php +++ /dev/null @@ -1,106 +0,0 @@ -
- -
-
-
Patient
-
: {{patientIdentity.name}}
-
-
-
Age
-
: {{patientIdentity.age}} years
-
-
-
Gender
-
: {{patientIdentity.gender}}
-
-
- -
-
-
Lab#
-
: {{accessnumber}}
-
-
-
MR#
-
: {{patientIdentity.rm}}
-
-
-
KTP
-
: {{patientIdentity.ktp}}
-
-
- -
- -
-
-
- Tube -
- - - - - - - - - - - - - - - - - - - - - - - {{#samples}} - - - - - - - - - {{/samples}} - - - - - - -
Sample CodeSample NameCollectedReceivedAction
Collection
All - - - - -
{{sampcode}}{{name}} - - - - - - - - - - -
-
- -
-
-
-
\ No newline at end of file diff --git a/app/Views/cs/modal_unvalidate.php b/app/Views/cs/modal_unvalidate.php deleted file mode 100644 index 790953c..0000000 --- a/app/Views/cs/modal_unvalidate.php +++ /dev/null @@ -1,16 +0,0 @@ -
-
-

Unvalidate

-
-
- - '> - -
- -
-
- -
-
-
\ No newline at end of file diff --git a/app/Views/doctor/index.php b/app/Views/doctor/index.php deleted file mode 100644 index 4207b6f..0000000 --- a/app/Views/doctor/index.php +++ /dev/null @@ -1,285 +0,0 @@ -extend('_layouts/main.php') ?> - -section('title') ?> -Doctor Glenlis -endSection() ?> - -section('content') ?> -
-
-
Dashboard,
-
- Hi, - - -
-
- -
-
-
'> -
-
- Date : -
- -
- -
- -
-
- -
- -
- -
- -
- -
-
-
-
-
- - - - - - -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SOrder DatetimePatient NameNo LabNo RegisterReffDoctorTestsResult ToValidationStatus
-
- - Printed
- Eng - -
- - - - - - - - - - get('userid'); - $val1 = $row['val1user']; - $val2 = $row['val2user']; - - $valButton = (($val1 === '-') || ($val2 === '-')) && ($user !== $val1 && $user !== $val2); - $unvalButton = ($val1 !== '-') || ($val2 !== '-'); - ?> - - - - - - - -
1 :
2 :
- - - - - - - -
-
-
-
- - - - - - - - - -endSection() ?> - -section('script') ?> - - - -endSection() ?> \ No newline at end of file diff --git a/app/Views/doctor/modal_request.php b/app/Views/doctor/modal_request.php deleted file mode 100644 index c0bb9e9..0000000 --- a/app/Views/doctor/modal_request.php +++ /dev/null @@ -1,40 +0,0 @@ -
-
val : -
-
- - -
-
- -
- -
- - \ No newline at end of file diff --git a/app/Views/doctor/modal_specimen.php b/app/Views/doctor/modal_specimen.php deleted file mode 100644 index faeef90..0000000 --- a/app/Views/doctor/modal_specimen.php +++ /dev/null @@ -1,106 +0,0 @@ -
- -
-
-
Patient
-
: {{patientIdentity.name}}
-
-
-
Age
-
: {{patientIdentity.age}} years
-
-
-
Gender
-
: {{patientIdentity.gender}}
-
-
- -
-
-
Lab#
-
: {{accessnumber}}
-
-
-
MR#
-
: {{patientIdentity.rm}}
-
-
-
KTP
-
: {{patientIdentity.ktp}}
-
-
- -
- -
-
-
- Tube -
- - - - - - - - - - - - - - - - - - - - - - - {{#samples}} - - - - - - - - - {{/samples}} - - - - - - -
Sample CodeSample NameCollectedReceivedAction
Collection
All - - - - -
{{sampcode}}{{name}} - - - - - - - - - - -
-
- -
-
-
-
\ No newline at end of file diff --git a/app/Views/doctor/modal_unvalidate.php b/app/Views/doctor/modal_unvalidate.php deleted file mode 100644 index 790953c..0000000 --- a/app/Views/doctor/modal_unvalidate.php +++ /dev/null @@ -1,16 +0,0 @@ -
-
-

Unvalidate

-
-
- - '> - -
- -
-
- -
-
-
\ No newline at end of file diff --git a/app/Views/dummy_page.php b/app/Views/dummy_page.php deleted file mode 100644 index 533e213..0000000 --- a/app/Views/dummy_page.php +++ /dev/null @@ -1,96 +0,0 @@ - - - - - Test display of HTML elements - - - -
-

Testing display of HTML elements

-

This is the document header

-
-
-
-

This is 1st level heading

-

This is a test paragraph.

-

This is 2nd level heading

-

This is a test paragraph.

-

This is 3rd level heading

-

This is a test paragraph.

-

This is 4th level heading

-

This is a test paragraph.

-
This is 5th level heading
-

This is a test paragraph.

-
This is 6th level heading
-

This is a test paragraph.

-
-
-

Basic block level elements

-

- This is a normal paragraph (p element). To add some length to it, let us mention that this page was primarily written for testing the effect of user style sheets. You can use it for various other purposes as well, like just checking how your browser displays various HTML elements by default. It can also be useful when testing conversions from HTML format to other formats, since some elements can go wrong then. -

-

- This is another paragraph. I think it needs to be added that the set of elements tested is not exhaustive in any sense. I have selected those elements for which it can make sense to write user style sheet rules, in my opionion. -

-
This is a div element. Authors may use such elements instead of paragraph markup for various reasons. (End of div.)
-
-

- This is a block quotation containing a single paragraph. Well, not quite, since this is not really quoted text, but I hope you understand the point. After all, this page does not use HTML markup very normally anyway. -

-
-

The following contains address information about the author, in an address element.

-
- Mon nom en francais, - example@example.com
- 3 Rue Jules Ferry, Neuilly Sur Seine, France 94000 -
-
-
-

Lists

-

- This is a paragraph before an unnumbered list (ul). Note that the spacing between a paragraph and a list before or after that is hard to tune in a user style sheet. You can't guess which paragraphs are logically related to a list, e.g. as a "list header". -

- -

The following is a menu list:

- -
  • One.
  • -
  • Two.
  • -
  • Three. Well, probably this list item should be longer so that it will probably wrap to the next line in rendering.
  • -
    -

    The following is a dir list:

    - -
  • One.
  • -
  • Two.
  • -
  • Three. Well, probably this list item should be longer so that it will probably wrap to the next line in rendering.
  • -
    -

    - This is a paragraph before a numbered list (ol). Note that the spacing between a paragraph and a list before or after that is hard to tune in a user style sheet. You can't guess which paragraphs are logically related to a list, e.g. as a "list header". -

    -
      -
    1. One.
    2. -
    3. Two.
    4. -
    5. Three. Well, probably this list item should be longer. Note that if items are short, lists look better if they are compactly presented, whereas for long items, it would be better to have more vertical spacing between items.
    6. -
    7. Four. This is the last item in this list. Let us terminate the list now without making any more fuss about it.
    8. -
    -

    - This is a paragraph before a definition list (dl). In principle, such a list should consist of terms and associated definitions. But many authors use dl elements for fancy "layout" things. Usually the effect is not too bad, if you design user style sheet rules for dl which are suitable for real definition lists. -

    -
    -
    recursion
    -
    see recursion
    -
    recursion, indirect
    -
    see indirect recursion
    -
    indirect recursion
    -
    see recursion, indirect
    -
    term
    -
    a word or other expression taken into specific use in a well-defined meaning, which is often defined rather rigorously, even formally, and may differ quite a lot from an everyday meaning
    -
    -
    - - diff --git a/app/Views/v2/admin/dialog_sample.php b/app/Views/lab/dialog_sample.php similarity index 100% rename from app/Views/v2/admin/dialog_sample.php rename to app/Views/lab/dialog_sample.php diff --git a/app/Views/v2/admin/dialog_unval.php b/app/Views/lab/dialog_unval.php similarity index 100% rename from app/Views/v2/admin/dialog_unval.php rename to app/Views/lab/dialog_unval.php diff --git a/app/Views/v2/admin/dialog_val.php b/app/Views/lab/dialog_val.php similarity index 100% rename from app/Views/v2/admin/dialog_val.php rename to app/Views/lab/dialog_val.php diff --git a/app/Views/v2/lab/index.php b/app/Views/lab/index.php similarity index 70% rename from app/Views/v2/lab/index.php rename to app/Views/lab/index.php index ca8ccba..0822f4a 100644 --- a/app/Views/v2/lab/index.php +++ b/app/Views/lab/index.php @@ -1,4 +1,4 @@ -extend('v2/lab/main'); ?> +extend('lab/main'); ?> section('content') ?>
    @@ -67,66 +67,94 @@ -
    - + + +
    - include('v2/admin/dialog_sample'); ?> - include('v2/admin/dialog_val'); ?> - include('v2/admin/dialog_unval'); ?> + include('admin/dialog_sample'); ?> + include('admin/dialog_val'); ?> + include('admin/dialog_unval'); ?>
    endSection(); ?> @@ -139,9 +167,10 @@ Alpine.data("dashboard", ()=> ({ // dashboard today: "", - filter: { date1: "", date2: "" }, - list: [], - counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 }, + filter: { date1: "", date2: "" }, + list: [], + isLoading: false, + counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 }, statusColor: { Pend: 'bg-white text-black font-bold', PartColl: 'bg-orange-300 text-white font-bold', @@ -164,20 +193,19 @@ init() { this.today = new Date().toISOString().slice(0, 10); - this.filter.date1 = "2025-03-03"; - this.filter.date2 = "2025-03-03"; - //this.filter.date1 = this.today; - //this.filter.date2 = this.today; - //this.fetchList(); + this.filter.date1 = this.today; + this.filter.date2 = this.today; + this.fetchList(); }, fetchList(){ + this.isLoading = true; this.list = []; let statusOrder = { Pend: 1, PartColl: 2, Coll: 3, PartRecv: 4, Recv: 5, Inc: 6, Fin: 7 }; let param = new URLSearchParams(this.filter).toString(); // reset counters before processing for (let k in this.counters) { this.counters[k] = 0; } - fetch(`${BASEURL}/admin/api/requests?${param}`, { + fetch(`${BASEURL}/api/requests?${param}`, { method: 'GET', headers: {'Content-Type': 'application/json'}, }).then(res => res.json()).then(data => { @@ -197,6 +225,8 @@ let codeB = statusOrder[b.STATS] ?? 0; return codeA - codeB; }); + }).finally(() => { + this.isLoading = false; }); }, @@ -238,6 +268,7 @@ */ item : '', isDialogSampleOpen : false, + isSampleLoading: false, openSampleDialog (accessnumber) { this.isDialogSampleOpen = true; @@ -249,16 +280,19 @@ }, fetchItem(accessnumber){ + this.isSampleLoading = true; this.item = []; - fetch(`${BASEURL}/admin/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}}) + fetch(`${BASEURL}/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}}) .then(res => res.json()).then(data => { this.item = data.data ?? {}; if (!Array.isArray(this.item.samples)) this.item.samples = []; + }).finally(() => { + this.isSampleLoading = false; }); }, collect(sampcode, accessnumber) { - fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, { + fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({samplenumber: sampcode, userid: ''}) }) @@ -269,7 +303,7 @@ uncollect(sampcode, accessnumber) { if(!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return ;} - fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, { + fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, { method: 'DELETE', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({samplenumber: sampcode, userid: ''}) }) @@ -280,7 +314,7 @@ unreceive(sampcode, accessnumber) { if(!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return ;} - fetch(`${BASEURL}/admin/api/samples/unreceive/${accessnumber}`, { + fetch(`${BASEURL}/api/samples/unreceive/${accessnumber}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({samplenumber: sampcode, userid : ''}) }) @@ -333,7 +367,7 @@ this.isDialogValOpen = false; }, validate(accessnumber, userid) { - fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, { + fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ userid:`${userid}` }) @@ -357,7 +391,7 @@ }, unvalidate(accessnumber, userid) { if(!confirm(`Unvalidate request ${accessnumber}?`)) { return ;} - fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, { + fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, { method: "DELETE", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ userid:`${userid}`, comment: this.unvalReason.trim() }) diff --git a/app/Views/v2/lab/main.php b/app/Views/lab/main.php similarity index 82% rename from app/Views/v2/lab/main.php rename to app/Views/lab/main.php index b9f6d89..bdfde16 100644 --- a/app/Views/v2/lab/main.php +++ b/app/Views/lab/main.php @@ -41,10 +41,10 @@ @@ -55,7 +55,7 @@ renderSection('script');?> diff --git a/app/Views/login.php b/app/Views/login.php index 0c665ca..3842394 100644 --- a/app/Views/login.php +++ b/app/Views/login.php @@ -1,100 +1,48 @@ - + - - - Login | GDC System - - - + + + Login - CMOD + + + + - -
    -
    -
    -
    -
    -
    🔒 GDC Login
    -

    Masukkan kredensial Anda untuk melanjutkan

    -
    - -
    - - getFlashdata('error')): ?> -
    - getFlashdata('error')) ?> -
    - - -
    - -
    - - -
    - -
    - - -
    - - -
    - -
    - © GDC Panda Laboratory System -
    -
    -
    -
    -
    + +
    +
    +
    +
    +
    + +
    +
    +

    CMOD

    +

    Sign in to continue

    +
    +
    + +
    +
    + +
    + +
    + +
    +
    +
    © 2025 - 5Panda. All rights reserved.
    +
    diff --git a/app/Views/user.php b/app/Views/user.php deleted file mode 100644 index fb081a7..0000000 --- a/app/Views/user.php +++ /dev/null @@ -1,207 +0,0 @@ -extend('_layouts/main.php') ?> - -section('title') ?> -User Management Glenlis -endSection() ?> - -section('content') ?> -
    - - -
    -

    👤 User Management Glenlis

    - -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    #NameRoleLevelActions
    -
    -
    - -
    -
    -
    -
    -
    - - - - - - - - - -endSection() ?> - -section('script') ?> - -endSection() ?> \ No newline at end of file diff --git a/app/Views/v2/admin/dialog_print.php b/app/Views/v2/admin/dialog_print.php deleted file mode 100644 index b9ee1b9..0000000 --- a/app/Views/v2/admin/dialog_print.php +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/app/Views/v2/admin/index.php b/app/Views/v2/admin/index.php deleted file mode 100644 index 6ae723c..0000000 --- a/app/Views/v2/admin/index.php +++ /dev/null @@ -1,425 +0,0 @@ -extend('v2/admin/main'); ?> - -section('content') ?> -
    -
    -
    - - -
    -
    -
    -

    - Requests Overview -

    -
    - - -
    - - - - - - - -
    -
    - - -
    -
    - -
    - - - - -
    -
    - -
    - - -
    - - - -
    - -
    -
    -
    - -
    - -
    - -
    - - include('v2/admin/dialog_sample'); ?> - include('v2/admin/dialog_val'); ?> - include('v2/admin/dialog_unval'); ?> - include('v2/admin/dialog_print'); ?> - -
    -endSection(); ?> - -section('script') ?> - -endSection(); ?> \ No newline at end of file diff --git a/app/Views/v2/dialog_setPassword.php b/app/Views/v2/dialog_setPassword.php deleted file mode 100644 index 4b09510..0000000 --- a/app/Views/v2/dialog_setPassword.php +++ /dev/null @@ -1,35 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/Views/v2/lab/dialog_unval.php b/app/Views/v2/lab/dialog_unval.php deleted file mode 100644 index 53abbee..0000000 --- a/app/Views/v2/lab/dialog_unval.php +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/Views/v2/lab/dialog_val.php b/app/Views/v2/lab/dialog_val.php deleted file mode 100644 index 64aaeb4..0000000 --- a/app/Views/v2/lab/dialog_val.php +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/app/Views/v2/login.php b/app/Views/v2/login.php deleted file mode 100644 index 8f82f7d..0000000 --- a/app/Views/v2/login.php +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - Login - CMOD - - - - - - -
    -
    -
    -
    -
    - -
    -
    -

    CMOD

    -

    Sign in to continue

    -
    -
    - -
    -
    - -
    - -
    - -
    -
    -
    © 2025 - 5Panda. All rights reserved.
    -
    - -