feat(nursing): add Nursing Station Master, ABDM credentials to Nurse Master, and Visual Bed Grid at /ipd/patient
This commit is contained in:
parent
7c04828e99
commit
69f0e1fa45
|
|
@ -1378,6 +1378,15 @@ $routes->group('setting', static function($routes) {
|
|||
$routes->post('admin/reffer/types/create', 'Setting\\Reffer::storeType');
|
||||
$routes->post('admin/reffer/types/update', 'Setting\\Reffer::updateType');
|
||||
$routes->post('admin/reffer/types/delete', 'Setting\\Reffer::deleteType');
|
||||
$routes->get('admin/nurse', 'Setting\\Nurse::index');
|
||||
$routes->post('admin/nurse/save', 'Setting\\Nurse::save');
|
||||
$routes->post('admin/nurse/delete', 'Setting\\Nurse::delete');
|
||||
$routes->post('admin/nurse/delete/(:num)', 'Setting\\Nurse::delete/$1');
|
||||
$routes->get('admin/nurse/list_json', 'Setting\\Nurse::list_json');
|
||||
$routes->get('admin/nursing-station', 'Setting\\NursingStation::index');
|
||||
$routes->post('admin/nursing-station/save', 'Setting\\NursingStation::save');
|
||||
$routes->post('admin/nursing-station/delete', 'Setting\\NursingStation::delete');
|
||||
$routes->get('admin/nursing-station/list_json', 'Setting\\NursingStation::list_json');
|
||||
$routes->get('admin/bed-management', 'Setting\\BedManagement::index', ['filter' => $settingsBedFilter]);
|
||||
$routes->get('admin/departments', 'Setting\\BedManagement::departments', ['filter' => $settingsBedFilter]);
|
||||
$routes->post('admin/departments/save', 'Setting\\BedManagement::saveDepartment', ['filter' => $settingsBedFilter]);
|
||||
|
|
|
|||
|
|
@ -44,8 +44,131 @@ class IpdPatient extends BaseController
|
|||
return $permission;
|
||||
}
|
||||
|
||||
$records = $this->ipdBillingModel->getCurrentAdmissions();
|
||||
|
||||
$stats = [
|
||||
'total_beds' => 0,
|
||||
'available' => 0,
|
||||
'occupied' => 0,
|
||||
'reserved' => 0,
|
||||
'maintenance' => 0,
|
||||
'cleaning' => 0,
|
||||
'total_admitted' => count($records),
|
||||
'nursing_stations' => 0,
|
||||
];
|
||||
$floors = [];
|
||||
$wards = [];
|
||||
|
||||
$stationModel = new \App\Models\NursingStationModel();
|
||||
$nursingStations = $stationModel->getActiveStations();
|
||||
$stats['nursing_stations'] = count($nursingStations);
|
||||
|
||||
if ($this->db->tableExists('bed_master') && $this->db->tableExists('ward_master')) {
|
||||
$builder = $this->db->table('bed_master b')
|
||||
->select('b.id as bed_id, b.bed_number, b.bed_code, b.bed_status, b.current_ipd_id')
|
||||
->select('b.has_oxygen, b.has_monitor, b.has_ventilator, b.is_isolation_bed')
|
||||
->select('w.id as ward_id, w.ward_name, w.floor_number, w.ward_type, w.nursing_station_id')
|
||||
->join('ward_master w', 'w.id = b.ward_id', 'left')
|
||||
->where('b.status', 'active')
|
||||
->orderBy('w.floor_number', 'ASC')
|
||||
->orderBy('w.ward_name', 'ASC')
|
||||
->orderBy('b.bed_number', 'ASC');
|
||||
|
||||
if ($this->db->tableExists('nursing_station_master')) {
|
||||
$builder->select('ns.station_name, ns.station_code')
|
||||
->join('nursing_station_master ns', 'ns.id = w.nursing_station_id', 'left');
|
||||
} else {
|
||||
$builder->select('NULL as station_name, NULL as station_code');
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('ipd_master')) {
|
||||
$builder
|
||||
->select('i.id as ipd_id, i.ipd_code, i.register_date, i.r_doc_name')
|
||||
->join('ipd_master i', 'i.id = b.current_ipd_id AND i.ipd_status = 0', 'left', false);
|
||||
|
||||
if ($this->db->tableExists('patient_master')) {
|
||||
$builder
|
||||
->select('p.p_code, p.p_fname, p.p_lname')
|
||||
->join('patient_master p', 'p.id = i.p_id', 'left');
|
||||
}
|
||||
}
|
||||
|
||||
$today = new \DateTime();
|
||||
|
||||
foreach ($builder->get()->getResult() as $row) {
|
||||
$floorNo = (string) ($row->floor_number !== null && $row->floor_number !== '' ? $row->floor_number : 'Ground Floor');
|
||||
$wardId = (int) ($row->ward_id ?? 0);
|
||||
$status = (string) ($row->bed_status ?? 'available');
|
||||
|
||||
$stats['total_beds']++;
|
||||
if (array_key_exists($status, $stats)) {
|
||||
$stats[$status]++;
|
||||
} elseif (strpos($status, 'maintenance') !== false) {
|
||||
$stats['maintenance']++;
|
||||
}
|
||||
|
||||
$daysAdmitted = null;
|
||||
if (! empty($row->register_date)) {
|
||||
try {
|
||||
$admitDate = new \DateTime((string) $row->register_date);
|
||||
$daysAdmitted = (int) $today->diff($admitDate)->days;
|
||||
} catch (\Throwable $e) {
|
||||
$daysAdmitted = null;
|
||||
}
|
||||
}
|
||||
|
||||
$patientName = trim(((string) ($row->p_fname ?? '')) . ' ' . ((string) ($row->p_lname ?? '')));
|
||||
|
||||
if (! isset($floors[$floorNo])) {
|
||||
$floors[$floorNo] = [];
|
||||
}
|
||||
if (! isset($floors[$floorNo][$wardId])) {
|
||||
$floors[$floorNo][$wardId] = [
|
||||
'ward_name' => (string) ($row->ward_name ?? 'Unknown Ward'),
|
||||
'ward_type' => (string) ($row->ward_type ?? ''),
|
||||
'station_name' => (string) ($row->station_name ?? 'Unassigned Station'),
|
||||
'station_code' => (string) ($row->station_code ?? ''),
|
||||
'beds' => [],
|
||||
'count' => ['total' => 0, 'available' => 0, 'occupied' => 0],
|
||||
];
|
||||
$wards[$wardId] = (string) ($row->ward_name ?? 'Unknown Ward');
|
||||
}
|
||||
|
||||
$floors[$floorNo][$wardId]['beds'][] = [
|
||||
'bed_id' => (int) ($row->bed_id ?? 0),
|
||||
'bed_number' => (string) ($row->bed_number !== '' && $row->bed_number !== null
|
||||
? $row->bed_number : ($row->bed_code ?? '-')),
|
||||
'bed_code' => (string) ($row->bed_code ?? ''),
|
||||
'bed_status' => $status,
|
||||
'patient_name' => $patientName,
|
||||
'patient_code' => (string) ($row->p_code ?? ''),
|
||||
'doctor_name' => trim((string) ($row->r_doc_name ?? '')),
|
||||
'ipd_code' => (string) ($row->ipd_code ?? ''),
|
||||
'ipd_id' => (int) ($row->ipd_id ?? 0),
|
||||
'admit_date' => (string) ($row->register_date ?? ''),
|
||||
'days_admitted' => $daysAdmitted,
|
||||
'has_oxygen' => (bool) ($row->has_oxygen ?? false),
|
||||
'has_monitor' => (bool) ($row->has_monitor ?? false),
|
||||
'has_ventilator' => (bool) ($row->has_ventilator ?? false),
|
||||
'is_isolation' => (bool) ($row->is_isolation_bed ?? false),
|
||||
'station_name' => (string) ($row->station_name ?? ''),
|
||||
];
|
||||
|
||||
$floors[$floorNo][$wardId]['count']['total']++;
|
||||
if ($status === 'available') {
|
||||
$floors[$floorNo][$wardId]['count']['available']++;
|
||||
} elseif ($status === 'occupied') {
|
||||
$floors[$floorNo][$wardId]['count']['occupied']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('ipd_nursing/patient_list', [
|
||||
'records' => $this->ipdBillingModel->getCurrentAdmissions(),
|
||||
'records' => $records,
|
||||
'stats' => $stats,
|
||||
'floors' => $floors,
|
||||
'wards' => $wards,
|
||||
'nursingStations' => $nursingStations,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +231,9 @@ class IpdPatient extends BaseController
|
|||
// Get bed assignment history
|
||||
$bedHistory = $this->bedAssignmentModel->getByIpd($ipdId);
|
||||
|
||||
$nurseModel = new \App\Models\NurseModel();
|
||||
$nurseList = $nurseModel->getActiveNurses();
|
||||
|
||||
return view('ipd_nursing/workspace', [
|
||||
'ipd_info' => $panelData['ipd_info'] ?? null,
|
||||
'person_info' => $person,
|
||||
|
|
@ -117,6 +243,7 @@ class IpdPatient extends BaseController
|
|||
'doctor_visit_fee_types' => $this->ipdBillingModel->getDoctorVisitFeeTypes(),
|
||||
'doctor_visit_fee_map' => $this->ipdBillingModel->getDoctorVisitFeeMap($doctorIds),
|
||||
'doc_list' => $docList,
|
||||
'nurse_list' => $nurseList,
|
||||
'history_fields' => $historyFields,
|
||||
'patient_history_row' => $patientHistoryRow,
|
||||
'opd_history_snapshot' => $opdHistorySnapshot,
|
||||
|
|
@ -268,11 +395,21 @@ class IpdPatient extends BaseController
|
|||
$recordedBy = $user->username ?? $user->email ?? 'User';
|
||||
$recordedById = $user->id ?? null;
|
||||
|
||||
$nurseIdPost = (int) ($this->request->getPost('recorded_by_nurse_id') ?? 0);
|
||||
if ($nurseIdPost > 0) {
|
||||
$nurseModel = new \App\Models\NurseModel();
|
||||
$nurseRow = $nurseModel->getNurseById($nurseIdPost);
|
||||
if ($nurseRow) {
|
||||
$recordedBy = ($nurseRow['nurse_code'] ? '[' . $nurseRow['nurse_code'] . '] ' : '') . $nurseRow['name'];
|
||||
$recordedById = $nurseRow['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'ipd_id' => $ipdId,
|
||||
'entry_type' => $entryType,
|
||||
'recorded_at' => $recordedAt,
|
||||
'shift_name' => $this->resolveShiftName($recordedAt),
|
||||
'shift_name' => '',
|
||||
'temperature_c' => $this->resolveTemperatureCFromPost(),
|
||||
'pulse_rate' => $this->request->getPost('pulse_rate') !== null && $this->request->getPost('pulse_rate') !== '' ? (int) $this->request->getPost('pulse_rate') : null,
|
||||
'resp_rate' => $this->request->getPost('resp_rate') !== null && $this->request->getPost('resp_rate') !== '' ? (int) $this->request->getPost('resp_rate') : null,
|
||||
|
|
|
|||
|
|
@ -3525,33 +3525,89 @@ class Ipd_discharge extends BaseController
|
|||
|
||||
public function surgery_master_lookup()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
$type = $this->normalizeSurgeryTermType((string) $this->request->getGet('type'));
|
||||
$q = trim((string) $this->request->getGet('q'));
|
||||
$masterOnly = (int) $this->request->getGet('master_only') === 1;
|
||||
|
||||
if (! $this->ensureDischargeSurgeryMasterTable()) {
|
||||
if ($q === '') {
|
||||
return $this->response->setJSON(['rows' => []]);
|
||||
}
|
||||
|
||||
$builder = $this->db->table('ipd_discharge_surgery_master')
|
||||
->select('id,term_type,term_name,term_code,icd_code')
|
||||
->where('is_active', 1)
|
||||
->where('term_type', $type);
|
||||
$cacheKey = 'surg_lkp_v4_' . md5($type . '_' . ($masterOnly ? 'm_' : 'all_') . mb_strtolower($q));
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $this->response->setJSON(['rows' => $cached]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
if ($q !== '') {
|
||||
$builder->groupStart()
|
||||
->like('term_name', $q)
|
||||
->orLike('term_code', $q)
|
||||
->orLike('icd_code', $q)
|
||||
->groupEnd();
|
||||
$rows = [];
|
||||
$seen = [];
|
||||
$maxLimit = 12;
|
||||
|
||||
if ($this->ensureDischargeSurgeryMasterTable()) {
|
||||
$masterRows = $this->db->table('ipd_discharge_surgery_master')
|
||||
->select('id,term_type,term_name,term_code,icd_code')
|
||||
->where('is_active', 1)
|
||||
->where('term_type', $type)
|
||||
->groupStart()
|
||||
->like('term_name', $q)
|
||||
->orLike('term_code', $q)
|
||||
->orLike('icd_code', $q)
|
||||
->groupEnd()
|
||||
->orderBy('term_name', 'ASC')
|
||||
->limit($maxLimit)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($masterRows as $row) {
|
||||
$name = (string) ($row['term_name'] ?? '');
|
||||
if ($name === '') continue;
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) continue;
|
||||
$seen[$k] = true;
|
||||
$rows[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'term_type' => (string) ($row['term_type'] ?? $type),
|
||||
'term_name' => $name,
|
||||
'term_code' => (string) ($row['term_code'] ?? ''),
|
||||
'icd_code' => (string) ($row['icd_code'] ?? ''),
|
||||
'source' => 'master',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $builder
|
||||
->orderBy('term_name', 'ASC')
|
||||
->limit(25)
|
||||
->get()
|
||||
->getResultArray();
|
||||
// SNOMED CT Procedure / Surgery Fallback (only if not master_only)
|
||||
if (! $masterOnly && count($rows) < $maxLimit) {
|
||||
$snomedRows = (new \App\Libraries\CsnotkTerminologyService())->searchProcedure($q, $maxLimit);
|
||||
foreach ($snomedRows as $row) {
|
||||
$name = (string) ($row['term'] ?? $row['fsn'] ?? '');
|
||||
if ($name === '') continue;
|
||||
$conceptId = (string) ($row['concept_id'] ?? '');
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) continue;
|
||||
$seen[$k] = true;
|
||||
$rows[] = [
|
||||
'id' => 0,
|
||||
'term_type' => $type,
|
||||
'term_name' => $name,
|
||||
'term_code' => $conceptId,
|
||||
'icd_code' => '',
|
||||
'source' => 'snomed_local',
|
||||
];
|
||||
if (count($rows) >= $maxLimit) break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['rows' => $rows]);
|
||||
$res = array_slice($rows, 0, $maxLimit);
|
||||
try {
|
||||
cache()->save($cacheKey, $res, 86400 * 30);
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
return $this->response->setJSON(['rows' => $res]);
|
||||
}
|
||||
|
||||
public function surgery_master_list()
|
||||
|
|
@ -3730,6 +3786,7 @@ class Ipd_discharge extends BaseController
|
|||
public function course_master_lookup()
|
||||
{
|
||||
$q = trim((string) $this->request->getGet('q'));
|
||||
$masterOnly = (int) ($this->request->getGet('master_only') ?? 0);
|
||||
$rows = [];
|
||||
|
||||
if ($q !== '') {
|
||||
|
|
@ -3753,6 +3810,20 @@ class Ipd_discharge extends BaseController
|
|||
'term_name' => (string) ($row['term_name'] ?? ''),
|
||||
'term_code' => (string) ($row['term_code'] ?? ''),
|
||||
'icd_code' => (string) ($row['icd_code'] ?? ''),
|
||||
'source' => 'master',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($masterOnly === 0 && count($rows) < 10 && method_exists($this, 'searchSnomedTerms')) {
|
||||
$snomedRows = $this->searchSnomedTerms($q, 10 - count($rows));
|
||||
foreach ($snomedRows as $s) {
|
||||
$rows[] = [
|
||||
'id' => 0,
|
||||
'term_name' => (string) ($s['term'] ?? ''),
|
||||
'term_code' => (string) ($s['concept_id'] ?? ''),
|
||||
'icd_code' => '',
|
||||
'source' => 'snomed',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -6131,6 +6202,24 @@ class Ipd_discharge extends BaseController
|
|||
$notice = 'Select a valid diagnosis row to remove.';
|
||||
$noticeType = 'warning';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->request->isAJAX() && in_array($action, ['add_surgery', 'remove_surgery', 'add_procedure', 'remove_procedure', 'add_diagnosis', 'remove_diagnosis', 'add_course', 'remove_course'], true)) {
|
||||
$surgeryRows = $this->byIpdRows('ipd_discharge_surgery', ['id', 'surgery_name', 'surgery_date', 'surgery_remark'], 'id ASC', $ipdId);
|
||||
$procedureRows = $this->byIpdRows('ipd_discharge_procedure', ['id', 'procedure_name', 'procedure_date', 'procedure_remark'], 'id ASC', $ipdId);
|
||||
$diagnosisRows = $this->byIpdRows('ipd_discharge_diagnosis', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId);
|
||||
$courseRows = $this->byIpdRows('ipd_discharge_course', ['id', 'comp_report', 'comp_remark'], 'id ASC', $ipdId);
|
||||
return $this->response->setJSON([
|
||||
'update' => ($savedAny ?? false) ? 1 : 0,
|
||||
'notice' => $notice ?? '',
|
||||
'noticeType' => $noticeType ?? 'info',
|
||||
'surgeryRows' => $surgeryRows,
|
||||
'procedureRows' => $procedureRows,
|
||||
'diagnosisRows' => $diagnosisRows,
|
||||
'courseRows' => $courseRows,
|
||||
'csrfName' => csrf_token(),
|
||||
'csrfHash' => csrf_hash(),
|
||||
]);
|
||||
} elseif ($action === 'add_course') {
|
||||
$name = trim((string) ($this->request->getPost('new_course_name') ?? ''));
|
||||
$remark = trim((string) ($this->request->getPost('new_course_remark') ?? ''));
|
||||
|
|
@ -8195,6 +8284,29 @@ class Ipd_discharge extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
$chiefComplaintsList = [];
|
||||
if ($this->tableHasColumns('ipd_discharge_complaint', ['ipd_id', 'comp_report'])) {
|
||||
$complaintRows = $this->byIpdRows('ipd_discharge_complaint', ['comp_report', 'comp_remark'], 'id ASC', $ipdId);
|
||||
foreach ($complaintRows as $row) {
|
||||
$term = trim((string) ($row['comp_report'] ?? ''));
|
||||
$duration = trim((string) ($row['comp_remark'] ?? ''));
|
||||
if ($term !== '') {
|
||||
$text = $term . ($duration !== '' ? (' - ' . $duration) : '');
|
||||
$chiefComplaintsList[] = ['text' => $text, 'code' => ''];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$complaintRemarkText = '';
|
||||
if ($this->tableHasColumns('ipd_discharge_complaint_remark', ['ipd_id', 'comp_remark'])) {
|
||||
$complaintRemarkRow = $this->firstRowByIpd('ipd_discharge_complaint_remark', $ipdId);
|
||||
$complaintRemarkText = trim((string) ($complaintRemarkRow['comp_remark'] ?? ''));
|
||||
}
|
||||
|
||||
if (empty($chiefComplaintsList) && $complaintRemarkText !== '') {
|
||||
$chiefComplaintsList[] = ['text' => $complaintRemarkText, 'code' => ''];
|
||||
}
|
||||
|
||||
$procedureRows = [];
|
||||
foreach ($this->byIpdRows('ipd_discharge_surgery', ['surgery_name', 'surgery_date'], 'id ASC', $ipdId) as $row) {
|
||||
$text = trim((string) ($row['surgery_name'] ?? ''));
|
||||
|
|
@ -8297,6 +8409,8 @@ class Ipd_discharge extends BaseController
|
|||
'start' => $admissionIso,
|
||||
'end' => $dischargeIso,
|
||||
],
|
||||
'chief_complaints' => $chiefComplaintsList,
|
||||
'chief_complaint_narrative' => $complaintRemarkText,
|
||||
'conditions' => $conditionRows,
|
||||
'procedures' => $procedureRows,
|
||||
'medications' => $medicationRows,
|
||||
|
|
|
|||
|
|
@ -2709,17 +2709,28 @@ class Opd_prescription extends BaseController
|
|||
*/
|
||||
public function opd_medicince_snomed_lookup()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'rows' => []]);
|
||||
}
|
||||
|
||||
$term = trim((string) $this->request->getGet('term'));
|
||||
$limit = max(1, min(20, (int) ($this->request->getGet('limit') ?: 10)));
|
||||
$limit = max(1, min(12, (int) ($this->request->getGet('limit') ?: 10)));
|
||||
|
||||
if ($term === '') {
|
||||
return $this->response->setJSON(['update' => 1, 'rows' => [], 'message' => 'No term provided']);
|
||||
}
|
||||
|
||||
$cacheKey = 'med_snomed_loc_' . md5(mb_strtolower($term) . '_' . $limit);
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $this->response->setJSON($cached);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
try {
|
||||
$svc = new \App\Libraries\CsnotkTerminologyService();
|
||||
$rows = $svc->searchSubstance($term, $limit);
|
||||
|
|
@ -2754,12 +2765,17 @@ class Opd_prescription extends BaseController
|
|||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
$payload = [
|
||||
'update' => 1,
|
||||
'rows' => $out,
|
||||
'count' => count($out),
|
||||
'message' => count($out) === 0 ? 'No SNOMED CT substance concepts found for "' . $term . '"' : '',
|
||||
]);
|
||||
];
|
||||
try {
|
||||
cache()->save($cacheKey, $payload, 86400 * 30);
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
return $this->response->setJSON($payload);
|
||||
}
|
||||
|
||||
public function opd_medicince_save()
|
||||
|
|
@ -3928,12 +3944,29 @@ class Opd_prescription extends BaseController
|
|||
|
||||
public function opd_invest_snomed_search()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
$q = trim((string) $this->request->getGet('q'));
|
||||
if ($q === '' || strlen($q) < 2) {
|
||||
return $this->response->setJSON(['rows' => []]);
|
||||
}
|
||||
|
||||
$cacheKey = 'opd_invest_snomed_v3_' . md5(mb_strtolower($q));
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $this->response->setJSON(['rows' => $cached]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
$svc = new \App\Libraries\CsnotkTerminologyService();
|
||||
$rows = $svc->searchProcedure($q, 20);
|
||||
$rows = $svc->searchProcedure($q, 12);
|
||||
|
||||
try {
|
||||
cache()->save($cacheKey, $rows, 86400 * 30);
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
return $this->response->setJSON(['rows' => $rows]);
|
||||
}
|
||||
|
||||
|
|
@ -6029,41 +6062,28 @@ class Opd_prescription extends BaseController
|
|||
|
||||
public function complaints_search()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
$q = trim((string) $this->request->getGet('q'));
|
||||
if ($q === '') {
|
||||
return $this->response->setJSON(['rows' => []]);
|
||||
}
|
||||
|
||||
$needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q));
|
||||
$cacheKey = 'complaint_search_v3_' . md5($needle);
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $this->response->setJSON(['rows' => $cached]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
$out = [];
|
||||
$seen = [];
|
||||
$needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q));
|
||||
$maxLimit = 12;
|
||||
|
||||
// 1. SNOMED CT via CSNOtk (finding / disorder semantic tags)
|
||||
$csnotkRows = (new CsnotkTerminologyService())->searchFinding($q, 20);
|
||||
foreach ($csnotkRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['term'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$conceptId = trim((string) ($row['concept_id'] ?? ''));
|
||||
$hierarchy = trim((string) ($row['hierarchy'] ?? ''));
|
||||
$k = mb_strtoupper($name) . '|' . $conceptId;
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'concept_id' => $conceptId,
|
||||
'hierarchy' => $hierarchy,
|
||||
'source' => 'snomed',
|
||||
];
|
||||
if (count($out) >= 20) {
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Local complaints_master fallback
|
||||
// 1. Local complaints_master (Primary hospital master list - ~1ms response)
|
||||
if ($this->db->tableExists('complaints_master')) {
|
||||
$localRows = $this->db->table('complaints_master')
|
||||
->select('Code as code, Name as name, name_hinglish, show_in_short, ai_hint')
|
||||
|
|
@ -6075,7 +6095,7 @@ class Opd_prescription extends BaseController
|
|||
->groupEnd()
|
||||
->orderBy('show_in_short', 'DESC')
|
||||
->orderBy('Name', 'ASC')
|
||||
->limit(20)
|
||||
->limit($maxLimit)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
|
|
@ -6096,41 +6116,72 @@ class Opd_prescription extends BaseController
|
|||
'source' => 'local',
|
||||
'name_hinglish' => (string) ($row['name_hinglish'] ?? ''),
|
||||
];
|
||||
if (count($out) >= 20) {
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Hospital frequent autotype keywords
|
||||
if (count($out) < $maxLimit) {
|
||||
$keywordRows = $this->fetchAutotypeKeywords(
|
||||
'complaints',
|
||||
$q,
|
||||
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||
8
|
||||
);
|
||||
foreach ($keywordRows as $keyword) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) $keyword);
|
||||
if ($name === '' || ($needle !== '' && mb_stripos($name, $needle) === false)) {
|
||||
continue;
|
||||
}
|
||||
$k = mb_strtoupper($name) . '|';
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'concept_id' => '',
|
||||
'hierarchy' => '',
|
||||
'source' => 'keyword',
|
||||
];
|
||||
if (count($out) >= $maxLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. User autotype keywords
|
||||
$keywordRows = $this->fetchAutotypeKeywords(
|
||||
'complaints',
|
||||
$q,
|
||||
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||
10
|
||||
);
|
||||
foreach ($keywordRows as $keyword) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) $keyword);
|
||||
if ($name === '' || ($needle !== '' && mb_stripos($name, $needle) === false)) {
|
||||
continue;
|
||||
}
|
||||
$k = mb_strtoupper($name) . '|';
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'concept_id' => '',
|
||||
'hierarchy' => '',
|
||||
'source' => 'keyword',
|
||||
];
|
||||
if (count($out) >= 20) {
|
||||
break;
|
||||
// 3. Local 1.7M SNOMED CT database (100% offline local DB query)
|
||||
if (count($out) < $maxLimit) {
|
||||
$csnotkRows = (new CsnotkTerminologyService())->searchFinding($q, $maxLimit);
|
||||
foreach ($csnotkRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['term'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$conceptId = trim((string) ($row['concept_id'] ?? ''));
|
||||
$hierarchy = trim((string) ($row['hierarchy'] ?? ''));
|
||||
$k = mb_strtoupper($name) . '|' . $conceptId;
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'concept_id' => $conceptId,
|
||||
'hierarchy' => $hierarchy,
|
||||
'source' => 'snomed_local',
|
||||
];
|
||||
if (count($out) >= $maxLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
$res = array_slice($out, 0, $maxLimit);
|
||||
try {
|
||||
cache()->save($cacheKey, $res, 86400 * 30); // 30-day server cache for repeat queries
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
return $this->response->setJSON(['rows' => $res]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -6241,142 +6292,153 @@ class Opd_prescription extends BaseController
|
|||
|
||||
public function provisional_diagnosis_search()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
$q = trim((string) $this->request->getGet('q'));
|
||||
if ($q === '') {
|
||||
return $this->response->setJSON(['rows' => []]);
|
||||
}
|
||||
|
||||
$needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q));
|
||||
$cacheKey = 'prov_diag_v3_' . md5($needle);
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $this->response->setJSON(['rows' => $cached]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
$out = [];
|
||||
$seen = [];
|
||||
$needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q));
|
||||
$hasLocalSnomedRows = false;
|
||||
$maxLimit = 12;
|
||||
|
||||
$csnotkRows = (new CsnotkTerminologyService())->searchDiagnosis($q, 20);
|
||||
foreach ($csnotkRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['term'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
if ($needle !== '' && mb_stripos($name, $needle) === false) {
|
||||
continue;
|
||||
}
|
||||
// 1. Local disease_master hospital master list (Primary ~1ms)
|
||||
if ($this->db->tableExists('disease_master')) {
|
||||
$fields = $this->db->getFieldNames('disease_master') ?? [];
|
||||
$nameField = in_array('Name', $fields, true) ? 'Name' : (in_array('name', $fields, true) ? 'name' : null);
|
||||
if ($nameField !== null) {
|
||||
$diseaseRows = $this->db->table('disease_master')
|
||||
->select($nameField . ' as name')
|
||||
->like($nameField, $q)
|
||||
->orderBy($nameField, 'ASC')
|
||||
->limit($maxLimit)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$conceptId = trim((string) ($row['concept_id'] ?? ''));
|
||||
$source = trim((string) ($row['source'] ?? 'csnotk'));
|
||||
if (in_array($source, ['local', 'snomed'], true)) {
|
||||
$hasLocalSnomedRows = true;
|
||||
}
|
||||
$k = mb_strtoupper($name) . '|' . $conceptId;
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => $conceptId,
|
||||
'snomed_term' => $name,
|
||||
'source' => $source,
|
||||
];
|
||||
|
||||
if (count($out) >= 20) {
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
foreach ($diseaseRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['name'] ?? ''));
|
||||
if ($name === '' || ($needle !== '' && mb_stripos($name, $needle) === false)) {
|
||||
continue;
|
||||
}
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => '',
|
||||
'snomed_term' => '',
|
||||
'source' => 'disease_master',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$snomedRows = ($hasLocalSnomedRows || mb_strlen($needle) < 3) ? [] : $this->fetchSnomedDiagnosisRows($q, 20);
|
||||
foreach ($snomedRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['term'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
if ($needle !== '' && mb_stripos($name, $needle) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => (string) ($row['concept_id'] ?? ''),
|
||||
'snomed_term' => $name,
|
||||
'source' => 'snomed',
|
||||
];
|
||||
|
||||
if (count($out) >= 20) {
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
// 2. Hospital frequent autotype keywords
|
||||
if (count($out) < $maxLimit) {
|
||||
$keywordRows = $this->fetchAutotypeKeywords(
|
||||
'provisional_diagnosis',
|
||||
$q,
|
||||
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||
8
|
||||
);
|
||||
foreach ($keywordRows as $keyword) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) $keyword);
|
||||
if ($name === '' || ($needle !== '' && mb_stripos($name, $needle) === false)) {
|
||||
continue;
|
||||
}
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => '',
|
||||
'snomed_term' => '',
|
||||
'source' => 'keyword',
|
||||
];
|
||||
if (count($out) >= $maxLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('disease_master')) {
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
// 3. Local 1.7M SNOMED CT Database (100% offline local DB query)
|
||||
if (count($out) < $maxLimit) {
|
||||
$csnotkRows = (new CsnotkTerminologyService())->searchDiagnosis($q, $maxLimit);
|
||||
foreach ($csnotkRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['term'] ?? ''));
|
||||
if ($name === '' || ($needle !== '' && mb_stripos($name, $needle) === false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$conceptId = trim((string) ($row['concept_id'] ?? ''));
|
||||
$source = trim((string) ($row['source'] ?? 'snomed_local'));
|
||||
$k = mb_strtoupper($name) . '|' . $conceptId;
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => $conceptId,
|
||||
'snomed_term' => $name,
|
||||
'source' => $source,
|
||||
];
|
||||
|
||||
if (count($out) >= $maxLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('disease_master') ?? [];
|
||||
$nameField = in_array('Name', $fields, true) ? 'Name' : (in_array('name', $fields, true) ? 'name' : null);
|
||||
if ($nameField === null) {
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
// 4. Secondary local SNOMED fallback if still below limit
|
||||
if (count($out) < $maxLimit) {
|
||||
$snomedRows = $this->fetchSnomedDiagnosisRows($q, $maxLimit);
|
||||
foreach ($snomedRows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['term'] ?? ''));
|
||||
if ($name === '' || ($needle !== '' && mb_stripos($name, $needle) === false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => (string) ($row['concept_id'] ?? ''),
|
||||
'snomed_term' => $name,
|
||||
'source' => 'snomed_local',
|
||||
];
|
||||
|
||||
if (count($out) >= $maxLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $this->db->table('disease_master')
|
||||
->select($nameField . ' as name')
|
||||
->like($nameField, $q)
|
||||
->orderBy($nameField, 'ASC')
|
||||
->limit(20)
|
||||
->get()
|
||||
->getResultArray();
|
||||
$res = array_slice($out, 0, $maxLimit);
|
||||
try {
|
||||
cache()->save($cacheKey, $res, 86400 * 30); // 30-day server cache
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
$keywordRows = $this->fetchAutotypeKeywords(
|
||||
'provisional_diagnosis',
|
||||
$q,
|
||||
max(0, (int) ($this->getCurrentUserId() ?? 0)),
|
||||
20
|
||||
);
|
||||
foreach ($rows as $row) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) ($row['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
if ($needle !== '' && mb_stripos($name, $needle) === false) {
|
||||
continue;
|
||||
}
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => '',
|
||||
'snomed_term' => '',
|
||||
'source' => 'disease_master',
|
||||
];
|
||||
}
|
||||
foreach ($keywordRows as $keyword) {
|
||||
$name = $this->normalizeAutocompleteSuggestionText((string) $keyword);
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
if ($needle !== '' && mb_stripos($name, $needle) === false) {
|
||||
continue;
|
||||
}
|
||||
$k = mb_strtoupper($name);
|
||||
if (isset($seen[$k])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$k] = true;
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'snomed_concept_id' => '',
|
||||
'snomed_term' => '',
|
||||
'source' => 'keyword',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]);
|
||||
return $this->response->setJSON(['rows' => $res]);
|
||||
}
|
||||
|
||||
private function fetchSnomedDiagnosisRows(string $query, int $limit = 20): array
|
||||
|
|
|
|||
|
|
@ -74,14 +74,24 @@ class BedManagement extends BaseController
|
|||
$departments = array_merge($departments, $departmentRows);
|
||||
}
|
||||
|
||||
$nursingStationModel = new \App\Models\NursingStationModel();
|
||||
$nursingStations = $nursingStationModel->getActiveStations();
|
||||
|
||||
$wardBuilder = $this->db->table('ward_master w')
|
||||
->orderBy('w.ward_name', 'ASC');
|
||||
|
||||
if ($this->db->tableExists('nursing_station_master')) {
|
||||
$wardBuilder->select('w.*, ns.station_name, ns.station_code')
|
||||
->join('nursing_station_master ns', 'ns.id = w.nursing_station_id', 'left');
|
||||
} else {
|
||||
$wardBuilder->select('w.*, NULL as station_name, NULL as station_code');
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('hc_department')) {
|
||||
$wardBuilder->select('w.*, COALESCE(d.vName, "All Department") AS department_name', false)
|
||||
$wardBuilder->select('COALESCE(d.vName, "All Department") AS department_name', false)
|
||||
->join('hc_department d', 'd.iId = w.department_id', 'left');
|
||||
} else {
|
||||
$wardBuilder->select('w.*, "All Department" AS department_name', false);
|
||||
$wardBuilder->select('"All Department" AS department_name', false);
|
||||
}
|
||||
|
||||
$wardRows = $wardBuilder->get()->getResult();
|
||||
|
|
@ -89,6 +99,7 @@ class BedManagement extends BaseController
|
|||
return view('Setting/Bed/ward_list', [
|
||||
'wards' => $wardRows,
|
||||
'departments' => $departments,
|
||||
'nursingStations' => $nursingStations,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +114,7 @@ class BedManagement extends BaseController
|
|||
$wardName = trim((string) $this->request->getPost('ward_name'));
|
||||
$departmentIdRaw = $this->request->getPost('department_id');
|
||||
$departmentId = $departmentIdRaw === null || $departmentIdRaw === '' ? 0 : (int) $departmentIdRaw;
|
||||
$nursingStationId = (int) ($this->request->getPost('nursing_station_id') ?? 0);
|
||||
|
||||
if ($wardCode === '' || $wardName === '') {
|
||||
return $this->response->setJSON(['update' => 0, 'error_text' => 'Ward code and ward name are required.']);
|
||||
|
|
@ -127,6 +139,7 @@ class BedManagement extends BaseController
|
|||
'ward_code' => $wardCode,
|
||||
'ward_name' => $wardName,
|
||||
'department_id' => $departmentId,
|
||||
'nursing_station_id' => $nursingStationId > 0 ? $nursingStationId : null,
|
||||
'building_id' => $this->request->getPost('building_id') ?: null,
|
||||
'floor_number' => $this->request->getPost('floor_number') ?: null,
|
||||
'ward_type' => $this->request->getPost('ward_type') ?: 'General',
|
||||
|
|
|
|||
108
app/Controllers/Setting/Nurse.php
Normal file
108
app/Controllers/Setting/Nurse.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controllers\Setting;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\NurseModel;
|
||||
|
||||
class Nurse extends BaseController
|
||||
{
|
||||
private NurseModel $nurseModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->nurseModel = new NurseModel();
|
||||
}
|
||||
|
||||
public function index(): string
|
||||
{
|
||||
$q = trim((string) ($this->request->getGet('q') ?? ''));
|
||||
$nurses = $this->nurseModel->getNurses($q);
|
||||
|
||||
return view('Setting/Nurse/Nurse_Search_V', [
|
||||
'nurses' => $nurses,
|
||||
'searchQuery' => $q,
|
||||
]);
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id') ?? 0);
|
||||
$nurseCode = trim((string) ($this->request->getPost('nurse_code') ?? ''));
|
||||
$name = trim((string) ($this->request->getPost('name') ?? ''));
|
||||
$hprId = trim((string) ($this->request->getPost('hpr_id') ?? ''));
|
||||
$registrationNo = trim((string) ($this->request->getPost('registration_no') ?? ''));
|
||||
$gender = trim((string) ($this->request->getPost('gender') ?? 'Female'));
|
||||
$designation = trim((string) ($this->request->getPost('designation') ?? 'Staff Nurse'));
|
||||
$qualification = trim((string) ($this->request->getPost('qualification') ?? ''));
|
||||
$contactNo = trim((string) ($this->request->getPost('contact_no') ?? ''));
|
||||
$email = trim((string) ($this->request->getPost('email') ?? ''));
|
||||
$department = trim((string) ($this->request->getPost('department') ?? 'Nursing'));
|
||||
$isActive = (int) ($this->request->getPost('is_active') ?? 1);
|
||||
|
||||
if ($name === '') {
|
||||
return $this->response->setJSON(['ok' => false, 'error' => 'Nurse Name is required.']);
|
||||
}
|
||||
|
||||
if ($nurseCode === '') {
|
||||
// Auto generate code if empty
|
||||
$nurseCode = 'NUR-' . str_pad((string) (rand(100, 999)), 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'nurse_code' => $nurseCode,
|
||||
'name' => $name,
|
||||
'hpr_id' => $hprId,
|
||||
'registration_no' => $registrationNo,
|
||||
'gender' => $gender,
|
||||
'designation' => $designation,
|
||||
'qualification' => $qualification,
|
||||
'contact_no' => $contactNo,
|
||||
'email' => $email,
|
||||
'department' => $department,
|
||||
'is_active' => $isActive,
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$this->nurseModel->updateNurse($id, $payload);
|
||||
$savedId = $id;
|
||||
} else {
|
||||
$savedId = $this->nurseModel->insertNurse($payload);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'message' => $id > 0 ? 'Nurse updated successfully.' : 'Nurse created successfully.',
|
||||
'id' => $savedId,
|
||||
'nurse' => $this->nurseModel->getNurseById($savedId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete($id = null)
|
||||
{
|
||||
$id = (int) ($id ?? $this->request->getPost('id') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->response->setJSON(['ok' => false, 'error' => 'Invalid nurse ID.']);
|
||||
}
|
||||
|
||||
$this->nurseModel->deleteNurse($id);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'message' => 'Nurse record deleted successfully.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function list_json()
|
||||
{
|
||||
$nurses = $this->nurseModel->getActiveNurses();
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'nurses' => $nurses,
|
||||
]);
|
||||
}
|
||||
}
|
||||
104
app/Controllers/Setting/NursingStation.php
Normal file
104
app/Controllers/Setting/NursingStation.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controllers\Setting;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\NurseModel;
|
||||
use App\Models\NursingStationModel;
|
||||
|
||||
class NursingStation extends BaseController
|
||||
{
|
||||
protected NursingStationModel $stationModel;
|
||||
protected NurseModel $nurseModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->stationModel = new NursingStationModel();
|
||||
$this->nurseModel = new NurseModel();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$q = trim((string) ($this->request->getGet('q') ?? ''));
|
||||
$stations = $this->stationModel->getStations($q);
|
||||
$nurses = $this->nurseModel->getActiveNurses();
|
||||
|
||||
return view('Setting/NursingStation/nursing_station_index', [
|
||||
'stations' => $stations,
|
||||
'nurses' => $nurses,
|
||||
'searchQuery' => $q,
|
||||
]);
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id') ?? 0);
|
||||
$stationCode = trim((string) ($this->request->getPost('station_code') ?? ''));
|
||||
$stationName = trim((string) ($this->request->getPost('station_name') ?? ''));
|
||||
$floorNumber = trim((string) ($this->request->getPost('floor_number') ?? ''));
|
||||
$inchargeNurseId = (int) ($this->request->getPost('incharge_nurse_id') ?? 0);
|
||||
$contactNo = trim((string) ($this->request->getPost('contact_no') ?? ''));
|
||||
$status = trim((string) ($this->request->getPost('status') ?? 'active'));
|
||||
$remarks = trim((string) ($this->request->getPost('remarks') ?? ''));
|
||||
|
||||
if ($stationName === '') {
|
||||
return $this->response->setJSON(['ok' => false, 'error' => 'Station Name is required.']);
|
||||
}
|
||||
|
||||
if ($stationCode === '') {
|
||||
$stationCode = 'NS-' . str_pad((string) (rand(100, 999)), 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
$inchargeNurseName = '';
|
||||
if ($inchargeNurseId > 0) {
|
||||
$nurseRow = $this->nurseModel->getNurseById($inchargeNurseId);
|
||||
if ($nurseRow) {
|
||||
$inchargeNurseName = trim(($nurseRow['nurse_code'] ? '[' . $nurseRow['nurse_code'] . '] ' : '') . $nurseRow['name']);
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'station_code' => $stationCode,
|
||||
'station_name' => $stationName,
|
||||
'floor_number' => $floorNumber,
|
||||
'incharge_nurse_id' => $inchargeNurseId,
|
||||
'incharge_nurse_name' => $inchargeNurseName,
|
||||
'contact_no' => $contactNo,
|
||||
'status' => $status,
|
||||
'remarks' => $remarks,
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$ok = $this->stationModel->updateStation($id, $payload);
|
||||
return $this->response->setJSON(['ok' => $ok, 'error' => $ok ? '' : 'Failed to update nursing station']);
|
||||
}
|
||||
|
||||
$newId = $this->stationModel->insertStation($payload);
|
||||
return $this->response->setJSON(['ok' => $newId > 0, 'id' => $newId, 'error' => $newId > 0 ? '' : 'Failed to insert nursing station']);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
if (! $this->request->isAJAX()) {
|
||||
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
|
||||
}
|
||||
|
||||
$id = (int) ($this->request->getPost('id') ?? 0);
|
||||
if ($id <= 0) {
|
||||
return $this->response->setJSON(['ok' => false, 'error' => 'Invalid Station ID']);
|
||||
}
|
||||
|
||||
$ok = $this->stationModel->deleteStation($id);
|
||||
return $this->response->setJSON(['ok' => $ok, 'error' => $ok ? '' : 'Failed to delete station']);
|
||||
}
|
||||
|
||||
public function list_json()
|
||||
{
|
||||
$stations = $this->stationModel->getActiveStations();
|
||||
return $this->response->setJSON(['ok' => true, 'stations' => $stations]);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,10 +28,10 @@ class CsnotkTerminologyService
|
|||
$baseUrl = trim((string) env('CSNOTK_BASE_URL', 'https://csnotk.e-atria.in'));
|
||||
}
|
||||
$this->baseUrl = rtrim($baseUrl, '/');
|
||||
$this->timeoutSec = max(1, (int) env('snomed.csnotk.timeoutSec', 5));
|
||||
$this->timeoutSec = 1;
|
||||
|
||||
$enabledRaw = strtolower(trim((string) env('snomed.csnotk.enabled', env('CSNOTK_ENABLED', ''))));
|
||||
$this->apiEnabled = in_array($enabledRaw, ['1', 'true', 'yes', 'on'], true) || $this->baseUrl !== '';
|
||||
// Force 100% Local DB mode (No external HTTP calls)
|
||||
$this->apiEnabled = false;
|
||||
|
||||
try {
|
||||
$db = db_connect();
|
||||
|
|
@ -41,63 +41,83 @@ class CsnotkTerminologyService
|
|||
$this->hasFtIndex = $this->checkFtIndex($db);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Local DB not available — will fall through to API
|
||||
// Local DB not available
|
||||
}
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->localAvailable || ($this->apiEnabled && $this->baseUrl !== '');
|
||||
return $this->localAvailable;
|
||||
}
|
||||
|
||||
// ─── Public search API ────────────────────────────────────────────────
|
||||
// ─── Public search API ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* General diagnosis search (findings + disorders).
|
||||
* @return array<int, array{concept_id:string,term:string,source:string}>
|
||||
*/
|
||||
public function searchDiagnosis(string $query, int $limit = 20): array
|
||||
public function searchDiagnosis(string $query, int $limit = 12): array
|
||||
{
|
||||
$q = trim($query);
|
||||
if ($q === '') {
|
||||
return [];
|
||||
}
|
||||
$limit = max(1, min(50, $limit));
|
||||
$limit = max(1, min(12, $limit));
|
||||
|
||||
// Local DB is primary — ~17ms vs ~330ms for external API from server
|
||||
$cacheKey = 'snomed_diag_loc_' . md5(mb_strtolower($q) . '_' . $limit);
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
// Local DB is 100% primary (<20ms)
|
||||
$rows = $this->localSearch($q, $limit, ['finding', 'disorder', 'morphologic abnormality', 'disease']);
|
||||
|
||||
if (count($rows) < $limit && mb_strlen($q) >= 3 && $this->apiEnabled && $this->baseUrl !== '') {
|
||||
$rows = $this->mergeFallback($rows, $this->apiSearchDiagnosis($q, $limit), $limit);
|
||||
}
|
||||
|
||||
return array_map(fn ($r) => [
|
||||
$res = array_map(fn ($r) => [
|
||||
'concept_id' => (string) ($r['concept_id'] ?? ''),
|
||||
'term' => (string) ($r['term'] ?? ''),
|
||||
'source' => (string) ($r['source'] ?? 'local'),
|
||||
'source' => 'snomed_local',
|
||||
], array_slice($rows, 0, $limit));
|
||||
|
||||
try {
|
||||
cache()->save($cacheKey, $res, 86400 * 30); // Cache for 30 days
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for clinical findings / symptoms / disorders (for chief complaints autocomplete).
|
||||
* @return array<int, array{concept_id:string,term:string,fsn:string,hierarchy:string,source:string}>
|
||||
*/
|
||||
public function searchFinding(string $query, int $limit = 20): array
|
||||
public function searchFinding(string $query, int $limit = 12): array
|
||||
{
|
||||
$q = trim($query);
|
||||
if ($q === '') {
|
||||
return [];
|
||||
}
|
||||
$limit = max(1, min(50, $limit));
|
||||
$limit = max(1, min(12, $limit));
|
||||
|
||||
// Local DB is primary — ~17ms vs ~330ms for external API from server
|
||||
$cacheKey = 'snomed_find_loc_' . md5(mb_strtolower($q) . '_' . $limit);
|
||||
try {
|
||||
if ($cached = cache($cacheKey)) {
|
||||
if (is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
// Local DB is 100% primary (<20ms)
|
||||
$rows = $this->localSearch($q, $limit, ['finding', 'disorder']);
|
||||
$res = array_slice($rows, 0, $limit);
|
||||
|
||||
if (count($rows) < $limit && mb_strlen($q) >= 3 && $this->apiEnabled && $this->baseUrl !== '') {
|
||||
$rows = $this->mergeFallback($rows, $this->apiSearchBySemTag($q, $limit, ['finding', 'disorder']), $limit);
|
||||
}
|
||||
try {
|
||||
cache()->save($cacheKey, $res, 86400 * 30); // Cache for 30 days
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
return array_slice($rows, 0, $limit);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -201,16 +221,15 @@ class CsnotkTerminologyService
|
|||
}
|
||||
|
||||
/**
|
||||
* FULLTEXT MATCH ... AGAINST (Boolean Mode) search.
|
||||
* FULLTEXT MATCH ... AGAINST (Boolean Mode) search on local 1.7M snomed_description table.
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
private function localFtSearch(string $q, int $limit): array
|
||||
{
|
||||
$words = preg_split('/\s+/', mb_strtolower(trim($q))) ?: [$q];
|
||||
$ftWords = array_filter($words, fn ($w) => mb_strlen($w) >= 3);
|
||||
$ftWords = array_filter($words, fn ($w) => mb_strlen($w) >= 2);
|
||||
|
||||
if (empty($ftWords)) {
|
||||
// FT cannot handle words shorter than innodb_ft_min_token_size (default 3)
|
||||
return $this->localPrefixSearch($q, $limit);
|
||||
}
|
||||
|
||||
|
|
@ -218,19 +237,19 @@ class CsnotkTerminologyService
|
|||
|
||||
try {
|
||||
return (array) $this->db->query(
|
||||
"SELECT d.concept_id, d.term
|
||||
"SELECT d.concept_id, d.term,
|
||||
(SELECT d2.term FROM snomed_description d2 WHERE d2.concept_id = d.concept_id AND d2.type_id = ? AND d2.active = 1 LIMIT 1) AS fsn
|
||||
FROM snomed_description d
|
||||
INNER JOIN snomed_concept c ON c.concept_id = d.concept_id AND c.active = 1
|
||||
WHERE d.active = 1
|
||||
AND d.language_code = 'en'
|
||||
AND d.type_id = ?
|
||||
AND MATCH(d.term) AGAINST(? IN BOOLEAN MODE)
|
||||
ORDER BY d.term ASC
|
||||
ORDER BY LENGTH(d.term) ASC
|
||||
LIMIT ?",
|
||||
[self::FSN_TYPE_ID, $ftQuery, $limit]
|
||||
)->getResultArray();
|
||||
} catch (\Throwable $e) {
|
||||
$this->hasFtIndex = false; // Index may not exist yet — disable for this request
|
||||
$this->hasFtIndex = false;
|
||||
return $this->localPrefixSearch($q, $limit);
|
||||
}
|
||||
}
|
||||
|
|
@ -244,14 +263,14 @@ class CsnotkTerminologyService
|
|||
$norm = mb_strtolower(trim($q));
|
||||
try {
|
||||
return (array) $this->db->query(
|
||||
"SELECT d.concept_id, d.term
|
||||
"SELECT d.concept_id, d.term,
|
||||
(SELECT d2.term FROM snomed_description d2 WHERE d2.concept_id = d.concept_id AND d2.type_id = ? AND d2.active = 1 LIMIT 1) AS fsn
|
||||
FROM snomed_description d
|
||||
INNER JOIN snomed_concept c ON c.concept_id = d.concept_id AND c.active = 1
|
||||
WHERE d.active = 1
|
||||
AND d.language_code = 'en'
|
||||
AND d.type_id = ?
|
||||
AND d.term_normalized LIKE ?
|
||||
ORDER BY d.term ASC
|
||||
ORDER BY LENGTH(d.term) ASC
|
||||
LIMIT ?",
|
||||
[self::FSN_TYPE_ID, $norm . '%', $limit]
|
||||
)->getResultArray();
|
||||
|
|
@ -261,7 +280,7 @@ class CsnotkTerminologyService
|
|||
}
|
||||
|
||||
/**
|
||||
* Substring search: term_normalized LIKE '%query%' (full scan — slower).
|
||||
* Substring search: term_normalized LIKE '%query%'.
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
private function localSubstringSearch(string $q, int $limit): array
|
||||
|
|
@ -269,14 +288,14 @@ class CsnotkTerminologyService
|
|||
$norm = mb_strtolower(trim($q));
|
||||
try {
|
||||
return (array) $this->db->query(
|
||||
"SELECT d.concept_id, d.term
|
||||
"SELECT d.concept_id, d.term,
|
||||
(SELECT d2.term FROM snomed_description d2 WHERE d2.concept_id = d.concept_id AND d2.type_id = ? AND d2.active = 1 LIMIT 1) AS fsn
|
||||
FROM snomed_description d
|
||||
INNER JOIN snomed_concept c ON c.concept_id = d.concept_id AND c.active = 1
|
||||
WHERE d.active = 1
|
||||
AND d.language_code = 'en'
|
||||
AND d.type_id = ?
|
||||
AND d.term_normalized LIKE ?
|
||||
ORDER BY d.term ASC
|
||||
ORDER BY LENGTH(d.term) ASC
|
||||
LIMIT ?",
|
||||
[self::FSN_TYPE_ID, '%' . $norm . '%', $limit]
|
||||
)->getResultArray();
|
||||
|
|
@ -304,8 +323,9 @@ class CsnotkTerminologyService
|
|||
}
|
||||
|
||||
$conceptId = trim((string) ($row['concept_id'] ?? ''));
|
||||
$fsn = trim((string) ($row['term'] ?? ''));
|
||||
if ($conceptId === '' || $fsn === '') {
|
||||
$term = trim((string) ($row['term'] ?? ''));
|
||||
$fsn = trim((string) ($row['fsn'] ?? ''));
|
||||
if ($conceptId === '' || $term === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -313,16 +333,17 @@ class CsnotkTerminologyService
|
|||
continue;
|
||||
}
|
||||
|
||||
// Extract semantic tag from FSN suffix: "Fever (finding)" → "finding"
|
||||
$hierarchy = '';
|
||||
$displayTerm = $fsn;
|
||||
if (preg_match('/^(.*)\(([^)]+)\)$/', $fsn, $m)) {
|
||||
$hierarchy = trim($m[2]);
|
||||
$displayTerm = trim($m[1]);
|
||||
// Extract semantic tag from FSN or term suffix
|
||||
$hierarchy = '';
|
||||
if ($fsn !== '' && preg_match('/^(.*)\(([^)]+)\)$/', $fsn, $m)) {
|
||||
$hierarchy = trim($m[2]);
|
||||
} elseif (preg_match('/^(.*)\(([^)]+)\)$/', $term, $m)) {
|
||||
$hierarchy = trim($m[2]);
|
||||
$term = trim($m[1]);
|
||||
}
|
||||
|
||||
// Apply semantic tag filter
|
||||
if (! empty($tagsLower)) {
|
||||
// Apply semantic tag filter if tags specified and hierarchy identified
|
||||
if (! empty($tagsLower) && $hierarchy !== '') {
|
||||
if (! in_array(mb_strtolower($hierarchy), $tagsLower, true)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -331,17 +352,17 @@ class CsnotkTerminologyService
|
|||
$seen[$conceptId] = true;
|
||||
$out[] = [
|
||||
'concept_id' => $conceptId,
|
||||
'term' => $displayTerm !== '' ? $displayTerm : $fsn,
|
||||
'fsn' => $fsn,
|
||||
'term' => $term,
|
||||
'fsn' => $fsn !== '' ? $fsn : $term,
|
||||
'hierarchy' => $hierarchy,
|
||||
'source' => 'local',
|
||||
'source' => 'snomed_local',
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
// ─── External API fallbacks ───────────────────────────────────────────
|
||||
// ─── External API fallbacks ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @return array<int, array{concept_id:string,term:string,fsn:string,hierarchy:string,source:string}>
|
||||
|
|
|
|||
108
app/Models/NurseModel.php
Normal file
108
app/Models/NurseModel.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
|
||||
class NurseModel
|
||||
{
|
||||
private BaseConnection $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = db_connect();
|
||||
$this->ensureNurseMasterTable();
|
||||
}
|
||||
|
||||
public function ensureNurseMasterTable(): bool
|
||||
{
|
||||
if (! $this->db->tableExists('nurse_master')) {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS nurse_master (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nurse_code VARCHAR(50) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
hpr_id VARCHAR(100) NULL,
|
||||
registration_no VARCHAR(100) NULL,
|
||||
gender VARCHAR(20) NULL,
|
||||
designation VARCHAR(100) NULL,
|
||||
qualification VARCHAR(255) NULL,
|
||||
contact_no VARCHAR(50) NULL,
|
||||
email VARCHAR(100) NULL,
|
||||
department VARCHAR(100) DEFAULT 'Nursing',
|
||||
is_active TINYINT DEFAULT 1,
|
||||
created_at DATETIME NULL,
|
||||
updated_at DATETIME NULL,
|
||||
INDEX idx_nurse_code (nurse_code),
|
||||
INDEX idx_hpr_id (hpr_id),
|
||||
INDEX idx_is_active (is_active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
$this->db->query($sql);
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('nurse_master') ?? [];
|
||||
if (! in_array('hpr_id', $fields, true)) {
|
||||
$this->db->query("ALTER TABLE nurse_master ADD COLUMN hpr_id VARCHAR(100) NULL AFTER name");
|
||||
}
|
||||
if (! in_array('registration_no', $fields, true)) {
|
||||
$this->db->query("ALTER TABLE nurse_master ADD COLUMN registration_no VARCHAR(100) NULL AFTER hpr_id");
|
||||
}
|
||||
if (! in_array('gender', $fields, true)) {
|
||||
$this->db->query("ALTER TABLE nurse_master ADD COLUMN gender VARCHAR(20) NULL AFTER registration_no");
|
||||
}
|
||||
if (! in_array('designation', $fields, true)) {
|
||||
$this->db->query("ALTER TABLE nurse_master ADD COLUMN designation VARCHAR(100) NULL AFTER gender");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getNurses(string $q = ''): array
|
||||
{
|
||||
$builder = $this->db->table('nurse_master');
|
||||
if ($q !== '') {
|
||||
$builder->groupStart()
|
||||
->like('name', $q)
|
||||
->orLike('nurse_code', $q)
|
||||
->orLike('hpr_id', $q)
|
||||
->orLike('registration_no', $q)
|
||||
->orLike('qualification', $q)
|
||||
->orLike('contact_no', $q)
|
||||
->groupEnd();
|
||||
}
|
||||
return $builder->orderBy('name', 'ASC')->get()->getResultArray();
|
||||
}
|
||||
|
||||
public function getActiveNurses(): array
|
||||
{
|
||||
return $this->db->table('nurse_master')
|
||||
->where('is_active', 1)
|
||||
->orderBy('name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
public function getNurseById(int $id): ?array
|
||||
{
|
||||
$row = $this->db->table('nurse_master')->where('id', $id)->get()->getRowArray();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
public function insertNurse(array $data): int
|
||||
{
|
||||
$data['created_at'] = date('Y-m-d H:i:s');
|
||||
$data['updated_at'] = date('Y-m-d H:i:s');
|
||||
$this->db->table('nurse_master')->insert($data);
|
||||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
public function updateNurse(int $id, array $data): bool
|
||||
{
|
||||
$data['updated_at'] = date('Y-m-d H:i:s');
|
||||
return (bool) $this->db->table('nurse_master')->where('id', $id)->update($data);
|
||||
}
|
||||
|
||||
public function deleteNurse(int $id): bool
|
||||
{
|
||||
return (bool) $this->db->table('nurse_master')->where('id', $id)->delete();
|
||||
}
|
||||
}
|
||||
95
app/Models/NursingStationModel.php
Normal file
95
app/Models/NursingStationModel.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
|
||||
class NursingStationModel
|
||||
{
|
||||
private BaseConnection $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = db_connect();
|
||||
$this->ensureNursingStationTable();
|
||||
}
|
||||
|
||||
public function ensureNursingStationTable(): bool
|
||||
{
|
||||
if (! $this->db->tableExists('nursing_station_master')) {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS nursing_station_master (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
station_code VARCHAR(50) NOT NULL,
|
||||
station_name VARCHAR(255) NOT NULL,
|
||||
floor_number VARCHAR(50) NULL,
|
||||
incharge_nurse_id INT NULL,
|
||||
incharge_nurse_name VARCHAR(255) NULL,
|
||||
contact_no VARCHAR(50) NULL,
|
||||
status ENUM('active', 'inactive') DEFAULT 'active',
|
||||
remarks TEXT NULL,
|
||||
created_at DATETIME NULL,
|
||||
updated_at DATETIME NULL,
|
||||
INDEX idx_station_code (station_code),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
$this->db->query($sql);
|
||||
}
|
||||
|
||||
if ($this->db->tableExists('ward_master')) {
|
||||
$fields = $this->db->getFieldNames('ward_master') ?? [];
|
||||
if (! in_array('nursing_station_id', $fields, true)) {
|
||||
$this->db->query("ALTER TABLE ward_master ADD COLUMN nursing_station_id INT NULL AFTER department_id");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getStations(string $q = ''): array
|
||||
{
|
||||
$builder = $this->db->table('nursing_station_master');
|
||||
if ($q !== '') {
|
||||
$builder->groupStart()
|
||||
->like('station_name', $q)
|
||||
->orLike('station_code', $q)
|
||||
->orLike('floor_number', $q)
|
||||
->orLike('incharge_nurse_name', $q)
|
||||
->groupEnd();
|
||||
}
|
||||
return $builder->orderBy('station_name', 'ASC')->get()->getResultArray();
|
||||
}
|
||||
|
||||
public function getActiveStations(): array
|
||||
{
|
||||
return $this->db->table('nursing_station_master')
|
||||
->where('status', 'active')
|
||||
->orderBy('station_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
public function getStationById(int $id): ?array
|
||||
{
|
||||
$row = $this->db->table('nursing_station_master')->where('id', $id)->get()->getRowArray();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
public function insertStation(array $data): int
|
||||
{
|
||||
$data['created_at'] = date('Y-m-d H:i:s');
|
||||
$data['updated_at'] = date('Y-m-d H:i:s');
|
||||
$this->db->table('nursing_station_master')->insert($data);
|
||||
return (int) $this->db->insertID();
|
||||
}
|
||||
|
||||
public function updateStation(int $id, array $data): bool
|
||||
{
|
||||
$data['updated_at'] = date('Y-m-d H:i:s');
|
||||
return (bool) $this->db->table('nursing_station_master')->where('id', $id)->update($data);
|
||||
}
|
||||
|
||||
public function deleteStation(int $id): bool
|
||||
{
|
||||
return (bool) $this->db->table('nursing_station_master')->where('id', $id)->delete();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<section class="content">
|
||||
<div class="pagetitle">
|
||||
<h1>Admin Panel</h1>
|
||||
<div class="pagetitle mb-3">
|
||||
<h1 class="mb-0">Admin Panel</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">Dashboard</li>
|
||||
<li class="breadcrumb-item active">Admin</li>
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="javascript:showAdminTiles()">Dashboard Admin</a></li>
|
||||
<li class="breadcrumb-item active d-none" id="admin_breadcrumb_sub">Sub Page</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
@ -49,100 +49,160 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<div class="row g-3 admin-tiles">
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/doctor') ?>','maindiv','Doctor Master');">
|
||||
<i class="bi bi-person-badge"></i>
|
||||
<span>Doctor Master</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/user-management') ?>','maindiv','User Management');">
|
||||
<i class="bi bi-people"></i>
|
||||
<span>User Management</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/ai-settings') ?>','maindiv','AI Settings');">
|
||||
<i class="bi bi-cpu"></i>
|
||||
<span>AI Settings</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/healthplix-settings') ?>','maindiv','HealthPlix Integration');">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
<span>HealthPlix Integration</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/abdm-gateway') ?>','maindiv','ABDM Gateway Config');">
|
||||
<i class="bi bi-hdd-network" style="color:#0d6efd"></i>
|
||||
<span>ABDM Gateway</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/abdm-report-doctors') ?>','maindiv','ABDM Report Doctors');">
|
||||
<i class="bi bi-person-vcard" style="color:#0d6efd"></i>
|
||||
<span>ABDM Report Doctors</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/hospital-profile') ?>','maindiv','Hospital Profile');">
|
||||
<i class="bi bi-building"></i>
|
||||
<span>Hospital Profile</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/insurance') ?>','maindiv','Insurance Master');">
|
||||
<i class="bi bi-shield-check"></i>
|
||||
<span>Insurance Master</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/reffer') ?>','maindiv','Referral Admin');">
|
||||
<i class="bi bi-share"></i>
|
||||
<span>Referral Admin</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/bank') ?>','maindiv','Bank & Payment Sources');">
|
||||
<i class="bi bi-bank"></i>
|
||||
<span>Bank & Payment</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/medical-bank') ?>','maindiv','Medical Bank & Payment Sources');">
|
||||
<i class="bi bi-credit-card-2-front"></i>
|
||||
<span>Medical Bank</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/bed-management') ?>','maindiv','Bed Management');">
|
||||
<i class="bi bi-hospital"></i>
|
||||
<span>Bed Management</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/ipd-examination-fields') ?>','maindiv','IPD Examination Fields');">
|
||||
<i class="bi bi-clipboard2-pulse"></i>
|
||||
<span>IPD Examination Fields</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('Storestock') ?>','maindiv','Hospital Stock');">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>Hospital Stock</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/admin/system-ops') ?>','maindiv','System Operations');">
|
||||
<i class="bi bi-server"></i>
|
||||
<span>System Operations</span>
|
||||
</a>
|
||||
<!-- Tiles Grid View -->
|
||||
<div id="admin_tiles_view">
|
||||
<div class="row g-3 admin-tiles">
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/doctor') ?>','Doctor Master');">
|
||||
<i class="bi bi-person-badge"></i>
|
||||
<span>Doctor Master</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/nurse') ?>','Nursing Staff Master');">
|
||||
<i class="bi bi-person-heart" style="color:#0d6efd"></i>
|
||||
<span>Nursing Staff Master</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/nursing-station') ?>','Nursing Station Master');">
|
||||
<i class="bi bi-hospital" style="color:#0d6efd"></i>
|
||||
<span>Nursing Station Master</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/user-management') ?>','User Management');">
|
||||
<i class="bi bi-people"></i>
|
||||
<span>User Management</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/ai-settings') ?>','AI Settings');">
|
||||
<i class="bi bi-cpu"></i>
|
||||
<span>AI Settings</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/healthplix-settings') ?>','HealthPlix Integration');">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
<span>HealthPlix Integration</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/abdm-gateway') ?>','ABDM Gateway Config');">
|
||||
<i class="bi bi-hdd-network" style="color:#0d6efd"></i>
|
||||
<span>ABDM Gateway</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/abdm-report-doctors') ?>','ABDM Report Doctors');">
|
||||
<i class="bi bi-person-vcard" style="color:#0d6efd"></i>
|
||||
<span>ABDM Report Doctors</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/hospital-profile') ?>','Hospital Profile');">
|
||||
<i class="bi bi-building"></i>
|
||||
<span>Hospital Profile</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/insurance') ?>','Insurance Master');">
|
||||
<i class="bi bi-shield-check"></i>
|
||||
<span>Insurance Master</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/reffer') ?>','Referral Admin');">
|
||||
<i class="bi bi-share"></i>
|
||||
<span>Referral Admin</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/bank') ?>','Bank & Payment Sources');">
|
||||
<i class="bi bi-bank"></i>
|
||||
<span>Bank & Payment</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/medical-bank') ?>','Medical Bank & Payment Sources');">
|
||||
<i class="bi bi-credit-card-2-front"></i>
|
||||
<span>Medical Bank</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/bed-management') ?>','Bed Management');">
|
||||
<i class="bi bi-hospital"></i>
|
||||
<span>Bed Management</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/ipd-examination-fields') ?>','IPD Examination Fields');">
|
||||
<i class="bi bi-clipboard2-pulse"></i>
|
||||
<span>IPD Examination Fields</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('Storestock') ?>','Hospital Stock');">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>Hospital Stock</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/system-ops') ?>','System Operations');">
|
||||
<i class="bi bi-server"></i>
|
||||
<span>System Operations</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row mt-4">
|
||||
<div class="col-12" id="maindiv"></div>
|
||||
|
||||
<!-- Admin Sub-page View Container -->
|
||||
<div id="admin_subpage_view" class="d-none">
|
||||
<div class="card shadow-sm border-0 mb-3">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center py-2">
|
||||
<strong class="text-primary fs-6 mb-0" id="admin_subpage_title"><i class="bi bi-gear me-1"></i> Admin Module</strong>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm py-1 px-3" onclick="showAdminTiles()">
|
||||
<i class="bi bi-arrow-left me-1"></i> Back to Admin Panel
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-3" id="maindiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function openAdminSubPage(url, title) {
|
||||
$('#admin_tiles_view').addClass('d-none');
|
||||
$('#admin_subpage_view').removeClass('d-none');
|
||||
if (title) {
|
||||
$('#admin_subpage_title').html('<i class="bi bi-gear me-1"></i> ' + title);
|
||||
$('#admin_breadcrumb_sub').removeClass('d-none').text(title);
|
||||
}
|
||||
load_form_div(url, 'maindiv', title);
|
||||
}
|
||||
|
||||
function showAdminTiles() {
|
||||
$('#maindiv').empty();
|
||||
$('#admin_subpage_view').addClass('d-none');
|
||||
$('#admin_tiles_view').removeClass('d-none');
|
||||
$('#admin_breadcrumb_sub').addClass('d-none').text('');
|
||||
}
|
||||
|
||||
(function() {
|
||||
var rawLoadFormDiv = window.load_form_div;
|
||||
window.load_form_div = function(ourl, xdiv, top_title) {
|
||||
if (xdiv === 'maindiv' && $('#admin_tiles_view').length > 0) {
|
||||
$('#admin_tiles_view').addClass('d-none');
|
||||
$('#admin_subpage_view').removeClass('d-none');
|
||||
if (top_title) {
|
||||
$('#admin_subpage_title').html('<i class="bi bi-gear me-1"></i> ' + top_title);
|
||||
$('#admin_breadcrumb_sub').removeClass('d-none').text(top_title);
|
||||
}
|
||||
}
|
||||
if (typeof rawLoadFormDiv === 'function') {
|
||||
rawLoadFormDiv(ourl, xdiv, top_title);
|
||||
}
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,15 @@
|
|||
<label class="form-label">Floor</label>
|
||||
<input class="form-control" id="floor_number" name="floor_number" type="number">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Nursing Station</label>
|
||||
<select class="form-select" id="nursing_station_id" name="nursing_station_id">
|
||||
<option value="0">-- Select Nursing Station --</option>
|
||||
<?php foreach (($nursingStations ?? []) as $st) { ?>
|
||||
<option value="<?= (int)$st['id'] ?>"><?= esc($st['station_name']) ?> (<?= esc($st['floor_number'] ?: 'Ground Floor') ?>)</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Capacity</label>
|
||||
<input class="form-control" id="total_capacity" name="total_capacity" type="number" min="0" value="0">
|
||||
|
|
@ -129,6 +138,7 @@
|
|||
<th>Code</th>
|
||||
<th>Name</th>
|
||||
<th>Department</th>
|
||||
<th>Nursing Station</th>
|
||||
<th>Type</th>
|
||||
<th>Capacity</th>
|
||||
<th>Status</th>
|
||||
|
|
@ -143,6 +153,13 @@
|
|||
<td><?= esc($row->ward_code ?? '') ?></td>
|
||||
<td><?= esc($row->ward_name ?? '') ?></td>
|
||||
<td><?= esc($row->department_name ?? 'All Department') ?></td>
|
||||
<td>
|
||||
<?php if (!empty($row->station_name)): ?>
|
||||
<span class="badge bg-primary-subtle text-primary border border-primary-subtle"><i class="bi bi-hospital me-1"></i><?= esc($row->station_name) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc($row->ward_type ?? '') ?></td>
|
||||
<td><?= esc($row->total_capacity ?? 0) ?></td>
|
||||
<td><?= esc($row->status ?? '') ?></td>
|
||||
|
|
@ -180,6 +197,7 @@
|
|||
function clearForm() {
|
||||
form.reset();
|
||||
$('#ward_id').val(0);
|
||||
$('#nursing_station_id').val(0);
|
||||
$('#total_capacity').val(0);
|
||||
clearAlert();
|
||||
}
|
||||
|
|
@ -213,6 +231,7 @@
|
|||
$('#ward_code').val(item.ward_code || '');
|
||||
$('#ward_name').val(item.ward_name || '');
|
||||
$('#department_id').val(item.department_id !== undefined ? item.department_id : 0);
|
||||
$('#nursing_station_id').val(item.nursing_station_id !== undefined && item.nursing_station_id !== null ? item.nursing_station_id : 0);
|
||||
$('#floor_number').val(item.floor_number || '');
|
||||
$('#total_capacity').val(item.total_capacity || 0);
|
||||
$('#ward_type').val(item.ward_type || 'General');
|
||||
|
|
|
|||
309
app/Views/Setting/Nurse/Nurse_Search_V.php
Normal file
309
app/Views/Setting/Nurse/Nurse_Search_V.php
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
<div class="container-fluid p-0">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-0 text-primary"><i class="bi bi-person-heart me-2"></i>Nursing Staff Master</h5>
|
||||
<small class="text-muted">Manage nursing staff records and credentials for hospital workspace</small>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" id="btn_add_nurse" data-bs-toggle="modal" data-bs-target="#nurseMasterModal">
|
||||
<i class="bi bi-plus-lg me-1"></i> Add New Nurse
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 mb-3">
|
||||
<div class="card-body p-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search"></i></span>
|
||||
<input type="text" class="form-control form-control-sm border-start-0" id="nurse_search_input" placeholder="Search by name, ID code, qualification..." value="<?= esc($searchQuery ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_refresh_nurse_list">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i> Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0" id="nurse_table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 50px;" class="ps-3">#</th>
|
||||
<th style="width: 120px;">Staff ID</th>
|
||||
<th>Full Name</th>
|
||||
<th style="width: 150px;">ABDM HPR ID</th>
|
||||
<th style="width: 140px;">Council Reg No.</th>
|
||||
<th style="width: 120px;">Designation</th>
|
||||
<th>Qualification</th>
|
||||
<th>Contact / Mobile</th>
|
||||
<th style="width: 90px;">Status</th>
|
||||
<th style="width: 110px;" class="text-end pe-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="nurse_table_body">
|
||||
<?php if (empty($nurses)): ?>
|
||||
<tr>
|
||||
<td colspan="10" class="text-center py-4 text-muted">
|
||||
<i class="bi bi-person-x fs-3 d-block mb-1"></i>
|
||||
No nursing staff records found. Click <strong>"Add New Nurse"</strong> to create one.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($nurses as $idx => $nurse): ?>
|
||||
<tr id="nurse_row_<?= (int)$nurse['id'] ?>" data-nurse='<?= json_encode($nurse, JSON_HEX_APOS | JSON_HEX_QUOT) ?>'>
|
||||
<td class="ps-3 fw-bold text-secondary"><?= $idx + 1 ?></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= esc($nurse['nurse_code']) ?></span></td>
|
||||
<td class="fw-semibold text-primary">
|
||||
<?= esc($nurse['name']) ?>
|
||||
<?php if (!empty($nurse['gender'])): ?>
|
||||
<small class="text-muted"> (<?= esc($nurse['gender']) ?>)</small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($nurse['hpr_id'])): ?>
|
||||
<span class="badge bg-primary-subtle text-primary border border-primary-subtle"><i class="bi bi-shield-check me-1"></i><?= esc($nurse['hpr_id']) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">-</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><code class="text-dark"><?= esc($nurse['registration_no'] ?: '-') ?></code></td>
|
||||
<td><span class="badge bg-secondary-subtle text-dark border"><?= esc($nurse['designation'] ?: 'Staff Nurse') ?></span></td>
|
||||
<td><?= esc($nurse['qualification'] ?: '-') ?></td>
|
||||
<td><?= esc($nurse['contact_no'] ?: '-') ?></td>
|
||||
<td>
|
||||
<?php if ((int)$nurse['is_active'] === 1): ?>
|
||||
<span class="badge bg-success">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm py-0 px-2 btn-edit-nurse" data-id="<?= (int)$nurse['id'] ?>">
|
||||
<i class="bi bi-pencil-square"></i> Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-2 btn-delete-nurse" data-id="<?= (int)$nurse['id'] ?>" data-name="<?= esc($nurse['name']) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for Add/Edit Nurse -->
|
||||
<div class="modal fade" id="nurseMasterModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-light py-2">
|
||||
<h5 class="modal-title text-primary fs-6" id="nurseModalTitle"><i class="bi bi-person-plus me-1"></i> Add Nursing Staff</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="nurseMasterForm">
|
||||
<input type="hidden" id="nurse_id" name="id" value="0">
|
||||
<div class="modal-body">
|
||||
<div id="nurse_form_status" class="alert alert-danger d-none py-2 px-3 mb-3"></div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold small mb-1">Staff Code / ID <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_code" name="nurse_code" placeholder="e.g. NUR-001" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold small mb-1">Designation</label>
|
||||
<select class="form-select form-select-sm" id="nurse_designation" name="designation">
|
||||
<option value="Staff Nurse">Staff Nurse</option>
|
||||
<option value="Nursing Supervisor">Nursing Supervisor</option>
|
||||
<option value="Head Nurse">Head Nurse</option>
|
||||
<option value="Sister Incharge">Sister Incharge</option>
|
||||
<option value="ANM">ANM (Auxiliary Nurse Midwife)</option>
|
||||
<option value="GNM">GNM (General Nurse & Midwife)</option>
|
||||
<option value="Nurse Practitioner">Nurse Practitioner</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold small mb-1">Status</label>
|
||||
<select class="form-select form-select-sm" id="nurse_is_active" name="is_active">
|
||||
<option value="1">Active</option>
|
||||
<option value="0">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<label class="form-label fw-bold small mb-1">Full Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_name" name="name" placeholder="Full name of nurse" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold small mb-1">Gender</label>
|
||||
<select class="form-select form-select-sm" id="nurse_gender" name="gender">
|
||||
<option value="Female">Female</option>
|
||||
<option value="Male">Male</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- ABDM / HPR & Registration Section -->
|
||||
<div class="col-12 mt-2">
|
||||
<div class="p-2 bg-light rounded border">
|
||||
<div class="fw-bold small text-primary mb-1"><i class="bi bi-shield-check me-1"></i> ABDM & Council Credentials</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small mb-1">ABDM HPR ID / Address</label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_hpr_id" name="hpr_id" placeholder="e.g. 12-3456-7890-1234 or nurse@hprid">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small mb-1">Nursing Council Registration No.</label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_registration_no" name="registration_no" placeholder="e.g. NC-98765/2023">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mt-2">
|
||||
<label class="form-label fw-bold small mb-1">Qualification</label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_qualification" name="qualification" placeholder="e.g. B.Sc Nursing, M.Sc Nursing, GNM">
|
||||
</div>
|
||||
<div class="col-md-6 mt-2">
|
||||
<label class="form-label fw-bold small mb-1">Department</label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_department" name="department" value="Nursing" placeholder="Department">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold small mb-1">Contact No</label>
|
||||
<input type="text" class="form-control form-control-sm" id="nurse_contact_no" name="contact_no" placeholder="Phone number">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold small mb-1">Email Address</label>
|
||||
<input type="email" class="form-control form-control-sm" id="nurse_email" name="email" placeholder="Email address">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light py-2">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm" id="btn_save_nurse">
|
||||
<i class="bi bi-check-lg me-1"></i> Save Nurse
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var saveUrl = '<?= base_url('setting/admin/nurse/save') ?>';
|
||||
var deleteUrlBase = '<?= base_url('setting/admin/nurse/delete') ?>';
|
||||
var csrfTokenName = '<?= csrf_token() ?>';
|
||||
|
||||
function resetNurseForm() {
|
||||
$('#nurse_id').val('0');
|
||||
$('#nurse_code').val('NUR-' + Math.floor(100 + Math.random() * 900));
|
||||
$('#nurse_name').val('');
|
||||
$('#nurse_hpr_id').val('');
|
||||
$('#nurse_registration_no').val('');
|
||||
$('#nurse_gender').val('Female');
|
||||
$('#nurse_designation').val('Staff Nurse');
|
||||
$('#nurse_qualification').val('');
|
||||
$('#nurse_contact_no').val('');
|
||||
$('#nurse_email').val('');
|
||||
$('#nurse_department').val('Nursing');
|
||||
$('#nurse_is_active').val('1');
|
||||
$('#nurse_form_status').addClass('d-none').text('');
|
||||
$('#nurseModalTitle').html('<i class="bi bi-person-plus me-1"></i> Add Nursing Staff');
|
||||
}
|
||||
|
||||
$('#btn_add_nurse').on('click', function() {
|
||||
resetNurseForm();
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-edit-nurse', function() {
|
||||
var id = $(this).data('id');
|
||||
var row = $('#nurse_row_' + id);
|
||||
if (!row.length) return;
|
||||
var data = row.data('nurse');
|
||||
if (!data) return;
|
||||
|
||||
$('#nurse_id').val(data.id || '0');
|
||||
$('#nurse_code').val(data.nurse_code || '');
|
||||
$('#nurse_name').val(data.name || '');
|
||||
$('#nurse_hpr_id').val(data.hpr_id || '');
|
||||
$('#nurse_registration_no').val(data.registration_no || '');
|
||||
$('#nurse_gender').val(data.gender || 'Female');
|
||||
$('#nurse_designation').val(data.designation || 'Staff Nurse');
|
||||
$('#nurse_qualification').val(data.qualification || '');
|
||||
$('#nurse_contact_no').val(data.contact_no || '');
|
||||
$('#nurse_email').val(data.email || '');
|
||||
$('#nurse_department').val(data.department || 'Nursing');
|
||||
$('#nurse_is_active').val(data.is_active !== undefined ? data.is_active : '1');
|
||||
$('#nurse_form_status').addClass('d-none').text('');
|
||||
$('#nurseModalTitle').html('<i class="bi bi-pencil-square me-1"></i> Edit Nursing Staff');
|
||||
|
||||
var modalEl = document.getElementById('nurseMasterModal');
|
||||
if (window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
} else {
|
||||
$(modalEl).modal('show');
|
||||
}
|
||||
});
|
||||
|
||||
$('#nurseMasterForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var $status = $('#nurse_form_status');
|
||||
$status.addClass('d-none').text('');
|
||||
|
||||
var formData = $(this).serialize();
|
||||
$.post(saveUrl, formData, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
var modalEl = document.getElementById('nurseMasterModal');
|
||||
if (window.bootstrap && window.bootstrap.Modal) {
|
||||
var modal = window.bootstrap.Modal.getInstance(modalEl);
|
||||
if (modal) modal.hide();
|
||||
} else {
|
||||
$(modalEl).modal('hide');
|
||||
}
|
||||
load_form_div('<?= base_url('setting/admin/nurse') ?>', 'maindiv', 'Nursing Staff Master');
|
||||
} else {
|
||||
$status.removeClass('d-none').text((resp && resp.error) ? resp.error : 'Failed to save nurse.');
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
$status.removeClass('d-none').text('Server error while saving nurse record.');
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-delete-nurse', function() {
|
||||
var id = $(this).data('id');
|
||||
var name = $(this).data('name');
|
||||
if (!confirm('Are you sure you want to delete nurse: ' + name + '?')) return;
|
||||
|
||||
$.post(deleteUrlBase + '/' + id, {}, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
load_form_div('<?= base_url('setting/admin/nurse') ?>', 'maindiv', 'Nursing Staff Master');
|
||||
} else {
|
||||
alert((resp && resp.error) ? resp.error : 'Failed to delete nurse.');
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
alert('Failed to delete nurse record.');
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_refresh_nurse_list').on('click', function() {
|
||||
load_form_div('<?= base_url('setting/admin/nurse') ?>', 'maindiv', 'Nursing Staff Master');
|
||||
});
|
||||
|
||||
$('#nurse_search_input').on('keyup', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
var q = encodeURIComponent($(this).val());
|
||||
load_form_div('<?= base_url('setting/admin/nurse') ?>?q=' + q, 'maindiv', 'Nursing Staff Master');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
279
app/Views/Setting/NursingStation/nursing_station_index.php
Normal file
279
app/Views/Setting/NursingStation/nursing_station_index.php
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
<div class="container-fluid p-0">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="text-primary mb-0"><i class="bi bi-hospital me-1"></i> Nursing Station Master</h5>
|
||||
<small class="text-muted">Manage hospital nursing stations and assign in-charge staff & floors</small>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" id="btn_add_station">
|
||||
<i class="bi bi-plus-lg me-1"></i> Add Nursing Station
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search & Filter Card -->
|
||||
<div class="card shadow-sm border-0 mb-3">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-md-9">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search text-muted"></i></span>
|
||||
<input type="text" class="form-control border-start-0" id="station_search_input" placeholder="Search by station name, code, floor, or nurse name..." value="<?= esc($searchQuery ?? '') ?>">
|
||||
<button class="btn btn-primary" type="button" id="btn_station_search">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 text-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_refresh_station_list">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i> Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stations Table -->
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0" id="station_table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 50px;" class="ps-3">#</th>
|
||||
<th style="width: 130px;">Station Code</th>
|
||||
<th>Station Name</th>
|
||||
<th style="width: 140px;">Floor / Location</th>
|
||||
<th>In-charge Nurse</th>
|
||||
<th>Contact No</th>
|
||||
<th style="width: 90px;">Status</th>
|
||||
<th style="width: 110px;" class="text-end pe-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="station_table_body">
|
||||
<?php if (empty($stations)): ?>
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-4 text-muted">
|
||||
<i class="bi bi-hospital fs-3 d-block mb-1"></i>
|
||||
No nursing stations found. Click <strong>"Add Nursing Station"</strong> to create one.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($stations as $idx => $st): ?>
|
||||
<tr id="station_row_<?= (int)$st['id'] ?>" data-station='<?= json_encode($st, JSON_HEX_APOS | JSON_HEX_QUOT) ?>'>
|
||||
<td class="ps-3 fw-bold text-secondary"><?= $idx + 1 ?></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= esc($st['station_code']) ?></span></td>
|
||||
<td class="fw-semibold text-primary"><?= esc($st['station_name']) ?></td>
|
||||
<td><span class="badge bg-info-subtle text-info border border-info-subtle"><?= esc($st['floor_number'] ?: 'Ground Floor') ?></span></td>
|
||||
<td>
|
||||
<?php if (!empty($st['incharge_nurse_name'])): ?>
|
||||
<span class="text-dark"><i class="bi bi-person-heart text-danger me-1"></i><?= esc($st['incharge_nurse_name']) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">- Not Assigned -</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc($st['contact_no'] ?: '-') ?></td>
|
||||
<td>
|
||||
<?php if ($st['status'] === 'active'): ?>
|
||||
<span class="badge bg-success">Active</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm py-0 px-2 btn-edit-station" data-id="<?= (int)$st['id'] ?>">
|
||||
<i class="bi bi-pencil-square"></i> Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-2 btn-delete-station" data-id="<?= (int)$st['id'] ?>" data-name="<?= esc($st['station_name']) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for Add/Edit Nursing Station -->
|
||||
<div class="modal fade" id="nursingStationModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-light py-2">
|
||||
<h5 class="modal-title text-primary fs-6" id="stationModalTitle"><i class="bi bi-plus-lg me-1"></i> Add Nursing Station</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="nursingStationForm">
|
||||
<input type="hidden" id="station_id" name="id" value="0">
|
||||
<div class="modal-body">
|
||||
<div id="station_form_status" class="alert alert-danger d-none py-2 px-3 mb-3"></div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold small mb-1">Station Code <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control form-control-sm" id="station_code" name="station_code" placeholder="e.g. NS-101" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold small mb-1">Status</label>
|
||||
<select class="form-select form-select-sm" id="station_status" name="status">
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold small mb-1">Station Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control form-control-sm" id="station_name" name="station_name" placeholder="e.g. 2nd Floor Wards Nursing Station" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold small mb-1">Floor / Location</label>
|
||||
<select class="form-select form-select-sm" id="station_floor_number" name="floor_number">
|
||||
<option value="Ground Floor">Ground Floor</option>
|
||||
<option value="1st Floor">1st Floor</option>
|
||||
<option value="2nd Floor">2nd Floor</option>
|
||||
<option value="3rd Floor">3rd Floor</option>
|
||||
<option value="4th Floor">4th Floor</option>
|
||||
<option value="ICU Block">ICU Block</option>
|
||||
<option value="OT Block">OT Block</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold small mb-1">Contact No</label>
|
||||
<input type="text" class="form-control form-control-sm" id="station_contact_no" name="contact_no" placeholder="Extension / Phone">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold small mb-1">In-charge Nursing Staff</label>
|
||||
<select class="form-select form-select-sm" id="station_incharge_nurse_id" name="incharge_nurse_id">
|
||||
<option value="0">-- Select In-charge Nurse --</option>
|
||||
<?php if (!empty($nurses)): ?>
|
||||
<?php foreach ($nurses as $nurse): ?>
|
||||
<option value="<?= (int)$nurse['id'] ?>"><?= esc(($nurse['nurse_code'] ? '[' . $nurse['nurse_code'] . '] ' : '') . $nurse['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold small mb-1">Remarks / Location Note</label>
|
||||
<textarea class="form-control form-control-sm" id="station_remarks" name="remarks" rows="2" placeholder="Optional notes"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light py-2">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm" id="btn_save_station">
|
||||
<i class="bi bi-check-lg me-1"></i> Save Station
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var saveUrl = '<?= base_url('setting/admin/nursing-station/save') ?>';
|
||||
var deleteUrlBase = '<?= base_url('setting/admin/nursing-station/delete') ?>';
|
||||
|
||||
function resetStationForm() {
|
||||
$('#station_id').val('0');
|
||||
$('#station_code').val('NS-' + Math.floor(100 + Math.random() * 900));
|
||||
$('#station_name').val('');
|
||||
$('#station_floor_number').val('2nd Floor');
|
||||
$('#station_incharge_nurse_id').val('0');
|
||||
$('#station_contact_no').val('');
|
||||
$('#station_status').val('active');
|
||||
$('#station_remarks').val('');
|
||||
$('#station_form_status').addClass('d-none').text('');
|
||||
$('#stationModalTitle').html('<i class="bi bi-plus-lg me-1"></i> Add Nursing Station');
|
||||
}
|
||||
|
||||
$('#btn_add_station').on('click', function() {
|
||||
resetStationForm();
|
||||
var modalEl = document.getElementById('nursingStationModal');
|
||||
if (window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
} else {
|
||||
$(modalEl).modal('show');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-edit-station', function() {
|
||||
var id = $(this).data('id');
|
||||
var row = $('#station_row_' + id);
|
||||
if (!row.length) return;
|
||||
var data = row.data('station');
|
||||
if (!data) return;
|
||||
|
||||
$('#station_id').val(data.id || '0');
|
||||
$('#station_code').val(data.station_code || '');
|
||||
$('#station_name').val(data.station_name || '');
|
||||
$('#station_floor_number').val(data.floor_number || 'Ground Floor');
|
||||
$('#station_incharge_nurse_id').val(data.incharge_nurse_id || '0');
|
||||
$('#station_contact_no').val(data.contact_no || '');
|
||||
$('#station_status').val(data.status || 'active');
|
||||
$('#station_remarks').val(data.remarks || '');
|
||||
$('#station_form_status').addClass('d-none').text('');
|
||||
$('#stationModalTitle').html('<i class="bi bi-pencil-square me-1"></i> Edit Nursing Station');
|
||||
|
||||
var modalEl = document.getElementById('nursingStationModal');
|
||||
if (window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
} else {
|
||||
$(modalEl).modal('show');
|
||||
}
|
||||
});
|
||||
|
||||
$('#nursingStationForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var $status = $('#station_form_status');
|
||||
$status.addClass('d-none').text('');
|
||||
|
||||
var formData = $(this).serialize();
|
||||
$.post(saveUrl, formData, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
var modalEl = document.getElementById('nursingStationModal');
|
||||
if (window.bootstrap && window.bootstrap.Modal) {
|
||||
var modal = window.bootstrap.Modal.getInstance(modalEl);
|
||||
if (modal) modal.hide();
|
||||
} else {
|
||||
$(modalEl).modal('hide');
|
||||
}
|
||||
load_form_div('<?= base_url('setting/admin/nursing-station') ?>', 'maindiv', 'Nursing Station Master');
|
||||
} else {
|
||||
$status.removeClass('d-none').text((resp && resp.error) ? resp.error : 'Failed to save station.');
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
$status.removeClass('d-none').text('Server error while saving station record.');
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-delete-station', function() {
|
||||
var id = $(this).data('id');
|
||||
var name = $(this).data('name');
|
||||
if (!confirm('Are you sure you want to delete nursing station "' + name + '"?')) return;
|
||||
|
||||
$.post(deleteUrlBase, { id: id, '<?= csrf_token() ?>': '<?= csrf_hash() ?>' }, function(resp) {
|
||||
if (resp && resp.ok) {
|
||||
load_form_div('<?= base_url('setting/admin/nursing-station') ?>', 'maindiv', 'Nursing Station Master');
|
||||
} else {
|
||||
alert((resp && resp.error) ? resp.error : 'Failed to delete station.');
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
alert('Server error while deleting station.');
|
||||
});
|
||||
});
|
||||
|
||||
$('#btn_station_search').on('click', function() {
|
||||
var q = $('#station_search_input').val();
|
||||
load_form_div('<?= base_url('setting/admin/nursing-station') ?>?q=' + encodeURIComponent(q), 'maindiv', 'Nursing Station Master');
|
||||
});
|
||||
|
||||
$('#station_search_input').on('keyup', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
$('#btn_station_search').click();
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn_refresh_station_list').on('click', function() {
|
||||
load_form_div('<?= base_url('setting/admin/nursing-station') ?>', 'maindiv', 'Nursing Station Master');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1047,9 +1047,12 @@
|
|||
<div class="mb-3" id="rx_sec_diagnosis">
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<h6 class="mb-0">Diagnosis</h6>
|
||||
<div class="d-flex gap-1">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="btn_opd_manage_diagnosis_master">Master CRUD</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary btn-save-autotype-keyword"
|
||||
data-section="diagnosis" data-target="diagnosis"
|
||||
title="Save as custom keyword">+keyword</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Healthplix-style inline diagnosis table -->
|
||||
|
|
@ -9707,10 +9710,6 @@
|
|||
return (row && row.finding_examinations) ? row.finding_examinations : '';
|
||||
});
|
||||
|
||||
if (localStorage.getItem(draftKey)) {
|
||||
setStatus('dirty', 'Draft available');
|
||||
}
|
||||
|
||||
loadAdviceList();
|
||||
loadRecentEntryChips();
|
||||
loadInvestigationList();
|
||||
|
|
@ -9718,5 +9717,263 @@
|
|||
loadOldPrescribedPanel();
|
||||
loadRxGroupCatalog();
|
||||
updateScanBanner();
|
||||
|
||||
/* ══ OPD Final Diagnosis Master CRUD Modal Handler ══════════════════ */
|
||||
var opdDiagMasterRows = [];
|
||||
var _opdDiagMasterNameTimer = null;
|
||||
var _opdDiagMasterNameXhr = null;
|
||||
var _opdDiagMasterNameCache = {};
|
||||
var _opdDiagMasterNameDdIdx = -1;
|
||||
|
||||
function closeOpdDiagMasterNameDd() {
|
||||
if (_opdDiagMasterNameTimer) { clearTimeout(_opdDiagMasterNameTimer); _opdDiagMasterNameTimer = null; }
|
||||
if (_opdDiagMasterNameXhr) { try { _opdDiagMasterNameXhr.abort(); } catch(e){} _opdDiagMasterNameXhr = null; }
|
||||
$('#opd_diagnosis_master_name_dropdown').hide().empty();
|
||||
_opdDiagMasterNameDdIdx = -1;
|
||||
}
|
||||
|
||||
function openOpdDiagMasterNameDd(rows) {
|
||||
var $dd = $('#opd_diagnosis_master_name_dropdown');
|
||||
if (!rows || !rows.length) { $dd.hide().empty(); return; }
|
||||
|
||||
var html = '';
|
||||
rows.forEach(function(item, idx) {
|
||||
var name = item.name || item.term || '';
|
||||
var snomedId = item.snomed_concept_id || item.concept_id || '';
|
||||
var source = item.source || 'snomed';
|
||||
var badgeClass = (source === 'disease_master' || source === 'local') ? 'bg-primary' : 'bg-info text-dark';
|
||||
var badgeText = snomedId ? ('SNOMED: ' + snomedId) : (source === 'disease_master' ? 'Master' : 'Local');
|
||||
|
||||
html += '<div class="dropdown-item py-1 px-2 opd-diag-master-dd-item" data-idx="' + idx + '" style="cursor:pointer; font-size:13px;">'
|
||||
+ '<div class="d-flex justify-content-between align-items-center">'
|
||||
+ '<span>' + $('<div>').text(name).html() + '</span>'
|
||||
+ '<span class="badge ' + badgeClass + ' ms-2" style="font-size:10px;">' + $('<div>').text(badgeText).html() + '</span>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
});
|
||||
|
||||
$dd.html(html).show();
|
||||
_opdDiagMasterNameDdIdx = -1;
|
||||
|
||||
$dd.find('.opd-diag-master-dd-item').on('click', function() {
|
||||
var idx = parseInt($(this).data('idx'), 10);
|
||||
if (rows[idx]) {
|
||||
var sel = rows[idx];
|
||||
var selName = sel.name || sel.term || '';
|
||||
var selSnomedId = sel.snomed_concept_id || sel.concept_id || '';
|
||||
var selSnomedTerm = sel.snomed_term || sel.fsn || selName;
|
||||
|
||||
$('#opd_diagnosis_master_name').val(selName);
|
||||
$('#opd_diagnosis_master_snomed_id').val(selSnomedId);
|
||||
$('#opd_diagnosis_master_snomed_term').val(selSnomedTerm);
|
||||
$('#opd_diagnosis_master_status').text(selSnomedId ? ('SNOMED concept ' + selSnomedId + ' selected.') : 'Diagnosis selected.').css('color', '#0f5132');
|
||||
}
|
||||
closeOpdDiagMasterNameDd();
|
||||
});
|
||||
}
|
||||
|
||||
function clearOpdDiagMasterForm() {
|
||||
closeOpdDiagMasterNameDd();
|
||||
$('#opd_diagnosis_master_code').val('0');
|
||||
$('#opd_diagnosis_master_name').val('');
|
||||
$('#opd_diagnosis_master_snomed_id').val('');
|
||||
$('#opd_diagnosis_master_snomed_term').val('');
|
||||
$('#opd_diagnosis_master_active').val('1');
|
||||
}
|
||||
|
||||
function renderOpdDiagMasterRows() {
|
||||
var $tbody = $('#opd_diagnosis_master_rows');
|
||||
if (!opdDiagMasterRows.length) {
|
||||
$tbody.html('<tr><td colspan="4" class="text-center text-muted">No records.</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
opdDiagMasterRows.forEach(function(row) {
|
||||
var code = parseInt(row.Code || '0', 10);
|
||||
var active = typeof row.is_active === 'undefined' || parseInt(row.is_active || '0', 10) === 1;
|
||||
html += '<tr>'
|
||||
+ '<td>' + $('<div>').text(row.Name || '').html() + '</td>'
|
||||
+ '<td>' + $('<div>').text(row.snomed_concept_id || '').html() + '</td>'
|
||||
+ '<td>' + (active ? 'Active' : 'Inactive') + '</td>'
|
||||
+ '<td><button type="button" class="btn btn-outline-primary btn-sm py-0 px-1 btn-opd-diag-master-edit" data-code="' + code + '">Edit</button> '
|
||||
+ (active ? '<button type="button" class="btn btn-outline-danger btn-sm py-0 px-1 btn-opd-diag-master-remove" data-code="' + code + '">Deactivate</button>' : '')
|
||||
+ '</td></tr>';
|
||||
});
|
||||
$tbody.html(html);
|
||||
}
|
||||
|
||||
function fetchOpdDiagMasterRows() {
|
||||
var filterVal = ($('#opd_diagnosis_master_search').val() || '').toString().trim();
|
||||
$.get('<?= base_url('Opd_prescription/disease_master_data') ?>', { start: 0, length: 100, filter: filterVal }, function(data) {
|
||||
opdDiagMasterRows = (data && data.data) ? data.data : [];
|
||||
renderOpdDiagMasterRows();
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
$(document).on('click', '#btn_opd_manage_diagnosis_master', function() {
|
||||
clearOpdDiagMasterForm();
|
||||
$('#opd_diagnosis_master_search').val('');
|
||||
$('#opd_diagnosis_master_status').text('');
|
||||
fetchOpdDiagMasterRows();
|
||||
var modalEl = document.getElementById('opdDiagnosisMasterModal');
|
||||
if (modalEl) {
|
||||
var modalObj = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl);
|
||||
modalObj.show();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-opd-diag-master-edit', function() {
|
||||
var code = parseInt($(this).data('code') || '0', 10);
|
||||
var row = opdDiagMasterRows.find(function(item) { return parseInt(item.Code || '0', 10) === code; });
|
||||
if (!row) return;
|
||||
$('#opd_diagnosis_master_code').val(String(code));
|
||||
$('#opd_diagnosis_master_name').val(row.Name || '');
|
||||
$('#opd_diagnosis_master_snomed_id').val(row.snomed_concept_id || '');
|
||||
$('#opd_diagnosis_master_snomed_term').val(row.snomed_term || '');
|
||||
$('#opd_diagnosis_master_active').val(typeof row.is_active === 'undefined' || parseInt(row.is_active || '0', 10) === 1 ? '1' : '0');
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-opd-diag-master-remove', function() {
|
||||
var code = parseInt($(this).data('code') || '0', 10);
|
||||
if (code <= 0 || !window.confirm('Deactivate this diagnosis?')) return;
|
||||
var csrfName = '<?= csrf_token() ?>';
|
||||
var csrfHash = '<?= csrf_hash() ?>';
|
||||
var payload = {};
|
||||
payload[csrfName] = csrfHash;
|
||||
$.post('<?= base_url('Opd_prescription/disease_master_remove') ?>/' + code, payload, function(data) {
|
||||
$('#opd_diagnosis_master_status').text((data && data.error_text) ? data.error_text : 'Diagnosis deactivated.').css('color', '#0f5132');
|
||||
fetchOpdDiagMasterRows();
|
||||
}, 'json');
|
||||
});
|
||||
|
||||
$('#btn_opd_diagnosis_master_refresh').on('click', fetchOpdDiagMasterRows);
|
||||
$('#opd_diagnosis_master_search').on('input', fetchOpdDiagMasterRows);
|
||||
$('#btn_opd_diagnosis_master_clear').on('click', clearOpdDiagMasterForm);
|
||||
$('#btn_opd_diagnosis_master_save').on('click', function() {
|
||||
var name = ($('#opd_diagnosis_master_name').val() || '').toString().trim();
|
||||
if (name === '') {
|
||||
$('#opd_diagnosis_master_status').text('Name is required.').css('color', '#842029');
|
||||
return;
|
||||
}
|
||||
var csrfName = '<?= csrf_token() ?>';
|
||||
var csrfHash = '<?= csrf_hash() ?>';
|
||||
var payload = {
|
||||
Code: parseInt($('#opd_diagnosis_master_code').val() || '0', 10),
|
||||
Name: name,
|
||||
snomed_concept_id: ($('#opd_diagnosis_master_snomed_id').val() || '').toString().trim(),
|
||||
snomed_term: ($('#opd_diagnosis_master_snomed_term').val() || '').toString().trim(),
|
||||
is_active: parseInt($('#opd_diagnosis_master_active').val() || '1', 10)
|
||||
};
|
||||
payload[csrfName] = csrfHash;
|
||||
$.post('<?= base_url('Opd_prescription/disease_master_save') ?>', payload, function(data) {
|
||||
if (!data || parseInt(data.update || '0', 10) !== 1) {
|
||||
$('#opd_diagnosis_master_status').text((data && data.error_text) ? data.error_text : 'Unable to save diagnosis.').css('color', '#842029');
|
||||
return;
|
||||
}
|
||||
$('#opd_diagnosis_master_status').text(data.error_text || 'Diagnosis saved.').css('color', '#0f5132');
|
||||
clearOpdDiagMasterForm();
|
||||
fetchOpdDiagMasterRows();
|
||||
}, 'json').fail(function() {
|
||||
$('#opd_diagnosis_master_status').text('Unable to save diagnosis.').css('color', '#842029');
|
||||
});
|
||||
});
|
||||
|
||||
$('#opd_diagnosis_master_name').on('input.opdDiagSrch', function() {
|
||||
var q = (this.value || '').trim();
|
||||
if (_opdDiagMasterNameTimer) clearTimeout(_opdDiagMasterNameTimer);
|
||||
if (_opdDiagMasterNameXhr) { try { _opdDiagMasterNameXhr.abort(); } catch(e){} _opdDiagMasterNameXhr = null; }
|
||||
if (q.length < 1) { closeOpdDiagMasterNameDd(); return; }
|
||||
var cacheKey = q.toUpperCase();
|
||||
if (_opdDiagMasterNameCache[cacheKey]) { openOpdDiagMasterNameDd(_opdDiagMasterNameCache[cacheKey]); return; }
|
||||
_opdDiagMasterNameTimer = setTimeout(function() {
|
||||
_opdDiagMasterNameXhr = $.ajax({
|
||||
url: '<?= base_url('Opd_prescription/provisional_diagnosis_search') ?>',
|
||||
data: { q: q },
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
_opdDiagMasterNameXhr = null;
|
||||
var rows = (data && data.rows) ? data.rows : [];
|
||||
_opdDiagMasterNameCache[cacheKey] = rows;
|
||||
openOpdDiagMasterNameDd(rows);
|
||||
},
|
||||
error: function() { _opdDiagMasterNameXhr = null; }
|
||||
});
|
||||
}, 250);
|
||||
}).on('keydown.opdDiagSrch', function(e) {
|
||||
var $dd = $('#opd_diagnosis_master_name_dropdown');
|
||||
var $items = $dd.find('.opd-diag-master-dd-item');
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
if (!$dd.is(':visible') || !$items.length) return;
|
||||
e.preventDefault();
|
||||
_opdDiagMasterNameDdIdx = e.key === 'ArrowDown'
|
||||
? Math.min(_opdDiagMasterNameDdIdx + 1, $items.length - 1)
|
||||
: Math.max(_opdDiagMasterNameDdIdx - 1, 0);
|
||||
$items.css('background', '').eq(_opdDiagMasterNameDdIdx).css('background', '#f0f4ff');
|
||||
} else if (e.key === 'Enter') {
|
||||
if ($dd.is(':visible') && _opdDiagMasterNameDdIdx >= 0 && _opdDiagMasterNameDdIdx < $items.length) {
|
||||
e.preventDefault();
|
||||
$items.eq(_opdDiagMasterNameDdIdx).trigger('click');
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
closeOpdDiagMasterNameDd();
|
||||
}
|
||||
}).on('blur.opdDiagSrch', function() {
|
||||
setTimeout(function() { closeOpdDiagMasterNameDd(); }, 200);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Final Diagnosis Master Modal for OPD Consult -->
|
||||
<div class="modal fade" id="opdDiagnosisMasterModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header py-2">
|
||||
<h5 class="modal-title fs-6 fw-bold">Final Diagnosis Master</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-sm" id="opd_diagnosis_master_search" placeholder="Search diagnosis or SNOMED term">
|
||||
</div>
|
||||
<div class="col-md-3 d-grid">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_opd_diagnosis_master_refresh">Refresh List</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height:240px;overflow-y:auto;">
|
||||
<table class="table table-sm table-bordered align-middle mb-0">
|
||||
<thead class="table-light"><tr><th>Name</th><th>SNOMED ID</th><th>Status</th><th style="width:140px;">Action</th></tr></thead>
|
||||
<tbody id="opd_diagnosis_master_rows"><tr><td colspan="4" class="text-center text-muted">No records.</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr class="my-2">
|
||||
<input type="hidden" id="opd_diagnosis_master_code" value="0">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6 position-relative">
|
||||
<label class="form-label mb-1 small fw-semibold">Name <small class="text-muted">(Type to search SNOMED)</small></label>
|
||||
<input type="text" class="form-control form-control-sm" id="opd_diagnosis_master_name" autocomplete="off" placeholder="Search diagnosis or SNOMED term">
|
||||
<div id="opd_diagnosis_master_name_dropdown" class="dropdown-menu shadow-sm w-100" style="display:none; position:absolute; top:100%; left:0; z-index:1060; max-height:200px; overflow-y:auto;"></div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1 small fw-semibold">SNOMED ID</label>
|
||||
<input type="text" class="form-control form-control-sm" id="opd_diagnosis_master_snomed_id">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1 small fw-semibold">Status</label>
|
||||
<select class="form-select form-select-sm" id="opd_diagnosis_master_active"><option value="1">Active</option><option value="0">Inactive</option></select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1 small fw-semibold">SNOMED Term</label>
|
||||
<input type="text" class="form-control form-control-sm" id="opd_diagnosis_master_snomed_term">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="btn_opd_diagnosis_master_save">Save</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_opd_diagnosis_master_clear">New</button>
|
||||
</div>
|
||||
<div id="opd_diagnosis_master_status" class="small text-muted mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,47 +1,360 @@
|
|||
<section class="content">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Admitted IPD Patient List (Nursing)</h5>
|
||||
<div class="pagetitle mb-3 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="mb-0 text-primary fs-4"><i class="bi bi-hospital me-2"></i>Admitted IPD Patient List & Nursing Map</h1>
|
||||
<small class="text-muted">Real-time bed management, nursing station assignments, and patient workspaces</small>
|
||||
</div>
|
||||
<div class="card-body table-responsive">
|
||||
<table class="table table-striped table-bordered" id="ipdNursingListTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IPD Code</th>
|
||||
<th>UHID</th>
|
||||
<th>Patient</th>
|
||||
<th>Bed</th>
|
||||
<th>Doctor</th>
|
||||
<th>Admit Date</th>
|
||||
<th>Days</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach (($records ?? []) as $row) : ?>
|
||||
<tr>
|
||||
<td><?= esc($row->ipd_code ?? '') ?></td>
|
||||
<td><?= esc($row->p_code ?? '') ?></td>
|
||||
<td><?= esc(trim((string) (($row->p_fname ?? '') . ' ' . ($row->p_rname ?? '')))) ?></td>
|
||||
<td><?= esc($row->Bed_Desc ?? '') ?></td>
|
||||
<td><?= esc($row->doc_name ?? '') ?></td>
|
||||
<td><?= esc($row->str_register_date ?? '') ?></td>
|
||||
<td><?= esc($row->no_days ?? '') ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="load_form('<?= base_url('ipd/patient/workspace/' . (int) ($row->id ?? 0)) ?>','Nursing Workspace');">Open</button>
|
||||
</td>
|
||||
</tr>
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="View Switch">
|
||||
<button type="button" class="btn btn-primary active" id="btn_view_grid" onclick="switchNursingView('grid')">
|
||||
<i class="bi bi-grid-3x3-gap-fill me-1"></i> Visual Bed Grid
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="btn_view_table" onclick="switchNursingView('table')">
|
||||
<i class="bi bi-table me-1"></i> Table View
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top KPI Stats Row (Matching Image 2 Style) -->
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-6 col-sm-4 col-md-2">
|
||||
<div class="card border-0 shadow-sm text-center py-2 bg-dark text-white rounded-3">
|
||||
<div class="fs-4 fw-bold mb-0"><?= (int)($stats['total_beds'] ?? 0) ?></div>
|
||||
<div class="small opacity-75">Total Beds</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-md-2">
|
||||
<div class="card border-0 shadow-sm text-center py-2 bg-success-subtle text-success rounded-3 border border-success-subtle">
|
||||
<div class="fs-4 fw-bold mb-0"><?= (int)($stats['available'] ?? 0) ?></div>
|
||||
<div class="small text-success-emphasis">Available</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-md-2">
|
||||
<div class="card border-0 shadow-sm text-center py-2 bg-danger-subtle text-danger rounded-3 border border-danger-subtle">
|
||||
<div class="fs-4 fw-bold mb-0"><?= (int)($stats['occupied'] ?? 0) ?></div>
|
||||
<div class="small text-danger-emphasis">Occupied</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-md-2">
|
||||
<div class="card border-0 shadow-sm text-center py-2 bg-warning-subtle text-warning-emphasis rounded-3 border border-warning-subtle">
|
||||
<div class="fs-4 fw-bold mb-0"><?= (int)($stats['reserved'] ?? 0) ?></div>
|
||||
<div class="small">Reserved</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-md-2">
|
||||
<div class="card border-0 shadow-sm text-center py-2 bg-primary-subtle text-primary rounded-3 border border-primary-subtle">
|
||||
<div class="fs-4 fw-bold mb-0"><?= (int)($stats['nursing_stations'] ?? 0) ?></div>
|
||||
<div class="small">Nursing Stations</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-md-2">
|
||||
<div class="card border-0 shadow-sm text-center py-2 bg-info-subtle text-info-emphasis rounded-3 border border-info-subtle">
|
||||
<div class="fs-4 fw-bold mb-0"><?= (int)($stats['total_admitted'] ?? 0) ?></div>
|
||||
<div class="small">Admitted Patients</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Toolbar -->
|
||||
<div class="card shadow-sm border-0 mb-3">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search text-muted"></i></span>
|
||||
<input type="text" class="form-control border-start-0" id="nursing_search_input" placeholder="Search patient name, IPD code, bed..." onkeyup="filterNursingBeds()">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="$('#nursing_search_input').val(''); filterNursingBeds();">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select form-select-sm" id="filter_nursing_station" onchange="filterNursingBeds()">
|
||||
<option value="">All Nursing Stations</option>
|
||||
<?php foreach (($nursingStations ?? []) as $st): ?>
|
||||
<option value="<?= esc($st['station_name']) ?>"><?= esc($st['station_name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select class="form-select form-select-sm" id="filter_nursing_ward" onchange="filterNursingBeds()">
|
||||
<option value="">All Wards</option>
|
||||
<?php foreach (($wards ?? []) as $wId => $wName): ?>
|
||||
<option value="<?= esc($wName) ?>"><?= esc($wName) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 text-end">
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Status Filters">
|
||||
<button type="button" class="btn btn-outline-secondary active btn-status-filter" data-status="all" onclick="setStatusFilter('all', this)">All</button>
|
||||
<button type="button" class="btn btn-outline-success btn-status-filter" data-status="available" onclick="setStatusFilter('available', this)">Free</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-status-filter" data-status="occupied" onclick="setStatusFilter('occupied', this)">Busy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visual Bed Grid View Container (Image 2 Style) -->
|
||||
<div id="view_grid_container">
|
||||
<?php if (empty($floors)): ?>
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body text-center py-5 text-muted">
|
||||
<i class="bi bi-hospital fs-1 d-block mb-2 text-secondary opacity-50"></i>
|
||||
<h5>No active beds or wards configured</h5>
|
||||
<p class="mb-0 small">Please setup Wards and Beds in Admin Panel > Bed Management.</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($floors as $floorName => $wardGroup): ?>
|
||||
<?php
|
||||
$floorBeds = 0; $floorFree = 0; $floorBusy = 0;
|
||||
foreach ($wardGroup as $w) {
|
||||
$floorBeds += $w['count']['total'];
|
||||
$floorFree += $w['count']['available'];
|
||||
$floorBusy += $w['count']['occupied'];
|
||||
}
|
||||
?>
|
||||
<!-- Floor Header Banner -->
|
||||
<div class="bg-dark text-white px-3 py-2 rounded-3 mb-3 d-flex align-items-center justify-content-between shadow-sm">
|
||||
<div class="fw-bold fs-6">
|
||||
<i class="bi bi-layers me-2 text-info"></i><?= esc($floorName) ?>
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge bg-light text-dark me-1"><?= $floorBeds ?> beds</span>
|
||||
<span class="badge bg-success me-1"><?= $floorFree ?> free</span>
|
||||
<span class="badge bg-danger"><?= $floorBusy ?> busy</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wards Grid Under Floor -->
|
||||
<?php foreach ($wardGroup as $wId => $wardInfo): ?>
|
||||
<div class="card shadow-sm border-0 mb-4 ward-block-card" data-ward="<?= esc($wardInfo['ward_name']) ?>" data-station="<?= esc($wardInfo['station_name']) ?>">
|
||||
<div class="card-header bg-light py-2 d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-diagram-3 text-primary fs-5"></i>
|
||||
<strong class="text-primary fs-6 mb-0"><?= esc($wardInfo['ward_name']) ?></strong>
|
||||
<?php if (!empty($wardInfo['ward_type'])): ?>
|
||||
<span class="badge bg-secondary-subtle text-dark border me-2"><?= esc($wardInfo['ward_type']) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($wardInfo['station_name']) && $wardInfo['station_name'] !== 'Unassigned Station'): ?>
|
||||
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">
|
||||
<i class="bi bi-hospital me-1"></i>Station: <?= esc($wardInfo['station_name']) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle me-1"><?= (int)$wardInfo['count']['available'] ?> Free</span>
|
||||
<span class="badge bg-danger-subtle text-danger border border-danger-subtle me-1"><?= (int)$wardInfo['count']['occupied'] ?> Occupied</span>
|
||||
<span class="badge bg-secondary"><?= (int)$wardInfo['count']['total'] ?> Total</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<div class="row g-2">
|
||||
<?php foreach ($wardInfo['beds'] as $bed): ?>
|
||||
<?php
|
||||
$isOccupied = ($bed['bed_status'] === 'occupied');
|
||||
$cardClass = 'border-success-subtle bg-success-subtle bg-opacity-25';
|
||||
$headerColor = 'text-success';
|
||||
$statusBadgeClass = 'bg-success';
|
||||
$statusLabel = 'Available';
|
||||
|
||||
if ($isOccupied) {
|
||||
$cardClass = 'border-danger-subtle bg-danger-subtle bg-opacity-25';
|
||||
$headerColor = 'text-danger';
|
||||
$statusBadgeClass = 'bg-danger';
|
||||
$statusLabel = 'Occupied';
|
||||
} elseif ($bed['bed_status'] === 'reserved') {
|
||||
$cardClass = 'border-warning-subtle bg-warning-subtle bg-opacity-25';
|
||||
$headerColor = 'text-warning-emphasis';
|
||||
$statusBadgeClass = 'bg-warning text-dark';
|
||||
$statusLabel = 'Reserved';
|
||||
} elseif (strpos($bed['bed_status'], 'maint') !== false || $bed['bed_status'] === 'cleaning') {
|
||||
$cardClass = 'border-secondary-subtle bg-secondary-subtle bg-opacity-25';
|
||||
$headerColor = 'text-secondary';
|
||||
$statusBadgeClass = 'bg-secondary';
|
||||
$statusLabel = ucfirst($bed['bed_status']);
|
||||
}
|
||||
|
||||
$searchText = strtolower($bed['bed_number'] . ' ' . $bed['bed_code'] . ' ' . $bed['patient_name'] . ' ' . $bed['doctor_name'] . ' ' . $bed['ipd_code'] . ' ' . $wardInfo['ward_name'] . ' ' . $bed['station_name']);
|
||||
?>
|
||||
<div class="col-6 col-sm-4 col-md-3 col-lg-2 bed-card-col"
|
||||
data-status="<?= esc($bed['bed_status']) ?>"
|
||||
data-ward="<?= esc($wardInfo['ward_name']) ?>"
|
||||
data-station="<?= esc($bed['station_name']) ?>"
|
||||
data-search="<?= esc($searchText) ?>">
|
||||
<div class="card h-100 border rounded-3 p-2 text-center transition-all <?= $cardClass ?>">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<span class="fw-bold small <?= $headerColor ?>"><?= esc($bed['bed_number']) ?></span>
|
||||
<span class="badge <?= $statusBadgeClass ?> font-monospace" style="font-size: 10px;"><?= $statusLabel ?></span>
|
||||
</div>
|
||||
|
||||
<?php if ($isOccupied): ?>
|
||||
<div class="my-1">
|
||||
<div class="fw-bold text-dark text-truncate small" title="<?= esc($bed['patient_name']) ?>">
|
||||
<i class="bi bi-person-fill text-danger me-1"></i><?= esc($bed['patient_name']) ?>
|
||||
</div>
|
||||
<?php if (!empty($bed['doctor_name'])): ?>
|
||||
<div class="text-muted text-truncate" style="font-size: 11px;" title="<?= esc($bed['doctor_name']) ?>">
|
||||
<?= esc($bed['doctor_name']) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($bed['days_admitted'] !== null): ?>
|
||||
<span class="badge bg-light text-dark border border-secondary-subtle mt-1" style="font-size: 10px;">
|
||||
<i class="bi bi-clock-history me-1"></i><?= $bed['days_admitted'] ?> days
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="my-2 text-success small fw-semibold">
|
||||
<i class="bi bi-check-circle me-1"></i>Ready
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Features Badges -->
|
||||
<div class="d-flex justify-content-center gap-1 my-1">
|
||||
<?php if ($bed['has_oxygen']): ?><span class="badge bg-white text-dark border" style="font-size:9px;" title="Oxygen Supply">O<sub>2</sub></span><?php endif; ?>
|
||||
<?php if ($bed['has_monitor']): ?><span class="badge bg-white text-dark border" style="font-size:9px;" title="Cardiac Monitor">MON</span><?php endif; ?>
|
||||
<?php if ($bed['has_ventilator']): ?><span class="badge bg-white text-dark border" style="font-size:9px;" title="Ventilator">VENT</span><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<div class="mt-auto pt-1">
|
||||
<?php if ($isOccupied && $bed['ipd_id'] > 0): ?>
|
||||
<button type="button" class="btn btn-primary btn-sm w-100 py-0 text-nowrap" style="font-size:11px;" onclick="load_form('<?= base_url('ipd/patient/workspace/' . (int)$bed['ipd_id']) ?>','Nursing Workspace');">
|
||||
Open Workspace
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm w-100 py-0 text-nowrap disabled" style="font-size:11px;">
|
||||
Vacant Bed
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Table View Container (Image 1 Style) -->
|
||||
<div id="view_table_container" class="d-none">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-light py-2">
|
||||
<h6 class="mb-0 text-primary fw-bold"><i class="bi bi-list-task me-1"></i> Admitted IPD Patient List</h6>
|
||||
</div>
|
||||
<div class="card-body p-0 table-responsive">
|
||||
<table class="table table-striped table-hover align-middle mb-0" id="ipdNursingListTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 50px;">#</th>
|
||||
<th>IPD Code</th>
|
||||
<th>UHID</th>
|
||||
<th>Patient Name</th>
|
||||
<th>Bed & Ward Location</th>
|
||||
<th>Doctor</th>
|
||||
<th>Admit Date</th>
|
||||
<th>Days</th>
|
||||
<th class="text-end pe-3">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($records)): ?>
|
||||
<tr>
|
||||
<td colspan="9" class="text-center py-4 text-muted">
|
||||
<i class="bi bi-person-x fs-3 d-block mb-1"></i> No current IPD admissions found.
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($records as $idx => $row) : ?>
|
||||
<tr>
|
||||
<td class="fw-bold text-secondary ps-3"><?= $idx + 1 ?></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= esc($row->ipd_code ?? '') ?></span></td>
|
||||
<td><code><?= esc($row->p_code ?? '') ?></code></td>
|
||||
<td class="fw-semibold text-primary"><?= esc(trim((string) (($row->p_fname ?? '') . ' ' . ($row->p_rname ?? '')))) ?></td>
|
||||
<td>
|
||||
<span class="badge bg-info-subtle text-info-emphasis border border-info-subtle">
|
||||
<i class="bi bi-hospital me-1"></i><?= esc($row->Bed_Desc ?? '') ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?= esc($row->doc_name ?? '') ?></td>
|
||||
<td><?= esc($row->str_register_date ?? '') ?></td>
|
||||
<td><span class="badge bg-secondary"><?= esc($row->no_days ?? '0') ?> days</span></td>
|
||||
<td class="text-end pe-3">
|
||||
<button type="button" class="btn btn-primary btn-sm py-1 px-3" onclick="load_form('<?= base_url('ipd/patient/workspace/' . (int) ($row->id ?? 0)) ?>','Nursing Workspace');">
|
||||
<i class="bi bi-box-arrow-in-right me-1"></i> Open Workspace
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
if (window.jQuery && $.fn.DataTable) {
|
||||
$('#ipdNursingListTable').DataTable();
|
||||
var currentStatusFilter = 'all';
|
||||
|
||||
function switchNursingView(mode) {
|
||||
if (mode === 'grid') {
|
||||
$('#view_grid_container').removeClass('d-none');
|
||||
$('#view_table_container').addClass('d-none');
|
||||
$('#btn_view_grid').addClass('btn-primary active').removeClass('btn-outline-primary');
|
||||
$('#btn_view_table').addClass('btn-outline-primary').removeClass('btn-primary active');
|
||||
} else {
|
||||
$('#view_grid_container').addClass('d-none');
|
||||
$('#view_table_container').removeClass('d-none');
|
||||
$('#btn_view_table').addClass('btn-primary active').removeClass('btn-outline-primary');
|
||||
$('#btn_view_grid').addClass('btn-outline-primary').removeClass('btn-primary active');
|
||||
if (window.jQuery && $.fn.DataTable && !$.fn.DataTable.isDataTable('#ipdNursingListTable')) {
|
||||
$('#ipdNursingListTable').DataTable();
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function setStatusFilter(status, btn) {
|
||||
currentStatusFilter = status;
|
||||
$('.btn-status-filter').removeClass('active btn-secondary btn-success btn-danger').addClass('btn-outline-secondary');
|
||||
$(btn).removeClass('btn-outline-secondary').addClass('active');
|
||||
filterNursingBeds();
|
||||
}
|
||||
|
||||
function filterNursingBeds() {
|
||||
var search = ($('#nursing_search_input').val() || '').toLowerCase().trim();
|
||||
var station = ($('#filter_nursing_station').val() || '').toLowerCase().trim();
|
||||
var ward = ($('#filter_nursing_ward').val() || '').toLowerCase().trim();
|
||||
|
||||
$('.bed-card-col').each(function() {
|
||||
var $col = $(this);
|
||||
var bStatus = ($col.data('status') || '').toLowerCase();
|
||||
var bWard = ($col.data('ward') || '').toLowerCase();
|
||||
var bStation = ($col.data('station') || '').toLowerCase();
|
||||
var bSearch = ($col.data('search') || '').toLowerCase();
|
||||
|
||||
var matchSearch = (search === '' || bSearch.indexOf(search) !== -1);
|
||||
var matchStation = (station === '' || bStation.indexOf(station) !== -1);
|
||||
var matchWard = (ward === '' || bWard.indexOf(ward) !== -1);
|
||||
var matchStatus = (currentStatusFilter === 'all' || bStatus === currentStatusFilter);
|
||||
|
||||
if (matchSearch && matchStation && matchWard && matchStatus) {
|
||||
$col.removeClass('d-none');
|
||||
} else {
|
||||
$col.addClass('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
$('.ward-block-card').each(function() {
|
||||
var $card = $(this);
|
||||
var visibleBeds = $card.find('.bed-card-col:not(.d-none)').length;
|
||||
if (visibleBeds === 0) {
|
||||
$card.addClass('d-none');
|
||||
} else {
|
||||
$card.removeClass('d-none');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -86,39 +86,60 @@ ksort($nurseNames);
|
|||
|
||||
.nursing-workspace-page .card {
|
||||
border: 1px solid #e6edf5;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||
box-shadow: 0 1px 3px rgba(16, 24, 40, 0.04);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs {
|
||||
gap: 0.35rem;
|
||||
border-bottom: 1px solid #e6edf5;
|
||||
padding: 0.65rem 0.85rem 0 !important;
|
||||
background: #f8f9fc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs .nav-link {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.45rem 0.45rem 0 0;
|
||||
color: #4154f1;
|
||||
border-radius: 0.45rem;
|
||||
color: #334155;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 0.85rem;
|
||||
margin-bottom: -1px;
|
||||
font-size: 0.88rem;
|
||||
padding: 0.65rem 0.85rem;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs .nav-link i {
|
||||
font-size: 1.05rem;
|
||||
margin-right: 0.65rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs .nav-link:hover {
|
||||
background: #eef2ff;
|
||||
border-color: #dbe2f3;
|
||||
border-color: #cbd5e1;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs .nav-link.active {
|
||||
background: #fff;
|
||||
border-color: #e6edf5 #e6edf5 #fff;
|
||||
color: #012970;
|
||||
background: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
color: #ffffff !important;
|
||||
box-shadow: 0 2px 6px rgba(13, 110, 253, 0.3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs .nav-link.active i {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.nursing-workspace-page .nursing-tab-pane {
|
||||
margin-top: 0.85rem;
|
||||
padding: 0.25rem 0.45rem 0.75rem;
|
||||
padding: 0.1rem;
|
||||
}
|
||||
|
||||
.nursing-workspace-page .nursing-tab-pane .card,
|
||||
|
|
@ -133,23 +154,11 @@ ksort($nurseNames);
|
|||
.nursing-workspace-page .table-responsive {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.nursing-workspace-page #nursing_entry_tabs {
|
||||
overflow-x: auto;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nursing-workspace-page #nursing_entry_tabs .nav-item {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="content nursing-workspace-page">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div class="card-header d-flex justify-content-between align-items-center py-2">
|
||||
<h5 class="mb-0">Nursing Workspace</h5>
|
||||
<div class="d-flex gap-2">
|
||||
<button
|
||||
|
|
@ -169,8 +178,8 @@ ksort($nurseNames);
|
|||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="load_form('<?= base_url('ipd/patient') ?>','IPD Patient List');">Back to IPD Patient List</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-1">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-0">
|
||||
<strong>IPD:</strong> <?= esc((string) ($ipd->ipd_code ?? '')) ?> |
|
||||
<strong>Patient:</strong> <?= esc((string) ($person->p_fname ?? '')) ?> |
|
||||
<strong>UHID:</strong> <?= esc((string) ($person->p_code ?? '')) ?>
|
||||
|
|
@ -179,41 +188,53 @@ ksort($nurseNames);
|
|||
</div>
|
||||
|
||||
<div id="nursing_panel_content">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header p-0">
|
||||
<ul class="nav nav-tabs card-header-tabs px-2 pt-2" id="nursing_entry_tabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_admission">Admission Snapshot</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_history">History & Physical Assessment</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link active" data-nursing-tab="nursing_tab_vitals">Vitals</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_fluid">Fluid Intake / Output</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_treatment">Treatment / Procedure Note</button>
|
||||
</li>
|
||||
<?php if ($canViewIpdOt) : ?>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_ot" data-url="<?= esc($otPanelUrl, 'attr') ?>">Surgery / OT</button>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_scan">Scan Paper -> Auto Fill</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_charge">Charge Entry</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_bed">Bed Management</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="row g-3">
|
||||
<!-- Left Navigation Menu -->
|
||||
<div class="col-md-4 col-lg-3">
|
||||
<div class="card shadow-sm border-0 sticky-top" style="top: 15px; z-index: 10;">
|
||||
<div class="card-header bg-light py-2 border-bottom">
|
||||
<strong class="text-dark" style="font-size: 0.88rem;">
|
||||
<i class="bi bi-layout-sidebar-inset me-1 text-primary"></i> Workspace Menu
|
||||
</strong>
|
||||
</div>
|
||||
<div class="p-2" id="nursing_entry_tabs" role="tablist">
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_admission">
|
||||
<i class="bi bi-person-vcard text-primary"></i> Admission Snapshot
|
||||
</button>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_history">
|
||||
<i class="bi bi-file-earmark-medical text-info"></i> History & Physical
|
||||
</button>
|
||||
<button type="button" class="nav-link active" data-nursing-tab="nursing_tab_vitals">
|
||||
<i class="bi bi-heart-pulse text-danger"></i> Vitals
|
||||
</button>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_fluid">
|
||||
<i class="bi bi-droplet text-primary"></i> Fluid Intake / Output
|
||||
</button>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_treatment">
|
||||
<i class="bi bi-prescription2 text-warning"></i> Treatment / Procedure Note
|
||||
</button>
|
||||
<?php if ($canViewIpdOt) : ?>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_ot" data-url="<?= esc($otPanelUrl, 'attr') ?>">
|
||||
<i class="bi bi-hospital text-secondary"></i> Surgery / OT
|
||||
</button>
|
||||
<?php endif ?>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_scan">
|
||||
<i class="bi bi-qr-code-scan text-success"></i> Scan Paper -> Auto Fill
|
||||
</button>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_charge">
|
||||
<i class="bi bi-currency-dollar text-success"></i> Charge Entry
|
||||
</button>
|
||||
<button type="button" class="nav-link" data-nursing-tab="nursing_tab_bed">
|
||||
<i class="bi bi-door-open text-primary"></i> Bed Management
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- Right Content Area -->
|
||||
<div class="col-md-8 col-lg-9">
|
||||
<div class="card shadow-sm border-0 mb-3">
|
||||
<div class="card-body p-3">
|
||||
|
||||
<!-- Bed Management Tab -->
|
||||
<div class="nursing-tab-pane d-none" id="nursing_tab_bed">
|
||||
|
|
@ -382,7 +403,17 @@ ksort($nurseNames);
|
|||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="entry_type" value="admission">
|
||||
<div class="col-md-3"><label class="form-label">Recorded At</label><input type="datetime-local" class="form-control" name="recorded_at" value="<?= date('Y-m-d\\TH:i') ?>"></div>
|
||||
<div class="col-md-2"><label class="form-label">Shift</label><input type="text" class="form-control nursing-shift-display" value="Morning" readonly></div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Nursing Staff (ID / Name)</label>
|
||||
<select name="recorded_by_nurse_id" class="form-select">
|
||||
<option value="0">-- Select Nursing Staff --</option>
|
||||
<?php foreach (($nurse_list ?? []) as $nRow): ?>
|
||||
<option value="<?= (int) $nRow['id'] ?>">
|
||||
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12"><label class="form-label">Admission Complaints / Reason</label><textarea class="form-control" name="treatment_text" rows="3" placeholder="Chief complaints and reason for admission"></textarea></div>
|
||||
<div class="col-md-1"><label class="form-label">Temp °F</label><input type="number" step="0.1" class="form-control" name="temperature_f"></div>
|
||||
<div class="col-md-1"><label class="form-label">Pulse</label><input type="number" class="form-control" name="pulse_rate"></div>
|
||||
|
|
@ -489,7 +520,17 @@ ksort($nurseNames);
|
|||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="entry_type" value="vitals">
|
||||
<div class="col-md-3"><label class="form-label">Recorded At</label><input type="datetime-local" class="form-control" name="recorded_at" value="<?= date('Y-m-d\\TH:i') ?>"></div>
|
||||
<div class="col-md-2"><label class="form-label">Shift</label><input type="text" class="form-control nursing-shift-display" value="Morning" readonly></div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Nursing Staff (ID / Name)</label>
|
||||
<select name="recorded_by_nurse_id" class="form-select">
|
||||
<option value="0">-- Select Nursing Staff --</option>
|
||||
<?php foreach (($nurse_list ?? []) as $nRow): ?>
|
||||
<option value="<?= (int) $nRow['id'] ?>">
|
||||
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-1"><label class="form-label">Temp °F</label><input type="number" step="0.1" class="form-control" name="temperature_f"></div>
|
||||
<div class="col-md-1"><label class="form-label">Pulse</label><input type="number" class="form-control" name="pulse_rate"></div>
|
||||
<div class="col-md-1"><label class="form-label">Resp</label><input type="number" class="form-control" name="resp_rate"></div>
|
||||
|
|
@ -507,7 +548,17 @@ ksort($nurseNames);
|
|||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="entry_type" value="fluid">
|
||||
<div class="col-md-3"><label class="form-label">Recorded At</label><input type="datetime-local" class="form-control" name="recorded_at" value="<?= date('Y-m-d\\TH:i') ?>"></div>
|
||||
<div class="col-md-2"><label class="form-label">Shift</label><input type="text" class="form-control nursing-shift-display" value="Morning" readonly></div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Nursing Staff (ID / Name)</label>
|
||||
<select name="recorded_by_nurse_id" class="form-select">
|
||||
<option value="0">-- Select Nursing Staff --</option>
|
||||
<?php foreach (($nurse_list ?? []) as $nRow): ?>
|
||||
<option value="<?= (int) $nRow['id'] ?>">
|
||||
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2"><label class="form-label">Direction</label><select class="form-select" name="fluid_direction"><option value="intake">Intake</option><option value="output">Output</option></select></div>
|
||||
<div class="col-md-2"><label class="form-label">Route</label><input type="text" class="form-control" name="fluid_route"></div>
|
||||
<div class="col-md-2"><label class="form-label">Amount (ml)</label><input type="number" class="form-control" name="fluid_amount_ml" required></div>
|
||||
|
|
@ -521,7 +572,17 @@ ksort($nurseNames);
|
|||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="entry_type" value="treatment">
|
||||
<div class="col-md-3"><label class="form-label">Recorded At</label><input type="datetime-local" class="form-control" name="recorded_at" value="<?= date('Y-m-d\\TH:i') ?>"></div>
|
||||
<div class="col-md-2"><label class="form-label">Shift</label><input type="text" class="form-control nursing-shift-display" value="Morning" readonly></div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Nursing Staff (ID / Name)</label>
|
||||
<select name="recorded_by_nurse_id" class="form-select">
|
||||
<option value="0">-- Select Nursing Staff --</option>
|
||||
<?php foreach (($nurse_list ?? []) as $nRow): ?>
|
||||
<option value="<?= (int) $nRow['id'] ?>">
|
||||
<?= esc(($nRow['nurse_code'] ? '[' . $nRow['nurse_code'] . '] ' : '') . $nRow['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-12"><label class="form-label">Treatment Given</label><textarea class="form-control" name="treatment_text" rows="3" required></textarea></div>
|
||||
<div class="col-md-12"><label class="form-label">Additional Note</label><textarea class="form-control" name="general_note" rows="2"></textarea></div>
|
||||
<div class="col-md-12"><button type="submit" class="btn btn-primary btn-sm">Save Treatment Note</button></div>
|
||||
|
|
@ -804,16 +865,16 @@ ksort($nurseNames);
|
|||
</div>
|
||||
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead><tr><th style="width: 150px;">Time</th><th style="width: 90px;">Type</th><th style="width: 90px;">Shift</th><th>Details</th><th style="width: 130px;">By</th></tr></thead>
|
||||
<thead><tr><th style="width: 150px;">Time</th><th style="width: 100px;">Type</th><th style="width: 180px;">Nursing Staff</th><th>Details</th></tr></thead>
|
||||
<tbody>
|
||||
<?php if (empty($entries)) : ?>
|
||||
<tr><td colspan="5" class="text-center text-muted" id="nursing_no_data_row">No nursing entries yet.</td></tr>
|
||||
<tr><td colspan="4" class="text-center text-muted" id="nursing_no_data_row">No nursing entries yet.</td></tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($entries as $entry) : ?>
|
||||
<tr class="nursing-entry-row" data-nurse="<?= esc((string) ($entry['recorded_by'] ?? '')) ?>" data-type="<?= esc(strtolower((string) ($entry['entry_type'] ?? ''))) ?>">
|
||||
<td><?= esc((string) ($entry['recorded_at'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($entry['entry_type'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($entry['shift_name'] ?? '')) ?></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= esc((string) ($entry['entry_type'] ?? '')) ?></span></td>
|
||||
<td><strong class="text-primary"><?= esc((string) ($entry['recorded_by'] ?? 'Staff')) ?></strong></td>
|
||||
<td>
|
||||
<?php if (($entry['entry_type'] ?? '') === 'vitals') : ?>
|
||||
Temp: <?= esc($toFahrenheit($entry['temperature_c'] ?? null)) ?> °F, Pulse: <?= esc((string) ($entry['pulse_rate'] ?? '')) ?>, Resp: <?= esc((string) ($entry['resp_rate'] ?? '')) ?>, BP: <?= esc((string) ($entry['bp_systolic'] ?? '')) ?>/<?= esc((string) ($entry['bp_diastolic'] ?? '')) ?>, SpO2: <?= esc((string) ($entry['spo2'] ?? '')) ?>, Wt: <?= esc((string) ($entry['weight_kg'] ?? '')) ?>
|
||||
|
|
@ -824,7 +885,6 @@ ksort($nurseNames);
|
|||
<?php endif; ?>
|
||||
<?php if (! empty($entry['general_note'])) : ?><br><small><strong>Note:</strong> <?= esc((string) $entry['general_note']) ?></small><?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc((string) ($entry['recorded_by'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
|
@ -871,6 +931,8 @@ ksort($nurseNames);
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user