diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b88d806 --- /dev/null +++ b/.env.example @@ -0,0 +1,70 @@ +#-------------------------------------------------------------------- +# Example Environment Configuration file +# +# This file can be used as a starting point for your own +# custom .env files, and contains most of the possible settings +# available in a default install. +# +# By default, all of the settings are commented out. If you want +# to override the setting, you must un-comment it by removing the '#' +# at the beginning of the line. +#-------------------------------------------------------------------- + +#-------------------------------------------------------------------- +# ENVIRONMENT +#-------------------------------------------------------------------- + +CI_ENVIRONMENT = development + +#-------------------------------------------------------------------- +# APP +#-------------------------------------------------------------------- + +# Set this to your public URL (trailing slash). Example for XAMPP: +app.baseURL = 'http://localhost:8080/' +# If you have trouble with `.`, you could also use `_`. +# app_baseURL = '' +# app.forceGlobalSecureRequests = false +# app.CSPEnabled = false + +#-------------------------------------------------------------------- +# DATABASE +#-------------------------------------------------------------------- + +database.default.hostname = localhost +database.default.database = +database.default.username = root +database.default.password = +database.default.DBDriver = MySQLi +database.default.DBPrefix = +database.default.port = 3306 + +# If you use MySQLi as tests, first update the values of Config\Database::$tests. +# database.tests.hostname = localhost +# database.tests.database = ci4_test +# database.tests.username = root +# database.tests.password = root +# database.tests.DBDriver = MySQLi +# database.tests.DBPrefix = +# database.tests.charset = utf8mb4 +# database.tests.DBCollat = utf8mb4_general_ci +# database.tests.port = 3306 + +#-------------------------------------------------------------------- +# ENCRYPTION +#-------------------------------------------------------------------- + + encryption.key = + +#-------------------------------------------------------------------- +# SESSION +#-------------------------------------------------------------------- + +session.driver = 'CodeIgniter\Session\Handlers\FileHandler' +# session.savePath = null + +#-------------------------------------------------------------------- +# LOGGER +#-------------------------------------------------------------------- + +# logger.threshold = 4 diff --git a/app/Config/App.php b/app/Config/App.php index fd7cf06..a7967f7 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -40,7 +40,7 @@ class App extends BaseConfig * something else. If you have configured your web server to remove this file * from your site URIs, set this variable to an empty string. */ - public string $indexPage = 'index.php'; + public string $indexPage = ''; /** * -------------------------------------------------------------------------- diff --git a/app/Config/Database.php b/app/Config/Database.php index 65cfa5c..6585c06 100644 --- a/app/Config/Database.php +++ b/app/Config/Database.php @@ -29,7 +29,7 @@ class Database extends Config 'hostname' => 'localhost', 'username' => 'root', 'password' => '', - 'database' => 'doctor_appointment_system', + 'database' => '', 'DBDriver' => 'MySQLi', 'DBPrefix' => '', 'pConnect' => false, diff --git a/app/Config/Filters.php b/app/Config/Filters.php index c8aa408..8101672 100644 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -12,6 +12,7 @@ use CodeIgniter\Filters\InvalidChars; use CodeIgniter\Filters\PageCache; use CodeIgniter\Filters\PerformanceMetrics; use CodeIgniter\Filters\SecureHeaders; +use App\Filters\AuthSession; class Filters extends BaseFilters { @@ -34,6 +35,7 @@ class Filters extends BaseFilters 'forcehttps' => ForceHTTPS::class, 'pagecache' => PageCache::class, 'performance' => PerformanceMetrics::class, + 'authSession' => AuthSession::class, ]; /** @@ -72,7 +74,7 @@ class Filters extends BaseFilters */ public array $globals = [ 'before' => [ - 'csrf', + // 'csrf', // temporarily disabled for local testing ], 'after' => [ // 'honeypot', @@ -104,5 +106,9 @@ class Filters extends BaseFilters * * @var array>> */ - public array $filters = []; + public array $filters = [ + 'authSession' => [ + 'before' => ['admin/*','doctor/*','patient/*','logout',], + ], + ]; } diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 78ef36f..6e2ef6e 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -7,12 +7,33 @@ use CodeIgniter\Router\RouteCollection; */ // $routes->get('/', 'Home::index'); $routes->get('/', 'Auth::login'); -$routes->post('/login', 'Auth::loginProcess'); +$routes->post('/login', 'Auth::loginProcess', ['as' => 'login']); $routes->get('/register', 'Auth::register'); $routes->post('/register', 'Auth::registerProcess'); + $routes->get('/logout', 'Auth::logout'); $routes->get('/admin/dashboard', 'Admin::dashboard'); +$routes->get('/admin/doctors', 'Admin::doctors'); +$routes->get('/admin/doctors/add', 'Admin::addDoctor'); +$routes->post('/admin/doctors/add', 'Admin::storeDoctor'); $routes->get('/admin/patients', 'Admin::patients'); -$routes->get('/admin/deletePatient/(:num)', 'Admin::deletePatient/$1'); -$routes->get('/patient/dashboard', 'Patient::dashboard'); +$routes->get('/admin/appointments', 'Admin::appointments'); +$routes->get('/admin/deleteDoctor/(:num)', 'Admin::deleteDoctor/$1'); +$routes->get('/admin/deletePatient/(:num)', 'Admin::deletePatient/$1'); + +$routes->get('/patient/dashboard', 'Patient::dashboard'); +$routes->post('/book-appointment', 'Patient::bookAppointment'); + +$routes->get('/doctor/dashboard', 'Doctor::dashboard'); +$routes->get('/doctor/profile', 'Doctor::profile'); +$routes->post('/doctor/profile', 'Doctor::profile'); +$routes->post('/doctor/appointment/(:num)/accept', 'Doctor::accept/$1'); +$routes->post('/doctor/appointment/(:num)/reject', 'Doctor::reject/$1'); + +$routes->get('/forgot-password', 'Auth::forgotPassword'); +$routes->post('/forgot-password', 'Auth::processForgotPassword'); +$routes->get('/reset-password/(:any)', 'Auth::resetPassword/$1'); +$routes->post('/reset-password', 'Auth::processResetPassword'); + +$routes->get('/admin/dashboard', 'Admin::dashboard'); \ No newline at end of file diff --git a/app/Controllers/Admin.php b/app/Controllers/Admin.php index 2595c83..179bac2 100644 --- a/app/Controllers/Admin.php +++ b/app/Controllers/Admin.php @@ -3,7 +3,9 @@ namespace App\Controllers; use App\Models\UserModel; +use App\Models\DoctorModel; use App\Models\PatientModel; +use App\Models\AppointmentModel; class Admin extends BaseController { @@ -12,10 +14,62 @@ class Admin extends BaseController if ($r = $this->requireRole('admin')) { return $r; } - $patientModel = new PatientModel(); + + $doctorModel = new DoctorModel(); + $patientModel = new PatientModel(); + $appointmentModel = new AppointmentModel(); + + $data['totalDoctors'] = $doctorModel->countAll(); $data['totalPatients'] = $patientModel->countAll(); + $data['totalAppointments'] = $appointmentModel->countAll(); + return view('admin/dashboard', $data); } + public function doctors() + { + if ($r = $this->requireRole('admin')) { + return $r; + } + + $db = \Config\Database::connect(); + + $query = $db->query(" + SELECT users.id, users.name, users.email, doctors.specialization + FROM users + JOIN doctors ON doctors.user_id = users.id + WHERE users.role = 'doctor' + "); + + $data['doctors'] = $query->getResult(); + + return view('admin/doctors', $data); + } + + 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(); + $db = \Config\Database::connect(); + + $doctor = $doctorModel->where('user_id', $id)->first(); + if ($doctor) { + $db->table('appointments')->where('doctor_id', $doctor['id'])->delete(); + $doctorModel->delete($doctor['id']); + } + + $userModel->delete($id); + + return redirect()->to(site_url('admin/doctors')); + } public function patients() { @@ -36,7 +90,7 @@ class Admin extends BaseController return view('admin/patients', $data); } - + public function deletePatient($id) { if ($r = $this->requireRole('admin')) { @@ -63,4 +117,159 @@ class Admin extends BaseController return redirect()->to(site_url('admin/patients')); } -} \ No newline at end of file + public function appointments() + { + if ($r = $this->requireRole('admin')) { + return $r; + } + + $db = \Config\Database::connect(); + + $query = $db->query(" + SELECT a.*, u1.name as patient_name, u2.name as doctor_name + FROM appointments a + JOIN patients p ON p.id = a.patient_id + JOIN users u1 ON u1.id = p.user_id + JOIN doctors d ON d.id = a.doctor_id + JOIN users u2 ON u2.id = d.user_id + "); + + $data['appointments'] = $query->getResult(); + + return view('admin/appointments', $data); + } + + public function addDoctor() + { + if ($r = $this->requireRole('admin')) { + return $r; + } + + return view('admin/add_doctor'); + } + + public function storeDoctor() + { + if ($r = $this->requireRole('admin')) { + return $r; + } + + $userModel = new UserModel(); + $doctorModel = new DoctorModel(); + $db = \Config\Database::connect(); + $validation = \Config\Services::validation(); + + $entries = $this->request->getPost('doctors'); + if (! is_array($entries) || $entries === []) { + $entries = [[ + 'name' => $this->request->getPost('name'), + 'email' => $this->request->getPost('email'), + 'password' => $this->request->getPost('password'), + 'specialization' => $this->request->getPost('specialization'), + 'experience' => $this->request->getPost('experience'), + 'fees' => $this->request->getPost('fees'), + 'available_from' => $this->request->getPost('available_from'), + 'available_to' => $this->request->getPost('available_to'), + ]]; + } + + $rules = [ + 'name' => 'required|min_length[3]|max_length[100]', + 'email' => 'required|valid_email', + 'password' => 'required|min_length[8]', + 'specialization' => 'required|min_length[2]|max_length[191]', + 'experience' => 'permit_empty|max_length[100]', + 'fees' => 'permit_empty|decimal', + 'available_from' => 'permit_empty|valid_date[H:i]', + 'available_to' => 'permit_empty|valid_date[H:i]', + ]; + + $emailsSeen = []; + $cleanRows = []; + $error = null; + + foreach ($entries as $i => $row) { + $row = [ + 'name' => trim((string) ($row['name'] ?? '')), + 'email' => trim((string) ($row['email'] ?? '')), + 'password' => (string) ($row['password'] ?? ''), + 'specialization' => trim((string) ($row['specialization'] ?? '')), + 'experience' => trim((string) ($row['experience'] ?? '')), + 'fees' => trim((string) ($row['fees'] ?? '')), + 'available_from' => trim((string) ($row['available_from'] ?? '')), + 'available_to' => trim((string) ($row['available_to'] ?? '')), + ]; + + $rowNumber = $i + 1; + + if (! $validation->setRules($rules)->run($row)) { + $rowErrors = $validation->getErrors(); + $error = 'Row ' . $rowNumber . ': ' . implode(', ', array_values($rowErrors)); + break; + } + + $emailKey = strtolower($row['email']); + if (isset($emailsSeen[$emailKey])) { + $error = 'Row ' . $rowNumber . ': Duplicate email in submitted rows.'; + break; + } + $emailsSeen[$emailKey] = true; + + if ($userModel->where('email', $row['email'])->first()) { + $error = 'Row ' . $rowNumber . ': Email already exists.'; + break; + } + + $cleanRows[] = $row; + } + + if ($error !== null || $cleanRows === []) { + return redirect()->back()->withInput()->with('error', $error ?? 'Please provide at least one doctor row.'); + } + + $db->transStart(); + + foreach ($cleanRows as $row) { + $userData = [ + 'name' => $row['name'], + 'email' => $row['email'], + 'password' => password_hash($row['password'], PASSWORD_DEFAULT), + 'role' => 'doctor', + 'status' => 'active', + ]; + + if (! $userModel->skipValidation(true)->insert($userData)) { + $db->transRollback(); + + return redirect()->back()->withInput()->with('error', 'Could not create user for ' . $row['email'] . '.'); + } + + $userId = (int) $userModel->getInsertID(); + + $doctorRow = [ + 'user_id' => $userId, + 'specialization' => $row['specialization'], + 'experience' => $row['experience'] !== '' ? $row['experience'] : null, + 'fees' => $row['fees'] !== '' ? $row['fees'] : null, + 'available_from' => $row['available_from'] !== '' ? $row['available_from'] : null, + 'available_to' => $row['available_to'] !== '' ? $row['available_to'] : null, + ]; + + if (! $doctorModel->skipValidation(true)->insert($doctorRow)) { + $db->transRollback(); + + return redirect()->back()->withInput()->with('error', 'Could not create doctor profile for ' . $row['email'] . '.'); + } + } + + $db->transComplete(); + + if (! $db->transStatus()) { + return redirect()->back()->withInput()->with('error', 'Transaction failed.'); + } + + $created = count($cleanRows); + + return redirect()->to(site_url('admin/doctors'))->with('success', $created . ' doctor account(s) created.'); + } +} diff --git a/app/Controllers/Auth.php b/app/Controllers/Auth.php index e23cc49..7674289 100644 --- a/app/Controllers/Auth.php +++ b/app/Controllers/Auth.php @@ -20,8 +20,9 @@ class Auth extends BaseController public function registerProcess() { $rules = [ - 'name' => 'required|min_length[3]|max_length[100]', + 'name' => 'required|min_length[3]|max_length[100]|alpha_numeric_punct', 'email' => 'required|valid_email|is_unique[users.email]', + 'phone' => 'required|min_length[10]|max_length[10]', 'password' => 'required|min_length[8]', ]; @@ -36,6 +37,7 @@ class Auth extends BaseController 'email' => $this->request->getPost('email'), 'password' => password_hash((string) $this->request->getPost('password'), PASSWORD_DEFAULT), 'role' => 'patient', + 'status' => 'active', ]; if (! $userModel->skipValidation(true)->insert($data)) { @@ -45,7 +47,10 @@ class Auth extends BaseController $user_id = $userModel->getInsertID(); $patientModel = new PatientModel(); - $patientModel->insert(['user_id' => $user_id]); + $patientModel->insert([ + 'user_id' => $user_id, + 'phone' => $this->request->getPost('phone'), + ]); return redirect()->to(site_url('/'))->with('success', 'Account created. You can log in now.'); } @@ -69,9 +74,17 @@ class Auth extends BaseController $user = $userModel->where('email', $email)->first(); if ($user && password_verify((string) $password, $user['password'])) { + $loginToken = bin2hex(random_bytes(32)); + + if (! $userModel->update($user['id'], ['session_token' => $loginToken])) { + return redirect()->back()->withInput()->with('error', 'Could not start session. Please try again.'); + } + + session()->regenerate(); session()->set([ - 'id' => $user['id'], - 'role' => $user['role'], + 'id' => $user['id'], + 'role' => $user['role'], + 'login_token' => $loginToken, ]); if ($user['role'] === 'admin') { @@ -89,8 +102,93 @@ class Auth extends BaseController public function logout() { + $userId = (int) session()->get('id'); + $token = (string) session()->get('login_token'); + + if ($userId > 0 && $token !== '') { + $db = \Config\Database::connect(); + $db->table('users') + ->where('id', $userId) + ->where('session_token', $token) + ->update(['session_token' => null]); + } + session()->destroy(); return redirect()->to(site_url('/')); } + public function forgotPassword() + { + return view('auth/forgot_password'); + } + + public function processForgotPassword() + { + $rules = [ + 'email' => 'required|valid_email', + ]; + + if (! $this->validate($rules)) { + return redirect()->back()->withInput(); + } + + $userModel = new UserModel(); + $email = $this->request->getPost('email'); + $user = $userModel->where('email', $email)->first(); + + if (! $user) { + return redirect()->back()->with('error', 'Email not found.'); + } + $resetToken = bin2hex(random_bytes(32)); + $tokenExpires = date('Y-m-d H:i:s', strtotime('+30 minutes')); + + $userModel->update($user['id'], [ + 'reset_token' => $resetToken, + 'reset_token_expires' => $tokenExpires, + ]); + $resetLink = site_url("reset-password/$resetToken"); + return redirect()->back()->with('success', "Reset link: $resetLink"); + } + + public function resetPassword($token) + { + $userModel = new UserModel(); + $user = $userModel->where('reset_token', $token)->first(); + + if (! $user || strtotime($user['reset_token_expires']) < time()) { + return redirect()->to(site_url('/'))->with('error', 'Invalid or expired reset link.'); + } + + return view('auth/reset_password', ['token' => $token]); + } + + public function processResetPassword() + { + $rules = [ + 'token' => 'required', + 'password' => 'required|min_length[8]', + ]; + + if (! $this->validate($rules)) { + return redirect()->back()->withInput(); + } + + $userModel = new UserModel(); + $token = $this->request->getPost('token'); + $newPassword = $this->request->getPost('password'); + + $user = $userModel->where('reset_token', $token)->first(); + + if (! $user || strtotime($user['reset_token_expires']) < time()) { + return redirect()->to(site_url('/'))->with('error', 'Invalid or expired reset link.'); + } + + $userModel->update($user['id'], [ + 'password' => password_hash($newPassword, PASSWORD_DEFAULT), + 'reset_token' => null, + 'reset_token_expires' => null, + ]); + + return redirect()->to(site_url('/'))->with('success', 'Password reset successful. You can now login.'); + } } diff --git a/app/Controllers/Doctor.php b/app/Controllers/Doctor.php index 1cdf466..b6795e0 100644 --- a/app/Controllers/Doctor.php +++ b/app/Controllers/Doctor.php @@ -132,7 +132,7 @@ class Doctor extends BaseController return redirect()->back()->with('error', 'Invalid appointment.'); } - $status = \App\Models\AppointmentModel::normalizeStatus($status); + $status = AppointmentModel::normalizeStatus($status); $appointmentModel->update($appointmentId, ['status' => $status]); return redirect()->back()->with('success', 'Appointment updated.'); diff --git a/app/Controllers/Patient.php b/app/Controllers/Patient.php index b2dabfb..4144afe 100644 --- a/app/Controllers/Patient.php +++ b/app/Controllers/Patient.php @@ -2,7 +2,7 @@ namespace App\Controllers; - +use App\Models\AppointmentModel; use App\Models\PatientModel; class Patient extends BaseController @@ -43,7 +43,7 @@ class Patient extends BaseController JOIN doctors ON doctors.id = a.doctor_id JOIN users u ON u.id = doctors.user_id WHERE a.patient_id = ? - ORDER BY a.appointment_date DESC, a.appointment_time DESC + ORDER BY a.appointment_date ASC, a.appointment_time ASC ', [$patient['id']])->getResult(); } @@ -57,7 +57,7 @@ class Patient extends BaseController } $rules = [ - 'doctor_id' => 'required|integer', + 'doctor_id' => 'required|integer', 'date' => 'required|valid_date', 'time' => 'required', ]; @@ -66,7 +66,7 @@ class Patient extends BaseController return redirect()->back()->withInput(); } - + $appointmentModel = new AppointmentModel(); $patientModel = new PatientModel(); $userId = (int) session()->get('id'); @@ -85,6 +85,21 @@ class Patient extends BaseController 'appointment_time' => $appointmentTime, ]; - + $taken = $appointmentModel + ->where('doctor_id', $data['doctor_id']) + ->where('appointment_date', $data['appointment_date']) + ->where('appointment_time', $appointmentTime) + ->whereIn('status', ['pending', 'approved']) + ->first(); + + if ($taken) { + return redirect()->back()->withInput()->with('error', 'That time slot is already booked for this doctor. Please choose another date or time.'); + } + + if (! $appointmentModel->insert($data)) { + return redirect()->back()->withInput()->with('error', 'Could not book appointment.'); + } + + return redirect()->to(site_url('patient/dashboard'))->with('success', 'Appointment requested.'); } } diff --git a/app/Database/Migrations/2026-03-29-120000_InitAppointmentSchema.php b/app/Database/Migrations/2026-03-29-120000_InitAppointmentSchema.php index 655e403..d87f336 100644 --- a/app/Database/Migrations/2026-03-29-120000_InitAppointmentSchema.php +++ b/app/Database/Migrations/2026-03-29-120000_InitAppointmentSchema.php @@ -53,7 +53,7 @@ class InitAppointmentSchema extends Migration 'doctor_id' => ['type' => 'INT', 'unsigned' => true], 'appointment_date' => ['type' => 'DATE'], 'appointment_time' => ['type' => 'TIME'], - 'status' => ['type' => 'VARCHAR', 'constraint' => 20, 'default' => 'pending'], + 'status' => ['type' => 'ENUM', 'constraint' => ['pending', 'approved', 'rejected'], 'default' => 'pending'], ]); $this->forge->addKey('id', true); $this->forge->addKey(['doctor_id', 'appointment_date', 'appointment_time']); diff --git a/app/Database/Migrations/2026-03-31-120001_AddPasswordResetColumns.php b/app/Database/Migrations/2026-03-31-120001_AddPasswordResetColumns.php new file mode 100644 index 0000000..e90e604 --- /dev/null +++ b/app/Database/Migrations/2026-03-31-120001_AddPasswordResetColumns.php @@ -0,0 +1,21 @@ +forge->addColumn('users', [ + 'reset_token' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true], + 'reset_token_expires' => ['type' => 'DATETIME', 'null' => true], + ]); + } + + public function down(): void + { + $this->forge->dropColumn('users', ['reset_token', 'reset_token_expires']); + } +} diff --git a/app/Database/Migrations/2026-04-01-000000_UpdateAppointmentStatusEnum.php b/app/Database/Migrations/2026-04-01-000000_UpdateAppointmentStatusEnum.php new file mode 100644 index 0000000..3543142 --- /dev/null +++ b/app/Database/Migrations/2026-04-01-000000_UpdateAppointmentStatusEnum.php @@ -0,0 +1,41 @@ +forge->modifyColumn('appointments', [ + 'status' => [ + 'name' => 'status', + 'type' => 'ENUM', + 'constraint' => ['pending', 'approved', 'rejected'], + 'default' => 'pending', + 'null' => false, + ], + ]); + + // Convert legacy status values + $db = \Config\Database::connect(); + $db->query("UPDATE `appointments` SET `status`='pending' WHERE `status` IS NULL OR `status`='' OR `status` NOT IN ('pending', 'approved', 'rejected')"); + $db->query("UPDATE `appointments` SET `status`='approved' WHERE `status`='confirmed'"); + $db->query("UPDATE `appointments` SET `status`='rejected' WHERE `status`='cancelled'"); + } + + public function down(): void + { + $this->forge->modifyColumn('appointments', [ + 'status' => [ + 'name' => 'status', + 'type' => 'ENUM', + 'constraint' => ['pending', 'confirmed', 'cancelled'], + 'default' => 'pending', + 'null' => false, + ], + ]); + } +} diff --git a/app/Database/Migrations/2026-04-02-000001_AddSessionTokenToUsers.php b/app/Database/Migrations/2026-04-02-000001_AddSessionTokenToUsers.php new file mode 100644 index 0000000..fb20857 --- /dev/null +++ b/app/Database/Migrations/2026-04-02-000001_AddSessionTokenToUsers.php @@ -0,0 +1,27 @@ + [ + 'type' => 'VARCHAR', + 'constraint' => 128, + 'null' => true, + 'after' => 'status', + ], + ]; + + $this->forge->addColumn('users', $fields); + } + + public function down(): void + { + $this->forge->dropColumn('users', 'session_token'); + } +} diff --git a/app/Filters/AuthSession.php b/app/Filters/AuthSession.php new file mode 100644 index 0000000..7ee9900 --- /dev/null +++ b/app/Filters/AuthSession.php @@ -0,0 +1,41 @@ +get('id'); + $role = (string) $session->get('role'); + $token = (string) $session->get('login_token'); + + if ($userId < 1 || $role === '' || $token === '') { + $session->destroy(); + + return redirect()->to(site_url('/'))->with('error', 'Please login first.'); + } + + $userModel = new UserModel(); + $user = $userModel->where('id', $userId)->where('role', $role)->first(); + + if (! $user || empty($user['session_token']) || ! hash_equals((string) $user['session_token'], $token)) { + $session->destroy(); + + return redirect()->to(site_url('/'))->with('error', 'Your session ended because your account logged in from another device/browser.'); + } + + return null; + } + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + // No-op + } +} diff --git a/app/Models/AppointmentModel.php b/app/Models/AppointmentModel.php new file mode 100644 index 0000000..99c3121 --- /dev/null +++ b/app/Models/AppointmentModel.php @@ -0,0 +1,76 @@ + - + - + '', + 'email' => '', + 'password' => '', + 'specialization' => '', + 'experience' => '', + 'fees' => '', + 'available_from' => '', + 'available_to' => '', + ]]; +} +?> -
+
-

