Refactor: Remove V2 namespace and consolidate role-based architecture

- Moved all V2 controllers (Lab, Requests, Samples, Users) to App\Controllers
- Removed deprecated role controllers (Admin, Doctor, Analyst, CustomerService)
- Simplified routes by removing /v2 prefix
- Added AGENTS.md with project conventions and TODO.md with task tracking
- Updated README.md with RBAC documentation
- Fixed hardcoded dates, status color mappings, and duplicate database calls
This commit is contained in:
mahdahar 2026-01-19 10:55:10 +07:00
parent 3b9c3dec10
commit b29f807295
54 changed files with 1284 additions and 3743 deletions

1
.gitignore vendored
View File

@ -125,3 +125,4 @@ _modules/*
/results/ /results/
/phpunit*.xml /phpunit*.xml
.roo/

267
AGENTS.md Normal file
View File

@ -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 `<?php echo $variable; ?>` syntax
- Pass data as associative array
```php
// Controller
$data['dataList'] = $results;
return view('admin/index', $data);
// View
<?php foreach ($dataList as $item): ?>
<tr><td><?= esc($item['name']) ?></td></tr>
<?php endforeach; ?>
```
### 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`

View File

@ -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 Copy `env` to `.env` and tailor for your app, specifically the baseURL
and any database settings. 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 ## Important Change with index.php
`index.php` is no longer in the root of the project! It has been moved inside the *public* folder, `index.php` is no longer in the root of the project! It has been moved inside the *public* folder,

129
TODO.md Normal file
View File

@ -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"
}
```

View File

@ -2,7 +2,6 @@
use CodeIgniter\Router\RouteCollection; use CodeIgniter\Router\RouteCollection;
// ------------------------------------------------------For Error Handling------------------------------------------------------ //
$routes->set404Override(function() { $routes->set404Override(function() {
$response = service('response'); $response = service('response');
$response->setStatusCode(404); $response->setStatusCode(404);
@ -11,96 +10,39 @@ $routes->set404Override(function() {
$routes->get('/unauthorized', 'ErrorPage::unauthorized'); $routes->get('/unauthorized', 'ErrorPage::unauthorized');
// ------------------------------------------------------Basic Page and Login/Logout------------------------------------------------------ //
$routes->get('/', 'Home::index'); $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->get('/logout', 'Auth::logout');
$routes->patch('/setPassword', 'Auth::setPassword');
$routes->get('label/coll/(:any)', 'Label::coll/$1'); $routes->get('label/coll/(:any)', 'Label::coll/$1');
$routes->get('label/dispatch/(:any)/(:any)', 'Label::dispatch/$1/$2'); $routes->get('label/dispatch/(:any)/(:any)', 'Label::dispatch/$1/$2');
$routes->get('label/all/(:any)', 'Label::print_all/$1'); $routes->get('label/all/(:any)', 'Label::print_all/$1');
// ------------------------------------------------------Page Based on Role------------------------------------------------------ //
$routes->group('admin', ['filter' => 'role:1'], function($routes) { $routes->group('admin', ['filter' => 'role:1'], function($routes) {
$routes->get('/', 'Admin::index'); $routes->get('', 'Admin::index');
$routes->get('modal_specimen', 'Admin::modal_specimen'); $routes->get('users', 'Admin::users');
$routes->get('user', 'User::index'); $routes->get('api/users', 'Users::index');
$routes->post('user/create', 'User::create'); $routes->post('api/users', 'Users::create');
$routes->post('user/update', 'User::update'); $routes->patch('api/users/(:any)', 'Users::update/$1');
$routes->post('user/delete', 'User::delete'); $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->group('lab', ['filter' => 'role:2'], function($routes) {
$routes->get('/', 'Doctor::index'); $routes->get('', 'Lab::index');
$routes->get('modal_specimen', 'Doctor::modal_specimen'); $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'); $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');
});
});

View File

@ -1,116 +1,21 @@
<?php <?php
namespace App\Controllers; namespace App\Controllers;
use CodeIgniter\Validation\CreditCardRules; use App\Controllers\BaseController;
class Admin extends BaseController { class Admin extends BaseController {
public function __construct() {
helper(['url', 'form', 'text']);
}
public function index() { public function index() {
$today = date('Y-m-d'); return view('admin/index');
$date1 = $this->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 === public function users() {
$counter = [ return view('admin/users');
'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);
}
public function modal_specimen() {
return view('admin/modal_specimen');
} }
} }

View File

@ -1,115 +0,0 @@
<?php
namespace App\Controllers;
class Analyst extends BaseController
{
public function index() {
$today = date('Y-m-d');
$date1 = $this->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');
}
}

View File

@ -2,11 +2,15 @@
namespace App\Controllers; namespace App\Controllers;
use App\Controllers\BaseController;
class Auth extends BaseController { class Auth extends BaseController {
public function loginPage() {
return view("login");
}
public function login() { public function login() {
if ($this->request->getMethod() === 'GET') {
return view('login');
} else if ($this->request->getMethod() === 'POST') {
helper(['form', 'url']); helper(['form', 'url']);
$session = session(); $session = session();
$db = \Config\Database::connect(); $db = \Config\Database::connect();
@ -14,22 +18,29 @@ class Auth extends BaseController {
$userid = strtoupper(trim($this->request->getPost('userid'))); $userid = strtoupper(trim($this->request->getPost('userid')));
$password = $this->request->getPost('password'); $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]); $query = $db->query("SELECT * FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]);
$user = $query->getRowArray(); $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']) { switch ((int)$user['USERLEVEL']) {
case 1: $role = 'admin'; break; case 1:
case 2: $role = 'doctor'; break; $role = 'admin';
case 3: $role = 'analyst'; break; break;
case 4: $role = 'cs'; break; case 2:
default: $role = ''; break; $role = 'analyst';
break;
case 3:
$role = 'phlebotomist';
break;
case 4:
$role = 'cs';
break;
default:
$role = '';
break;
} }
// Simpan session
$session->set([ $session->set([
'isLoggedIn' => true, 'isLoggedIn' => true,
'userid' => (string) $user['USERID'], 'userid' => (string) $user['USERID'],
@ -37,25 +48,39 @@ class Auth extends BaseController {
'userrole' => (string) $role, 'userrole' => (string) $role,
]); ]);
// Redirect sesuai level dari data didatabase
switch ((int)$user['USERLEVEL']) { switch ((int)$user['USERLEVEL']) {
case 1: return redirect()->to('/admin'); case 1:
case 2: return redirect()->to('/doctor'); return redirect()->to('admin');
case 3: return redirect()->to('/analyst'); case 2:
case 4: return redirect()->to('/cs'); return redirect()->to('lab');
default: return redirect()->to('/login'); case 3:
return redirect()->to('analyst');
case 4:
return redirect()->to('cs');
default:
return redirect()->to('login');
} }
} else { } else {
$session->setFlashdata('error', 'USERID atau PASSWORD salah.'); $session->setFlashdata('error', 'USERID atau PASSWORD salah.');
return redirect()->back(); return redirect()->back();
} }
}
} }
public function logout() { public function logout() {
session()->destroy(); $session = session();
return redirect()->to('/login'); $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);
} }
} }

View File

@ -1,111 +0,0 @@
<?php
namespace App\Controllers;
class CustomerService extends BaseController
{
public function index() {
$today = date('Y-m-d');
$date1 = $this->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);
}
}

View File

@ -1,115 +0,0 @@
<?php
namespace App\Controllers;
class Doctor extends BaseController
{
public function index() {
$today = date('Y-m-d');
$date1 = $this->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');
}
}

View File

@ -14,12 +14,12 @@ class Home extends BaseController {
} }
// Jika sudah login, arahkan sesuai level // Jika sudah login, arahkan sesuai level
switch ($session->get('level')) { switch ($session->get('userlevel')) {
case 1: return redirect()->to('/admin'); case 1: return redirect()->to('admin');
case 2: return redirect()->to('/dokter'); case 2: return redirect()->to('lab');
case 3: return redirect()->to('/analis'); case 3: return redirect()->to('analyst');
case 4: return redirect()->to('/cs'); case 4: return redirect()->to('cs');
default: return redirect()->to('/login'); default: return redirect()->to('login');
} }
} }

View File

@ -1,5 +1,6 @@
<?php <?php
namespace App\Controllers\V2;
namespace App\Controllers;
use App\Controllers\BaseController; use App\Controllers\BaseController;
@ -10,7 +11,7 @@ class Lab extends BaseController {
} }
public function index() { public function index() {
return view('v2/lab/index'); return view('lab/index');
} }
} }

View File

@ -1,5 +1,5 @@
<?php <?php
namespace App\Controllers\V2; namespace App\Controllers;
use CodeIgniter\API\ResponseTrait; use CodeIgniter\API\ResponseTrait;
use App\Controllers\BaseController; use App\Controllers\BaseController;
@ -7,11 +7,10 @@ class Requests extends BaseController {
use ResponseTrait; use ResponseTrait;
public function index() { public function index() {
$db = db_connect(); $db = \Config\Database::connect();
$date1 = $this->request->getGet('date1'); $date1 = $this->request->getGet('date1');
$date2 = $this->request->getGet('date2'); $date2 = $this->request->getGet('date2');
$db = \Config\Database::connect();
$sql = "SELECT * from GDC_CMOD.dbo.V_DASHBOARD_DEV where $sql = "SELECT * from GDC_CMOD.dbo.V_DASHBOARD_DEV where
COLLECTIONDATE between '$date1 00:00' and '$date2 23:59' 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'";
@ -27,7 +26,7 @@ class Requests extends BaseController {
public function show($accessnumber) { public function show($accessnumber) {
$db = db_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
@ -51,7 +50,7 @@ class Requests extends BaseController {
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$userid = $input['userid']; $userid = $input['userid'];
$comment = $input['comment']; $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, $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);
@ -63,11 +62,9 @@ class Requests extends BaseController {
public function val($accessnumber) { public function val($accessnumber) {
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$userid = $input['userid']; $userid = $input['userid'];
$db = db_connect(); $db = \Config\Database::connect();
//cek val
$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();
//$data['data'] = $result;
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);
@ -79,10 +76,8 @@ class Requests extends BaseController {
$isval2 = $row['ISVAL2']; $isval2 = $row['ISVAL2'];
$val1user = $row['VAL1USER']; $val1user = $row['VAL1USER'];
if( $isval1 == 1 ) { if( $isval1 == 1 ) {
// val done
if ( $isval2 == 1 ) { return $this->response->setJSON(['message'=> 'validation done, not updating anything']); } if ( $isval2 == 1 ) { return $this->response->setJSON(['message'=> 'validation done, not updating anything']); }
else { else {
// val2 if user val1 != userid
if($val1user != $userid) { if($val1user != $userid) {
$sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'"; $sql = "update GDC_CMOD.dbo.CM_REQUESTS set ISVAL2=1, VAL2USER='$userid', VAL2DATE=GETDATE() where ACCESSNUMBER='$accessnumber'";
$data['val'] = 2; $data['val'] = 2;
@ -93,7 +88,6 @@ class Requests extends BaseController {
} }
} }
} else { } else {
// val1
$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;

View File

@ -1,5 +1,5 @@
<?php <?php
namespace App\Controllers\V2; namespace App\Controllers;
use CodeIgniter\API\ResponseTrait; use CodeIgniter\API\ResponseTrait;
use App\Controllers\BaseController; use App\Controllers\BaseController;
@ -90,7 +90,6 @@ class Samples extends BaseController {
$db = \Config\Database::connect(); $db = \Config\Database::connect();
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$samplenumber = $input['samplenumber']; $samplenumber = $input['samplenumber'];
// update firebird
$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
@ -101,6 +100,7 @@ class Samples extends BaseController {
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 = '';
foreach( $rows as $row ) { foreach( $rows as $row ) {
$hon = $row['EXTERNALORDERNUMBER']; $hon = $row['EXTERNALORDERNUMBER'];
$testcode = $row['TESTCODE']; $testcode = $row['TESTCODE'];

View File

@ -1,5 +1,5 @@
<?php <?php
namespace App\Controllers\V2; namespace App\Controllers;
use CodeIgniter\API\ResponseTrait; use CodeIgniter\API\ResponseTrait;
use App\Controllers\BaseController; use App\Controllers\BaseController;
@ -9,7 +9,6 @@ class Users extends BaseController {
protected $db; protected $db;
public function __construct() { public function __construct() {
// Koneksi database dan validation service
$this->db = \Config\Database::connect(); $this->db = \Config\Database::connect();
} }
@ -26,13 +25,11 @@ class Users extends BaseController {
public function create() { public function create() {
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
// ambil input
$userid = $input['userid']; $userid = $input['userid'];
$userlevel = $input['userlevel']; $userlevel = $input['userlevel'];
$password = $input['password']; $password = $input['password'];
$password_2 = $input['password_2']; $password_2 = $input['password_2'];
// Cek Password Apakah Sama
if ($password != $password_2) { if ($password != $password_2) {
return $this->response->setJSON(['message'=> 'Password not the same']); 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']); 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]); $sql = $this->db->query("SELECT USERID FROM gdc_cmod.dbo.USERS WHERE USERID = ?", [$userid]);
$query = $sql->getRowArray(); $query = $sql->getRowArray();
if ($query != null) { if ($query != null) {
return $this->response->setJSON(['message'=> 'Userid already exists']); return $this->response->setJSON(['message'=> 'Userid already exists']);
} }
// Hash Password
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// Insert
$this->db->transBegin(); $this->db->transBegin();
try { try {
$sqlInsert = " $sqlInsert = "
@ -62,32 +56,25 @@ class Users extends BaseController {
$this->db->transCommit(); $this->db->transCommit();
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Kalau ada error, rollback semua perubahan
$this->db->transRollback(); $this->db->transRollback();
// (Opsional) tampilkan atau log error
return $this->response->setJSON(['message'=> 'Server error']); return $this->response->setJSON(['message'=> 'Server error']);
} }
return $this->response->setJSON(['message'=> 'User '.$userid.' Berhasil ditambahkan!']); return $this->response->setJSON(['message'=> 'User '.$userid.' Berhasil ditambahkan!']);
} }
public function update() { public function update($id = null) {
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
$userid = $input['userid']; $userid = $input['userid'];
$userlevel = $input['userlevel']; $userlevel = $input['userlevel'];
$password = $input['password']; $password = $input['password'];
$password_2 = $input['password_2']; $password_2 = $input['password_2'];
// Jika password tidak kosong - Lakukan Full Update
if ( $password != '' || $password_2 != '') { if ( $password != '' || $password_2 != '') {
// Cek Password Apakah Sama
if ($password != $password_2) { if ($password != $password_2) {
return $this->response->setJSON(['message'=> 'Password not the same']); return $this->response->setJSON(['message'=> 'Password not the same']);
} }
// Hash Password
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$sqlUpdate =" $sqlUpdate ="
UPDATE gdc_cmod.dbo.USERS UPDATE gdc_cmod.dbo.USERS
@ -97,7 +84,6 @@ class Users extends BaseController {
"; ";
$fullUpdate = true; $fullUpdate = true;
// Jika password kosong - Lakukan Partial Update Saja
} else { } else {
$sqlUpdate =" $sqlUpdate ="
UPDATE gdc_cmod.dbo.USERS UPDATE gdc_cmod.dbo.USERS
@ -107,7 +93,6 @@ class Users extends BaseController {
$fullUpdate = false; $fullUpdate = false;
} }
// Insert
$this->db->transBegin(); $this->db->transBegin();
try { try {
@ -120,10 +105,7 @@ class Users extends BaseController {
$this->db->transCommit(); $this->db->transCommit();
} catch (\Throwable $e) { } catch (\Throwable $e) {
// Kalau ada error, rollback semua perubahan
$this->db->transRollback(); $this->db->transRollback();
// (Opsional) tampilkan atau log error
return $this->response->setJSON(['message'=> 'Terjadi kesalahan pada server.']); return $this->response->setJSON(['message'=> 'Terjadi kesalahan pada server.']);
} }

View File

@ -1,112 +0,0 @@
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
class V2 extends BaseController {
public function index() {
$session = session();
if (! $session->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);
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use CodeIgniter\API\ResponseTrait;
class Admin extends BaseController {
use ResponseTrait;
protected $db;
public function __construct() {
$this->db = \Config\Database::connect();
helper(['url', 'form', 'text']);
}
public function index() {
return view('v2/admin/index');
}
public function users() {
return view('v2/admin/users');
}
}

View File

@ -1,157 +0,0 @@
<?php
// 1_Digunakan Untuk Nav Link Pada Side Bar Yg Aktif dan Tidak
$uri = service('uri');
$path = $uri->getPath();
$activeUrls = ['/admin', '/cs', '/analyst', '/doctor'];
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<?= $this->renderSection('title'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<link href="https://cdn.datatables.net/v/bs5/dt-2.3.4/r-3.0.7/datatables.min.css" rel="stylesheet" integrity="sha384-RaJlMsTv+nhuWA/3SQzc3dPVUOKfEb08YW4YZsaNK3UNFUhjvLkn/SwJEfKSavGD" crossorigin="anonymous">
<style>
table {
font-size: 0.7rem;
}
/* Untuk Card Status Counter */
.status-card {
width: 123px;
/* lebar tetap */
height: 60px;
/* tinggi seragam */
border-radius: 8px;
background-color: #fff;
border : solid 1px #ddd ;
/* box-shadow: 0 3px 5px rgba(0, 0, 0, 0.08); */
transition: transform 0.15s ease, box-shadow 0.15s ease;
display: flex;
/* flex untuk isi card */
align-items: center;
/* tengah vertikal */
justify-content: center;
/* tengah horizontal */
flex-direction: column;
/* urut atas ke bawah */
}
.status-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.2);
}
.status-icon {
font-size: 1.1rem;
}
.status-count {
font-weight: 700;
font-size: 1.1rem;
}
.status-label {
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
/* biar teks tidak turun ke bawah */
overflow: hidden;
text-overflow: ellipsis;
/* kalau terlalu panjang, potong dengan "..." */
max-width: 100%;
/* biar rapih dalam box */
}
.dot {
height: 7px;
width: 7px;
border-radius: 50%;
display: inline-block;
margin: 0;
}
/* Untuk Cursor di Tabel */
.pointercol {
cursor: pointer;
}
/* 1 */
.Pend {
background-color: white;
}
/* 2 */
.PartColl {
background-color: #ff99aaaf !important;
}
/* 3 */
.Collected {
background-color: #d6303086 !important;
}
/* 4 */
.PartRecv {
background-color: #a0c0d9af !important;
}
/* 5 */
.Received {
background-color: #0985e39d !important;
}
/* 6 */
.Incomplete {
background-color: #fbff009a !important;
}
/* 7 */
.Final {
background-color: #90EE90 !important;
}
/* 8 */
.FinVerification {
background-color: #09ff00ab !important;
}
/* 9 */
.PenVerification {
background-color: #ee5a24c7 !important;
}
/* 10 */
.Suspend {
background-color: purple;
}
.btn-xs { font-size : 0.7rem; }
.text-xs { font-size : 0.7rem; }
</style>
<?= $this->renderSection('css'); ?>
</head>
<body class="d-flex flex-column min-vh-100">
<?= $this->renderSection('content'); ?>
<footer class="bg-dark text-white mt-auto py-1 px-3 fs-6">
<small>© 2025 - 5Panda</small>
</footer>
<script>
console.log("salam 5panda" );
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/v/bs5/dt-2.3.4/r-3.0.7/datatables.min.js" integrity="sha384-O4V7rOTTcSRflQBTMk8URAYWhGGEMgmmLFrqu3e83FQtze3vmndvrH3GcRdrfXRu" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/2.3.4/js/dataTables.bootstrap5.js"></script>
<script src="https://unpkg.com/mustache@4.2.0/mustache.min.js"></script>
<?= $this->renderSection('script'); ?>
</body>
</html>

