0) { $experienceParts[] = $years . ' year' . ($years === 1 ? '' : 's'); } if ($months !== null && $months > 0) { $experienceParts[] = $months . ' month' . ($months === 1 ? '' : 's'); } return $experienceParts !== [] ? implode(' ', $experienceParts) : null; } private function parseExperienceString(?string $experience): array { $experience = trim((string) $experience); $years = 0; $months = 0; if ($experience !== '' && preg_match('/(\d+)\s+year/i', $experience, $yearMatch)) { $years = (int) $yearMatch[1]; } if ($experience !== '' && preg_match('/(\d+)\s+month/i', $experience, $monthMatch)) { $months = (int) $monthMatch[1]; } return [ 'experience_years' => $years, 'experience_months' => $months, ]; } private function getPatientFormData(int $userId): ?array { $userModel = new UserModel(); $patientModel = new PatientModel(); $user = $userModel->find($userId); $patient = $patientModel->findByUserId($userId); if (! $user || ! $patient) { return null; } return [ 'patient' => $patient, 'user' => $user, 'first_name' => $user['first_name'] ?? '', 'last_name' => $user['last_name'] ?? '', ]; } private function getDoctorFormData(int $userId): ?array { $userModel = new UserModel(); $doctorModel = new DoctorModel(); $specializationModel = new SpecializationModel(); $doctorSpecializationModel = new DoctorSpecializationModel(); $user = $userModel->find($userId); $doctor = $doctorModel->findByUserId($userId); if (! $user || ! $doctor) { return null; } $selectedSpecializations = $doctorSpecializationModel->getNamesForDoctor((int) $doctor['id']); if ($selectedSpecializations === []) { $selectedSpecializations = $this->parseSpecializations($doctor['specialization'] ?? []); } return array_merge($this->parseExperienceString($doctor['experience'] ?? null), [ 'doctor' => $doctor, 'user' => $user, 'first_name' => $user['first_name'] ?? '', 'last_name' => $user['last_name'] ?? '', 'specializationOptions' => $specializationModel->getOptionNames(), 'selectedSpecializations' => $selectedSpecializations, ]); } public function dashboard() { if ($r = $this->requireRole('admin')) { return $r; } $doctorModel = new DoctorModel(); $patientModel = new PatientModel(); $appointmentModel = new AppointmentModel(); $userModel = new UserModel(); $adminId = (int) session()->get('id'); $adminUser = $userModel->find($adminId); $data['totalDoctors'] = $doctorModel->countAll(); $data['totalPatients'] = $patientModel->countAll(); $data['totalAppointments'] = $appointmentModel->countAll(); $data['activeToday'] = $appointmentModel->countForDate(date('Y-m-d')); $data['recentActivity'] = $appointmentModel->getRecentActivity(6); $data['latestDoctors'] = $doctorModel->getLatestDoctors(5); $data['latestPatients'] = $patientModel->getLatestPatients(5); $data['adminName'] = $this->formatFullName($adminUser['first_name'] ?? '', $adminUser['last_name'] ?? '', 'Administrator'); $data['adminEmail'] = $adminUser['email'] ?? ''; return view('admin/dashboard', $data); } public function doctors() { if ($r = $this->requireRole('admin')) { return $r; } return view('admin/doctors'); } public function deleteDoctor($id) { if ($r = $this->requireRole('admin')) { return $r; } $id = (int) $id; if ($id < 1) { return redirect()->to(site_url('admin/doctors')); } $userModel = new UserModel(); $doctorModel = new DoctorModel(); $appointmentModel = new AppointmentModel(); $doctorSpecializationModel = new DoctorSpecializationModel(); $doctor = $doctorModel->findByUserId($id); if ($doctor) { $appointmentModel->deleteByDoctorId((int) $doctor['id']); $doctorSpecializationModel->deleteByDoctorId((int) $doctor['id']); $doctorModel->delete($doctor['id']); } $userModel->delete($id); return redirect()->to(site_url('admin/doctors')); } public function patients() { if ($r = $this->requireRole('admin')) { return $r; } return view('admin/patients'); } public function deletePatient($id) { if ($r = $this->requireRole('admin')) { return $r; } $id = (int) $id; if ($id < 1) { return redirect()->to(site_url('admin/patients')); } $userModel = new UserModel(); $patientModel = new PatientModel(); $appointmentModel = new AppointmentModel(); $patient = $patientModel->findByUserId($id); if ($patient) { $appointmentModel->deleteByPatientId((int) $patient['id']); $patientModel->delete($patient['id']); } $userModel->delete($id); return redirect()->to(site_url('admin/patients')); } public function appointments() { if ($r = $this->requireRole('admin')) { return $r; } $appointmentModel = new AppointmentModel(); $data['appointments'] = $appointmentModel->getAdminAppointments(); return view('admin/appointments', $data); } public function addDoctor() { if ($r = $this->requireRole('admin')) { return $r; } $specializationModel = new SpecializationModel(); return view('admin/add_doctor', [ 'specializationOptions' => $specializationModel->getOptionNames(), 'isEdit' => false, ]); } public function addPatient() { if ($r = $this->requireRole('admin')) { return $r; } return view('admin/add_patient', [ 'isEdit' => false, ]); } public function editDoctor($encryptedId) { if ($r = $this->requireRole('admin')) { return $r; } $id = decrypt_id($encryptedId); if (! ctype_digit((string) $id)) { return redirect()->to(site_url('admin/doctors'))->with('error', 'Invalid doctor link.'); } $doctorData = $this->getDoctorFormData((int) $id); if (! $doctorData) { return redirect()->to(site_url('admin/doctors'))->with('error', 'Doctor not found.'); } return view('admin/add_doctor', array_merge($doctorData, [ 'isEdit' => true, ])); } public function editPatient($encryptedId) { if ($r = $this->requireRole('admin')) { return $r; } $id = decrypt_id($encryptedId); if (! ctype_digit((string) $id)) { return redirect()->to(site_url('admin/patients'))->with('error', 'Invalid patient link.'); } $patientData = $this->getPatientFormData((int) $id); if (! $patientData) { return redirect()->to(site_url('admin/patients'))->with('error', 'Patient not found.'); } return view('admin/add_patient', array_merge($patientData, [ 'isEdit' => true, ])); } public function storeDoctor() { if ($r = $this->requireRole('admin')) { return $r; } $rules = [ 'first_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'last_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'email' => 'required|valid_email|is_unique[users.email]', 'experience_years' => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[60]', 'experience_months' => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[11]', 'fees' => 'permit_empty|decimal', ]; if (! $this->validate($rules)) { return redirect()->back()->withInput(); } $userModel = new UserModel(); $doctorModel = new DoctorModel(); $specializationModel = new SpecializationModel(); $doctorSpecializationModel = new DoctorSpecializationModel(); $db = \Config\Database::connect(); $firstName = trim((string) $this->request->getPost('first_name')); $lastName = trim((string) $this->request->getPost('last_name')); $specializationInput = $this->request->getPost('specialization'); $specializations = $this->parseSpecializations($specializationInput); if ($specializations === []) { return redirect()->back()->withInput()->with('error', 'Please select at least one specialization.'); } $specializationValue = implode(', ', $specializations); if (strlen($specializationValue) > 191) { return redirect()->back()->withInput()->with('error', 'Selected specializations are too long. Please reduce them.'); } $yearsRaw = (string) $this->request->getPost('experience_years'); $monthsRaw = (string) $this->request->getPost('experience_months'); $years = $yearsRaw === '' ? null : (int) $yearsRaw; $months = $monthsRaw === '' ? null : (int) $monthsRaw; $experience = $this->buildExperienceString($years, $months); $generatedPassword = $this->generateAccountPassword(); $db->transStart(); $userData = [ 'first_name' => $firstName, 'last_name' => $lastName, 'email' => trim((string) $this->request->getPost('email')), 'password' => password_hash($generatedPassword, PASSWORD_DEFAULT), 'role' => 'doctor', 'status' => 'active', ]; if (! $userModel->skipValidation(true)->insert($userData)) { $db->transRollback(); return redirect()->back()->withInput()->with('error', 'Could not create doctor login account.'); } $userId = (int) $userModel->getInsertID(); $doctorRow = [ 'user_id' => $userId, 'specialization' => $specializationValue, 'experience' => $experience, 'fees' => $this->request->getPost('fees') !== '' && $this->request->getPost('fees') !== null ? $this->request->getPost('fees') : null, ]; if (! $doctorModel->skipValidation(true)->insert($doctorRow)) { $db->transRollback(); return redirect()->back()->withInput()->with('error', 'Could not create doctor profile.'); } $doctorId = (int) $doctorModel->getInsertID(); $specializationMap = $specializationModel->ensureNamesExist($specializations); $specializationIds = array_values($specializationMap); $doctorSpecializationModel->syncDoctorSpecializations($doctorId, $specializationIds, (int) session()->get('id')); $db->transComplete(); if (! $db->transStatus()) { return redirect()->back()->withInput()->with('error', 'Transaction failed.'); } return redirect()->to(site_url('admin/doctors'))->with('success', 'Doctor account created. Generated password: ' . $generatedPassword); } public function storePatient() { if ($r = $this->requireRole('admin')) { return $r; } $rules = [ 'first_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'last_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'email' => 'required|valid_email|is_unique[users.email]', 'phone' => 'required|min_length[10]|max_length[15]', 'age' => 'permit_empty|integer|greater_than_equal_to[0]|less_than_equal_to[120]', 'gender' => 'permit_empty|in_list[male,female,other]', ]; if (! $this->validate($rules)) { return redirect()->back()->withInput(); } $userModel = new UserModel(); $patientModel = new PatientModel(); $db = \Config\Database::connect(); $firstName = trim((string) $this->request->getPost('first_name')); $lastName = trim((string) $this->request->getPost('last_name')); $generatedPassword = $this->generateAccountPassword(); $ageRaw = (string) $this->request->getPost('age'); $db->transStart(); $userData = [ 'first_name' => $firstName, 'last_name' => $lastName, 'email' => trim((string) $this->request->getPost('email')), 'password' => password_hash($generatedPassword, PASSWORD_DEFAULT), 'role' => 'patient', 'status' => 'active', ]; if (! $userModel->skipValidation(true)->insert($userData)) { $db->transRollback(); return redirect()->back()->withInput()->with('error', 'Could not create patient login account.'); } $userId = (int) $userModel->getInsertID(); $patientRow = [ 'user_id' => $userId, 'age' => $ageRaw !== '' ? (int) $ageRaw : null, 'gender' => $this->request->getPost('gender') !== '' ? $this->request->getPost('gender') : null, 'phone' => trim((string) $this->request->getPost('phone')), ]; if (! $patientModel->skipValidation(true)->insert($patientRow)) { $db->transRollback(); return redirect()->back()->withInput()->with('error', 'Could not create patient profile.'); } $db->transComplete(); if (! $db->transStatus()) { return redirect()->back()->withInput()->with('error', 'Transaction failed.'); } return redirect()->to(site_url('admin/patients'))->with('success', 'Patient account created. Generated password: ' . $generatedPassword); } public function updateDoctor($encryptedId) { if ($r = $this->requireRole('admin')) { return $r; } $id = decrypt_id($encryptedId); if (! ctype_digit((string) $id)) { return redirect()->to(site_url('admin/doctors'))->with('error', 'Invalid doctor link.'); } $userId = (int) $id; $doctorData = $this->getDoctorFormData($userId); if (! $doctorData) { return redirect()->to(site_url('admin/doctors'))->with('error', 'Doctor not found.'); } $rules = [ 'first_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'last_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'email' => 'required|valid_email|is_unique[users.email,id,' . $userId . ']', 'experience_months' => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[11]', 'experience_years' => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[60]', 'fees' => 'permit_empty|decimal', ]; if (! $this->validate($rules)) { return redirect()->back()->withInput(); } $userModel = new UserModel(); $doctorModel = new DoctorModel(); $specializationModel = new SpecializationModel(); $doctorSpecializationModel = new DoctorSpecializationModel(); $db = \Config\Database::connect(); $firstName = trim((string) $this->request->getPost('first_name')); $lastName = trim((string) $this->request->getPost('last_name')); $specializationInput = $this->request->getPost('specialization'); $specializations = $this->parseSpecializations($specializationInput); if ($specializations === []) { return redirect()->back()->withInput()->with('error', 'Please select at least one specialization.'); } $specializationValue = implode(', ', $specializations); if (strlen($specializationValue) > 191) { return redirect()->back()->withInput()->with('error', 'Selected specializations are too long. Please reduce them.'); } $yearsRaw = (string) $this->request->getPost('experience_years'); $monthsRaw = (string) $this->request->getPost('experience_months'); $years = $yearsRaw === '' ? null : (int) $yearsRaw; $months = $monthsRaw === '' ? null : (int) $monthsRaw; $experience = $this->buildExperienceString($years, $months); $db->transStart(); $userModel->update($userId, [ 'first_name' => $firstName, 'last_name' => $lastName, 'email' => trim((string) $this->request->getPost('email')), ]); $doctorModel->update($doctorData['doctor']['id'], [ 'specialization' => $specializationValue, 'experience' => $experience, 'fees' => $this->request->getPost('fees') !== '' && $this->request->getPost('fees') !== null ? $this->request->getPost('fees') : null, ]); $specializationMap = $specializationModel->ensureNamesExist($specializations); $doctorSpecializationModel->syncDoctorSpecializations((int) $doctorData['doctor']['id'], array_values($specializationMap), (int) session()->get('id')); $db->transComplete(); if (! $db->transStatus()) { return redirect()->back()->withInput()->with('error', 'Could not update doctor.'); } return redirect()->to(site_url('admin/doctors'))->with('success', 'Doctor updated successfully.'); } public function updatePatient($encryptedId) { if ($r = $this->requireRole('admin')) { return $r; } $id = decrypt_id($encryptedId); if (! ctype_digit((string) $id)) { return redirect()->to(site_url('admin/patients'))->with('error', 'Invalid patient link.'); } $userId = (int) $id; $patientData = $this->getPatientFormData($userId); if (! $patientData) { return redirect()->to(site_url('admin/patients'))->with('error', 'Patient not found.'); } $rules = [ 'first_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'last_name' => 'required|min_length[2]|max_length[50]|alpha_space', 'email' => 'required|valid_email|is_unique[users.email,id,' . $userId . ']', 'phone' => 'required|min_length[10]|max_length[15]', 'age' => 'permit_empty|integer|greater_than_equal_to[0]|less_than_equal_to[120]', 'gender' => 'permit_empty|in_list[male,female,other]', ]; if (! $this->validate($rules)) { return redirect()->back()->withInput(); } $userModel = new UserModel(); $patientModel = new PatientModel(); $db = \Config\Database::connect(); $firstName = trim((string) $this->request->getPost('first_name')); $lastName = trim((string) $this->request->getPost('last_name')); $ageRaw = (string) $this->request->getPost('age'); $db->transStart(); $userModel->update($userId, [ 'first_name' => $firstName, 'last_name' => $lastName, 'email' => trim((string) $this->request->getPost('email')), ]); $patientModel->update($patientData['patient']['id'], [ 'age' => $ageRaw !== '' ? (int) $ageRaw : null, 'gender' => $this->request->getPost('gender') !== '' ? $this->request->getPost('gender') : null, 'phone' => trim((string) $this->request->getPost('phone')), ]); $db->transComplete(); if (! $db->transStatus()) { return redirect()->back()->withInput()->with('error', 'Could not update patient.'); } return redirect()->to(site_url('admin/patients'))->with('success', 'Patient updated successfully.'); } public function checkEmail() { $email = (string) $this->request->getPost('email'); $excludeId = (int) $this->request->getPost('exclude_id'); $userModel = new UserModel(); return $this->response->setJSON([ 'exists' => $userModel->emailExistsExcept($email, $excludeId > 0 ? $excludeId : null) ]); } public function getDoctors() { if ($r = $this->requireRole('admin')) { return $r; } helper('encryption'); $doctorModel = new DoctorModel(); $doctors = $doctorModel->getAdminDoctorList('doctor_id', 'asc'); $payload = []; foreach ($doctors as $doc) { $payload[] = [ 'user_id' => (int) ($doc->user_id ?? 0), 'formatted_user_id' => $doc->formatted_user_id ?? null, 'name' => $doc->name ?? null, 'email' => $doc->email ?? null, 'specialization' => $doc->specialization ?? null, 'experience' => $doc->experience ?? null, 'fees' => $doc->fees ?? null, 'status' => $doc->status ?? null, 'edit_token' => encrypt_id($doc->user_id ?? 0), ]; } return $this->response->setJSON([ 'data' => $payload, ]); } public function getPatients() { if ($r = $this->requireRole('admin')) { return $r; } helper('encryption'); $patientModel = new PatientModel(); $patients = $patientModel->getAdminPatientList('patient_id', 'asc'); $payload = []; foreach ($patients as $patient) { $payload[] = [ 'user_id' => (int) ($patient->user_id ?? 0), 'formatted_user_id' => $patient->formatted_user_id ?? null, 'name' => $patient->name ?? null, 'email' => $patient->email ?? null, 'phone' => $patient->phone ?? null, 'status' => $patient->status ?? null, 'edit_token' => encrypt_id($patient->user_id ?? 0), ]; } return $this->response->setJSON([ 'data' => $payload, ]); } }