Add doctor

+

Add Doctors

getFlashdata('error')): ?>
getFlashdata('error')) ?>
-
+ -
- - - +
+

Add one or many doctors, then submit once.

+
-
- - - -
+
+ $doctor): ?> +
+
+
Doctor No:
+ +
-
- - - -
+
+
+ + +
+
+ + +
-
- - - -
+
+ + +
+
+ + +
-
- - - -
- -
- - - -
- -
-
- - - -
-
- - - -
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
Cancel - +
+ + diff --git a/app/Views/admin/appointments.php b/app/Views/admin/appointments.php new file mode 100644 index 0000000..013d085 --- /dev/null +++ b/app/Views/admin/appointments.php @@ -0,0 +1,54 @@ + + + + + + Appointments + + + + + +
+ +

Appointments

+ +
+ + + + + + + + + + + + + + + + + + + + + + + status ?? '')); ?> + + + + +
Sl NoPatientDoctorDateTimeStatus
patient_name) ?>doctor_name) ?>appointment_date) ?>appointment_time) ?>
+ +
+ + + +
+ + diff --git a/app/Views/admin/patients.php b/app/Views/admin/patients.php index 88a7a9c..660ac4c 100644 --- a/app/Views/admin/patients.php +++ b/app/Views/admin/patients.php @@ -18,7 +18,7 @@ - + diff --git a/app/Views/auth/forgot_password.php b/app/Views/auth/forgot_password.php new file mode 100644 index 0000000..ee9c7d2 --- /dev/null +++ b/app/Views/auth/forgot_password.php @@ -0,0 +1,50 @@ + + + + + + Forgot Password + + + + + +
+
+

