2026-04-13 19:04:12 +05:30

693 lines
24 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\UserModel;
use App\Models\DoctorModel;
use App\Models\DoctorSpecializationModel;
use App\Models\PatientModel;
use App\Models\AppointmentModel;
use App\Models\SpecializationModel;
class Admin extends BaseController
{
private function formatFullName(?string $firstName, ?string $lastName, string $fallback = ''): string
{
$fullName = trim(trim((string) $firstName) . ' ' . trim((string) $lastName));
return $fullName !== '' ? $fullName : $fallback;
}
private function generateAccountPassword(int $length = 12): string
{
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%';
$maxIndex = strlen($alphabet) - 1;
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $alphabet[random_int(0, $maxIndex)];
}
return $password;
}
private function parseSpecializations($specializationInput): array
{
$specializations = [];
if (is_array($specializationInput)) {
foreach ($specializationInput as $item) {
$item = trim((string) $item);
if ($item !== '' && ! in_array($item, $specializations, true)) {
$specializations[] = $item;
}
}
} else {
$single = trim((string) $specializationInput);
if ($single !== '') {
$specializations[] = $single;
}
}
return $specializations;
}
private function buildExperienceString(?int $years, ?int $months): ?string
{
$experienceParts = [];
if ($years !== null && $years > 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,
]);
}
}