From 4ae2c75fddf03ba5adf5b96d4931204d034d479a Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Tue, 3 Feb 2026 16:55:13 +0700 Subject: [PATCH] feat: Add department filtering to Controls, Tests, and Entry pages Implemented comprehensive department filtering across multiple pages in the QC system to enable users to filter data by laboratory department. ## Backend Changes **Models:** - MasterControlsModel: Enhanced search() method to accept optional dept_id parameter, added LEFT JOIN with master_depts to include department info - MasterTestsModel: Updated search() to support dept_id filtering with JOIN **Controllers:** - MasterControlsController: Modified index() to accept and pass dept_id parameter - MasterTestsController: Modified index() to accept and pass dept_id parameter - EntryApiController: Updated getControls() to filter by dept_id using model search, added debug logging for troubleshooting ## Frontend Changes **Views Updated:** 1. Controls page (/master/control) - Added department dropdown with DaisyUI styling - Active filter badge and clear button - Fetch controls filtered by selected department - Added department field to control form dialog 2. Tests page (/master/test) - Added department dropdown with active state indication - Filter tests by department - Clear button to reset filter 3. Daily Entry page (/entry/daily) - Added department dropdown in filter section - Resets control selection when department changes - Fetches controls and tests filtered by department 4. Monthly Entry page (/entry/monthly) - Added department dropdown with month selector - Resets test selection when department changes - Fetches tests filtered by department ## Key Features - Dropdown UI shows "All Departments" as default - Selected department name displayed in dropdown button - Clear button appears when filter is active - Active department highlighted in dropdown menu - Loading state while fetching departments - Automatic reset of dependent selections when department changes - Consistent UI pattern across all pages using DaisyUI components ## Bug Fixes - Fixed syntax error in MasterControlsModel search() method - Removed duplicate/corrupted code that was causing incorrect results - Added proper deptName field to SELECT query in MasterControlsModel ## Important Note: Department IDs Required **ACTION REQUIRED**: Existing controls and tests in the database must be assigned to departments for the filter to work correctly. To update existing records, run: UPDATE master_controls SET dept_id = 1 WHERE dept_id IS NULL; UPDATE master_tests SET dept_id = 1 WHERE dept_id IS NULL; Replace '1' with a valid department ID from master_depts table. Alternatively, edit each control/test through the UI to assign a department. ## Technical Details - Alpine.js data binding for reactive department selection - API expects 'dept_id' query parameter (snake_case) - Internal state uses camelCase (deptId) - Departments loaded on init via /api/master/depts - Search requests include both keyword and dept_id parameters --- app/Controllers/Api/EntryApiController.php | 16 ++-- .../Master/MasterControlsController.php | 5 +- .../Master/MasterTestsController.php | 5 +- app/Models/Master/MasterControlsModel.php | 42 ++++++-- app/Models/Master/MasterTestsModel.php | 6 +- app/Views/entry/daily.php | 72 ++++++++++++++ app/Views/entry/monthly.php | 78 ++++++++++++++- .../master/control/dialog_control_form.php | 28 ++++++ app/Views/master/control/index.php | 95 ++++++++++++++++++- app/Views/master/test/index.php | 85 +++++++++++++++++ 10 files changed, 407 insertions(+), 25 deletions(-) diff --git a/app/Controllers/Api/EntryApiController.php b/app/Controllers/Api/EntryApiController.php index 188516d..eda5b2b 100644 --- a/app/Controllers/Api/EntryApiController.php +++ b/app/Controllers/Api/EntryApiController.php @@ -37,14 +37,14 @@ class EntryApiController extends BaseController public function getControls() { try { + $keyword = $this->request->getGet('keyword'); $deptId = $this->request->getGet('dept_id'); $date = $this->request->getGet('date'); - if ($deptId) { - $controls = $this->controlModel->where('dept_id', $deptId)->where('deleted_at', null)->findAll(); - } else { - $controls = $this->controlModel->where('deleted_at', null)->findAll(); - } + $controls = $this->controlModel->search($keyword, $deptId); + + // Debug logging + log_message('debug', 'getControls: keyword=' . var_export($keyword, true) . ', deptId=' . var_export($deptId, true) . ', date=' . var_export($date, true) . ', found=' . count($controls)); // Filter expired controls if date provided if ($date) { @@ -61,16 +61,20 @@ class EntryApiController extends BaseController 'controlName' => $c['controlName'], 'lot' => $c['lot'], 'producer' => $c['producer'], - 'expDate' => $c['expDate'] + 'expDate' => $c['expDate'], + 'deptName' => $c['deptName'] ?? null ]; }, $controls); + log_message('debug', 'getControls: returning ' . count($data) . ' controls'); + return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $data ], 200); } catch (\Exception $e) { + log_message('error', 'getControls error: ' . $e->getMessage()); return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } diff --git a/app/Controllers/Master/MasterControlsController.php b/app/Controllers/Master/MasterControlsController.php index 8989ea6..8fcbe05 100644 --- a/app/Controllers/Master/MasterControlsController.php +++ b/app/Controllers/Master/MasterControlsController.php @@ -19,10 +19,7 @@ class MasterControlsController extends BaseController { ]; } - public function index() { - $keyword = $this->request->getGet('keyword'); - try { - $rows = $this->model->search($keyword); + public function index() { $keyword = $this->request->getGet('keyword'); $deptId = $this->request->getGet('dept_id'); try { $rows = $this->model->search($keyword, $deptId); return $this->respond([ 'status' => 'success', 'message' => 'fetch success', diff --git a/app/Controllers/Master/MasterTestsController.php b/app/Controllers/Master/MasterTestsController.php index fec313a..14b1167 100644 --- a/app/Controllers/Master/MasterTestsController.php +++ b/app/Controllers/Master/MasterTestsController.php @@ -18,10 +18,7 @@ class MasterTestsController extends BaseController { ]; } - public function index() { - $keyword = $this->request->getGet('keyword'); - try { - $rows = $this->model->search($keyword); + public function index() { $keyword = $this->request->getGet('keyword'); $deptId = $this->request->getGet('dept_id'); try { $rows = $this->model->search($keyword, $deptId); return $this->respond([ 'status' => 'success', 'message' => 'fetch success', diff --git a/app/Models/Master/MasterControlsModel.php b/app/Models/Master/MasterControlsModel.php index 521828c..bd668ed 100644 --- a/app/Models/Master/MasterControlsModel.php +++ b/app/Models/Master/MasterControlsModel.php @@ -19,14 +19,40 @@ class MasterControlsModel extends BaseModel { protected $useTimestamps = true; protected $useSoftDeletes = true; - public function search($keyword = null) { - if ($keyword) { - return $this->groupStart() - ->like('control_name', $keyword) - ->orLike('lot', $keyword) - ->groupEnd() - ->findAll(); + public function search($keyword = null, $deptId = null) { + $builder = $this->builder(); + $builder->select(' + master_controls.control_id as controlId, + master_controls.control_name as controlName, + master_controls.lot, + master_controls.producer, + master_controls.exp_date as expDate, + master_depts.dept_name as deptName + '); + $builder->join('master_depts', 'master_depts.dept_id = master_controls.dept_id', 'left'); + $builder->where('master_controls.deleted_at', null); + + if ($deptId) { + $builder->where('master_controls.dept_id', $deptId); } - return $this->findAll(); + + if ($keyword) { + $builder->groupStart() + ->like('master_controls.control_name', $keyword) + ->orLike('master_controls.lot', $keyword) + ->orLike('master_controls.producer', $keyword) + ->groupEnd(); + } + + $builder->orderBy('master_controls.control_name', 'ASC'); + + $results = $builder->get()->getResultArray(); + + // Add deptName after camelCase conversion from BaseModel + foreach ($results as &$row) { + $row['deptName'] = $row['deptName'] ?? null; + } + + return $results; } } diff --git a/app/Models/Master/MasterTestsModel.php b/app/Models/Master/MasterTestsModel.php index c19c7ee..487c54b 100644 --- a/app/Models/Master/MasterTestsModel.php +++ b/app/Models/Master/MasterTestsModel.php @@ -22,7 +22,7 @@ class MasterTestsModel extends BaseModel { protected $useTimestamps = true; protected $useSoftDeletes = true; - public function search($keyword = null) { + public function search($keyword = null, $deptId = null) { $builder = $this->builder(); $builder->select(' master_tests.test_id as testId, @@ -38,6 +38,10 @@ class MasterTestsModel extends BaseModel { $builder->join('master_depts', 'master_depts.dept_id = master_tests.dept_id', 'left'); $builder->where('master_tests.deleted_at', null); + if ($deptId) { + $builder->where('master_tests.dept_id', $deptId); + } + if ($keyword) { $builder->groupStart() ->like('master_tests.test_name', $keyword) diff --git a/app/Views/entry/daily.php b/app/Views/entry/daily.php index 6f0bcdf..20749e2 100644 --- a/app/Views/entry/daily.php +++ b/app/Views/entry/daily.php @@ -39,6 +39,44 @@ +