Forgot Password

+ + getFlashdata('success')): ?> +
+ getFlashdata('success') ?> +
+ + + getFlashdata('error')): ?> +
getFlashdata('error')) ?>
+ + +
+ + +
+ + + has('errors.email')): ?> +
+ +
+ + + + +
+

+ Remember your password? Login here +

+
+
+
+ + + \ No newline at end of file diff --git a/app/Views/auth/login.php b/app/Views/auth/login.php index 4dff93c..0d342e7 100644 --- a/app/Views/auth/login.php +++ b/app/Views/auth/login.php @@ -21,8 +21,8 @@ getFlashdata('error')): ?>
getFlashdata('error')) ?>
- - + +
@@ -44,6 +44,10 @@ + +

Don't have an account?

Create account diff --git a/app/Views/auth/register.php b/app/Views/auth/register.php index 77f6dca..dd3944e 100644 --- a/app/Views/auth/register.php +++ b/app/Views/auth/register.php @@ -38,6 +38,14 @@
+
+ + + +
+
+ + + + + Reset Password + + + + + +
+
+

Reset Password

+ + getFlashdata('success')): ?> +
getFlashdata('success')) ?>
+ + getFlashdata('error')): ?> +
getFlashdata('error')) ?>
+ + +
+ + + + +
+ + + has('errors.password')): ?> +
+ +
+ + + + +
+

+ Back to Login +

+
+
+
+ + + \ No newline at end of file
#Sl No Name Phone Action