From 2aa2ef50f2b450ca1193414450fd8f99132f3de6 Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Thu, 2 Apr 2026 13:02:29 +0700 Subject: [PATCH] feat: add v2 dashboards and server-side request pagination Introduces v2 role routes/views and moves request list filtering, sorting, and pagination to the backend for better performance. Also switches shared pages to a generated Tailwind CSS bundle with supporting npm assets. --- .gitignore | 3 +- app/Config/Routes.php | 5 + app/Controllers/Pages/AdminController.php | 6 + app/Controllers/Pages/CsController.php | 6 + app/Controllers/Pages/LabController.php | 6 + .../Pages/PhlebotomistController.php | 6 + app/Controllers/Pages/SuperuserController.php | 6 + app/Controllers/RequestsController.php | 169 ++- app/Views/errors/notfound.php | 6 +- app/Views/errors/unauthorized.php | 6 +- app/Views/login.php | 6 +- app/Views/shared/content_requests.php | 16 +- app/Views/shared/layout.php | 6 +- app/Views/shared/script_requests.php | 165 +-- package-lock.json | 1106 +++++++++++++++++ package.json | 14 + public/css/app.generated.css | 2 + resources/css/app.css | 8 + 18 files changed, 1388 insertions(+), 154 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/css/app.generated.css create mode 100644 resources/css/app.css diff --git a/.gitignore b/.gitignore index efd69d1..556f563 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,5 @@ _modules/* .vscode/ /results/ -/phpunit*.xml \ No newline at end of file +/phpunit*.xml +node_modules/ \ No newline at end of file diff --git a/app/Config/Routes.php b/app/Config/Routes.php index bc05ffd..eda4f56 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -69,6 +69,7 @@ $routes->group('api', function ($routes) { $routes->group('superuser', ['filter' => 'role:0'], function ($routes) { $routes->get('', 'Pages\SuperuserController::index'); + $routes->get('v2', 'Pages\SuperuserController::indexV2'); $routes->get('users', 'Pages\SuperuserController::users'); $routes->get('validate', 'Pages\SuperuserController::validatePage'); $routes->get('pdf-batch', 'Pages\SuperuserController::pdfBatch'); @@ -80,23 +81,27 @@ $routes->group('api', ['filter' => 'role:0'], function ($routes) { $routes->group('admin', ['filter' => 'role:1'], function ($routes) { $routes->get('', 'Pages\AdminController::index'); + $routes->get('v2', 'Pages\AdminController::indexV2'); $routes->get('users', 'Pages\AdminController::users'); $routes->get('validate', 'Pages\AdminController::validationPage'); }); $routes->group('lab', ['filter' => 'role:2'], function ($routes) { $routes->get('', 'Pages\LabController::index'); + $routes->get('v2', 'Pages\LabController::indexV2'); $routes->get('validate', 'Pages\LabController::validationPage'); }); $routes->group('phlebo', ['filter' => 'role:3'], function ($routes) { $routes->get('', 'Pages\PhlebotomistController::collect'); $routes->get('dashboard', 'Pages\PhlebotomistController::index'); + $routes->get('v2', 'Pages\PhlebotomistController::indexV2'); $routes->get('collect', 'Pages\PhlebotomistController::collect'); }); $routes->group('cs', ['filter' => 'role:4'], function ($routes) { $routes->get('', 'Pages\CsController::index'); + $routes->get('v2', 'Pages\CsController::indexV2'); }); $routes->get('/dummypage', 'Home::dummyPage'); diff --git a/app/Controllers/Pages/AdminController.php b/app/Controllers/Pages/AdminController.php index c4a4f70..218dc92 100644 --- a/app/Controllers/Pages/AdminController.php +++ b/app/Controllers/Pages/AdminController.php @@ -18,6 +18,12 @@ class AdminController extends BaseController return view('admin/index', ['roleConfig' => $config['admin']]); } + public function indexV2() + { + $config = require APPPATH . 'Views/shared/config.php'; + return view('v2/admin/index', ['roleConfig' => $config['admin']]); + } + public function users() { $config = require APPPATH . 'Views/shared/config.php'; diff --git a/app/Controllers/Pages/CsController.php b/app/Controllers/Pages/CsController.php index c6e0c98..c9c46e9 100644 --- a/app/Controllers/Pages/CsController.php +++ b/app/Controllers/Pages/CsController.php @@ -18,4 +18,10 @@ class CsController extends BaseController return view('cs/index', ['roleConfig' => $config['cs']]); } + public function indexV2() + { + $config = require APPPATH . 'Views/shared/config.php'; + return view('v2/cs/index', ['roleConfig' => $config['cs']]); + } + } diff --git a/app/Controllers/Pages/LabController.php b/app/Controllers/Pages/LabController.php index bd6e41f..d95318e 100644 --- a/app/Controllers/Pages/LabController.php +++ b/app/Controllers/Pages/LabController.php @@ -18,6 +18,12 @@ class LabController extends BaseController return view('lab/index', ['roleConfig' => $config['lab']]); } + public function indexV2() + { + $config = require APPPATH . 'Views/shared/config.php'; + return view('v2/lab/index', ['roleConfig' => $config['lab']]); + } + public function validationPage() { $config = require APPPATH . 'Views/shared/config.php'; diff --git a/app/Controllers/Pages/PhlebotomistController.php b/app/Controllers/Pages/PhlebotomistController.php index 6963173..b709b01 100644 --- a/app/Controllers/Pages/PhlebotomistController.php +++ b/app/Controllers/Pages/PhlebotomistController.php @@ -18,6 +18,12 @@ class PhlebotomistController extends BaseController return view('phlebo/index', ['roleConfig' => $config['phlebo']]); } + public function indexV2() + { + $config = require APPPATH . 'Views/shared/config.php'; + return view('v2/phlebo/index', ['roleConfig' => $config['phlebo']]); + } + public function collect() { diff --git a/app/Controllers/Pages/SuperuserController.php b/app/Controllers/Pages/SuperuserController.php index 8495c75..26fc9a3 100644 --- a/app/Controllers/Pages/SuperuserController.php +++ b/app/Controllers/Pages/SuperuserController.php @@ -18,6 +18,12 @@ class SuperuserController extends BaseController return view('superuser/index', ['roleConfig' => $config['superuser']]); } + public function indexV2() + { + $config = require APPPATH . 'Views/shared/config.php'; + return view('v2/superuser/index', ['roleConfig' => $config['superuser']]); + } + public function users() { $config = require APPPATH . 'Views/shared/config.php'; diff --git a/app/Controllers/RequestsController.php b/app/Controllers/RequestsController.php index f8aaa51..d76f8bb 100644 --- a/app/Controllers/RequestsController.php +++ b/app/Controllers/RequestsController.php @@ -7,38 +7,169 @@ class RequestsController extends BaseController { use ResponseTrait; + private const MAX_PAGE_SIZE = 100; + public function index() { $db = \Config\Database::connect(); - $date1 = $this->request->getGet('date1'); - $date2 = $this->request->getGet('date2'); - $userroleid = session()->get('userroleid'); + $today = date('Y-m-d'); + $date1 = (string) ($this->request->getGet('date1') ?: $today); + $date2 = (string) ($this->request->getGet('date2') ?: $today); + $userroleid = (int) session()->get('userroleid'); - // Only allow Lab role (role 2) - if ($userroleid == 2) { - $sql = "SELECT d.*, r.REPORT_LANG, r.ISPDF from GDC_CMOD.dbo.V_DASHBOARD_DEV d - LEFT JOIN GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER=d.SP_ACCESSNUMBER - where d.COLLECTIONDATE between '$date1 00:00' and '$date2 23:59' - and d.ODR_DDATE between '$date1 00:00' and '$date2 23:59' - and (d.TESTS IS NOT NULL AND d.TESTS like '%[A-Za-z]%')"; - } else { - $sql = "SELECT d.*, r.REPORT_LANG, r.ISPDF from GDC_CMOD.dbo.V_DASHBOARD_DEV d - LEFT JOIN GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER=d.SP_ACCESSNUMBER - where d.COLLECTIONDATE between '$date1 00:00' and '$date2 23:59' - and d.ODR_DDATE between '$date1 00:00' and '$date2 23:59'"; - } + $page = max(1, (int) ($this->request->getGet('page') ?? 1)); + $pageSize = (int) ($this->request->getGet('pageSize') ?? 50); + $pageSize = max(1, min(self::MAX_PAGE_SIZE, $pageSize)); + $offset = ($page - 1) * $pageSize; + + $sortCol = (string) ($this->request->getGet('sortCol') ?? 'REQDATE'); + $sortDirRaw = strtoupper((string) ($this->request->getGet('sortDir') ?? 'DESC')); + $sortDir = $sortDirRaw === 'ASC' ? 'ASC' : 'DESC'; + $sortSql = $this->resolveSortColumn($sortCol); + + $filterKey = (string) ($this->request->getGet('filterKey') ?? 'Total'); + $search = trim((string) ($this->request->getGet('search') ?? '')); + + [$baseWhereSql, $baseParams] = $this->buildBaseWhereClause($date1, $date2, $userroleid); + [$whereSql, $whereParams] = $this->buildDashboardWhereClause($baseWhereSql, $baseParams, $filterKey, $search); + + $rowsSql = "SELECT + d.REQDATE, + d.[Name], + d.SP_ACCESSNUMBER, + d.HOSTORDERNUMBER, + d.REFF, + d.DOC, + d.TESTS, + d.ODR_CRESULT_TO, + d.STATS, + d.COLLECTIONDATE, + d.[Name] AS PATNAME, + CAST(NULL AS VARCHAR(32)) AS PATNUMBER, + r.VAL1USER, + r.VAL2USER, + r.ISPENDING, + r.REPORT_LANG, + r.ISPDF + FROM GDC_CMOD.dbo.V_DASHBOARD_DEV d + LEFT JOIN GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER = d.SP_ACCESSNUMBER + WHERE {$whereSql} + ORDER BY {$sortSql} {$sortDir} + OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"; + + $rowsParams = [...$whereParams, $offset, $pageSize]; + $rows = $db->query($rowsSql, $rowsParams)->getResultArray(); + + $countSql = "SELECT COUNT(1) AS total + FROM GDC_CMOD.dbo.V_DASHBOARD_DEV d + LEFT JOIN GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER = d.SP_ACCESSNUMBER + WHERE {$whereSql}"; + $countRow = $db->query($countSql, $whereParams)->getRowArray(); + $totalRows = (int) ($countRow['total'] ?? 0); + + $counterSql = "SELECT + SUM(CASE WHEN d.STATS = 'Pend' THEN 1 ELSE 0 END) AS Pend, + SUM(CASE WHEN d.STATS IN ('Coll', 'PartColl') THEN 1 ELSE 0 END) AS Coll, + SUM(CASE WHEN d.STATS IN ('Recv', 'PartRecv') THEN 1 ELSE 0 END) AS Recv, + SUM(CASE WHEN d.STATS = 'Inc' THEN 1 ELSE 0 END) AS Inc, + SUM(CASE WHEN d.STATS = 'Fin' THEN 1 ELSE 0 END) AS Fin, + COUNT(1) AS Total, + SUM(CASE WHEN r.VAL1USER IS NOT NULL AND r.VAL2USER IS NOT NULL AND ISNULL(r.ISPENDING, 0) <> 1 THEN 1 ELSE 0 END) AS Validated + FROM GDC_CMOD.dbo.V_DASHBOARD_DEV d + LEFT JOIN GDC_CMOD.dbo.CM_REQUESTS r ON r.ACCESSNUMBER = d.SP_ACCESSNUMBER + WHERE {$baseWhereSql}"; + $counterRow = $db->query($counterSql, $baseParams)->getRowArray() ?? []; - $rows = $db->query($sql)->getResultArray(); foreach ($rows as &$row) { $row['COLLECTIONDATE'] = date('Y-m-d H:i', strtotime($row['COLLECTIONDATE'])); - $row['ODR_DDATE'] = date('Y-m-d H:i', strtotime($row['ODR_DDATE'])); $row['REQDATE'] = date('Y-m-d H:i', strtotime($row['REQDATE'])); $this->normalizeTelephoneFields($row); } - $data['data'] = $rows; + + $data = [ + 'data' => $rows, + 'pagination' => [ + 'page' => $page, + 'pageSize' => $pageSize, + 'totalRows' => $totalRows, + 'totalPages' => max(1, (int) ceil($totalRows / $pageSize)), + ], + 'counters' => [ + 'Pend' => (int) ($counterRow['Pend'] ?? 0), + 'Coll' => (int) ($counterRow['Coll'] ?? 0), + 'Recv' => (int) ($counterRow['Recv'] ?? 0), + 'Inc' => (int) ($counterRow['Inc'] ?? 0), + 'Fin' => (int) ($counterRow['Fin'] ?? 0), + 'Total' => (int) ($counterRow['Total'] ?? 0), + ], + 'validatedCount' => (int) ($counterRow['Validated'] ?? 0), + ]; + return $this->response->setJSON($data); } + private function buildBaseWhereClause(string $date1, string $date2, int $userroleid): array + { + $where = [ + 'd.COLLECTIONDATE BETWEEN ? AND ?', + 'd.ODR_DDATE BETWEEN ? AND ?', + ]; + $params = [ + $date1 . ' 00:00', + $date2 . ' 23:59', + $date1 . ' 00:00', + $date2 . ' 23:59', + ]; + + if ($userroleid === 2) { + $where[] = "(d.TESTS IS NOT NULL AND d.TESTS LIKE '%[A-Za-z]%')"; + } + + return [implode(' AND ', $where), $params]; + } + + private function buildDashboardWhereClause(string $baseWhereSql, array $baseParams, string $filterKey, string $search): array + { + $where = [$baseWhereSql]; + $params = $baseParams; + + $statusClauses = [ + 'Pend' => "d.STATS = 'Pend'", + 'Coll' => "d.STATS IN ('Coll', 'PartColl')", + 'Recv' => "d.STATS IN ('Recv', 'PartRecv')", + 'Inc' => "d.STATS = 'Inc'", + 'Fin' => "d.STATS = 'Fin'", + 'Validated' => 'r.VAL1USER IS NOT NULL AND r.VAL2USER IS NOT NULL AND ISNULL(r.ISPENDING, 0) <> 1', + ]; + + if (isset($statusClauses[$filterKey])) { + $where[] = $statusClauses[$filterKey]; + } + + if ($search !== '') { + $where[] = '(d.SP_ACCESSNUMBER LIKE ? OR d.HOSTORDERNUMBER LIKE ? OR d.[Name] LIKE ? OR d.REFF LIKE ? OR d.DOC LIKE ? OR d.TESTS LIKE ? OR d.ODR_CRESULT_TO LIKE ?)'; + $like = '%' . $search . '%'; + $params = [...$params, $like, $like, $like, $like, $like, $like, $like]; + } + + return [implode(' AND ', $where), $params]; + } + + private function resolveSortColumn(string $sortCol): string + { + $map = [ + 'REQDATE' => 'd.REQDATE', + 'Name' => 'd.[Name]', + 'SP_ACCESSNUMBER' => 'd.SP_ACCESSNUMBER', + 'HOSTORDERNUMBER' => 'd.HOSTORDERNUMBER', + 'REFF' => 'd.REFF', + 'DOC' => 'd.DOC', + 'STATS' => 'd.STATS', + ]; + + return $map[$sortCol] ?? 'd.REQDATE'; + } + public function show($accessnumber) { diff --git a/app/Views/errors/notfound.php b/app/Views/errors/notfound.php index b883d29..64608b0 100644 --- a/app/Views/errors/notfound.php +++ b/app/Views/errors/notfound.php @@ -5,9 +5,7 @@ Not Found - - - +