View File

@ -0,0 +1,45 @@
<dialog class="modal" :open="isDialogPreviewOpen">
<template x-if="previewAccessnumber">
<div class="modal-box w-11/12 max-w-7xl h-[90vh] flex flex-col p-0 overflow-hidden bg-base-100">
<!-- Header -->
<div class="flex justify-between items-center p-3 bg-base-200 border-b border-base-300">
<h3 class="font-bold text-lg flex items-center gap-2">
<i class="fa fa-eye text-primary"></i>
Preview
<span class="badge badge-ghost text-xs" x-text="previewAccessnumber"></span>
</h3>
<div class="flex items-center gap-2">
<div class="join shadow-sm">
<button @click="previewType = 'preview'" :class="previewType === 'preview' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">Preview</button>
<button @click="setPreviewType('ind')" :class="previewType === 'ind' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">IND</button>
<button @click="setPreviewType('eng')" :class="previewType === 'eng' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">ENG</button>
<button @click="setPreviewType('pdf')" :class="previewType === 'pdf' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">PDF</button>
</div>
<button class="btn btn-sm btn-circle btn-ghost" @click="closePreviewDialog()">
<i class="fa fa-times"></i>
</button>
</div>
</div>
<!-- Content -->
<div class="flex-1 bg-base-300 relative p-1">
<iframe id="preview-iframe" x-ref="previewIframe" :src="getPreviewUrl()" class="w-full h-full rounded shadow-sm bg-white"></iframe>
</div>
<!-- Footer -->
<div class="p-3 bg-base-200 border-t border-base-300 flex justify-end items-center gap-4">
<label class="label cursor-pointer gap-2 mb-0">
<input type="checkbox" x-model="reviewed" class="checkbox checkbox-sm checkbox-primary" />
<span class="label-text text-sm">I have reviewed the results</span>
</label>
<div class="flex gap-2">
<button class="btn btn-sm btn-ghost" @click="closePreviewDialog()">Cancel</button>
<button id="validate-btn" x-ref="validateBtn" class="btn btn-sm btn-success"
@click="validate(previewAccessnumber, '<?=session('userid');?>')" :disabled="!reviewed">
<i class="fa fa-check mr-1"></i> Validate
</button>
</div>
</div>
</div>
</template>
</dialog>

View File

@ -2,6 +2,15 @@
<div class="modal-box w-2/3 max-w-5xl"> <div class="modal-box w-2/3 max-w-5xl">
<p class='text-right'><button class="btn btn-xs btn-neutral" @click="closeSampleDialog()">X</button></p> <p class='text-right'><button class="btn btn-xs btn-neutral" @click="closeSampleDialog()">X</button></p>
<template x-if="isSampleLoading">
<div class="text-center py-10">
<span class="loading loading-spinner loading-lg text-primary"></span>
<p class="mt-2">Loading data...</p>
</div>
</template>
<template x-if="!isSampleLoading">
<div>
<table class="table table-xs table-compact w-full mb-4"> <table class="table table-xs table-compact w-full mb-4">
<tr> <tr>
<td>MR# </td> <td x-text="': '+item.patnumber"></td> <td>MR# </td> <td x-text="': '+item.patnumber"></td>
@ -85,4 +94,6 @@
</table> </table>
</div> </div>
</template>
</div>
</dialog> </dialog>

View File

@ -0,0 +1,28 @@
<dialog class="modal" :open="isDialogSetPasswordOpen">
<div class="modal-box w-96">
<h3 class="font-bold text-lg mb-4">Change Password</h3>
<div class="form-control w-full">
<label class="label">
<span class="label-text">New Password</span>
</label>
<input type="password" x-model="password" class="input input-bordered w-full" placeholder="Enter new password" />
</div>
<div class="form-control w-full mt-3">
<label class="label">
<span class="label-text">Confirm Password</span>
</label>
<input type="password" x-model="confirm_password" class="input input-bordered w-full" placeholder="Confirm new password" />
</div>
<div x-show="error" class="alert alert-error mt-3 text-sm">
<span x-text="error"></span>
</div>
<div class="modal-action">
<button class="btn btn-ghost" @click="closeDialogSetPassword()">Cancel</button>
<button class="btn btn-primary" @click="savePassword('<?=session('userid'); ?>')" :disabled="isLoading">
<span x-show="isLoading" class="loading loading-spinner loading-sm"></span>
Save
</button>
</div>
</div>
<div class="modal-backdrop bg-black/30" @click="closeDialogSetPassword()"></div>
</dialog>

View File

@ -0,0 +1,20 @@
<dialog class="modal" :open="isDialogUnvalOpen">
<template x-if="unvalAccessnumber">
<div class="modal-box w-96">
<h3 class="font-bold text-lg mb-4 text-warning">
<i class="fa fa-exclamation-triangle mr-2"></i>Unvalidate Request
</h3>
<p class="text-sm mb-3" x-text="'Access Number: ' + unvalAccessnumber"></p>
<textarea class="textarea textarea-bordered w-full" rows="4"
x-model="unvalReason" placeholder="Enter reason for unvalidation..."></textarea>
<p class='text-right mt-3'>
<button class="btn btn-sm btn-ghost" @click="closeUnvalDialog()">Cancel</button>
<button id="unvalidate-btn" class="btn btn-sm btn-warning"
@click="unvalidate(unvalAccessnumber, '<?=session('userid');?>')"
:disabled="!unvalReason.trim()">
<i class="fa fa-undo mr-1"></i>Unvalidate
</button>
</p>
</div>
</template>
</dialog>

View File

@ -1,287 +1,401 @@
<?= $this->extend('_layouts/main.php') ?> <?= $this->extend('admin/main'); ?>
<?= $this->section('title') ?>
<title>Admin Glenlis</title>
<?= $this->endSection() ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<div class="container-fluid px-3"> <main class="p-4 flex-1 flex flex-col gap-2" x-data="dashboard">
<div class='row p-1'> <div class="card bg-base-100 shadow-xl h-full border border-base-200 overflow-hidden">
<div class='col fw-bold'>Dashboard, <?=session('userrole');?></div> <div class="card-body p-0 h-full flex flex-col">
<div class='col text-end'>
Hi, <?=session('userid');?> <!-- Header & Filters -->
<button class="btn btn-sm btn-secondary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-list"></i></button> <div class="p-4 border-b border-base-200 bg-base-50">
<div class="dropdown"> <div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-4">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1"> <div class="flex-1">
<li><a class="dropdown-item" href="<?=base_url('admin/');?>">Dashboard</a></li> <h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
<li><a class="dropdown-item" href="<?=base_url('');?>">Report</a></li> <i class="fa fa-chart-bar text-primary"></i> Requests Overview
<li><a class="dropdown-item" href="<?=base_url('admin/user');?>">User</a></li> </h2>
<li><a class="dropdown-item" href="<?=base_url('logout');?>">Log Out</a></li> </div>
</ul>
<!-- Status Filters -->
<div class="join shadow-sm bg-base-100 rounded-lg">
<button @click="filterKey = 'Total'" :class="filterKey === 'Total' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">
All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span>
</button>
<button @click="filterKey = 'Pend'" :class="filterKey === 'Pend' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Pending <span class="badge badge-sm ml-1" x-text="counters.Pend"></span>
</button>
<button @click="filterKey = 'Coll'" :class="filterKey === 'Coll' ? 'btn-active btn-warning text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Coll <span class="badge badge-sm badge-warning ml-1" x-text="counters.Coll"></span>
</button>
<button @click="filterKey = 'Recv'" :class="filterKey === 'Recv' ? 'btn-active btn-info text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Recv <span class="badge badge-sm badge-info ml-1" x-text="counters.Recv"></span>
</button>
<button @click="filterKey = 'Inc'" :class="filterKey === 'Inc' ? 'btn-active btn-error text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Inc <span class="badge badge-sm badge-error ml-1" x-text="counters.Inc"></span>
</button>
<button @click="filterKey = 'Fin'" :class="filterKey === 'Fin' ? 'btn-active btn-success text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Fin <span class="badge badge-sm badge-success ml-1" x-text="counters.Fin"></span>
</button>
<button @click="filterKey = 'Validated'" :class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Val <span class="badge badge-sm badge-primary ml-1" x-text="validatedCount"></span>
</button>
</div>
</div>
<!-- Search & Date Filter -->
<div class="flex flex-col md:flex-row gap-3 items-end bg-base-100 p-3 rounded-lg border border-base-200 shadow-sm">
<div class="form-control">
<label class="label text-xs font-bold py-1 text-base-content/60">Date Range</label>
<div class="join">
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date1"/>
<span class="join-item btn btn-sm btn-ghost no-animation bg-base-200 font-normal px-2">-</span>
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date2"/>
</div>
</div>
<div class="flex gap-2">
<button class="btn btn-sm btn-primary" @click='fetchList()'><i class='fa fa-search'></i> Search</button>
<button class="btn btn-sm btn-neutral" @click='reset()'><i class='fa fa-sync-alt'></i> Reset</button>
</div>
<span class="flex-1"></span>
<div class="form-control w-full md:w-auto">
<label class='input input-sm input-bordered'>
<i class="fa fa-filter"></i>
<input type="text" placeholder="Type to filter..." x-model="filterTable" />
</label>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="flex-1 overflow-y-auto px-4 pb-4">
<div class='col'> <template x-if="isLoading">
<form method="GET" action='<?= base_url('/admin') ?>'> <table class="table table-xs table-zebra w-full">
<div class="row align-items-center g-1"> <thead class="bg-base-100 sticky top-0 z-10">
<div class="col-auto fw-semibold text-muted"> <tr>
Date : <th style='width:7%;'><div class="skeleton h-4 w-20"></div></th>
</div> <th style='width:15%;'><div class="skeleton h-4 w-32"></div></th>
<th style='width:7%;'><div class="skeleton h-4 w-16"></div></th>
<div class="col-auto"> <th style='width:7%;'><div class="skeleton h-4 w-16"></div></th>
<input type="date" class="form-control form-control-sm" name="date1" <th style='width:8%;'><div class="skeleton h-4 w-16"></div></th>
value="<?php echo $date1; ?>" /> <th style='width:8%;'><div class="skeleton h-4 w-20"></div></th>
</div> <th style='width:15%;'><div class="skeleton h-4 w-32"></div></th>
<th style='width:3%;'><div class="skeleton h-4 w-12"></div></th>
<div class="col-auto">-</div> <th style='width:5%;'><div class="skeleton h-4 w-16"></div></th>
<th style='width:5%;'><div class="skeleton h-4 w-16"></div></th>
<div class="col-auto"> <th style='width:4%;'><div class="skeleton h-4 w-12"></div></th>
<input type="date" class="form-control form-control-sm" name="date2" </tr>
value="<?php echo $date2; ?>" /> </thead>
</div> <tbody>
<template x-for="i in 5" :key="i">
<div class="col-auto"> <tr>
<button type="submit" class="btn btn-sm btn-primary">Search</button> <td colspan="11"><div class="skeleton h-4 w-full"></div></td>
</div> </tr>
</template>
<div class="col-auto"> <button type="button" class="btn btn-sm btn-outline-dark" onclick="window.location.href='<?=base_url();?>';"> RESET </button> </div> </tbody>
</div> </table>
</form> </template>
</div> <template x-if="!isLoading && !list.length">
<div id="filterGroup" class="col text-end"> <div class="text-center py-10">
<button class='btn btn-sm btn-outline-secondary' data-type='secondary'><?=$counter['pending'];?> <small>Pending</small></button> <i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
<button class='btn btn-sm btn-outline-info' data-type='info'><?=$counter['collect'];?> <small>Collect</small></button> <p>No records found</p>
<button class='btn btn-sm btn-outline-primary' data-type='primary'><?=$counter['recv'];?> <small>Receive</small></button> </div>
<button class='btn btn-sm btn-outline-warning' data-type='warning'><?=$counter['incomplete'];?> <small>Incomplete</small></button> </template>
<button class='btn btn-sm btn-outline-success' data-type='success'><?=$counter['complete'];?> <small>Complete</small></button> <template x-if="!isLoading && list.length">
<button class='btn btn-sm btn-dark' data-type='dark'><?=$counter['total'];?> <small>Total</small></button> <table class="table table-xs table-zebra w-full">
</div> <thead class="bg-base-100 sticky top-0 z-10">
<div class="col-1">
<button id="filterValBtn" class='btn btn-sm btn-outline-primary' onclick="filterVal('<?=session('userid');?>')"> <small>Validate</small></button>
</div>
</div>
<div class="table table-responsive-scroll">
<table id="datatables" class="table table-sm table-hover table-bordered align-middle table-striped">
<thead class="table-primary">
<tr> <tr>
<th style="width:1%;">S</th>
<th style='width:7%;'>Order Datetime</th> <th style='width:7%;'>Order Datetime</th>
<th>Patient Name</th> <th style='width:15%;'>Patient Name</th>
<th style='width:7%;'>No Lab</th> <th style='width:7%;'>No Lab</th>
<th style='width:7%;'>No Register</th> <th style='width:7%;'>No Register</th>
<th style='width:8%;'>Reff</th> <th style='width:8%;'>Reff</th>
<th style='width:8%;'>Doctor</th> <th style='width:8%;'>Doctor</th>
<th style='width:15%;'>Tests</th> <th style='width:15%;'>Tests</th>
<th style='width:5%;'>Result To</th> <th style='width:3%;'>ResTo</th>
<th style='width:5%;'>Validation</th> <th style='width:5%;'>Val</th>
<th style='width:5%;'>Result</th>
<th style='width:4%;'>Status</th> <th style='width:4%;'>Status</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach($dataList as $row) : ?> <template x-for="req in filtered" :key="req.SP_ACCESSNUMBER">
<?php <tr class="hover:bg-base-300">
switch ($row['statscode']) { <td x-text="req.REQDATE"></td>
case 1: $class = 'BackPend'; break; <td x-text="req.Name"></td>
case 2: $class = 'PartColl'; break; <td x-text="req.SP_ACCESSNUMBER"></td>
case 3: $class = 'Collected'; break; <td x-text="req.HOSTORDERNUMBER"></td>
case 4: $class = 'PartRecv'; break; <td x-text="req.REFF"></td>
case 5: $class = 'Received'; break; <td x-text="req.DOC"></td>
case 6: $class = 'Incomplete'; break; <td x-text="req.TESTS"></td>
case 7: $class = 'Final'; break; <td x-text="req.ODR_CRESULT_TO"></td>
case 8: $class = 'FinVerification'; break;
case 9: $class = 'PenVerification'; break;
default: $class = ''; break;
}
$tests = esc($row['tests']);
$tests = str_replace(",", ", ", $tests);
$accessnumber = $row['sp_accessnumber'];
?>
<tr>
<!--
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['statscode']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reqdate']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['patname']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['sp_accessnumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['hostordernumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reff']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['doc']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['tests']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['odr_cresult_to']) ?> </td>
-->
<td> <?= esc($row['statscode']) ?> </td> <td> <?= esc($row['reqdate']) ?> </td> <td> <?= esc($row['patname']) ?> </td>
<td> <?= esc($row['sp_accessnumber']) ?> </td> <td> <?= esc($row['hostordernumber']) ?> </td> <td> <?= esc($row['reff']) ?> </td>
<td> <?= esc($row['doc']) ?> </td> <td> <?= $tests; ?> </td>
<td> <td>
<?= esc($row['odr_cresult_to']) ?> <br/> <div class='flex gap-1 items-center'>
<?php if($row['valcounter'] == 2) { ?> <div class='w-15'>
<input type="checkbox" name="printed" id='printed'> Printed <br/> <p>1: <span x-text="req.VAL1USER"></span></p>
<input type="checkbox" name="eng" id='eng'> Eng <p>2: <span x-text="req.VAL2USER"></span></p>
<?php } ?> </div>
<template x-if="req.ISVAL == 1 && req.ISPENDING != 1">
<div class='text-center'>
<template x-if="req.VAL1USER == '<?=session('userid');?>' || req.VAL2USER == '<?=session('userid');?>'">
<button class="btn btn-xs btn-outline btn-secondary" @click="openUnvalDialog(req.SP_ACCESSNUMBER)"><i class="fa-solid fa-rotate-right"></i></button>
</template>
</div>
</template>
</div>
</td> </td>
<td>
<td class="text-center"> <template x-if="req.STATS !== 'PartColl' && req.STATS !== 'Coll' && req.STATS !== 'Pend'">
<table class='mx-auto'> <button class="btn btn-xs btn-outline btn-primary" @click="openPreviewDialog(req.SP_ACCESSNUMBER, 'preview')">Preview</button>
<tr> </template>
<td id='val1user-<?=$accessnumber;?>'>1 : <?=$row['val1user'];?></td>
</tr>
<tr>
<td id='val2user-<?=$accessnumber;?>'>2 : <?=$row['val2user'];?></td>
</tr>
<?php if($row['isvaltd'] != 0) { ?>
<?php
$user = session()->get('userid');
$val1 = $row['val1user'];
$val2 = $row['val2user'];
$valButton = (($val1 === '-') || ($val2 === '-')) && ($user !== $val1 && $user !== $val2);
$unvalButton = ($val1 !== '-') || ($val2 !== '-');
?>
<tr>
<td id="btnContainer-<?=$accessnumber;?>">
<?php if ($unvalButton): ?>
<button id='unvalBtn-<?=$accessnumber;?>' class='btn btn-xs btn-outline-secondary px-1 py-0 mb-1' onclick="unvalidateShow(<?=$row['sp_accessnumber'];?>)" data-bs-toggle="modal" data-bs-target="#unvalidateModal"><i class="bi bi-arrow-counterclockwise"></i></button>
<?php endif; ?>
<?php if ($valButton): ?>
<button id='valBtn-<?= $accessnumber; ?>' class='btn btn-xs btn-outline-success px-1 py-0 mb-1' onclick="validateShow(<?= $row['sp_accessnumber']; ?>)" data-bs-toggle="modal" data-bs-target="#validateModal"> <i class="bi bi-check-lg"></i></button>
<?php endif; ?>
</td> </td>
<td><button x-text="req.STATS === 'Fin' ? 'Final' : req.STATS" class="btn btn-xs"
:class="statusColor[req.STATS]" @click="openSampleDialog(req.SP_ACCESSNUMBER)"></button></td>
</tr> </tr>
<?php } ?> </template>
</table>
</td>
<td class="pointercol <?= esc($class) ?>" data-access="<?= $row['sp_accessnumber'] ?>" onclick="detailShow('<?=session('userrole');?>')" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['stats']) ?> </td>
</tr>
<?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </template>
</div> </div>
<!-- Modal Specimen Collection-->
<div class="modal fade" id="detailModal" aria-hidden="true" aria-labelledby="detailModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fw-bold fs-4" id="detailModal">Specimen Collection</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="specimenModalContent"> </div>
</div>
</div>
</div> </div>
<div class="modal fade" id="validateModal" aria-hidden="true" aria-labelledby="validateModal" tabindex="-1"> <?php echo $this->include('admin/dialog_sample'); ?>
<div class="modal-dialog modal-xl modal-dialog-centered"> <?php echo $this->include('admin/dialog_unval'); ?>
<div class="modal-content"> <?php echo $this->include('admin/dialog_preview'); ?>
<div class="modal-body" id="validateModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="unvalidateModal" aria-hidden="true" aria-labelledby="unvalidateModal" tabindex="-1"> </main>
<div class="modal-dialog modal-xl modal-dialog-centered"> <?= $this->endSection(); ?>
<div class="modal-content">
<div class="modal-body" id="unvalidateModalContent"> </div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('script') ?> <?= $this->section('script') ?>
<script> <script type="module">
$(document).ready(function () { import Alpine from '<?=base_url("js/app.js");?>';
table = $('#datatables').DataTable({
layout: { document.addEventListener('alpine:init', () => {
topEnd: 'search', Alpine.data("dashboard", ()=> ({
bottomStart: null, bottomEnd: null // dashboard
today: "",
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',
Coll: 'bg-orange-500 text-white font-bold',
PartRecv: 'bg-blue-200 text-black font-bold',
Recv: 'bg-blue-500 text-white font-bold',
Inc: 'bg-yellow-500 text-white font-bold',
Fin: 'bg-green-500 text-white font-bold',
}, },
order: [[0, 'asc'], [3, 'asc']], filterTable :"",
paging: false, filterKey: 'Total',
scrollCollapse: true, statusMap: {
scrollY: '70vh', Total: [],
columnDefs: [{ Pend: ['Pend'],
className: 'text-center', Coll: ['Coll', 'PartColl'],
targets: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Recv: ['Recv', 'PartRecv'],
}, // semua kolom di tengah Inc: ['Inc'],
{ Fin: ['Fin'],
className: 'text-start', },
targets: [10]
init() {
this.today = new Date().toISOString().slice(0, 10);
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();
for (let k in this.counters) { this.counters[k] = 0; }
fetch(`${BASEURL}/api/requests?${param}`, {
method: 'GET',
headers: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(data => {
this.list = data.data ?? [];
this.filterKey = 'Total';
this.list.forEach(item => {
if (this.counters[item.STATS] !== undefined) { this.counters[item.STATS]++; this.counters.Total++; }
else {
if(item.STATS == 'PartColl') { this.counters.Coll++; }
else if(item.STATS == 'PartRecv') { this.counters.Recv++; }
this.counters.Total++;
} }
],
responsive: true,
}); });
this.list.sort((a, b) => {
$('#filterGroup .btn').on('click', function () { let codeA = statusOrder[a.STATS] ?? 0;
$('#filterGroup .btn').each(function () { let codeB = statusOrder[b.STATS] ?? 0;
const type = $(this).data('type'); return codeA - codeB;
$(this).removeClass(`btn-${type}`).addClass(`btn-outline-${type}`);
});
const type = $(this).data('type');
$(this).removeClass(`btn-outline-${type}`).addClass(`btn-${type}`);
let vals;
if(type=='secondary') { vals = [0]; }
if(type=='info') { vals = ['1','2']; }
if(type=='primary') { vals = ['3','4','5']; }
if(type=='warning') { vals = [6,7]; }
if(type=='success') { vals = [8]; }
if(type=='dark') {vals=[];}
regex = vals.join('|');
table.column(0).search(regex, true, false).draw();
}); });
}).finally(() => {
this.isLoading = false;
}); });
},
reset() {
this.filter.date1 = this.today;
this.filter.date2 = this.today;
this.fetchList();
},
isValidated (item) {
// Select the column by index (1) and apply a search that excludes "Test" return item.ISVAL == 1 && item.ISPENDING != 1;
// The regex: ^((?!Test).)*$ },
// - ^...$ anchors the start and end get filtered() {
// - (?!Test) is a negative lookahead, failing the match if "Test" is found let filteredList = this.list;
function filterVal(userid) { if (this.filterKey === 'Validated') {
var currentSearch = table.column(9).search(); filteredList = filteredList.filter(item => this.isValidated(item));
if (currentSearch.includes(userid)) {
table.column(9).search( '' ).draw();
$("#filterValBtn").removeClass(`btn-primary`).addClass(`btn-outline-primary`);
} else { } else {
table.column(9).search( `^((?!${userid}).)*$`, true, false ).draw(); const validStatuses = this.statusMap[this.filterKey];
$("#filterValBtn").removeClass(`btn-outline-primary`).addClass(`btn-primary`); 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;
},
function unvalidate(accessnumber,userid) { /*
if(confirm("Are you sure?")) { sample dialog
fetch(`${BASE_URL}/api/request/validate/${accessnumber}`, { */
method: "DELETE", 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: '<?= session('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: '<?= session('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 : '<?= session('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"}, headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` }) body: JSON.stringify({ userid:`${userid}` })
}).then(response => { }).then(response => {
console.log("Response:", response); this.closePreviewDialog();
$(`#val1user-${accessnumber}`).text("1 : "); this.fetchList();
$(`#val2user-${accessnumber}`).text("2 : "); console.log('Validate clicked for', this.previewAccessnumber, 'by user', userid);
$(`#valBtn-${accessnumber}`).prop('disabled', false);
$(`#unvalBtn-${accessnumber}`).prop('disabled', false);
}).catch(error => {
console.log("Error:", error);
}); });
} },
}
const BASE_URL = "<?= base_url(); ?>"; /*
</script> unvalidate dialog
<script src="<?=base_url('js/admin/specimen.js');?>"></script> */
<script src="<?=base_url('js/admin/request.js');?>"></script> isDialogUnvalOpen : false,
<?= $this->endSection() ?> 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();
</script>
<?= $this->endSection(); ?>

View File

@ -41,21 +41,21 @@
<span class="text-xl"><i class="fa fa-user"></i></span> <span class="text-xl"><i class="fa fa-user"></i></span>
</div> </div>
<ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-xl border border-base-200 mt-2"> <ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-xl border border-base-200 mt-2">
<li><a class="active:bg-primary" href="<?=base_url('v2/admin') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li> <li><a class="active:bg-primary" href="<?=base_url('admin') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
<li><a class="active:bg-primary" href="<?=base_url('v2/admin/users') ?>"><i class="fa fa-users mr-2"></i> Users </a></li> <li><a class="active:bg-primary" href="<?=base_url('admin/users') ?>"><i class="fa fa-users mr-2"></i> Users </a></li>
<li><a @click.prevent="openDialogSetPassword()" class="active:bg-primary"><i class="fa fa-key mr-2"></i> Change Password</a></li> <li><a @click.prevent="openDialogSetPassword()" class="active:bg-primary"><i class="fa fa-key mr-2"></i> Change Password</a></li>
<li class="divider my-1"></li> <li class="divider my-1"></li>
<li><a href="<?=base_url('v2/logout')?>" class="text-error hover:bg-error/10"><i class="fa fa-sign-out-alt mr-2"></i> Logout</a></li> <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>
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
<?=$this->renderSection('content');?> <?=$this->renderSection('content');?>
<?=$this->include('v2/dialog_setPassword');?> <?=$this->include('admin/dialog_setPassword');?>
<footer class='bg-base-100 p-1'>&copy; <?=date('Y');?> - 5Panda</footer> <footer class='bg-base-100 p-1'>&copy; <?=date('Y');?> - 5Panda</footer>
<script> <script>
window.BASEURL = "<?=base_url("v2");?>"; window.BASEURL = "<?=base_url("admin");?>";
</script> </script>
<?=$this->renderSection('script');?> <?=$this->renderSection('script');?>
</body> </body>

View File

@ -1,40 +0,0 @@
<div class='row mb-2 mx-2'>
<div class="col">val : <?=$val1user;?> - <?=$val2user;?></div>
<div class='col text-end'>
<button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button>
<!-- <button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button> -->
</div>
</div>
<div class="row mx-auto">
<iframe id="result-iframe" src="http://glenlis/spooler_db/main_dev.php?acc=<?=$accessnumber;?>" width="750px" height="600px"> -->
<!-- <iframe id="result-iframe" src="<?=base_url('dummypage');?>" width="750px" height="600px"> -->
<p>Your browser does not support iframes. Please contact the technical support.</p>
</iframe>
</div>
<script>
{
// verifymodal
let iframe = document.getElementById('result-iframe');
let button = document.getElementById('result-button');
iframe.addEventListener('load', () => {
//console.log('loaded');
let iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
let scrollElement = iframeDoc.scrollingElement || iframeDoc.documentElement;
iframeDoc.addEventListener('scroll', () => {
let scrollTop = scrollElement.scrollTop;
let scrollHeight = scrollElement.scrollHeight;
let clientHeight = scrollElement.clientHeight;
// Check if scrolled to bottom
if (scrollTop + clientHeight >= scrollHeight - 2) {
button.disabled = false;
}
});
});
}
</script>

View File

@ -1,106 +0,0 @@
<div class="row mb-4 p-3">
<div class="col-8">
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Patient</div>
<div class="info-value">: {{patientIdentity.name}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Age</div>
<div class="info-value">: {{patientIdentity.age}} years</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Gender</div>
<div class="info-value">: {{patientIdentity.gender}}</div>
</div>
</div>
<div class="col-4">
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">Lab#</div>
<div class="info-value">: {{accessnumber}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">MR#</div>
<div class="info-value">: {{patientIdentity.rm}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">KTP</div>
<div class="info-value">: {{patientIdentity.ktp}}</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title fs-4 mb-2">
<i class="bi bi-flask-florence-fill"></i> Tube
</h5>
<table class="table fs-6 text-start table-hover">
<thead>
<tr>
<th>Sample Code</th>
<th>Sample Name</th>
<th class='text-center'>Collected</th>
<th class='text-center'>Received</th>
<th>Action</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td> <td>Collection</td> <td></td> <td></td>
<td><button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button></td>
</tr>
<tr>
<td></td> <td>All</td> <td></td> <td></td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick=""><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
</tr>
{{#samples}}
<tr id='row-{{accessnumber}}-{{sampcode}}'>
<td>{{sampcode}}</td>
<td>{{name}}</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input coll-checkbox"
{{#is_coll}}checked{{/is_coll}}
disabled
>
</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input recv-checkbox"
{{#is_recv}}checked{{/is_recv}}
disabled
>
</td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 1)"><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 0)"><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="unreceive({{accessnumber}}, {{sampcode}})"><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
<td>
</td>
</tr>
{{/samples}}
<tr class="text-center">
<td colspan="6">
<h6 class="p-0 m-0">
<i class="bi bi-pencil-square" role="button" onclick="commentValue()"></i>
</h6>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,16 +0,0 @@
<form id='unValForm'>
<div class="row mb-2">
<h4>Unvalidate <?=$accessnumber;?></h4>
</div>
<div class="row m-2">
<input type='hidden' name='accessnumber' value='<?=$accessnumber;?>'>
<input type='hidden' name='userid' value='<?=session('userid');?>'>
<textarea class='form-control' name='comment' placeholder="Note ..." rows='5'></textarea>
</div>
<div class='row mb-2 mx-2'>
<div class='col text-end'>
<button class='btn btn-xs btn-warning' onclick="event.preventDefault();unvalidateRequest('unValForm', <?=$accessnumber;?>);">Un-Validate</button>
</div>
</div>
</form>

View File

@ -1,4 +1,4 @@
<?= $this->extend('v2/admin/main'); ?> <?= $this->extend('admin/main'); ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<div x-data="users" class="contents"> <div x-data="users" class="contents">
@ -15,7 +15,28 @@
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table table-zebra w-full" x-show="list.length > 0"> <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> <thead>
<tr> <tr>
<th>User ID</th> <th>User ID</th>
@ -44,10 +65,13 @@
</template> </template>
</tbody> </tbody>
</table> </table>
<div x-show="list.length === 0" class="text-center py-10 opacity-50"> </template>
<i class="fa fa-spinner fa-spin text-4xl mb-2"></i> <template x-if="!isLoading && !list.length">
<p>Loading users...</p> <div class="text-center py-10">
<i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
<p>No users found</p>
</div> </div>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -150,7 +174,7 @@
document.addEventListener('alpine:init', () => { document.addEventListener('alpine:init', () => {
Alpine.data("users", () => ({ Alpine.data("users", () => ({
list: [], list: [],
mode: 'create', // create | edit mode: 'create',
isLoading: false, isLoading: false,
errorMsg: '', errorMsg: '',
form: { form: {
@ -165,10 +189,13 @@
}, },
fetchUsers() { fetchUsers() {
fetch(`${BASEURL}/admin/api/users`) this.isLoading = true;
fetch(`${BASEURL}/api/users`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
this.list = data.data ?? []; this.list = data.data ?? [];
}).finally(() => {
this.isLoading = false;
}); });
}, },
@ -182,9 +209,6 @@
return map[level] || 'badge-ghost'; return map[level] || 'badge-ghost';
}, },
/*
User Modal
*/
openUserModal(targetMode, user = null) { openUserModal(targetMode, user = null) {
this.mode = targetMode; this.mode = targetMode;
this.errorMsg = ''; this.errorMsg = '';
@ -213,13 +237,13 @@
try { try {
let res; let res;
if(this.mode == 'create') { if(this.mode == 'create') {
res = await fetch(`${BASEURL}/admin/api/users`, { res = await fetch(`${BASEURL}/api/users`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form) body: JSON.stringify(this.form)
}); });
} else { } else {
res = await fetch(`${BASEURL}/admin/api/users/${this.form.userid}`, { res = await fetch(`${BASEURL}/api/users/${this.form.userid}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form) body: JSON.stringify(this.form)

View File

@ -1,285 +0,0 @@
<?= $this->extend('_layouts/main.php') ?>
<?= $this->section('title') ?>
<title>Analyst Glenlis</title>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="container-fluid px-3">
<div class='row p-1'>
<div class='col fw-bold'>Dashboard, <?=session('userrole');?></div>
<div class='col text-end'>
Hi, <?=session('userid');?>
<button class="btn btn-sm btn-secondary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-list"></i></button>
<div class="dropdown">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item" href="<?=base_url('analyst/');?>">Dashboard</a></li>
<li><a class="dropdown-item" href="<?=base_url('');?>">Report</a></li>
<li><a class="dropdown-item" href="<?=base_url('logout');?>">Log Out</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
<div class='col'>
<form method="GET" action='<?= base_url('/analyst') ?>'>
<div class="row align-items-center g-1">
<div class="col-auto fw-semibold text-muted">
Date :
</div>
<div class="col-auto">
<input type="date" class="form-control form-control-sm" name="date1"
value="<?php echo $date1; ?>" />
</div>
<div class="col-auto">-</div>
<div class="col-auto">
<input type="date" class="form-control form-control-sm" name="date2"
value="<?php echo $date2; ?>" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Search</button>
</div>
<div class="col-auto"> <button type="button" class="btn btn-sm btn-outline-dark" onclick="window.location.href='<?=base_url();?>';"> RESET </button> </div>
</div>
</form>
</div>
<div id="filterGroup" class="col text-end">
<button class='btn btn-sm btn-outline-secondary' data-type='secondary'><?=$counter['pending'];?> <small>Pending</small></button>
<button class='btn btn-sm btn-outline-info' data-type='info'><?=$counter['collect'];?> <small>Collect</small></button>
<button class='btn btn-sm btn-outline-primary' data-type='primary'><?=$counter['recv'];?> <small>Receive</small></button>
<button class='btn btn-sm btn-outline-warning' data-type='warning'><?=$counter['incomplete'];?> <small>Incomplete</small></button>
<button class='btn btn-sm btn-outline-success' data-type='success'><?=$counter['complete'];?> <small>Complete</small></button>
<button class='btn btn-sm btn-dark' data-type='dark'><?=$counter['total'];?> <small>Total</small></button>
</div>
<div class="col-1">
<button id="filterValBtn" class='btn btn-sm btn-outline-primary' onclick="filterVal('<?=session('userid');?>')"> <small>Validate</small></button>
</div>
</div>
<div class="table table-responsive-scroll">
<table id="datatables" class="table table-sm table-hover table-bordered align-middle table-striped">
<thead class="table-primary">
<tr>
<th style="width:1%;">S</th>
<th style='width:7%;'>Order Datetime</th>
<th>Patient Name</th>
<th style='width:7%;'>No Lab</th>
<th style='width:7%;'>No Register</th>
<th style='width:8%;'>Reff</th>
<th style='width:8%;'>Doctor</th>
<th style='width:15%;'>Tests</th>
<th style='width:5%;'>Result To</th>
<th style='width:5%;'>Validation</th>
<th style='width:4%;'>Status</th>
</tr>
</thead>
<tbody>
<?php foreach($dataList as $row) : ?>
<?php
switch ($row['statscode']) {
case 1: $class = 'BackPend'; break;
case 2: $class = 'PartColl'; break;
case 3: $class = 'Collected'; break;
case 4: $class = 'PartRecv'; break;
case 5: $class = 'Received'; break;
case 6: $class = 'Incomplete'; break;
case 7: $class = 'Final'; break;
case 8: $class = 'FinVerification'; break;
case 9: $class = 'PenVerification'; break;
default: $class = ''; break;
}
$tests = esc($row['tests']);
$tests = str_replace(",", ", ", $tests);
$accessnumber = $row['sp_accessnumber'];
?>
<tr>
<!--
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['statscode']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reqdate']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['patname']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['sp_accessnumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['hostordernumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reff']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['doc']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['tests']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['odr_cresult_to']) ?> </td>
-->
<td> <?= esc($row['statscode']) ?> </td> <td> <?= esc($row['reqdate']) ?> </td> <td> <?= esc($row['patname']) ?> </td>
<td> <?= esc($row['sp_accessnumber']) ?> </td> <td> <?= esc($row['hostordernumber']) ?> </td> <td> <?= esc($row['reff']) ?> </td>
<td> <?= esc($row['doc']) ?> </td> <td> <?= $tests; ?> </td>
<td>
<?= esc($row['odr_cresult_to']) ?> <br/>
<?php if($row['valcounter'] == 2) { ?>
<input type="checkbox" name="printed" id='printed'> Printed <br/>
<input type="checkbox" name="eng" id='eng'> Eng
<?php } ?>
</td>
<td class="text-center">
<table class='mx-auto'>
<tr>
<td id='val1user-<?=$accessnumber;?>'>1 : <?=$row['val1user'];?></td>
</tr>
<tr>
<td id='val2user-<?=$accessnumber;?>'>2 : <?=$row['val2user'];?></td>
</tr>
<?php if($row['isvaltd'] != 0) { ?>
<?php
$user = session()->get('userid');
$val1 = $row['val1user'];
$val2 = $row['val2user'];
$valButton = (($val1 === '-') || ($val2 === '-')) && ($user !== $val1 && $user !== $val2);
$unvalButton = ($val1 !== '-') || ($val2 !== '-');
?>
<tr>
<td id="btnContainer-<?=$accessnumber;?>">
<?php if ($unvalButton): ?>
<button id='unvalBtn-<?=$accessnumber;?>' class='btn btn-xs btn-outline-secondary px-1 py-0 mb-1' onclick="unvalidateShow(<?=$row['sp_accessnumber'];?>)" data-bs-toggle="modal" data-bs-target="#unvalidateModal"><i class="bi bi-arrow-counterclockwise"></i></button>
<?php endif; ?>
<?php if ($valButton): ?>
<button id='valBtn-<?= $accessnumber; ?>' class='btn btn-xs btn-outline-success px-1 py-0 mb-1' onclick="validateShow(<?= $row['sp_accessnumber']; ?>)" data-bs-toggle="modal" data-bs-target="#validateModal"> <i class="bi bi-check-lg"></i></button>
<?php endif; ?>
</td>
</tr>
<?php } ?>
</table>
</td>
<td class="pointercol <?= esc($class) ?>" data-access="<?= $row['sp_accessnumber'] ?>" onclick="detailShow('<?=session('userrole');?>')" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['stats']) ?> </td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal Specimen Collection-->
<div class="modal fade" id="detailModal" aria-hidden="true" aria-labelledby="detailModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fw-bold fs-4" id="detailModal">Specimen Collection</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="specimenModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="validateModal" aria-hidden="true" aria-labelledby="validateModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-body" id="validateModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="unvalidateModal" aria-hidden="true" aria-labelledby="unvalidateModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-body" id="unvalidateModalContent"> </div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('script') ?>
<script>
$(document).ready(function () {
table = $('#datatables').DataTable({
layout: {
topEnd: 'search',
bottomStart: null, bottomEnd: null
},
order: [[0, 'asc'], [3, 'asc']],
paging: false,
scrollCollapse: true,
scrollY: '70vh',
columnDefs: [{
className: 'text-center',
targets: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}, // semua kolom di tengah
{
className: 'text-start',
targets: [10]
}
],
responsive: true,
});
$('#filterGroup .btn').on('click', function () {
$('#filterGroup .btn').each(function () {
const type = $(this).data('type');
$(this).removeClass(`btn-${type}`).addClass(`btn-outline-${type}`);
});
const type = $(this).data('type');
$(this).removeClass(`btn-outline-${type}`).addClass(`btn-${type}`);
let vals;
if(type=='secondary') { vals = [0]; }
if(type=='info') { vals = ['1','2']; }
if(type=='primary') { vals = ['3','4','5']; }
if(type=='warning') { vals = [6,7]; }
if(type=='success') { vals = [8]; }
if(type=='dark') {vals=[];}
regex = vals.join('|');
table.column(0).search(regex, true, false).draw();
});
});
// Select the column by index (1) and apply a search that excludes "Test"
// The regex: ^((?!Test).)*$
// - ^...$ anchors the start and end
// - (?!Test) is a negative lookahead, failing the match if "Test" is found
function filterVal(userid) {
var currentSearch = table.column(9).search();
if (currentSearch.includes(userid)) {
table.column(9).search( '' ).draw();
$("#filterValBtn").removeClass(`btn-primary`).addClass(`btn-outline-primary`);
} else {
table.column(9).search( `^((?!${userid}).)*$`, true, false ).draw();
$("#filterValBtn").removeClass(`btn-outline-primary`).addClass(`btn-primary`);
}
}
function unvalidate(accessnumber,userid) {
if(confirm("Are you sure?")) {
fetch(`${BASE_URL}/api/request/validate/${accessnumber}`, {
method: "DELETE",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` })
}).then(response => {
console.log("Response:", response);
$(`#val1user-${accessnumber}`).text("1 : ");
$(`#val2user-${accessnumber}`).text("2 : ");
$(`#valBtn-${accessnumber}`).prop('disabled', false);
$(`#unvalBtn-${accessnumber}`).prop('disabled', false);
}).catch(error => {
console.log("Error:", error);
});
}
}
const BASE_URL = "<?= base_url(); ?>";
</script>
<script src="<?=base_url('js/admin/specimen.js');?>"></script>
<script src="<?=base_url('js/admin/request.js');?>"></script>
<?= $this->endSection() ?>

View File

@ -1,40 +0,0 @@
<div class='row mb-2 mx-2'>
<div class="col">val : <?=$val1user;?> - <?=$val2user;?></div>
<div class='col text-end'>
<button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button>
<!-- <button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button> -->
</div>
</div>
<div class="row mx-auto">
<iframe id="result-iframe" src="http://glenlis/spooler_db/main_dev.php?acc=<?=$accessnumber;?>" width="750px" height="600px"> -->
<!-- <iframe id="result-iframe" src="<?=base_url('dummypage');?>" width="750px" height="600px"> -->
<p>Your browser does not support iframes. Please contact the technical support.</p>
</iframe>
</div>
<script>
{
// verifymodal
let iframe = document.getElementById('result-iframe');
let button = document.getElementById('result-button');
iframe.addEventListener('load', () => {
//console.log('loaded');
let iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
let scrollElement = iframeDoc.scrollingElement || iframeDoc.documentElement;
iframeDoc.addEventListener('scroll', () => {
let scrollTop = scrollElement.scrollTop;
let scrollHeight = scrollElement.scrollHeight;
let clientHeight = scrollElement.clientHeight;
// Check if scrolled to bottom
if (scrollTop + clientHeight >= scrollHeight - 2) {
button.disabled = false;
}
});
});
}
</script>

View File

@ -1,106 +0,0 @@
<div class="row mb-4 p-3">
<div class="col-8">
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Patient</div>
<div class="info-value">: {{patientIdentity.name}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Age</div>
<div class="info-value">: {{patientIdentity.age}} years</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Gender</div>
<div class="info-value">: {{patientIdentity.gender}}</div>
</div>
</div>
<div class="col-4">
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">Lab#</div>
<div class="info-value">: {{accessnumber}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">MR#</div>
<div class="info-value">: {{patientIdentity.rm}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">KTP</div>
<div class="info-value">: {{patientIdentity.ktp}}</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title fs-4 mb-2">
<i class="bi bi-flask-florence-fill"></i> Tube
</h5>
<table class="table fs-6 text-start table-hover">
<thead>
<tr>
<th>Sample Code</th>
<th>Sample Name</th>
<th class='text-center'>Collected</th>
<th class='text-center'>Received</th>
<th>Action</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td> <td>Collection</td> <td></td> <td></td>
<td><button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button></td>
</tr>
<tr>
<td></td> <td>All</td> <td></td> <td></td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick=""><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
</tr>
{{#samples}}
<tr id='row-{{accessnumber}}-{{sampcode}}'>
<td>{{sampcode}}</td>
<td>{{name}}</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input coll-checkbox"
{{#is_coll}}checked{{/is_coll}}
disabled
>
</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input recv-checkbox"
{{#is_recv}}checked{{/is_recv}}
disabled
>
</td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 1)"><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 0)"><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="unreceive({{accessnumber}}, {{sampcode}})"><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
<td>
</td>
</tr>
{{/samples}}
<tr class="text-center">
<td colspan="6">
<h6 class="p-0 m-0">
<i class="bi bi-pencil-square" role="button" onclick="commentValue()"></i>
</h6>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,16 +0,0 @@
<form id='unValForm'>
<div class="row mb-2">
<h4>Unvalidate <?=$accessnumber;?></h4>
</div>
<div class="row m-2">
<input type='hidden' name='accessnumber' value='<?=$accessnumber;?>'>
<input type='hidden' name='userid' value='<?=session('userid');?>'>
<textarea class='form-control' name='comment' placeholder="Note ..." rows='5'></textarea>
</div>
<div class='row mb-2 mx-2'>
<div class='col text-end'>
<button class='btn btn-xs btn-warning' onclick="event.preventDefault();unvalidateRequest('unValForm', <?=$accessnumber;?>);">Un-Validate</button>
</div>
</div>
</form>

View File

@ -1,261 +0,0 @@
<?= $this->extend('_layouts/main.php') ?>
<?= $this->section('title') ?>
<title>Customer Service Glenlis</title>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="container-fluid px-3">
<div class='row p-1'>
<div class='col fw-bold'>Dashboard, <?=session('userrole');?></div>
<div class='col text-end'>
Hi, <?=session('userid');?>
<button class="btn btn-sm btn-secondary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-list"></i></button>
<div class="dropdown">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item" href="<?=base_url('cs/');?>">Dashboard</a></li>
<li><a class="dropdown-item" href="<?=base_url('');?>">Report</a></li>
<li><a class="dropdown-item" href="<?=base_url('logout');?>">Log Out</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
<div class='col'>
<form method="GET" action='<?= base_url('/cs') ?>'>
<div class="row align-items-center g-1">
<div class="col-auto fw-semibold text-muted">
Date :
</div>
<div class="col-auto">
<input type="date" class="form-control form-control-sm" name="date1"
value="<?php echo $date1; ?>" />
</div>
<div class="col-auto">-</div>
<div class="col-auto">
<input type="date" class="form-control form-control-sm" name="date2"
value="<?php echo $date2; ?>" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Search</button>
</div>
<div class="col-auto"> <button type="button" class="btn btn-sm btn-outline-dark" onclick="window.location.href='<?=base_url();?>';"> RESET </button> </div>
</div>
</form>
</div>
<div id="filterGroup" class="col text-end">
<button class='btn btn-sm btn-outline-secondary' data-type='secondary'><?=$counter['pending'];?> <small>Pending</small></button>
<button class='btn btn-sm btn-outline-info' data-type='info'><?=$counter['collect'];?> <small>Collect</small></button>
<button class='btn btn-sm btn-outline-primary' data-type='primary'><?=$counter['recv'];?> <small>Receive</small></button>
<button class='btn btn-sm btn-outline-warning' data-type='warning'><?=$counter['incomplete'];?> <small>Incomplete</small></button>
<button class='btn btn-sm btn-outline-success' data-type='success'><?=$counter['complete'];?> <small>Complete</small></button>
<button class='btn btn-sm btn-dark' data-type='dark'><?=$counter['total'];?> <small>Total</small></button>
</div>
<div class="col-1">
<button id="filterValBtn" class='btn btn-sm btn-outline-primary' onclick="filterVal('<?=session('userid');?>')"> <small>Validate</small></button>
</div>
</div>
<div class="table table-responsive-scroll">
<table id="datatables" class="table table-sm table-hover table-bordered align-middle table-striped">
<thead class="table-primary">
<tr>
<th style="width:1%;">S</th>
<th style='width:7%;'>Order Datetime</th>
<th>Patient Name</th>
<th style='width:7%;'>No Lab</th>
<th style='width:7%;'>No Register</th>
<th style='width:8%;'>Reff</th>
<th style='width:8%;'>Doctor</th>
<th style='width:15%;'>Tests</th>
<th style='width:5%;'>Result To</th>
<th style='width:5%;'>Validation</th>
<th style='width:4%;'>Status</th>
</tr>
</thead>
<tbody>
<?php foreach($dataList as $row) : ?>
<?php
switch ($row['statscode']) {
case 1: $class = 'BackPend'; break;
case 2: $class = 'PartColl'; break;
case 3: $class = 'Collected'; break;
case 4: $class = 'PartRecv'; break;
case 5: $class = 'Received'; break;
case 6: $class = 'Incomplete'; break;
case 7: $class = 'Final'; break;
case 8: $class = 'FinVerification'; break;
case 9: $class = 'PenVerification'; break;
default: $class = ''; break;
}
$tests = esc($row['tests']);
$tests = str_replace(",", ", ", $tests);
$accessnumber = $row['sp_accessnumber'];
?>
<tr>
<!--
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['statscode']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reqdate']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['patname']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['sp_accessnumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['hostordernumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reff']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['doc']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['tests']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['odr_cresult_to']) ?> </td>
-->
<td> <?= esc($row['statscode']) ?> </td> <td> <?= esc($row['reqdate']) ?> </td> <td> <?= esc($row['patname']) ?> </td>
<td> <?= esc($row['sp_accessnumber']) ?> </td> <td> <?= esc($row['hostordernumber']) ?> </td> <td> <?= esc($row['reff']) ?> </td>
<td> <?= esc($row['doc']) ?> </td> <td> <?= $tests; ?> </td>
<td>
<?= esc($row['odr_cresult_to']) ?> <br/>
<?php if($row['valcounter'] == 2) { ?>
<input type="checkbox" name="printed" id='printed'> Printed <br/>
<input type="checkbox" name="eng" id='eng'> Eng
<?php } ?>
</td>
<td class="text-center">
<table class='mx-auto'>
<tr>
<td id='val1user-<?=$accessnumber;?>'>1 : <?=$row['val1user'];?></td>
</tr>
<tr>
<td id='val2user-<?=$accessnumber;?>'>2 : <?=$row['val2user'];?></td>
</tr>
</table>
</td>
<td class="pointercol <?= esc($class) ?>" > <?= esc($row['stats']) ?> </td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal Specimen Collection-->
<div class="modal fade" id="detailModal" aria-hidden="true" aria-labelledby="detailModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fw-bold fs-4" id="detailModal">Specimen Collection</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="specimenModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="validateModal" aria-hidden="true" aria-labelledby="validateModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-body" id="validateModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="unvalidateModal" aria-hidden="true" aria-labelledby="unvalidateModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-body" id="unvalidateModalContent"> </div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('script') ?>
<script>
$(document).ready(function () {
table = $('#datatables').DataTable({
layout: {
topEnd: 'search',
bottomStart: null, bottomEnd: null
},
order: [[0, 'asc'], [3, 'asc']],
paging: false,
scrollCollapse: true,
scrollY: '70vh',
columnDefs: [{
className: 'text-center',
targets: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}, // semua kolom di tengah
{
className: 'text-start',
targets: [10]
}
],
responsive: true,
});
$('#filterGroup .btn').on('click', function () {
$('#filterGroup .btn').each(function () {
const type = $(this).data('type');
$(this).removeClass(`btn-${type}`).addClass(`btn-outline-${type}`);
});
const type = $(this).data('type');
$(this).removeClass(`btn-outline-${type}`).addClass(`btn-${type}`);
let vals;
if(type=='secondary') { vals = [0]; }
if(type=='info') { vals = ['1','2']; }
if(type=='primary') { vals = ['3','4','5']; }
if(type=='warning') { vals = [6,7]; }
if(type=='success') { vals = [8]; }
if(type=='dark') {vals=[];}
regex = vals.join('|');
table.column(0).search(regex, true, false).draw();
});
});
// Select the column by index (1) and apply a search that excludes "Test"
// The regex: ^((?!Test).)*$
// - ^...$ anchors the start and end
// - (?!Test) is a negative lookahead, failing the match if "Test" is found
function filterVal(userid) {
var currentSearch = table.column(9).search();
if (currentSearch.includes(userid)) {
table.column(9).search( '' ).draw();
$("#filterValBtn").removeClass(`btn-primary`).addClass(`btn-outline-primary`);
} else {
table.column(9).search( `^((?!${userid}).)*$`, true, false ).draw();
$("#filterValBtn").removeClass(`btn-outline-primary`).addClass(`btn-primary`);
}
}
function unvalidate(accessnumber,userid) {
if(confirm("Are you sure?")) {
fetch(`${BASE_URL}/api/request/validate/${accessnumber}`, {
method: "DELETE",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` })
}).then(response => {
console.log("Response:", response);
$(`#val1user-${accessnumber}`).text("1 : ");
$(`#val2user-${accessnumber}`).text("2 : ");
$(`#valBtn-${accessnumber}`).prop('disabled', false);
$(`#unvalBtn-${accessnumber}`).prop('disabled', false);
}).catch(error => {
console.log("Error:", error);
});
}
}
const BASE_URL = "<?= base_url(); ?>";
</script>
<script src="<?=base_url('js/admin/specimen.js');?>"></script>
<script src="<?=base_url('js/admin/request.js');?>"></script>
<?= $this->endSection() ?>

View File

@ -1,40 +0,0 @@
<div class='row mb-2 mx-2'>
<div class="col">val : <?=$val1user;?> - <?=$val2user;?></div>
<div class='col text-end'>
<button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button>
<!-- <button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button> -->
</div>
</div>
<div class="row mx-auto">
<iframe id="result-iframe" src="http://glenlis/spooler_db/main_dev.php?acc=<?=$accessnumber;?>" width="750px" height="600px"> -->
<!-- <iframe id="result-iframe" src="<?=base_url('dummypage');?>" width="750px" height="600px"> -->
<p>Your browser does not support iframes. Please contact the technical support.</p>
</iframe>
</div>
<script>
{
// verifymodal
let iframe = document.getElementById('result-iframe');
let button = document.getElementById('result-button');
iframe.addEventListener('load', () => {
//console.log('loaded');
let iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
let scrollElement = iframeDoc.scrollingElement || iframeDoc.documentElement;
iframeDoc.addEventListener('scroll', () => {
let scrollTop = scrollElement.scrollTop;
let scrollHeight = scrollElement.scrollHeight;
let clientHeight = scrollElement.clientHeight;
// Check if scrolled to bottom
if (scrollTop + clientHeight >= scrollHeight - 2) {
button.disabled = false;
}
});
});
}
</script>

View File

@ -1,106 +0,0 @@
<div class="row mb-4 p-3">
<div class="col-8">
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Patient</div>
<div class="info-value">: {{patientIdentity.name}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Age</div>
<div class="info-value">: {{patientIdentity.age}} years</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Gender</div>
<div class="info-value">: {{patientIdentity.gender}}</div>
</div>
</div>
<div class="col-4">
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">Lab#</div>
<div class="info-value">: {{accessnumber}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">MR#</div>
<div class="info-value">: {{patientIdentity.rm}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">KTP</div>
<div class="info-value">: {{patientIdentity.ktp}}</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title fs-4 mb-2">
<i class="bi bi-flask-florence-fill"></i> Tube
</h5>
<table class="table fs-6 text-start table-hover">
<thead>
<tr>
<th>Sample Code</th>
<th>Sample Name</th>
<th class='text-center'>Collected</th>
<th class='text-center'>Received</th>
<th>Action</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td> <td>Collection</td> <td></td> <td></td>
<td><button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button></td>
</tr>
<tr>
<td></td> <td>All</td> <td></td> <td></td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick=""><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
</tr>
{{#samples}}
<tr id='row-{{accessnumber}}-{{sampcode}}'>
<td>{{sampcode}}</td>
<td>{{name}}</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input coll-checkbox"
{{#is_coll}}checked{{/is_coll}}
disabled
>
</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input recv-checkbox"
{{#is_recv}}checked{{/is_recv}}
disabled
>
</td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 1)"><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 0)"><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="unreceive({{accessnumber}}, {{sampcode}})"><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
<td>
</td>
</tr>
{{/samples}}
<tr class="text-center">
<td colspan="6">
<h6 class="p-0 m-0">
<i class="bi bi-pencil-square" role="button" onclick="commentValue()"></i>
</h6>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,16 +0,0 @@
<form id='unValForm'>
<div class="row mb-2">
<h4>Unvalidate <?=$accessnumber;?></h4>
</div>
<div class="row m-2">
<input type='hidden' name='accessnumber' value='<?=$accessnumber;?>'>
<input type='hidden' name='userid' value='<?=session('userid');?>'>
<textarea class='form-control' name='comment' placeholder="Note ..." rows='5'></textarea>
</div>
<div class='row mb-2 mx-2'>
<div class='col text-end'>
<button class='btn btn-xs btn-warning' onclick="event.preventDefault();unvalidateRequest('unValForm', <?=$accessnumber;?>);">Un-Validate</button>
</div>
</div>
</form>

View File

@ -1,285 +0,0 @@
<?= $this->extend('_layouts/main.php') ?>
<?= $this->section('title') ?>
<title>Doctor Glenlis</title>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="container-fluid px-3">
<div class='row p-1'>
<div class='col fw-bold'>Dashboard, <?=session('userrole');?></div>
<div class='col text-end'>
Hi, <?=session('userid');?>
<button class="btn btn-sm btn-secondary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-list"></i></button>
<div class="dropdown">
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item" href="<?=base_url('doctor/');?>">Dashboard</a></li>
<li><a class="dropdown-item" href="<?=base_url('');?>">Report</a></li>
<li><a class="dropdown-item" href="<?=base_url('logout');?>">Log Out</a></li>
</ul>
</div>
</div>
</div>
<div class="row">
<div class='col'>
<form method="GET" action='<?= base_url('/doctor') ?>'>
<div class="row align-items-center g-1">
<div class="col-auto fw-semibold text-muted">
Date :
</div>
<div class="col-auto">
<input type="date" class="form-control form-control-sm" name="date1"
value="<?php echo $date1; ?>" />
</div>
<div class="col-auto">-</div>
<div class="col-auto">
<input type="date" class="form-control form-control-sm" name="date2"
value="<?php echo $date2; ?>" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Search</button>
</div>
<div class="col-auto"> <button type="button" class="btn btn-sm btn-outline-dark" onclick="window.location.href='<?=base_url();?>';"> RESET </button> </div>
</div>
</form>
</div>
<div id="filterGroup" class="col text-end">
<button class='btn btn-sm btn-outline-secondary' data-type='secondary'><?=$counter['pending'];?> <small>Pending</small></button>
<button class='btn btn-sm btn-outline-info' data-type='info'><?=$counter['collect'];?> <small>Collect</small></button>
<button class='btn btn-sm btn-outline-primary' data-type='primary'><?=$counter['recv'];?> <small>Receive</small></button>
<button class='btn btn-sm btn-outline-warning' data-type='warning'><?=$counter['incomplete'];?> <small>Incomplete</small></button>
<button class='btn btn-sm btn-outline-success' data-type='success'><?=$counter['complete'];?> <small>Complete</small></button>
<button class='btn btn-sm btn-dark' data-type='dark'><?=$counter['total'];?> <small>Total</small></button>
</div>
<div class="col-1">
<button id="filterValBtn" class='btn btn-sm btn-outline-primary' onclick="filterVal('<?=session('userid');?>')"> <small>Validate</small></button>
</div>
</div>
<div class="table table-responsive-scroll">
<table id="datatables" class="table table-sm table-hover table-bordered align-middle table-striped">
<thead class="table-primary">
<tr>
<th style="width:1%;">S</th>
<th style='width:7%;'>Order Datetime</th>
<th>Patient Name</th>
<th style='width:7%;'>No Lab</th>
<th style='width:7%;'>No Register</th>
<th style='width:8%;'>Reff</th>
<th style='width:8%;'>Doctor</th>
<th style='width:15%;'>Tests</th>
<th style='width:5%;'>Result To</th>
<th style='width:5%;'>Validation</th>
<th style='width:4%;'>Status</th>
</tr>
</thead>
<tbody>
<?php foreach($dataList as $row) : ?>
<?php
switch ($row['statscode']) {
case 1: $class = 'BackPend'; break;
case 2: $class = 'PartColl'; break;
case 3: $class = 'Collected'; break;
case 4: $class = 'PartRecv'; break;
case 5: $class = 'Received'; break;
case 6: $class = 'Incomplete'; break;
case 7: $class = 'Final'; break;
case 8: $class = 'FinVerification'; break;
case 9: $class = 'PenVerification'; break;
default: $class = ''; break;
}
$tests = esc($row['tests']);
$tests = str_replace(",", ", ", $tests);
$accessnumber = $row['sp_accessnumber'];
?>
<tr>
<!--
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['statscode']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reqdate']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['patname']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['sp_accessnumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['hostordernumber']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['reff']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['doc']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['tests']) ?> </td>
<td class="pointercol" data-access="<?= $row['sp_accessnumber'] ?>" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['odr_cresult_to']) ?> </td>
-->
<td> <?= esc($row['statscode']) ?> </td> <td> <?= esc($row['reqdate']) ?> </td> <td> <?= esc($row['patname']) ?> </td>
<td> <?= esc($row['sp_accessnumber']) ?> </td> <td> <?= esc($row['hostordernumber']) ?> </td> <td> <?= esc($row['reff']) ?> </td>
<td> <?= esc($row['doc']) ?> </td> <td> <?= $tests; ?> </td>
<td>
<?= esc($row['odr_cresult_to']) ?> <br/>
<?php if($row['valcounter'] == 2) { ?>
<input type="checkbox" name="printed" id='printed'> Printed <br/>
<input type="checkbox" name="eng" id='eng'> Eng
<?php } ?>
</td>
<td class="text-center">
<table class='mx-auto'>
<tr>
<td id='val1user-<?=$accessnumber;?>'>1 : <?=$row['val1user'];?></td>
</tr>
<tr>
<td id='val2user-<?=$accessnumber;?>'>2 : <?=$row['val2user'];?></td>
</tr>
<?php if($row['isvaltd'] != 0) { ?>
<?php
$user = session()->get('userid');
$val1 = $row['val1user'];
$val2 = $row['val2user'];
$valButton = (($val1 === '-') || ($val2 === '-')) && ($user !== $val1 && $user !== $val2);
$unvalButton = ($val1 !== '-') || ($val2 !== '-');
?>
<tr>
<td id="btnContainer-<?=$accessnumber;?>">
<?php if ($unvalButton): ?>
<button id='unvalBtn-<?=$accessnumber;?>' class='btn btn-xs btn-outline-secondary px-1 py-0 mb-1' onclick="unvalidateShow(<?=$row['sp_accessnumber'];?>)" data-bs-toggle="modal" data-bs-target="#unvalidateModal"><i class="bi bi-arrow-counterclockwise"></i></button>
<?php endif; ?>
<?php if ($valButton): ?>
<button id='valBtn-<?= $accessnumber; ?>' class='btn btn-xs btn-outline-success px-1 py-0 mb-1' onclick="validateShow(<?= $row['sp_accessnumber']; ?>)" data-bs-toggle="modal" data-bs-target="#validateModal"> <i class="bi bi-check-lg"></i></button>
<?php endif; ?>
</td>
</tr>
<?php } ?>
</table>
</td>
<td class="pointercol <?= esc($class) ?>" data-access="<?= $row['sp_accessnumber'] ?>" onclick="detailShow('<?=session('userrole');?>')" data-bs-toggle="modal" data-bs-target="#detailModal"> <?= esc($row['stats']) ?> </td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal Specimen Collection-->
<div class="modal fade" id="detailModal" aria-hidden="true" aria-labelledby="detailModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fw-bold fs-4" id="detailModal">Specimen Collection</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="specimenModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="validateModal" aria-hidden="true" aria-labelledby="validateModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-body" id="validateModalContent"> </div>
</div>
</div>
</div>
<div class="modal fade" id="unvalidateModal" aria-hidden="true" aria-labelledby="unvalidateModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-body" id="unvalidateModalContent"> </div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('script') ?>
<script>
$(document).ready(function () {
table = $('#datatables').DataTable({
layout: {
topEnd: 'search',
bottomStart: null, bottomEnd: null
},
order: [[0, 'asc'], [3, 'asc']],
paging: false,
scrollCollapse: true,
scrollY: '70vh',
columnDefs: [{
className: 'text-center',
targets: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}, // semua kolom di tengah
{
className: 'text-start',
targets: [10]
}
],
responsive: true,
});
$('#filterGroup .btn').on('click', function () {
$('#filterGroup .btn').each(function () {
const type = $(this).data('type');
$(this).removeClass(`btn-${type}`).addClass(`btn-outline-${type}`);
});
const type = $(this).data('type');
$(this).removeClass(`btn-outline-${type}`).addClass(`btn-${type}`);
let vals;
if(type=='secondary') { vals = [0]; }
if(type=='info') { vals = ['1','2']; }
if(type=='primary') { vals = ['3','4','5']; }
if(type=='warning') { vals = [6,7]; }
if(type=='success') { vals = [8]; }
if(type=='dark') {vals=[];}
regex = vals.join('|');
table.column(0).search(regex, true, false).draw();
});
});
// Select the column by index (1) and apply a search that excludes "Test"
// The regex: ^((?!Test).)*$
// - ^...$ anchors the start and end
// - (?!Test) is a negative lookahead, failing the match if "Test" is found
function filterVal(userid) {
var currentSearch = table.column(9).search();
if (currentSearch.includes(userid)) {
table.column(9).search( '' ).draw();
$("#filterValBtn").removeClass(`btn-primary`).addClass(`btn-outline-primary`);
} else {
table.column(9).search( `^((?!${userid}).)*$`, true, false ).draw();
$("#filterValBtn").removeClass(`btn-outline-primary`).addClass(`btn-primary`);
}
}
function unvalidate(accessnumber,userid) {
if(confirm("Are you sure?")) {
fetch(`${BASE_URL}/api/request/validate/${accessnumber}`, {
method: "DELETE",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` })
}).then(response => {
console.log("Response:", response);
$(`#val1user-${accessnumber}`).text("1 : ");
$(`#val2user-${accessnumber}`).text("2 : ");
$(`#valBtn-${accessnumber}`).prop('disabled', false);
$(`#unvalBtn-${accessnumber}`).prop('disabled', false);
}).catch(error => {
console.log("Error:", error);
});
}
}
const BASE_URL = "<?= base_url(); ?>";
</script>
<script src="<?=base_url('js/admin/specimen.js');?>"></script>
<script src="<?=base_url('js/admin/request.js');?>"></script>
<?= $this->endSection() ?>

View File

@ -1,40 +0,0 @@
<div class='row mb-2 mx-2'>
<div class="col">val : <?=$val1user;?> - <?=$val2user;?></div>
<div class='col text-end'>
<button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button>
<!-- <button id='result-button' class='btn btn-xs btn-success' onclick='validate(<?=$accessnumber;?>, "<?=session('userid');?>" )' disabled>Verify</button> -->
</div>
</div>
<div class="row mx-auto">
<iframe id="result-iframe" src="http://glenlis/spooler_db/main_dev.php?acc=<?=$accessnumber;?>" width="750px" height="600px"> -->
<!-- <iframe id="result-iframe" src="<?=base_url('dummypage');?>" width="750px" height="600px"> -->
<p>Your browser does not support iframes. Please contact the technical support.</p>
</iframe>
</div>
<script>
{
// verifymodal
let iframe = document.getElementById('result-iframe');
let button = document.getElementById('result-button');
iframe.addEventListener('load', () => {
//console.log('loaded');
let iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
let scrollElement = iframeDoc.scrollingElement || iframeDoc.documentElement;
iframeDoc.addEventListener('scroll', () => {
let scrollTop = scrollElement.scrollTop;
let scrollHeight = scrollElement.scrollHeight;
let clientHeight = scrollElement.clientHeight;
// Check if scrolled to bottom
if (scrollTop + clientHeight >= scrollHeight - 2) {
button.disabled = false;
}
});
});
}
</script>

View File

@ -1,106 +0,0 @@
<div class="row mb-4 p-3">
<div class="col-8">
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Patient</div>
<div class="info-value">: {{patientIdentity.name}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Age</div>
<div class="info-value">: {{patientIdentity.age}} years</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-25 fw-bold">Gender</div>
<div class="info-value">: {{patientIdentity.gender}}</div>
</div>
</div>
<div class="col-4">
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">Lab#</div>
<div class="info-value">: {{accessnumber}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">MR#</div>
<div class="info-value">: {{patientIdentity.rm}}</div>
</div>
<div class="info-pair d-flex">
<div class="info-label w-50 fw-bold">KTP</div>
<div class="info-value">: {{patientIdentity.ktp}}</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title fs-4 mb-2">
<i class="bi bi-flask-florence-fill"></i> Tube
</h5>
<table class="table fs-6 text-start table-hover">
<thead>
<tr>
<th>Sample Code</th>
<th>Sample Name</th>
<th class='text-center'>Collected</th>
<th class='text-center'>Received</th>
<th>Action</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td> <td>Collection</td> <td></td> <td></td>
<td><button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button></td>
</tr>
<tr>
<td></td> <td>All</td> <td></td> <td></td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick=""><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick=""><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
</tr>
{{#samples}}
<tr id='row-{{accessnumber}}-{{sampcode}}'>
<td>{{sampcode}}</td>
<td>{{name}}</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input coll-checkbox"
{{#is_coll}}checked{{/is_coll}}
disabled
>
</td>
<td class='text-center'>
<input type="checkbox" class="form-check-input recv-checkbox"
{{#is_recv}}checked{{/is_recv}}
disabled
>
</td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1"><i class="bi bi-printer"></i></button>
<button class="btn btn-success px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 1)"><h6 class="p-0 m-0">Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="collect({{accessnumber}}, {{sampcode}}, 0)"><h6 class="p-0 m-0">Un-Coll.</h6></button>
<button class="btn btn-warning px-2 py-1" onclick="unreceive({{accessnumber}}, {{sampcode}})"><h6 class="p-0 m-0">Un-Recv.</h6></button>
</td>
<td>
</td>
</tr>
{{/samples}}
<tr class="text-center">
<td colspan="6">
<h6 class="p-0 m-0">
<i class="bi bi-pencil-square" role="button" onclick="commentValue()"></i>
</h6>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -1,16 +0,0 @@
<form id='unValForm'>
<div class="row mb-2">
<h4>Unvalidate <?=$accessnumber;?></h4>
</div>
<div class="row m-2">
<input type='hidden' name='accessnumber' value='<?=$accessnumber;?>'>
<input type='hidden' name='userid' value='<?=session('userid');?>'>
<textarea class='form-control' name='comment' placeholder="Note ..." rows='5'></textarea>
</div>
<div class='row mb-2 mx-2'>
<div class='col text-end'>
<button class='btn btn-xs btn-warning' onclick="event.preventDefault();unvalidateRequest('unValForm', <?=$accessnumber;?>);">Un-Validate</button>
</div>
</div>
</form>

View File

@ -1,96 +0,0 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title> Test display of HTML elements </title>
<!-- based off of https://www.cs.tut.fi/~jkorpela/www/testel.html, with additions of newer features -->
</head>
<body>
<header>
<h1>Testing display of HTML elements</h1>
<p>This is the document <code>header</code></p>
</header>
<main>
<article aria-label="headings">
<h1>This is 1st level heading</h1>
<p>This is a test paragraph.</p>
<h2>This is 2nd level heading</h2>
<p>This is a test paragraph.</p>
<h3>This is 3rd level heading</h3>
<p>This is a test paragraph.</p>
<h4>This is 4th level heading</h4>
<p>This is a test paragraph.</p>
<h5>This is 5th level heading</h5>
<p>This is a test paragraph.</p>
<h6>This is 6th level heading</h6>
<p>This is a test paragraph.</p>
</article>
<article>
<h2>Basic block level elements</h2>
<p>
This is a normal paragraph (<code>p</code> element). To add some length to it, let us mention that this page was primarily written for testing the effect of <strong>user style sheets</strong>. 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.
</p>
<p>
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.
</p>
<div>This is a <code>div</code> element. Authors may use such elements instead of paragraph markup for various reasons. (End of <code>div</code>.)</div>
<blockquote>
<p>
This is a block quotation containing a single paragraph. Well, not quite, since this is not <em>really</em> quoted text, but I hope you understand the point. After all, this page does not use HTML markup very normally anyway.
</p>
</blockquote>
<p>The following contains address information about the author, in an <code>address</code> element.</p>
<address>
<a href="../somelink.html" lang="fr" hreflang="en">Mon nom en francais</a>,
<a href="mailto:example@example.com">example@example.com</a><br>
3 Rue Jules Ferry, Neuilly Sur Seine, France 94000
</address>
</article>
<article>
<h2>Lists</h2>
<p>
This is a paragraph before an <strong>unnumbered</strong> list (<code>ul</code>). 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".
</p>
<ul>
<li> One.</li>
<li> Two.</li>
<li> Three. Well, probably this list item should be longer. Note that for short items lists look better if they are compactly presented, whereas for long items, it would be better to have more vertical spacing between items.</li>
<li> Four. This is the last item in this list. Let us terminate the list now without making any more fuss about it.</li>
</ul>
<p>The following is a <code>menu</code> list:</p>
<menu>
<li> One.</li>
<li> Two.</li>
<li> Three. Well, probably this list item should be longer so that it will probably wrap to the next line in rendering.</li>
</menu>
<p>The following is a <code>dir</code> list:</p>
<dir>
<li> One.</li>
<li> Two.</li>
<li> Three. Well, probably this list item should be longer so that it will probably wrap to the next line in rendering.</li>
</dir>
<p>
This is a paragraph before a <strong>numbered</strong> list (<code>ol</code>). 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".
</p>
<ol>
<li> One.</li>
<li> Two.</li>
<li> 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.</li>
<li> Four. This is the last item in this list. Let us terminate the list now without making any more fuss about it.</li>
</ol>
<p>
This is a paragraph before a <strong>definition</strong> list (<code>dl</code>). In principle, such a list should consist of <em>terms</em> and associated definitions. But many authors use <code>dl</code> elements for fancy "layout" things. Usually the effect is not <em>too</em> bad, if you design user style sheet rules for <code>dl</code> which are suitable for real definition lists.
</p>
<dl>
<dt> recursion</dt>
<dd> see recursion</dd>
<dt> recursion, indirect</dt>
<dd> see indirect recursion</dd>
<dt> indirect recursion</dt>
<dd> see recursion, indirect</dd>
<dt> term</dt>
<dd> 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</dd>
</dl>
</article>
</body>
</html>

View File

@ -1,4 +1,4 @@
<?= $this->extend('v2/lab/main'); ?> <?= $this->extend('lab/main'); ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<main class="p-4 flex-1 flex flex-col gap-2" x-data="dashboard"> <main class="p-4 flex-1 flex flex-col gap-2" x-data="dashboard">
@ -68,14 +68,43 @@
</div> </div>
<div class="flex-1 overflow-y-auto px-4 pb-4"> <div class="flex-1 overflow-y-auto px-4 pb-4">
<template x-if="list.length"> <template x-if="isLoading">
<table class="table table-xs table-zebra w-full">
<thead class="bg-base-100 sticky top-0 z-10">
<tr>
<th style='width:7%;'><div class="skeleton h-4 w-20"></div></th>
<th style='width:15%;'><div class="skeleton h-4 w-32"></div></th>
<th style='width:7%;'><div class="skeleton h-4 w-16"></div></th>
<th style='width:8%;'><div class="skeleton h-4 w-16"></div></th>
<th style='width:8%;'><div class="skeleton h-4 w-20"></div></th>
<th style='width:15%;'><div class="skeleton h-4 w-32"></div></th>
<th style='width:5%;'><div class="skeleton h-4 w-12"></div></th>
<th style='width:5%;'><div class="skeleton h-4 w-16"></div></th>
<th style='width:4%;'><div class="skeleton h-4 w-12"></div></th>
</tr>
</thead>
<tbody>
<template x-for="i in 5" :key="i">
<tr>
<td colspan="9"><div class="skeleton h-4 w-full"></div></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 records found</p>
</div>
</template>
<template x-if="!isLoading && list.length">
<table class="table table-xs table-zebra w-full"> <table class="table table-xs table-zebra w-full">
<thead class="bg-base-100 sticky top-0 z-10"> <thead class="bg-base-100 sticky top-0 z-10">
<tr> <tr>
<th style='width:7%;'>Order Datetime</th> <th style='width:7%;'>Order Datetime</th>
<th style='width:15%;'>Patient Name</th> <th style='width:15%;'>Patient Name</th>
<th style='width:7%;'>No Lab</th> <th style='width:7%;'>No Lab</th>
<th style='width:7%;'>No Register</th>
<th style='width:8%;'>Reff</th> <th style='width:8%;'>Reff</th>
<th style='width:8%;'>Doctor</th> <th style='width:8%;'>Doctor</th>
<th style='width:15%;'>Tests</th> <th style='width:15%;'>Tests</th>
@ -90,7 +119,6 @@
<td x-text="req.REQDATE"></td> <td x-text="req.REQDATE"></td>
<td x-text="req.Name"></td> <td x-text="req.Name"></td>
<td x-text="req.SP_ACCESSNUMBER"></td> <td x-text="req.SP_ACCESSNUMBER"></td>
<td x-text="req.HOSTORDERNUMBER"></td>
<td x-text="req.REFF"></td> <td x-text="req.REFF"></td>
<td x-text="req.DOC"></td> <td x-text="req.DOC"></td>
<td x-text="req.TESTS"></td> <td x-text="req.TESTS"></td>
@ -124,9 +152,9 @@
</div> </div>
<?php echo $this->include('v2/admin/dialog_sample'); ?> <?php echo $this->include('admin/dialog_sample'); ?>
<?php echo $this->include('v2/admin/dialog_val'); ?> <?php echo $this->include('admin/dialog_val'); ?>
<?php echo $this->include('v2/admin/dialog_unval'); ?> <?php echo $this->include('admin/dialog_unval'); ?>
</main> </main>
<?= $this->endSection(); ?> <?= $this->endSection(); ?>
@ -141,6 +169,7 @@
today: "", today: "",
filter: { date1: "", date2: "" }, filter: { date1: "", date2: "" },
list: [], list: [],
isLoading: false,
counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 }, counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 },
statusColor: { statusColor: {
Pend: 'bg-white text-black font-bold', Pend: 'bg-white text-black font-bold',
@ -164,20 +193,19 @@
init() { init() {
this.today = new Date().toISOString().slice(0, 10); this.today = new Date().toISOString().slice(0, 10);
this.filter.date1 = "2025-03-03"; this.filter.date1 = this.today;
this.filter.date2 = "2025-03-03"; this.filter.date2 = this.today;
//this.filter.date1 = this.today; this.fetchList();
//this.filter.date2 = this.today;
//this.fetchList();
}, },
fetchList(){ fetchList(){
this.isLoading = true;
this.list = []; this.list = [];
let statusOrder = { Pend: 1, PartColl: 2, Coll: 3, PartRecv: 4, Recv: 5, Inc: 6, Fin: 7 }; let statusOrder = { Pend: 1, PartColl: 2, Coll: 3, PartRecv: 4, Recv: 5, Inc: 6, Fin: 7 };
let param = new URLSearchParams(this.filter).toString(); let param = new URLSearchParams(this.filter).toString();
// reset counters before processing // reset counters before processing
for (let k in this.counters) { this.counters[k] = 0; } for (let k in this.counters) { this.counters[k] = 0; }
fetch(`${BASEURL}/admin/api/requests?${param}`, { fetch(`${BASEURL}/api/requests?${param}`, {
method: 'GET', method: 'GET',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(data => { }).then(res => res.json()).then(data => {
@ -197,6 +225,8 @@
let codeB = statusOrder[b.STATS] ?? 0; let codeB = statusOrder[b.STATS] ?? 0;
return codeA - codeB; return codeA - codeB;
}); });
}).finally(() => {
this.isLoading = false;
}); });
}, },
@ -238,6 +268,7 @@
*/ */
item : '', item : '',
isDialogSampleOpen : false, isDialogSampleOpen : false,
isSampleLoading: false,
openSampleDialog (accessnumber) { openSampleDialog (accessnumber) {
this.isDialogSampleOpen = true; this.isDialogSampleOpen = true;
@ -249,16 +280,19 @@
}, },
fetchItem(accessnumber){ fetchItem(accessnumber){
this.isSampleLoading = true;
this.item = []; 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 => { .then(res => res.json()).then(data => {
this.item = data.data ?? {}; this.item = data.data ?? {};
if (!Array.isArray(this.item.samples)) this.item.samples = []; if (!Array.isArray(this.item.samples)) this.item.samples = [];
}).finally(() => {
this.isSampleLoading = false;
}); });
}, },
collect(sampcode, accessnumber) { collect(sampcode, accessnumber) {
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, { fetch(`${BASEURL}/api/samples/collect/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'}, method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'}) body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
}) })
@ -269,7 +303,7 @@
uncollect(sampcode, accessnumber) { uncollect(sampcode, accessnumber) {
if(!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return ;} 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'}, method: 'DELETE', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'}) body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
}) })
@ -280,7 +314,7 @@
unreceive(sampcode, accessnumber) { unreceive(sampcode, accessnumber) {
if(!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return ;} 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'}, method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid : '<?= session('userid'); ?>'}) body: JSON.stringify({samplenumber: sampcode, userid : '<?= session('userid'); ?>'})
}) })
@ -333,7 +367,7 @@
this.isDialogValOpen = false; this.isDialogValOpen = false;
}, },
validate(accessnumber, userid) { validate(accessnumber, userid) {
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, { fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
method: "POST", method: "POST",
headers: {"Content-Type": "application/json"}, headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` }) body: JSON.stringify({ userid:`${userid}` })
@ -357,7 +391,7 @@
}, },
unvalidate(accessnumber, userid) { unvalidate(accessnumber, userid) {
if(!confirm(`Unvalidate request ${accessnumber}?`)) { return ;} if(!confirm(`Unvalidate request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, { fetch(`${BASEURL}/api/requests/validate/${accessnumber}`, {
method: "DELETE", method: "DELETE",
headers: {"Content-Type": "application/json"}, headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}`, comment: this.unvalReason.trim() }) body: JSON.stringify({ userid:`${userid}`, comment: this.unvalReason.trim() })

View File

@ -41,10 +41,10 @@
<span class="text-xl"><i class="fa fa-user"></i></span> <span class="text-xl"><i class="fa fa-user"></i></span>
</div> </div>
<ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-xl border border-base-200 mt-2"> <ul tabindex="-1" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-xl border border-base-200 mt-2">
<li><a class="active:bg-primary" href="<?=base_url('v2/lab') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li> <li><a class="active:bg-primary" href="<?=base_url('lab') ?>"><i class="fa fa-chart-bar mr-2"></i> Dashboard</a></li>
<li><a class="active:bg-primary" href="<?=base_url('v2/setPassword') ?>"><i class="fa fa-key mr-2"></i> Set Password</a></li> <li><a class="active:bg-primary" href="<?=base_url('setPassword') ?>"><i class="fa fa-key mr-2"></i> Set Password</a></li>
<li class="divider my-1"></li> <li class="divider my-1"></li>
<li><a href="<?=base_url('v2/logout')?>" class="text-error hover:bg-error/10"><i class="fa fa-sign-out-alt mr-2"></i> Logout</a></li> <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>
</ul> </ul>
</div> </div>
</div> </div>
@ -55,7 +55,7 @@
<footer class='bg-base-100 p-1'>&copy; <?=date('Y');?> - 5Panda</footer> <footer class='bg-base-100 p-1'>&copy; <?=date('Y');?> - 5Panda</footer>
<script> <script>
window.BASEURL = "<?=base_url("v2/lab");?>"; window.BASEURL = "<?=base_url("lab");?>";
</script> </script>
<?=$this->renderSection('script');?> <?=$this->renderSection('script');?>
</body> </body>

View File

@ -1,100 +1,48 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="corporate">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login | GDC System</title> <title>Login - CMOD</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style> <link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
body { <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/js/all.min.js"></script>
background: linear-gradient(135deg, #e0e7ff, #f8fafc);
font-family: "Inter", "Segoe UI", sans-serif;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.card {
border: none;
border-radius: 1rem;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
}
.form-control {
border-radius: 0.5rem;
padding: 0.75rem 1rem;
}
.btn-primary {
border-radius: 0.5rem;
padding: 0.75rem;
background: linear-gradient(135deg, #3b82f6, #2563eb);
border: none;
transition: all 0.2s ease;
}
.btn-primary:hover {
background: linear-gradient(135deg, #2563eb, #1e40af);
}
.app-title {
font-weight: 700;
font-size: 1.5rem;
color: #1e3a8a;
}
.error-message {
background: #fee2e2;
color: #991b1b;
border-radius: 0.5rem;
padding: 0.75rem;
margin-bottom: 1rem;
text-align: center;
font-size: 0.9rem;
}
</style>
</head> </head>
<body> <body class="min-h-screen flex items-center justify-center bg-base-200">
<div class="container"> <div class="w-full max-w-sm mx-auto">
<div class="row justify-content-center"> <div class="card bg-base-100 shadow-xl">
<div class="col-md-5"> <div class="card-body items-center text-center">
<div class="card p-5 shadow-lg"> <div class="mb-4">
<div class="text-center "> <div class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center mx-auto text-primary">
<div class="app-title">🔒 GDC Login</div> <i class="fa fa-user-shield text-3xl"></i>
<p class="text-muted">Masukkan kredensial Anda untuk melanjutkan</p>
</div> </div>
<hr>
<?php if(session()->getFlashdata('error')): ?>
<div class="error-message">
<?= esc(session()->getFlashdata('error')) ?>
</div> </div>
<?php endif; ?> <h2 class="card-title text-2xl font-semibold text-base-content mb-1">CMOD</h2>
<p class="mb-6 text-sm text-base-content/70">Sign in to continue</p>
<form method="POST" action="<?= base_url('login') ?>"> <form method="post" action="<?=base_url('login')?>" class="w-full flex flex-col gap-4">
<?= csrf_field() ?> <div class="form-control">
<div class="mb-3 mt-3"> <label class="input input-bordered flex items-center gap-2 w-full">
<label class="form-label text-muted">User ID</label> <i class="fa fa-user text-base-content/50"></i>
<input type="text" style="text-transform: uppercase;" class="form-control" name="userid" placeholder="" required autofocus> <input type="text" name="userid" placeholder="User ID" class="grow" required />
</label>
</div> </div>
<div class="form-control">
<div class="mb-3"> <label class="input input-bordered flex items-center gap-2 w-full">
<label class="form-label text-muted">Password</label> <i class="fa fa-lock text-base-content/50"></i>
<input type="password" class="form-control" name="password" placeholder="" required> <input type="password" name="password" placeholder="Password" class="grow" required />
</label>
</div> </div>
<button type="submit" class="btn btn-primary btn-block mt-2 w-full font-medium">
<button type="submit" class="btn btn-primary w-100">Login</button> Login
</button>
</form> </form>
<div class="mt-6 text-center">
<div class="text-center mt-4"> <a href="#" class="link link-hover text-sm text-base-content/60">Forgot password?</a>
<small class="text-muted">© <?= date('Y') ?> GDC Panda Laboratory System</small>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="text-center mt-6 text-xs text-base-content/40">&copy; 2025 - 5Panda. All rights reserved.</div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,207 +0,0 @@
<?= $this->extend('_layouts/main.php') ?>
<?= $this->section('title') ?>
<title>User Management Glenlis</title>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="container-fluid px-5 py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4 mt-3">
<h3 class="fw-bold text-dark mb-0">👤 User Management Glenlis</h3>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
Add User
</button>
</div>
<?php if (session('success')): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= esc(session('success')) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<!-- Table -->
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-info">
<tr>
<th width="5%">#</th>
<th>Name</th>
<th>Role</th>
<th>Level</th>
<th width="20%" class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<?php $i=1; foreach ($data as $row) : ?>
<tr>
<td><?= $i++ ?></td>
<td><?= $row['USERID'] ?></td>
<!-- Untuk User Level -->
<?php if ($row['USERLEVEL'] == 1) : ?>
<td> <button type="button" class="btn btn-light btn-sm position-relative"> 👨🏻‍💻 Admin </button> </td>
<td><span class="badge text-bg-success"><?= $row['USERLEVEL'] ?></span></td>
<?php elseif ($row['USERLEVEL'] == 2) : ?>
<td> <button type="button" class="btn btn-light btn-sm position-relative"> 🩺 Doctor </button> </td>
<td><span class="badge text-bg-primary"><?= $row['USERLEVEL'] ?></span></td>
<?php elseif ($row['USERLEVEL'] == 3) : ?>
<td> <button type="button" class="btn btn-light btn-sm position-relative"> 👩🏻‍🏭 Analyst </button> </td>
<td><span class="badge text-bg-info"><?= $row['USERLEVEL'] ?></span></td>
<?php elseif ($row['USERLEVEL'] == 4) : ?>
<td> <button type="button" class="btn btn-light btn-sm position-relative"> 👩‍💼 Customer Service </button> </td>
<td><span class="badge text-bg-warning"><?= $row['USERLEVEL'] ?></span></td>
<?php endif; ?>
<td class="text-center">
<div class="row g-1 justify-content-center">
<div class="col-8">
<button class="btn btn-sm btn-outline-primary w-100"
data-bs-toggle="modal"
data-bs-target="#editUserModal<?= $row['USERID'] ?>">
Edit
</button>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Modal: Add User -->
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form method="POST" action="<?= base_url('admin/user/create') ?>">
<?= csrf_field() ?>
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="addUserModalLabel"> Add New User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<?php if (session('errors.server_error')): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= esc(session('errors.server_error')) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="mb-3">
<label class="form-label">Userid</label>
<input type="text" name="userid" class="form-control" placeholder="enter userid" style="text-transform: uppercase;" value="<?= old('userid') ?>" required>
<?php if (session('errors.userid')): ?>
<div class="text-danger small">&nbsp;<?= esc(session('errors.userid')) ?></div>
<?php endif; ?>
</div>
<div class="mb-3">
<label class="form-label">Role</label>
<select class="form-select" name="userlevel" required>
<option value="1" <?= old('userlevel') == '1' ? 'selected' : '' ?>>Admin</option>
<option value="2" <?= old('userlevel') == '2' ? 'selected' : '' ?>>Doctor</option>
<option value="3" <?= old('userlevel') == '3' ? 'selected' : '' ?>>Analyst</option>
<option value="4" <?= old('userlevel') == '4' ? 'selected' : '' ?>>Customer Service</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" name="password" class="form-control" placeholder="Enter Password" required>
</div>
<div class="mb-3">
<label class="form-label">Confirm Password</label>
<input type="password" name="password_2" class="form-control" placeholder="Confirm Password" required>
<?php if (session('errors.password')): ?>
<div class="text-danger small">&nbsp;<?= esc(session('errors.password')) ?></div>
<?php endif; ?>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save User</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal: Edit User -->
<?php foreach ($data as $row) : ?>
<div class="modal fade" id="editUserModal<?=$row['USERID']?>" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form method="POST" action="<?= base_url('admin/user/update') ?>">
<?= csrf_field() ?>
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="editUserModalLabel">Edit User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<input type="hidden" name="userid" value="<?= esc($row['USERID']) ?>">
<div class="modal-body">
<?php if (session('errors.server_error')): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= esc(session('errors.server_error')) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="mb-3">
<label class="form-label">Userid</label>
<input type="text" disabled class="form-control" style="text-transform: uppercase;" value="<?= esc($row['USERID']) ?>">
</div>
<div class="mb-3">
<label class="form-label">Role</label>
<select class="form-select" name="userlevel" required>
<option value="1" <?= esc($row['USERLEVEL']) == '1' ? 'selected' : '' ?>>Admin</option>
<option value="2" <?= esc($row['USERLEVEL']) == '2' ? 'selected' : '' ?>>Doctor</option>
<option value="3" <?= esc($row['USERLEVEL']) == '3' ? 'selected' : '' ?>>Analyst</option>
<option value="4" <?= esc($row['USERLEVEL']) == '4' ? 'selected' : '' ?>>Customer Service</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" name="password" class="form-control" placeholder="Enter Password">
</div>
<div class="mb-3">
<label class="form-label">Confirm Password</label>
<input type="password" name="password_2" class="form-control" placeholder="Confirm Password">
<?php if (session('errors.password')): ?>
<div class="text-danger small">&nbsp;<?= esc(session('errors.password')) ?></div>
<?php endif; ?>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
<?= $this->endSection() ?>
<?= $this->section('script') ?>
<script>
document.addEventListener("DOMContentLoaded", function() {
<?php if (session('showModal')): ?>
var targetModal = new bootstrap.Modal(document.getElementById('<?= session('showModal') ?>'));
targetModal.show();
<?php endif; ?>
});
</script>
<?= $this->endSection() ?>

View File

@ -1,23 +0,0 @@
<dialog class="modal" :open="isDialogPrintOpen">
<template x-if="printAccessnumber">
<div class="modal-box w-11/12 max-w-7xl h-[90vh] flex flex-col p-0 overflow-hidden bg-base-100">
<!-- Header -->
<div class="flex justify-between items-center p-3 bg-base-200 border-b border-base-300">
<h3 class="font-bold text-lg flex items-center gap-2">
<i class="fa fa-print text-primary"></i>
Print Preview
<span class="badge badge-neutral text-xs" x-text="printType ? printType.toUpperCase() : ''"></span>
<span class="badge badge-ghost text-xs" x-text="printAccessnumber"></span>
</h3>
<button class="btn btn-sm btn-circle btn-ghost" @click="closePrintDialog()">
<i class="fa fa-times"></i>
</button>
</div>
<!-- Content -->
<div class="flex-1 bg-base-300 relative p-1">
<iframe :src="getPrintUrl()" class="w-full h-full rounded shadow-sm bg-white"></iframe>
</div>
</div>
</template>
</dialog>

View File

@ -1,425 +0,0 @@
<?= $this->extend('v2/admin/main'); ?>
<?= $this->section('content') ?>
<main class="p-4 flex-1 flex flex-col gap-2" x-data="dashboard">
<div class="card bg-base-100 shadow-xl h-full border border-base-200 overflow-hidden">
<div class="card-body p-0 h-full flex flex-col">
<!-- Header & Filters -->
<div class="p-4 border-b border-base-200 bg-base-50">
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-4">
<div class="flex-1">
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
<i class="fa fa-chart-bar text-primary"></i> Requests Overview
</h2>
</div>
<!-- Status Filters -->
<div class="join shadow-sm bg-base-100 rounded-lg">
<button @click="filterKey = 'Total'" :class="filterKey === 'Total' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">
All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span>
</button>
<button @click="filterKey = 'Pend'" :class="filterKey === 'Pend' ? 'btn-active btn-neutral text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Pending <span class="badge badge-sm ml-1" x-text="counters.Pend"></span>
</button>
<button @click="filterKey = 'Coll'" :class="filterKey === 'Coll' ? 'btn-active btn-warning text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Coll <span class="badge badge-sm badge-warning ml-1" x-text="counters.Coll"></span>
</button>
<button @click="filterKey = 'Recv'" :class="filterKey === 'Recv' ? 'btn-active btn-info text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Recv <span class="badge badge-sm badge-info ml-1" x-text="counters.Recv"></span>
</button>
<button @click="filterKey = 'Inc'" :class="filterKey === 'Inc' ? 'btn-active btn-error text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Inc <span class="badge badge-sm badge-error ml-1" x-text="counters.Inc"></span>
</button>
<button @click="filterKey = 'Fin'" :class="filterKey === 'Fin' ? 'btn-active btn-success text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Fin <span class="badge badge-sm badge-success ml-1" x-text="counters.Fin"></span>
</button>
<button @click="filterKey = 'Validated'" :class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'" class="btn btn-sm join-item">
Val <span class="badge badge-sm badge-primary ml-1" x-text="validatedCount"></span>
</button>
</div>
</div>
<!-- Search & Date Filter -->
<div class="flex flex-col md:flex-row gap-3 items-end bg-base-100 p-3 rounded-lg border border-base-200 shadow-sm">
<div class="form-control">
<label class="label text-xs font-bold py-1 text-base-content/60">Date Range</label>
<div class="join">
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date1"/>
<span class="join-item btn btn-sm btn-ghost no-animation bg-base-200 font-normal px-2">-</span>
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date2"/>
</div>
</div>
<div class="flex gap-2">
<button class="btn btn-sm btn-primary" @click='fetchList()'><i class='fa fa-search'></i> Search</button>
<button class="btn btn-sm btn-neutral" @click='reset()'><i class='fa fa-sync-alt'></i> Reset</button>
</div>
<span class="flex-1"></span>
<div class="form-control w-full md:w-auto">
<label class='input input-sm input-bordered'>
<i class="fa fa-filter"></i>
<input type="text" placeholder="Type to filter..." x-model="filterTable" />
</label>
</div>
</div>
</div>
<div class="flex-1 overflow-y-auto px-4 pb-4">
<template x-if="list.length">
<table class="table table-xs table-zebra w-full">
<thead class="bg-base-100 sticky top-0 z-10">
<tr>
<th style='width:7%;'>Order Datetime</th>
<th style='width:15%;'>Patient Name</th>
<th style='width:7%;'>No Lab</th>
<th style='width:7%;'>No Register</th>
<th style='width:8%;'>Reff</th>
<th style='width:8%;'>Doctor</th>
<th style='width:15%;'>Tests</th>
<th style='width:3%;'>ResTo</th>
<th style='width:5%;'>Val</th>
<th style='width:5%;'>Result</th>
<th style='width:4%;'>Status</th>
</tr>
</thead>
<tbody>
<template x-for="req in filtered" :key="req.SP_ACCESSNUMBER">
<tr class="hover:bg-base-300">
<td x-text="req.REQDATE"></td>
<td x-text="req.Name"></td>
<td x-text="req.SP_ACCESSNUMBER"></td>
<td x-text="req.HOSTORDERNUMBER"></td>
<td x-text="req.REFF"></td>
<td x-text="req.DOC"></td>
<td x-text="req.TESTS"></td>
<td x-text="req.ODR_CRESULT_TO"></td>
<td>
<div class='flex gap-1 items-center'>
<div class='w-15'>
<p>1: <span x-text="req.VAL1USER"></span></p>
<p>2: <span x-text="req.VAL2USER"></span></p>
</div>
<template x-if="req.ISVAL == 1 && req.ISPENDING != 1">
<div class='text-center'>
<template x-if="req.VAL1USER == '<?=session('userid');?>' || req.VAL2USER == '<?=session('userid');?>'">
<button class="btn btn-xs btn-outline btn-secondary" @click="openUnvalDialog(req.SP_ACCESSNUMBER)"><i class="fa-solid fa-rotate-right"></i></button>
</template>
<template x-if="req.VAL1USER != '<?=session('userid');?>' && req.VAL2USER != '<?=session('userid');?>'">
<button class="btn btn-xs btn-outline btn-success" @click="openValDialog(req.SP_ACCESSNUMBER)"><i class="fa-solid fa-check"></i></button>
</template>
</div>
</template>
</div>
</td>
<td>
<template x-if="!req.VAL2USER">
<button class="btn btn-xs btn-outline btn-primary" @click="openPrintDialog(req.SP_ACCESSNUMBER, 'preview')">Preview</button>
</template>
<template x-if="req.VAL2USER">
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-xs btn-outline btn-primary">
Print <i class="fa fa-chevron-down text-[10px]"></i>
</div>
<ul tabindex="0" class="dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box w-28 border border-base-200">
<li><a @click="openPrintDialog(req.SP_ACCESSNUMBER, 'preview')">Preview</a></li>
<li><a @click="openPrintDialog(req.SP_ACCESSNUMBER, 'ind')">IND</a></li>
<li><a @click="openPrintDialog(req.SP_ACCESSNUMBER, 'eng')">ENG</a></li>
<li><a @click="openPrintDialog(req.SP_ACCESSNUMBER, 'pdf')">PDF</a></li>
</ul>
</div>
</template>
</td>
<td><button x-text="req.STATS === 'Fin' ? 'Final' : req.STATS" class="btn btn-xs"
:class="statusColor[req.STATS]" @click="openSampleDialog(req.SP_ACCESSNUMBER)"></button></td>
</tr>
</template>
</tbody>
</table>
</template>
</div>
</div>
<?php echo $this->include('v2/admin/dialog_sample'); ?>
<?php echo $this->include('v2/admin/dialog_val'); ?>
<?php echo $this->include('v2/admin/dialog_unval'); ?>
<?php echo $this->include('v2/admin/dialog_print'); ?>
</main>
<?= $this->endSection(); ?>
<?= $this->section('script') ?>
<script type="module">
import Alpine from '<?=base_url("js/app.js");?>';
document.addEventListener('alpine:init', () => {
Alpine.data("dashboard", ()=> ({
// dashboard
today: "",
filter: { date1: "", date2: "" },
list: [],
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',
Coll: 'bg-orange-500 text-white font-bold',
PartRecv: 'bg-blue-200 text-black font-bold',
Recv: 'bg-blue-500 text-white font-bold',
Inc: 'bg-yellow-500 text-white font-bold',
Fin: 'bg-green-500 text-white font-bold',
},
filterTable :"",
filterKey: 'Total',
statusMap: {
Total: [],
Pend: ['Pend'],
Coll: ['Coll', 'PartColl'],
Recv: ['Recv'],
Inc: ['Inc'],
Fin: ['Fin'],
},
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();
},
fetchList(){
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}`, {
method: 'GET',
headers: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(data => {
this.list = data.data ?? [];
this.filterKey = 'Total';
// count + sort in a single loop
this.list.forEach(item => {
if (this.counters[item.STATS] !== undefined) { this.counters[item.STATS]++; this.counters.Total++; }
else {
if(item.STATS == 'PartColl') { this.counters.Coll++; }
else if(item.STATS == 'PartRecv') { this.counters.Recv++; }
this.counters.Total++;
}
});
this.list.sort((a, b) => {
let codeA = statusOrder[a.STATS] ?? 0;
let codeB = statusOrder[b.STATS] ?? 0;
return codeA - codeB;
});
});
},
reset() {
this.filter.date1 = this.today;
this.filter.date2 = this.today;
this.fetchList();
},
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,
openSampleDialog (accessnumber) {
this.isDialogSampleOpen = true;
this.fetchItem(accessnumber)
},
closeSampleDialog () {
this.isDialogSampleOpen = false;
},
fetchItem(accessnumber){
this.item = [];
fetch(`${BASEURL}/admin/api/samples/${accessnumber}`, { method: 'GET', headers: {'Content-Type': 'application/json'}})
.then(res => res.json()).then(data => {
this.item = data.data ?? {};
if (!Array.isArray(this.item.samples)) this.item.samples = [];
});
},
collect(sampcode, accessnumber) {
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
})
.then(res => res.json()).then(data => {
this.fetchItem(accessnumber);
});
},
uncollect(sampcode, accessnumber) {
if(!confirm(`Uncollect sample ${sampcode} from request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/admin/api/samples/collect/${accessnumber}`, {
method: 'DELETE', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid: '<?= session('userid'); ?>'})
})
.then(res => res.json()).then(data => {
this.fetchItem(accessnumber);
});
},
unreceive(sampcode, accessnumber) {
if(!confirm(`Unreceive sample ${sampcode} from request ${accessnumber}?`)) { return ;}
fetch(`${BASEURL}/admin/api/samples/unreceive/${accessnumber}`, {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({samplenumber: sampcode, userid : '<?= session('userid'); ?>'})
})
.then(res => res.json()).then(data => {
this.fetchItem(accessnumber);
});
},
/*
validate dialog
*/
isDialogValOpen : false,
isValidateEnabled: false,
valAccessnumber : null,
openValDialog (accessnumber) {
this.isDialogValOpen = true;
this.valAccessnumber = accessnumber;
this.$nextTick(() => {
// refs will be available after render
const iframe = this.$root.querySelector('#result-iframe') || (this.$refs && this.$refs.resultIframe);
const validateBtn = this.$root.querySelector('#validate-btn') || (this.$refs && this.$refs.validateBtn);
if (!iframe || !validateBtn) return;
const setup = () => {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const scrollable = doc.documentElement || doc.body;
const checkScroll = () => {
try {
const atBottom = (scrollable.scrollHeight - scrollable.scrollTop - scrollable.clientHeight) < 2;
this.isValidateEnabled = atBottom;
validateBtn.disabled = !atBottom;
} catch (e) { /* cross-origin or not ready */ }
};
iframe.contentWindow.removeEventListener('scroll', checkScroll);
iframe.contentWindow.addEventListener('scroll', checkScroll);
checkScroll();
} catch (e) { /* ignore cross-origin */ }
};
// If iframe already loaded, setup immediately; otherwise wait for load
if (iframe.contentWindow && (iframe.contentDocument && iframe.contentDocument.readyState === 'complete')) {
setup();
} else {
iframe.addEventListener('load', setup);
}
});
},
closeValDialog () {
this.isDialogValOpen = false;
},
validate(accessnumber, userid) {
fetch(`${BASEURL}/admin/api/requests/validate/${accessnumber}`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ userid:`${userid}` })
}).then(response => {
this.closeValDialog();
this.fetchList();
console.log('Validate clicked for', this.valAccessnumber, '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}/admin/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;
},
/*
print dialog
*/
isDialogPrintOpen : false,
printAccessnumber : null,
printType : null,
openPrintDialog (accessnumber, type) {
this.printAccessnumber = accessnumber;
this.printType = type;
this.isDialogPrintOpen = true;
},
closePrintDialog () {
this.isDialogPrintOpen = false;
},
getPrintUrl() {
// Base URL from existing modal_request.php reference
let base = 'http://glenlis/spooler_db/main_dev.php';
let url = `${base}?acc=${this.printAccessnumber}`;
// Append params based on type
if (this.printType === 'ind') url += '&lang=ID';
if (this.printType === 'eng') url += '&lang=EN';
if (this.printType === 'pdf') url += '&output=pdf';
return url;
},
}));
});
Alpine.start();
</script>
<?= $this->endSection(); ?>

View File

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

View File

@ -1,10 +0,0 @@
<dialog class="modal" :open="isDialogUnvalOpen">
<div class="modal-box">
<textarea class="textarea textarea-bordered w-full" rows="5" x-model="unvalReason" placeholder="Enter reason for unvalidation..."></textarea>
<p class='text-right mt-2'>
<button class="btn btn-sm btn-neutral" @click="closeUnvalDialog()">Cancel</button>
<button id="unvalidate-btn" x-ref="unvalidateBtn" class="btn btn-sm btn-warning"
@click="unvalidate(unvalAccessnumber, '<?=session('userid');?>')" :disabled="!unvalReason.trim()">Unvalidate</button>
</p>
</div>
</dialog>

View File

@ -1,13 +0,0 @@
<dialog class="modal" :open="isDialogValOpen">
<template x-if="valAccessnumber">
<div class="modal-box w-2/3 max-w-5xl">
<p class='text-right mx-3 mb-2'>
<button class="btn btn-sm btn-neutral" @click="closeValDialog()">Cancel</button>
<button id="validate-btn" x-ref="validateBtn" class="btn btn-sm btn-success"
@click="validate(valAccessnumber, '<?=session('userid');?>')" :disabled="!isValidateEnabled">Validate</button>
</p>
<!-- <iframe id="result-iframe" src="http://glenlis/spooler_db/main_dev.php?acc=" width="750px" height="600px"></iframe> -->
<iframe id="result-iframe" x-ref="resultIframe" src="<?=base_url('dummypage');?>" width="750px" height="600px"></iframe>
</div>
</template>
</dialog>

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-theme="corporate">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - CMOD</title>
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/js/all.min.js"></script>
</head>
<body class="min-h-screen flex items-center justify-center bg-base-200">
<div class="w-full max-w-sm mx-auto">
<div class="card bg-base-100 shadow-xl">
<div class="card-body items-center text-center">
<div class="mb-4">
<div class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center mx-auto text-primary">
<i class="fa fa-user-shield text-3xl"></i>
</div>
</div>
<h2 class="card-title text-2xl font-semibold text-base-content mb-1">CMOD</h2>
<p class="mb-6 text-sm text-base-content/70">Sign in to continue</p>
<form method="post" action="<?=base_url('v2/login')?>" class="w-full flex flex-col gap-4">
<div class="form-control">
<label class="input input-bordered flex items-center gap-2 w-full">
<i class="fa fa-user text-base-content/50"></i>
<input type="text" name="userid" placeholder="User ID" class="grow" required />
</label>
</div>
<div class="form-control">
<label class="input input-bordered flex items-center gap-2 w-full">
<i class="fa fa-lock text-base-content/50"></i>
<input type="password" name="password" placeholder="Password" class="grow" required />
</label>
</div>
<button type="submit" class="btn btn-primary btn-block mt-2 w-full font-medium">
Login
</button>
</form>
<div class="mt-6 text-center">
<a href="#" class="link link-hover text-sm text-base-content/60">Forgot password?</a>
</div>
</div>
</div>
<div class="text-center mt-6 text-xs text-base-content/40">&copy; 2025 - 5Panda. All rights reserved.</div>
</div>
</body>
</html>