From 0758a70434afa0cc31400f82c2f0309f75239fda Mon Sep 17 00:00:00 2001 From: ivan-sim Date: Thu, 3 Oct 2024 16:14:11 +0700 Subject: [PATCH] GRAB X APOTEK X Hospital PORTAL --- Modules/HospitalPortal/Helpers/GrabHelper.php | 93 +++ .../Http/Controllers/ApotekController.php | 549 ++++++++++++++++++ Modules/HospitalPortal/Routes/api.php | 9 + .../Controllers/Api/NavigationController.php | 1 + database/seeders/NavigationSeeder.php | 24 +- database/seeders/PermissionTableSeeder.php | 13 +- .../hospital-portal/src/components/Logo.tsx | 2 +- .../hospital-portal/src/components/Table.tsx | 497 ++++++++++++++++ .../components/nav-section/vertical/index.tsx | 58 +- .../hospital-portal/src/guards/GuestGuard.tsx | 10 +- .../src/guards/RoleBasedGuard.tsx | 9 +- frontend/hospital-portal/src/lang/en-US.json | 62 +- frontend/hospital-portal/src/lang/id-ID.json | 62 +- .../dashboard/header/AccountPopover.tsx | 16 +- .../dashboard/navbar/NavbarVertical.tsx | 89 ++- .../src/pages/DashboardApotek.tsx | 119 ++++ frontend/hospital-portal/src/routes/index.tsx | 59 +- .../sections/dashboardApotek/TableList.tsx | 450 ++++++++++++++ .../hospital-portal/src/utils/formatTime.ts | 34 ++ 19 files changed, 2107 insertions(+), 49 deletions(-) create mode 100644 Modules/HospitalPortal/Helpers/GrabHelper.php create mode 100644 Modules/HospitalPortal/Http/Controllers/ApotekController.php create mode 100644 frontend/hospital-portal/src/pages/DashboardApotek.tsx create mode 100644 frontend/hospital-portal/src/sections/dashboardApotek/TableList.tsx diff --git a/Modules/HospitalPortal/Helpers/GrabHelper.php b/Modules/HospitalPortal/Helpers/GrabHelper.php new file mode 100644 index 00000000..baf18ae2 --- /dev/null +++ b/Modules/HospitalPortal/Helpers/GrabHelper.php @@ -0,0 +1,93 @@ +client = new Client(); + } + + public function getToken() + { + $url = env('TOKEN_URL_GRAB'); + $headers = [ + 'Content-Type' => 'application/json', + ]; + $body = json_encode([ + 'client_id' => env('CLIENT_ID_GRAB'), + 'client_secret' => env('CLIENT_SECRET_GRAB'), + 'grant_type' => 'client_credentials', + 'scope' => 'grab_express.partner_deliveries', + ]); + + $request = new Request('POST', $url, $headers, $body); + $response = $this->client->send($request); + $data = json_decode($response->getBody()->getContents(), true); + + return $data['access_token'] ?? null; + } + + public function createDelivery($token,$body) + { + $url = env('BASE_URL_GRAB').'/v1/deliveries'; + $headers = [ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ]; + $request = new Request('POST', $url, $headers, $body); + $response = $this->client->send($request); + + return $response->getBody()->getContents(); + } + + public function normalizePhoneNumber($phoneNumber) { + // Remove any non-digit characters (e.g., '+', '-', spaces) + $phoneNumber = preg_replace('/\D/', '', $phoneNumber); + + // Check if the cleaned phone number is numeric + if (!is_numeric($phoneNumber)) { + return null; // Return false or handle the error as needed + } + + // Handle phone numbers starting with '62' or '+62' + if (substr($phoneNumber, 0, 2) === '62') { + $phoneNumber = '0' . substr($phoneNumber, 2); + } + + // Handle phone numbers that already start with '8' + if (substr($phoneNumber, 0, 1) === '8') { + $phoneNumber = '0' . $phoneNumber; + } + + return $phoneNumber; + } + + public function getScheduleTimes() + { + // Create a DateTime object for the current time in Jakarta timezone + $pickupTimeFrom = new DateTime('now', new DateTimeZone('Asia/Jakarta')); + + // Add 30 minutes to the current time for pickupTimeFrom + $pickupTimeFrom->modify('+30 minutes'); + + // Clone the pickupTimeFrom to calculate pickupTimeTo + $pickupTimeTo = clone $pickupTimeFrom; + // Add 1 hour to pickupTimeFrom for pickupTimeTo + $pickupTimeTo->modify('+1 hour'); + + // Format the times to ISO 8601 format (e.g., 2024-10-02T12:37:28+07:00) + return [ + "pickupTimeFrom" => $pickupTimeFrom->format(DateTime::ATOM), + "pickupTimeTo" => $pickupTimeTo->format(DateTime::ATOM), + ]; + } +} diff --git a/Modules/HospitalPortal/Http/Controllers/ApotekController.php b/Modules/HospitalPortal/Http/Controllers/ApotekController.php new file mode 100644 index 00000000..24f18691 --- /dev/null +++ b/Modules/HospitalPortal/Http/Controllers/ApotekController.php @@ -0,0 +1,549 @@ +has('per_page') ? $request->input('per_page') : 10; + + $results = DB::connection('oldlms')->table('tx_prescriptions') + ->join('tx_prescription_orders', 'tx_prescription_orders.nIDPrescription', '=', 'tx_prescriptions.nID') + ->join('tm_users', 'tm_users.nID', '=', 'tx_prescriptions.nIDUser') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('tx_prescriptions.sKodeResep', 'like', "%" . $search . "%") + ->orWhere('tm_users.sFirstName', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + if($orderBy == 'apotek') + { + $orderBy = 'nIDApotek'; + } + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') && !$request->input('end_date'), function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('tx_prescriptions.dTanggalresep', '<', $start_date); + }); + }) + ->when($request->input('status'), function ($query, $status) { + $query->where(function ($query) use ($status) { + $query->where('tx_prescription_orders.sStatus', '=', $status); + }); + }) + ->select( + 'tx_prescriptions.nID as id', + 'tx_prescription_orders.nID as nID_orders', + 'tx_prescriptions.nIDUser', + 'tx_prescriptions.sKodeResep as no_resep', + 'tx_prescriptions.dTanggalResep as tanggal', + 'tx_prescription_orders.nIDApotek', + 'tx_prescription_orders.sStatus as status', + 'tx_prescription_orders.sStatus', + DB::raw(' + CASE + WHEN tx_prescription_orders.sStatus = "waiting_pharmacy" THEN "Diterima" + WHEN tx_prescription_orders.sStatus = "order_prepared" THEN "Siap Diambil" + WHEN tx_prescription_orders.sStatus = "ready" THEN "Cari Kurir" + WHEN tx_prescription_orders.sStatus = "waiting_for_courir" THEN "Kurir Sudah Ambil Pesanan" + WHEN tx_prescription_orders.sStatus = "package_picked_up" THEN "Sedang diantar ke Alamat Tujuan" + WHEN tx_prescription_orders.sStatus = "package_on_delivery" THEN "Selesai" + ELSE "" + END AS button_accept'), + DB::raw('CONCAT(tm_users.sFirstName, " ", tm_users.sMiddleName, " ", tm_users.sLastName) as pasien'), + DB::raw(' + (SELECT CONCAT(u.sFirstName, " ", u.sMiddleName, " ", u.sLastName) + FROM tm_dokter d + LEFT JOIN tm_users u ON u.nID = d.nIDUser + WHERE d.nID = tx_prescriptions.nIDDokter LIMIT 1) AS nama_dokter + '), + DB::raw(' + (SELECT s.sSpesialis + FROM tm_dokter d + LEFT JOIN tm_spesialis s ON s.nID = d.nIDSpesialis + WHERE d.nID = tx_prescriptions.nIDDokter LIMIT 1) AS spesialis + '), + DB::raw(' + (SELECT t.sSIP + FROM tm_dokter d + LEFT JOIN tx_jadwal_dokter t ON t.nIDDokter = d.nID + WHERE d.nID = tx_prescriptions.nIDDokter LIMIT 1) AS sip + '), + DB::raw(' + (SELECT u.sPhone + FROM tm_dokter d + LEFT JOIN tm_users u ON u.nID = d.nIDUser + WHERE d.nID = tx_prescriptions.nIDDokter LIMIT 1) AS no_ponsel_dokter + '), + DB::raw(' + (SELECT tm_users_detail.dTanggalLahir + FROM tm_users_detail + WHERE tm_users_detail.nIDUser = tm_users.nID LIMIT 1) AS tgl_lahir_pasien + '), + DB::raw(' + (SELECT CASE + WHEN tm_users_detail.nIDJenisKelamin = 1 THEN "Male" + ELSE "Female" + END + FROM tm_users_detail + WHERE tm_users_detail.nIDUser = tm_users.nID LIMIT 1) AS jenis_kelamin_pasien + '), + DB::raw(' + (SELECT CONCAT(tm_users_detail.sHeight, "/", tm_users_detail.sWeight) + FROM tm_users_detail + WHERE tm_users_detail.nIDUser = tm_users.nID LIMIT 1) AS tinggi_berat + '), + DB::raw(' + (SELECT tm_users_insurance.sNoPolis + FROM tm_users_insurance + LEFT JOIN tm_insurance ON tm_users_insurance.nIDInsurance = tm_insurance.nID + WHERE tm_users_insurance.nIDUser = tx_prescriptions.nIDUser LIMIT 1) AS no_polis + '), + DB::raw(' + (SELECT tm_insurance.sInsurance + FROM tm_users_insurance + LEFT JOIN tm_insurance ON tm_users_insurance.nIDInsurance = tm_insurance.nID + WHERE tm_users_insurance.nIDUser = tx_prescriptions.nIDUser LIMIT 1) AS perusahaan_asuransi + '), + DB::raw(' + (SELECT tm_users_insurance.sCorporateName + FROM tm_users_insurance + LEFT JOIN tm_insurance ON tm_users_insurance.nIDInsurance = tm_insurance.nID + WHERE tm_users_insurance.nIDUser = tx_prescriptions.nIDUser LIMIT 1) AS nama_perusahaan + '), + DB::raw(' + (SELECT tm_users_insurance.sProductCode + FROM tm_users_insurance + LEFT JOIN tm_insurance ON tm_users_insurance.nIDInsurance = tm_insurance.nID + WHERE tm_users_insurance.nIDUser = tx_prescriptions.nIDUser LIMIT 1) AS kode_produk + '), + DB::raw(' + ( + SELECT + CASE + WHEN tm_users_insurance.sPlanCode = "A" THEN "Alba" + WHEN tm_users_insurance.sPlanCode = "B" THEN "Blue" + WHEN tm_users_insurance.sPlanCode = "S" THEN "Silver" + WHEN tm_users_insurance.sPlanCode = "G" THEN "Gold" + WHEN tm_users_insurance.sPlanCode = "P" THEN "Platinum" + WHEN tm_users_insurance.sPlanCode = "D" THEN "Diamond" + ELSE "" + END AS kelas_asuransi + FROM tm_users_insurance + LEFT JOIN tm_insurance ON tm_users_insurance.nIDInsurance = tm_insurance.nID + WHERE tm_users_insurance.nIDUser = tx_prescriptions.nIDUser + LIMIT 1 + ) AS kelas_asuransi + '), + DB::raw(' + ( + SELECT + CASE + WHEN tm_hubungan_keluarga.sHubunganKeluarga = "Husband" THEN "S" + WHEN tm_hubungan_keluarga.sHubunganKeluarga = "Wife" THEN "I" + WHEN tm_hubungan_keluarga.sHubunganKeluarga = "Child" THEN "A" + ELSE "P" + END AS tipe_member + FROM tm_hubungan_keluarga + WHERE tm_hubungan_keluarga.nID = tm_users.nIDHubunganKeluarga + LIMIT 1 + ) AS tipe_member + '), + 'tx_prescription_orders.sAddress AS alamat_penerima', + 'tx_prescription_orders.sDeliveryMethod AS pengiriman', + 'tx_prescription_orders.sDeliveryPrice AS total_kirim', + 'tx_prescriptions.sNoRefProvider AS noref_dokter', + DB::raw('DATE_ADD(tx_prescriptions.dTanggalResep, INTERVAL 1 DAY) as valid_tanggal'), + 'tx_prescriptions.sNomorPenjamin AS nomor_penjamin', + DB::raw(' + (SELECT tx_livechat.dCreateOn + FROM tx_livechat + WHERE tx_livechat.nID = tx_prescriptions.nIDLivechat LIMIT 1) AS tgl_livechat + '), + 'tx_prescriptions.sDiagnose as diagnosa', + 'tx_prescription_orders.nDeliveryID', + 'tx_prescription_orders.sDeliveryStatus' + ) + ->paginate($limit); + $apotekIds = $results->pluck(value: 'nIDApotek')->unique()->filter(); + $organizations = DB::connection('mysql')->table('organizations') + ->whereIn('id', $apotekIds) + ->pluck('name', 'id'); + $results->getCollection()->transform(function ($item) use ($organizations) { + $item->apotek = $organizations->get($item->nIDApotek, '-'); // Default to 'Unknown' if not found + return $item; + }); + // Transform results to include allergies + $results->getCollection()->transform(function ($item) use ($organizations) { + // Get the allergies for each user + $allergies = $this->getAllergies($item->nIDUser); + + // Concatenate the allergy description + $alergi_desc = ''; + foreach ($allergies as $row) { + $alergi_desc .= $row->sAlergi . ' - ' . $row->sKeterangan . ', '; + } + + // Set apotek and allergies data + $item->apotek = $organizations->get($item->nIDApotek, '-'); // Default to 'Unknown' if not found + $item->alergi_desc = rtrim($alergi_desc, ', '); // Remove the trailing comma + + return $item; + }); + // Extract unique Prescription IDs from the results + $prescriptionIDs = $results->pluck('id')->unique()->filter(); + + // Fetch prescription items based on Prescription IDs + $items = DB::connection('oldlms')->table('tx_prescription_items') + ->whereIn('nIDPrescription', $prescriptionIDs) + ->select('nIDPrescription', 'sItemName', 'sSigna', 'sTiming', 'sDuration', 'nQty', 'sNote') + ->get() + ->groupBy('nIDPrescription'); // Group items by their Prescription ID + // Final transformation: Attach prescription items to each prescription + $results->getCollection()->transform(function ($item) use ($items) { + $item->prescription_items = $items->get($item->id, collect())->map(function ($prescriptionItem) { + return [ + 'sItemName' => $prescriptionItem->sItemName, + 'sSigna' => $prescriptionItem->sSigna, + 'sTiming' => $prescriptionItem->sTiming, + 'sDuration' => $prescriptionItem->sDuration, + 'nQty' => $prescriptionItem->nQty, + 'sNote' => $prescriptionItem->sNote, + ]; + }); + return $item; + }); + return response()->json(Helper::paginateResources($results)); + } + + public function getAllergies($nIDUser) { + return DB::connection('oldlms') + ->table('tx_alergi_users as txa') + ->join('tm_alergi as ta', 'ta.nID', '=', 'txa.nIDAlergi') + ->select('ta.sAlergi', 'ta.sKeterangan') + ->where('txa.nIDUser', $nIDUser) + ->get(); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('hospitalportal::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('hospitalportal::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('hospitalportal::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + $data = ['sStatus' => $request->sStatus]; + $validator = Validator::make($request->all(), [ + 'sStatus' => 'required|string|max:255', + ], [ + 'sStatus.required' => trans('Validation.required',['attribute' => 'Status']), + ]); + + if ($validator->fails()) + { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); + } + else + { + try { + // Define the valid transitions + $valid_transitions = [ + 'waiting_for_payment' => 'waiting_pharmacy', + 'waiting_pharmacy' => 'order_prepared', + 'order_prepared' => 'ready', + 'ready' => 'waiting_for_courir', + 'waiting_for_courir' => 'package_picked_up', + 'package_picked_up' => 'package_on_delivery', + 'package_on_delivery' => 'package_delivered' + ]; + + // Get the current status from the request + $inputStatus = $request->sStatus; + + // Check if the input status has a valid next status in the map + if (isset($valid_transitions[$inputStatus])) { + // Get the next status + $status = $valid_transitions[$inputStatus]; + + // Start the transaction + DB::connection('oldlms')->beginTransaction(); + + // Update the status in the database + DB::connection('oldlms')->table('tx_prescription_orders') + ->where('tx_prescription_orders.nID', '=', $id) + ->update([ + 'sStatus' => $status, + 'sUpdateBy' => auth()->user()->id, + 'dUpdateOn' => date('Y-m-d H:i:s'), + ]); + + // Commit the transaction + DB::connection('oldlms')->commit(); + + // Return success response + return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200); + } else { + // If the input status is not valid, return an error + return ApiResponse::apiResponse('Invalid Status', $data, 'The input status is not valid.', 400); + } + } catch (\Exception $e) { + // Rollback the transaction if an error occurs + DB::connection('oldlms')->rollBack(); + + // Handle error, could log or return as response + return ApiResponse::apiResponse('Server Error', $data, $e->getMessage(), 500); + } + + } + } + + public function getDriver(Request $request, $id) + { + $data = ['sStatus' => $request->sStatus]; + + // Validation + $validator = Validator::make($request->all(), [ + 'sStatus' => 'required|string|max:255', + ], [ + 'sStatus.required' => trans('Validation.required', ['attribute' => 'Status']), + ]); + + if ($validator->fails()) { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); + } + + try { + // Fetch prescription data + $prescriptions = DB::connection('oldlms')->table('tx_prescription_orders') + ->leftJoin('tx_prescriptions', 'tx_prescriptions.nID', '=', 'tx_prescription_orders.nIDPrescription') + ->where('tx_prescription_orders.nID', '=', $id) + ->select( + 'tx_prescriptions.nIDUser', + 'tx_prescriptions.nID as id_prescription', + 'tx_prescriptions.sKodeResep as merchantOrderID', + 'tx_prescription_orders.nIDApotek' + ) + ->first(); + + if (!$prescriptions) { + return ApiResponse::apiResponse('Not Found', [], 'Prescription not found', 404); + } + + // Fetch prescription items (medicine data) + $items = DB::connection('oldlms')->table('tx_prescription_items') + ->where('nIDPrescription', $prescriptions->id_prescription) + ->select('nIDPrescription', 'sItemName', 'nQty', 'nHarga') + ->get(); + + $packages = []; + foreach ($items as $item) { + $packages[] = [ + 'name' => $item->sItemName, + 'description' => $item->sItemName, + 'quantity' => (float)$item->nQty, + 'price' => (float)$item->nHarga, + 'dimensions' => [ + 'height' => 0, + 'width' => 0, + 'depth' => 0, + 'weight' => 0, + ] + ]; + } + + // Fetch pharmacy (apotek) data + $apotek = DB::connection('mysql')->table('organizations') + ->leftJoin('addresses', 'addresses.id', '=', 'organizations.main_address_id') + ->where('organizations.id', '=', $prescriptions->nIDApotek) + ->select('organizations.name as nama_apotek', 'addresses.lat', 'addresses.lng') + ->first(); + + // Fetch patient (pasien) data + $pasien = DB::connection('oldlms')->table('tm_users') + ->leftJoin('tm_users_detail', 'tm_users_detail.nIDuser', '=', 'tm_users.nID') + ->where('tm_users.nID', '=', $prescriptions->nIDUser) + ->select( + DB::raw('CONCAT(tm_users.sFirstName, " ", IFNULL(tm_users.sMiddleName, ""), " ", tm_users.sLastName) as nama_pasien'), + 'tm_users.sFirstName as firstname', + 'tm_users.sLastName as lastname', + 'tm_users.sEmail as email', + 'tm_users.sPhone as phone', + 'tm_users_detail.sLatitude as latitude', + 'tm_users_detail.sLongitude as longitude', + DB::raw('(SELECT tm_provinsi.sProvinsi FROM tm_provinsi WHERE tm_provinsi.nID = tm_users_detail.nIDProvinsi LIMIT 1) AS provinsi'), + DB::raw('(SELECT tm_kota.sKota FROM tm_kota WHERE tm_kota.nID = tm_users_detail.nIDKota LIMIT 1) AS kota'), + DB::raw('(SELECT tm_kecamatan.sKecamatan FROM tm_kecamatan WHERE tm_kecamatan.nID = tm_users_detail.nIDKecamatan LIMIT 1) AS kecamatan'), + DB::raw('(SELECT tm_kelurahan.sKelurahan FROM tm_kelurahan WHERE tm_kelurahan.nID = tm_users_detail.nIDKelurahan LIMIT 1) AS kelurahan'), + DB::raw('(SELECT tm_kelurahan.nKodePos FROM tm_kelurahan WHERE tm_kelurahan.nID = tm_users_detail.nIDKelurahan LIMIT 1) AS kode_pos') + ) + ->first(); + + if (!$pasien) { + return ApiResponse::apiResponse('Not Found', [], 'Patient not found', 404); + } + + if(!$pasien->email) + { + return ApiResponse::apiResponse('Not Found', [], 'Email Patient not found', 404); + } + + if(!$pasien->phone) + { + return ApiResponse::apiResponse('Not Found', [], 'Phone Patient not found', 404); + } + + // Use GrabHelper to handle the API calls + $grabHelper = new GrabHelper(); + $token = $grabHelper->getToken(); + $body = json_encode([ + "merchantOrderID" => $prescriptions->merchantOrderID, + "serviceType" => "INSTANT", + "vehicleType" => "BIKE", + "codType" => "REGULAR", + "paymentMethod" => "CASHLESS", + "highValue" => false, + "packages" => $packages, + "origin" => [ + "address" => $apotek->nama_apotek, + "coordinates" => [ + "latitude" => (float)$apotek->lat, + "longitude" => (float)$apotek->lng + ] + ], + "destination" => [ + "address" => "{$pasien->provinsi}, {$pasien->kota}, {$pasien->kecamatan}, {$pasien->kelurahan}, {$pasien->kode_pos}", + "coordinates" => [ + "latitude" => (float)$pasien->latitude, + "longitude" => (float)$pasien->longitude + ] + ], + "recipient" => [ + "firstName" => $pasien->firstname, + "lastName" => $pasien->lastname, + "email" => $pasien->email, + "phone" => $grabHelper->normalizePhoneNumber($pasien->phone), + "smsEnabled" => true + ], + "sender" => [ + "firstName" => env('SENDER_NAME_GRAB'), + "companyName" => env('SENDER_COMPANY_NAME_GRAB'), + "email" => env('SENDER_EMAIL_GRAB'), + "phone" => env('SENDER_PHONE_GRAB'), + "smsEnabled" => true + ], + "schedule" => $grabHelper->getScheduleTimes() + ]); + + // Create the delivery request + $response = $grabHelper->createDelivery($token, $body); + $data_grab = json_decode($response, true); + + // Transaction to update status + DB::connection('oldlms')->beginTransaction(); + DB::connection('oldlms')->table('tx_prescription_orders') + ->where('tx_prescription_orders.nID', '=', $id) + ->update([ + 'nDeliveryID' => $data_grab['deliveryID'], + 'sDeliveryStatus' => $data_grab['status'], + 'sStatus' => 'waiting_for_courir', + 'sUpdateBy' => auth()->user()->id, + 'dUpdateOn' => now(), + ]); + + + $data_logs = [ + 'sType' => 'out', + 'sContext' => 'grab', + 'sTarget' => 'deliveries/create-grab', + 'sRequest' => $body, + 'sResponse' => $response, + 'sCreateBy' => auth()->user()->id, + 'dCreateOn' => now(), + ]; + + DB::connection('oldlms')->table('api_logs') + ->insert($data_logs); + + DB::connection('oldlms')->commit(); + + // Return success response + return ApiResponse::apiResponse("Success", $data_grab, trans('Message.success'), 200); + + } catch (\Exception $e) { + // Rollback transaction on error + DB::connection('oldlms')->rollBack(); + + return ApiResponse::apiResponse('Server Error', [], $e->getMessage(), 500); + } + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } +} diff --git a/Modules/HospitalPortal/Routes/api.php b/Modules/HospitalPortal/Routes/api.php index a0591a36..d7f4fd37 100755 --- a/Modules/HospitalPortal/Routes/api.php +++ b/Modules/HospitalPortal/Routes/api.php @@ -7,8 +7,10 @@ use Modules\HospitalPortal\Http\Controllers\Api\MemberController; use Modules\HospitalPortal\Http\Controllers\ClaimController; use Modules\HospitalPortal\Http\Controllers\Api\NotificationController; use Modules\HospitalPortal\Http\Controllers\Api\RequestLogController; +use Modules\HospitalPortal\Http\Controllers\ApotekController; use Modules\HospitalPortal\Http\Middleware\Authentication; use Modules\HospitalPortal\Http\Middleware\Authorization; +use Modules\Internal\Http\Controllers\Api\NavigationController; /* |-------------------------------------------------------------------------- @@ -36,6 +38,10 @@ Route::prefix('v1')->group(function() { Route::middleware('auth:sanctum')->group(function () { + // Navigation + Route::get('navigations', [NavigationController::class, 'index']); + + Route::post('logout', [AuthController::class, 'logout'])->name('logout'); Route::get('/user', function (Request $request) { return $request->user(); @@ -66,6 +72,9 @@ Route::prefix('v1')->group(function() { //Set read notification Route::post('set-read-notification', 'setReadNotification'); }); + Route::get('get-prescription-orders',[ApotekController::class, 'index']); + Route::put('put-prescription-orders/{id}',[ApotekController::class, 'update']); + Route::put('put-driver-prescription-orders/{id}',[ApotekController::class, 'getDriver']); }); // Request Final LOG Route::controller(RequestLogController::class)->group(function () { diff --git a/Modules/Internal/Http/Controllers/Api/NavigationController.php b/Modules/Internal/Http/Controllers/Api/NavigationController.php index 74b8e79f..cec2bb8e 100755 --- a/Modules/Internal/Http/Controllers/Api/NavigationController.php +++ b/Modules/Internal/Http/Controllers/Api/NavigationController.php @@ -54,6 +54,7 @@ class NavigationController extends Controller 'permission' => $child['permission'], ]; }, $navItem['children']), + 'icon' => $navItem['icon'], 'permission' => $navItem['permission'], ]; }, $navigationMaster) diff --git a/database/seeders/NavigationSeeder.php b/database/seeders/NavigationSeeder.php index 8581a634..4a4610d0 100755 --- a/database/seeders/NavigationSeeder.php +++ b/database/seeders/NavigationSeeder.php @@ -281,7 +281,27 @@ class NavigationSeeder extends Seeder ] ], 'permission' => 'user-management-client-portal' - ] + ], + ####################### HOSPITAL PORTAL ######################### + [ + 'title' => 'Dashboard', + 'path' => '/dashboard', + 'icon' => 'dashboard', + 'permission' => 'dashboard-hospital-portal' + ], + [ + 'title' => 'Claim', + 'path' => '/claim', + 'icon' => 'ic_booking', + 'permission' => 'dashboard-claim-hospital-portal' + ], + ####################### CS LMS & APOTEK PORTAL ######################### + [ + 'title' => 'Dashboard', + 'path' => '/prescription-orders', + 'icon' => 'dashboard', + 'permission' => 'dashboard-apotek-portal' + ], ]; foreach ($menuItems as $menuItemData) { @@ -292,6 +312,7 @@ class NavigationSeeder extends Seeder [ 'title' => $menuItemData['title'], 'path' => $menuItemData['path'] ?? null, + 'icon' => $menuItemData['icon'] ?? null, 'permission' => $menuItemData['permission'] ?? null ]); @@ -304,6 +325,7 @@ class NavigationSeeder extends Seeder [ 'title' => $childData['title'], 'path' => $childData['path'] ?? null, + 'icon' => $childData['icon'] ?? null, 'parent_id' => $menuItem->id, 'permission' => $childData['permission'] ?? null ]); diff --git a/database/seeders/PermissionTableSeeder.php b/database/seeders/PermissionTableSeeder.php index 232d8e91..098a5eb5 100755 --- a/database/seeders/PermissionTableSeeder.php +++ b/database/seeders/PermissionTableSeeder.php @@ -91,12 +91,21 @@ class PermissionTableSeeder extends Seeder 'user-role-list-client-portal', 'user-access-list-client-portal' ] - ] + ], + ####################### HOSPITAL PORTAL ######################### + [ + 'type' => 'hospital-portal', + 'datas' => [ + 'dashboard-hospital-portal', + 'dashboard-claim-hospital-portal', + 'dashboard-apotek-portal', + ] + ], ]; foreach ($permissions as $values) { foreach ($values['datas'] as $value) { - Permission::updateOrCreate(['name' => $value], + Permission::updateOrCreate(['name' => $value, 'guard_name' => $values['type']], [ 'name' => $value, 'guard_name' => $values['type'] diff --git a/frontend/hospital-portal/src/components/Logo.tsx b/frontend/hospital-portal/src/components/Logo.tsx index de8e5831..8d76167d 100755 --- a/frontend/hospital-portal/src/components/Logo.tsx +++ b/frontend/hospital-portal/src/components/Logo.tsx @@ -25,5 +25,5 @@ export default function Logo({ disabledLink = false, sx }: Props) { return <>{logo}; } - return {logo}; + return {logo}; } diff --git a/frontend/hospital-portal/src/components/Table.tsx b/frontend/hospital-portal/src/components/Table.tsx index d1df63f1..5bd2c78a 100755 --- a/frontend/hospital-portal/src/components/Table.tsx +++ b/frontend/hospital-portal/src/components/Table.tsx @@ -24,12 +24,21 @@ import { Typography, LinearProgress, linearProgressClasses, + Collapse, + Divider, + IconButton, + Modal, + CircularProgress } from '@mui/material'; + +import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; import { visuallyHidden } from '@mui/utils'; import { DatePicker, LocalizationProvider, MobileDatePicker } from '@mui/x-date-pickers'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + +import { fDate, fDateSuffix, fDateTime, fDateTimeWithAge } from '../utils/formatTime'; /* ---------------------------------- axios --------------------------------- */ import axios from '../utils/axios'; /* ---------------------------------- react --------------------------------- */ @@ -50,6 +59,10 @@ import GetAppIcon from '@mui/icons-material/GetApp'; import { LanguageContext } from '@/contexts/LanguageContext'; import CancelIcon from '@mui/icons-material/Cancel'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CloseIcon from '@mui/icons-material/Close'; +import InfoIcon from '@mui/icons-material/Info'; +import { enqueueSnackbar } from 'notistack'; +import useAuth from '@/hooks/useAuth'; /* --------------------------------- styled --------------------------------- */ const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ @@ -79,6 +92,9 @@ export default function Table({ searchs, exportReport, selected, + openRowId, // Receive the currently opened row ID + setOpenRowId, // Receive the function to set the opened row ID + reloadData, }: TableListProps) { /* ------------------------------- handle sort ------------------------------ */ const handleRequestSort = async (event: React.MouseEvent, property: string) => { @@ -209,6 +225,114 @@ export default function Table({ params.setAppliedParams(parameters); }; /* -------------------------------------------------------------------------- */ + //============================== APOTEK =======================================// + const {user} = useAuth(); + const formattedRoleName = user?.role.name; + // List of statuses for 'apotek' +const allowedStatusesForApotek = ['waiting_pharmacy', 'order_prepared']; +const allowedStatusesForCSLMS = ['ready', 'waiting_for_courir', 'package_picked_up', 'package_on_delivery']; + + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(true); + + const handleClick = () => { + // handleClickKonfirmasi(row); // Call the function passed from parent + setOpen(true); // Show modal + setLoading(true); // Show loading animation + + // Simulate finding driver (3 seconds delay) + setTimeout(() => { + setLoading(false); // Hide loading after 3 seconds + }, 3000); + }; + + + + const [openDialogStatus, setOpenDialogStatus] = useState(false); + const [dialogIDRow, setDialogIDRow] = useState(null); + const [dialogIDRowDriver, setDialogIDRowDriver] = useState(null); + const [txtStatusDriver, setTxtStatusDriver] = useState(''); + const [txtIDDriver, setTxtIDDriver] = useState(''); + const handleClose = (row:any) => { + setDialogIDRowDriver(row.id === dialogIDRowDriver ? null : row.id); + if (reloadData) { + reloadData(); + } + }; + const handleCloseDialogUpdate = () => { + setOpenDialogStatus(false); + } + const handleEditDataStatus = (data: any) => { + setOpenDialogStatus(true); + } + const [isDisabled, setIsDisabled] = useState(false); + const handleClickKonfirmasi = (row: any) => { + setTxtStatusDriver(''); + setTxtIDDriver(''); + if(row.sStatus === 'ready') + { + //close + setDialogIDRow(row.id === dialogIDRow ? null : row.id); + //get driver + setDialogIDRowDriver(row.id === dialogIDRowDriver ? null : row.id); + // setOpen(true); // Show modal + setLoading(true); // Show loading animation + + // Simulate finding driver (3 seconds delay) + // setTimeout(() => { + // setLoading(false); // Hide loading after 3 seconds + // }, 3000); + + const updateData = { + sStatus: row.sStatus + }; + + axios + .put(`/put-driver-prescription-orders/${row.nID_orders}`, updateData) + .then((response) => { + setTxtStatusDriver(response?.data?.data?.status); + setTxtIDDriver(response?.data?.data?.deliveryID); + setLoading(false); + // enqueueSnackbar(response?.data?.meta?.message, { variant: 'success' }); + + // Call reloadData to refresh the table + + }) + .catch((error) => { + const errorMessage = error.response?.data?.meta?.message || 'Failed to update status'; + enqueueSnackbar(errorMessage, { variant: 'error' }); + setLoading(false); + setIsDisabled(false); // Re-enable the button after success + }); + } + else + { + putPrescriptionOrders(row); + } + } + const putPrescriptionOrders = (row: any) => { + setIsDisabled(true); // Disable button after clicking + const updateData = { + sStatus: row.sStatus + }; + axios + .put(`/put-prescription-orders/${row.nID_orders}`, updateData) + .then((response) => { + enqueueSnackbar(response?.data?.meta?.message, { variant: 'success' }); + setDialogIDRow(row.id === dialogIDRow ? null : row.id); + setIsDisabled(false); // Re-enable the button after success + + // Call reloadData to refresh the table + if (reloadData) { + reloadData(); + } + }) + .catch((error) => { + const errorMessage = error.response?.data?.meta?.message || 'Failed to update status'; + enqueueSnackbar(errorMessage, { variant: 'error' }); + setIsDisabled(false); // Re-enable the button after success + }); + } return ( // @@ -416,6 +540,7 @@ export default function Table({ ) : rows && rows.length >= 1 ? ( rows.map((row, rowIndex) => ( + <> {!selected.useSelected ? ( '' @@ -440,6 +565,378 @@ export default function Table({ ))} + {/* COLLAPSIBLE ROW */} + + + + + {/* Icon inside a Box for spacing and alignment */} + + + + {/* Adjust size and color */} + + + {row.status} + + + + {row.nDeliveryID && ( + + + Grab Delivery ID: {row.nDeliveryID} + + + )} + + + {setOpenRowId(openRowId === row.id ? null : row.id);}} sx={{ padding: '20px', maxWidth: '1200px', margin: '0 auto', backgroundColor: '#ffffff', borderRadius: '8px' }}> + + + {/* Row 1 */} + + {localeData.txtProviderDoctorInformation} + + + {localeData.txtNamaDokter} : + + + {row.nama_dokter} + + + {localeData.txtSpesialisasiDokter} : + + + {row.spesialis} + + + SIP : + + + {row.sip} + + + {localeData.txtNoPonsel} : + + + {row.no_ponsel_dokter} + + + + + + {localeData.txtInformasiPasien} + + + {localeData.txtNamaPasien} : + + + {row.pasien} + + + {localeData.txtTanggalLahirUmur} : + + + {row.tgl_lahir_pasien ? fDateTimeWithAge(row.tgl_lahir_pasien) : ''} + + + {localeData.txtJenisKelamin} : + + + {row.jenis_kelamin_pasien} + + + {localeData.txtTinggi} : + + + {row.tinggi_berat} + + + + + {/* Divider */} + + + + + + {localeData.txtInformasiAsuransi} + + + {localeData.txtNomorKartu} : + + + {row.no_polis} + + + {localeData.txtAsuransi} : + + + {row.perusahaan_asuransi} + + + {localeData.txtPerusahaan} : + + + {row.nama_perusahaan} + + + {localeData.txtTipeAsuransi} : + + + {row.kode_produk} + + + {localeData.txtKelasAsuransi} : + + + {row.kelas_asuransi} + + + {localeData.txtTipeMember} : + + + {row.tipe_member} + + + + + + {localeData.txtInformasiOrder} + + + {localeData.txtNamaPenerima} : + + + {row.pasien} + + + {localeData.txtAlamatPenerima} : + + + {row.alamat_penerima} + + + {localeData.txtPengiriman} : + + + {row.pengiriman} + + + {localeData.txtTotal} : + + + {row.total_kirim} + + + + + {/* Divider */} + + + + + {/* Row 2 */} + + {localeData.txtInformasiResep} + + + {localeData.txtNomorResep} : + + + {row.no_resep} + + + {localeData.txtNomorRefDokter} : + + + {row.noref_dokter} + + + {localeData.txtTanggalTerbitResep} : + + + {row.tanggal} + + + {localeData.txtTanggalKedaluwarsa} : + + + {row.valid_tanggal} + + + + + + {localeData.txtInformasiKonsultasi} + + + {localeData.txtNomorPenjamin} : + + + {row.nomor_penjamin} + + + {localeData.txtTanggalKonsultasi} : + + + {row.tgl_livechat} + + + {localeData.txtAlergi} : + + + {row.alergi_desc} + + + {localeData.txtDiagnosis} : + + + {row.diagnosa} + + + + + + + + {setOpenRowId(openRowId === row.id ? null : row.id);}} sx={{ padding: '20px', maxWidth: '1200px', margin: '20px auto', backgroundColor: '#ffffff', borderRadius: '8px' }}> + + + {localeData.txtObat} + {localeData.txtSigna} + {localeData.txtWaktu} + {localeData.txtDurasi} + {localeData.txtJumlah} + {localeData.txtCatatanObat} + + + + {row.prescription_items ? + ( + row.prescription_items.map((row : any, rowIndex: any) => ( + + {row.sItemName} + {row.sSigna} + {row.sTiming} + {row.sDuration} + {row.nQty} + {row.sNote} + + )) + ) : ( + + + {localeData.txtDataNotFound} + + + )} + + + + + {/* Close Button on the Left */} + + {/* Accept Button on the Right */} + {row.button_accept && formattedRoleName === 'admin-apotek' && allowedStatusesForApotek.includes(row.sStatus) ? ( + + ) : ''} + {row.button_accept && formattedRoleName === 'cs-lms' && allowedStatusesForCSLMS.includes(row.sStatus) ? ( + + ) : ''} + + + + + {/* Dialog Update Status */} + + + + + {localeData.txtConfirmation} + + setDialogIDRow(row.id === dialogIDRow ? null : row.id)}> + + + + + + + {localeData.txtDialogConfirmation} + + + {localeData.txtNomorResep} + {row.no_resep} + + + {localeData.txtTanggalTerbitResep} + {row.tanggal} + + + + + + + + + {/* Modal for fullscreen display */} + handleClose(row)}> + + {loading ? ( + <> + + + {localeData.txtFindingDriver} + + + ) : ( + <> + Info! + + {txtIDDriver ? ( + `${localeData.txtDriverFound} ${txtIDDriver} status ${txtStatusDriver}.` + ) : ( + // You can add an alternative UI or message here, or leave it empty + `Failed to get driver, please check the message info.` + )} + + + + + + )} + + + + + + + )) ) : ( diff --git a/frontend/hospital-portal/src/components/nav-section/vertical/index.tsx b/frontend/hospital-portal/src/components/nav-section/vertical/index.tsx index de247933..de4b13e3 100755 --- a/frontend/hospital-portal/src/components/nav-section/vertical/index.tsx +++ b/frontend/hospital-portal/src/components/nav-section/vertical/index.tsx @@ -28,26 +28,42 @@ export default function NavSectionVertical({ isCollapse = false, ...other }: NavSectionProps) { - return ( - - {navConfig.map((group, index) => ( - - - {group.subheader} - + return ( + + {navConfig.length === 0 ? ( // Check if navConfig is empty + + + {/* Optional: You can put a title or leave it empty */} + + +
Loading...
{/* Adjust marginTop as needed */} +
+ ) : ( + navConfig.map((group, index) => ( + + + {group.subheader} + + + {group.items.map((list) => ( + + ))} + + )) + )} +
+ ); - {group.items.map((list) => ( - - ))} -
- ))} -
- ); } diff --git a/frontend/hospital-portal/src/guards/GuestGuard.tsx b/frontend/hospital-portal/src/guards/GuestGuard.tsx index 9446f89e..b72964f9 100755 --- a/frontend/hospital-portal/src/guards/GuestGuard.tsx +++ b/frontend/hospital-portal/src/guards/GuestGuard.tsx @@ -13,8 +13,14 @@ type GuestGuardProps = { export default function GuestGuard({ children }: GuestGuardProps) { const { isAuthenticated } = useAuth(); - - if (isAuthenticated) { + const {user} = useAuth(); + const formattedRoleName = user?.role.name; + if((formattedRoleName === 'admin-apotek' || formattedRoleName === 'cs-lms') && isAuthenticated) + { + return ; + } + if(formattedRoleName === 'hospital-admin) + { return ; } diff --git a/frontend/hospital-portal/src/guards/RoleBasedGuard.tsx b/frontend/hospital-portal/src/guards/RoleBasedGuard.tsx index ebc6a9fe..4db3314b 100755 --- a/frontend/hospital-portal/src/guards/RoleBasedGuard.tsx +++ b/frontend/hospital-portal/src/guards/RoleBasedGuard.tsx @@ -1,5 +1,7 @@ import { ReactNode } from 'react'; import { Container, Alert, AlertTitle } from '@mui/material'; +import useAuth from '@/hooks/useAuth'; +import Page404 from '@/pages/Page404'; // ---------------------------------------------------------------------- @@ -9,9 +11,10 @@ type RoleBasedGuardProp = { }; const useCurrentRole = () => { - // Logic here to get current user role - const role = 'admin'; - return role; + // Fetch the role from useAuth + const { user } = useAuth(); + const formattedRoleName = user?.role.name || ''; // Default to empty string if role is undefined + return formattedRoleName; }; export default function RoleBasedGuard({ accessibleRoles, children }: RoleBasedGuardProp) { diff --git a/frontend/hospital-portal/src/lang/en-US.json b/frontend/hospital-portal/src/lang/en-US.json index 2f260eca..f0cc26c8 100755 --- a/frontend/hospital-portal/src/lang/en-US.json +++ b/frontend/hospital-portal/src/lang/en-US.json @@ -2,7 +2,7 @@ "greeting": "Hello", "buttonText": "Click Me", "infoLogin": "Enter the registered account", - "txtLogin1" : "Sign in to Hospital Portal", + "txtLogin1" : "Sign in", "txtLogin2" : "Enter your details below", "txtCardSearchMember1" : "Membership Query", "txtCardSearchMember2" : "Search Member", @@ -28,7 +28,7 @@ "txtRequestCode" : "Request Code", "txtName" : "Name", "txtStatus" : "Status", - "txtSearch" : "Search Name or Member ID...", + "txtSearch" : "Search...", "txtAll" : "All", "txtSubmissionDate" : "Admission Date", "txtDataNotFound" : "Data Not Found", @@ -77,6 +77,62 @@ "txtSecond": "Second", "txtPleaseInput": "Please enter your new password.", "txtNewPassword": "New Password", - "txtConfPassword": "Confirm Kata Sandi" + "txtConfPassword": "Confirm Kata Sandi", + "txtPrescriptionNumber" : "Prescription Number", + "txtPrescriptionDate" : "Date", + "txtPrescriptionPatient" : "Patient", + "txtPrescriptionPharmacy" : "Pharmacy", + "txtPrescriptionDetail" : "Detail", + "txtPrescriptionDownload" : "Download Prescription", + "txtProviderDoctorInformation" : "Provider & Doctor Information", + "txtInformasiPasien" : "Patient Information", + "txtNamaDokter" : "Doctor's Name", + "txtSpesialisasiDokter" : "Doctor's Specialization", + "txtNoPonsel" : "Mobile No.", + "txtInformasiAsuransi": "Insurance Information", + "txtNomorKartu": "Card Number", + "txtAsuransi": "Insurance", + "txtPerusahaan": "Company", + "txtTipeAsuransi": "Insurance Type", + "txtKelasAsuransi": "Insurance Class", + "txtTipeMember": "Member Type", + "txtInformasiOrder": "Order Information", + "txtNamaPenerima": "Recipient Name", + "txtAlamatPenerima": "Recipient Address", + "txtPengiriman": "Delivery", + "txtTotal": "Total", + "txtInformasiResep": "Prescription Information", + "txtNomorResep": "Prescription Number", + "txtNomorRefDokter": "Doctor's Reference Number", + "txtTanggalTerbitResep": "Prescription Issue Date", + "txtTanggalKedaluwarsa": "Expiration Date", + "txtInformasiKonsultasi": "Consultation Information", + "txtNomorPenjamin": "Guarantor Number", + "txtTanggalKonsultasi": "Consultation Date", + "txtAlergi": "Allergy", + "txtDiagnosis": "Diagnosis", + "txtObat": "Medicine", + "txtSigna": "Signa", + "txtWaktu": "Time", + "txtDurasi": "Duration", + "txtJumlah": "Quantity", + "txtCatatanObat": "Medicine Notes", + "txtDiterima": "Received", + "txtNamaPasien": "Patient Name", + "txtTanggalLahirUmur": "Date of Birth / Age", + "txtJenisKelamin": "Gender", + "txtTinggi" : "Height / Weight", + "txtLabelNew": "New", + "txtLabelAccepted": "Accepted", + "txtLabelReady": "Ready", + "txtLabelWaitingCourir": "Waiting Courir", + "txtLabelPackagePickedUp": "Package Picked Up", + "txtLabelPackageOnDelivery": "Package On Delivery", + "txtLabelPackageDelivered": "Package Delivered", + "txtLabelWaitingForPayment": "Waiting For Payment", + "txtOK": "Yes", + "txtFindingDriver": "Finding a driver...", + "txtDriverFound": "The driver’s been grabbed with the ID", + "txtButtonClose": "Close" } diff --git a/frontend/hospital-portal/src/lang/id-ID.json b/frontend/hospital-portal/src/lang/id-ID.json index 52ea72a6..11264a04 100755 --- a/frontend/hospital-portal/src/lang/id-ID.json +++ b/frontend/hospital-portal/src/lang/id-ID.json @@ -2,7 +2,7 @@ "greeting": "Halo", "buttonText": "Klik Saya", "infoLogin": "Masukan akun yang telah terdaftar", - "txtLogin1" : "Masuk ke Hospital Portal", + "txtLogin1" : "Sign in", "txtLogin2" : "Masukkan detail Anda di bawah ini", "txtCardSearchMember1" : "Pengajuan Jaminan", "txtCardSearchMember2" : "Cari Anggota", @@ -28,7 +28,7 @@ "txtRequestCode" : "Kode Pengajuan", "txtName" : "Nama", "txtStatus" : "Status", - "txtSearch" : "Cari Nama atau ID Anggota...", + "txtSearch" : "Cari...", "txtAll" : "Semua", "txtSubmissionDate" : "Tanggal Masuk", "txtDataNotFound" : "Data Tidak Ditemukan", @@ -77,5 +77,61 @@ "txtSecond": "Detik", "txtPleaseInput": "Mohon masukan kata sandi baru Anda.", "txtNewPassword": "Kata Sandi Baru", - "txtConfPassword": "Konfirmasi Kata Sandi" + "txtConfPassword": "Konfirmasi Kata Sandi", + "txtPrescriptionNumber" : "No Resep", + "txtPrescriptionDate" : "Tanggal", + "txtPrescriptionPatient" : "Pasien", + "txtPrescriptionPharmacy" : "Apotek", + "txtPrescriptionDetail" : "Detail", + "txtPrescriptionDownload" : "Download Resep", + "txtProviderDoctorInformation" : "Informasi Provider & Dokter", + "txtInformasiPasien" : "Informasi Pasien", + "txtNamaDokter" : "Nama Dokter", + "txtSpesialisasiDokter" : "Spesialisasi Dokter", + "txtNoPonsel" : "No Ponsel", + "txtInformasiAsuransi": "Informasi Asuransi", + "txtNomorKartu": "Nomor Kartu", + "txtAsuransi": "Asuransi", + "txtPerusahaan": "Perusahaan", + "txtTipeAsuransi": "Tipe Asuransi", + "txtKelasAsuransi": "Kelas Asuransi", + "txtTipeMember": "Tipe Member", + "txtInformasiOrder": "Informasi Order", + "txtNamaPenerima": "Nama Penerima", + "txtAlamatPenerima": "Alamat Penerima", + "txtPengiriman": "Pengiriman", + "txtTotal": "Total", + "txtInformasiResep": "Informasi Resep", + "txtNomorResep": "Nomor Resep", + "txtNomorRefDokter": "Nomor Ref Dokter", + "txtTanggalTerbitResep": "Tanggal Terbit Resep", + "txtTanggalKedaluwarsa": "Tanggal Kedaluwarsa", + "txtInformasiKonsultasi": "Informasi Konsultasi", + "txtNomorPenjamin": "Nomor Penjamin", + "txtTanggalKonsultasi": "Tanggal Konsultasi", + "txtAlergi": "Alergi", + "txtDiagnosis": "Diagnosis", + "txtObat": "Obat", + "txtSigna": "Signa", + "txtWaktu": "Waktu", + "txtDurasi": "Durasi", + "txtJumlah": "Jumlah", + "txtCatatanObat": "Catatan Obat", + "txtDiterima": "Diterima", + "txtNamaPasien": "Nama Pasien", + "txtTanggalLahirUmur": "Tanggal Lahir / Umur", + "txtJenisKelamin": "Jenis Kelamin", + "txtTinggi" : "Tinggi / Berat", + "txtLabelNew": "Baru", + "txtLabelAccepted": "Diterima Apotek", + "txtLabelReady": "Pesanan Siap Diambil", + "txtLabelWaitingCourir": "Menunggu Kurir", + "txtLabelPackagePickedUp": "Paket Sudah Diambil", + "txtLabelPackageOnDelivery": "Sedang Diantar ke Alamat Tujuan", + "txtLabelPackageDelivered": "Sudah diterima Pasien", + "txtLabelWaitingForPayment": "Menunggu Pembayaran", + "txtOK": "Ya", + "txtFindingDriver" : "Sedang mencari driver...", + "txtDriverFound" : "Driver sudah didapat dengan ID", + "txtButtonClose": "Tutup" } diff --git a/frontend/hospital-portal/src/layouts/dashboard/header/AccountPopover.tsx b/frontend/hospital-portal/src/layouts/dashboard/header/AccountPopover.tsx index 12042b0f..79dafb6c 100755 --- a/frontend/hospital-portal/src/layouts/dashboard/header/AccountPopover.tsx +++ b/frontend/hospital-portal/src/layouts/dashboard/header/AccountPopover.tsx @@ -10,6 +10,8 @@ import useAuth from '@/hooks/useAuth'; import { getUser } from '@/utils/token'; + // Join the words with a space + // ---------------------------------------------------------------------- const MENU_OPTIONS = [ @@ -34,6 +36,12 @@ export default function AccountPopover() { const navigate = useNavigate(); const { logout } = useAuth(); + const {user} = useAuth(); + const formattedRoleName = user?.role.name + .split('-') // Split the string by '-' + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) // Capitalize the first letter of each word + .join(' '); + const handleOpen = (event: React.MouseEvent) => { setOpen(event.currentTarget); }; @@ -70,7 +78,7 @@ export default function AccountPopover() { > @@ -90,7 +98,7 @@ export default function AccountPopover() { > - Hospital Admin + {formattedRoleName} {storedUser?.email} @@ -99,7 +107,7 @@ export default function AccountPopover() { - + {/* {MENU_OPTIONS.map((option) => ( ))} - + */} diff --git a/frontend/hospital-portal/src/layouts/dashboard/navbar/NavbarVertical.tsx b/frontend/hospital-portal/src/layouts/dashboard/navbar/NavbarVertical.tsx index 35a449d5..32928bc5 100755 --- a/frontend/hospital-portal/src/layouts/dashboard/navbar/NavbarVertical.tsx +++ b/frontend/hospital-portal/src/layouts/dashboard/navbar/NavbarVertical.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; // @mui import { styled, useTheme } from '@mui/material/styles'; @@ -15,10 +15,26 @@ import Logo from '@/components/Logo'; import Scrollbar from '@/components/Scrollbar'; import { NavSectionVertical } from '@/components/nav-section'; // -import navConfig from './NavConfig'; +// import navConfig from './NavConfig'; import NavbarDocs from './NavbarDocs'; +import useAuth from '@/hooks/useAuth'; import NavbarAccount from './NavbarAccount'; import CollapseButton from './CollapseButton'; +import axios from '@/utils/axios'; +import SvgIconStyle from '@/components/SvgIconStyle'; + + +const getIcon = (name: string) => ( + + ); + + const ICONS = { + user: getIcon('ic_user'), + ecommerce: getIcon('ic_ecommerce'), + analytics: getIcon('ic_analytics'), + dashboard: getIcon('ic_dashboard'), + ic_booking: getIcon('ic_booking'), + }; // ---------------------------------------------------------------------- @@ -43,11 +59,76 @@ export default function NavbarVertical({ isOpenSidebar, onCloseSidebar }: Props) const { pathname } = useLocation(); + const {user} = useAuth(); + const formattedRoleName = user?.full_name + .split('-') // Split the string by '-' + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) // Capitalize the first letter of each word + .join(' '); // Join the words with a space + const isDesktop = useResponsive('up', 'lg'); const { isCollapse, collapseClick, collapseHover, onToggleCollapse, onHoverEnter, onHoverLeave } = useCollapseDrawer(); + const [navConfig, setNavConfig] = useState([]); + // console.log(navConfig); + useEffect(() => { + const fetchNavConfig = async () => { + try { + const response = await axios.get('/navigations'); + const data = response.data.items; + // console.log(data); + + // Pastikan user dan user.permissions terdefinisi dan merupakan array + const userPermissions = user?.permissions?.map(permission => permission.name) || []; + + // Fungsi untuk memeriksa apakah pengguna memiliki izin untuk item tertentu + const hasPermission = (permission) => { + return userPermissions.includes(permission); + }; + + // Filter data berdasarkan izin pengguna + const filteredNavConfig = data.map(section => { + if (section.children && section.children.length > 0) { + // Cek apakah ada satu atau lebih children yang memiliki izin + const filteredChildren = section.children.filter(child => hasPermission(child.permission)); + + if (filteredChildren.length > 0) { + return { + ...section, + children: filteredChildren + }; + } else { + return null; // Lewati bagian yang tidak memiliki children dengan izin + } + } + // Jika tidak ada children, cek izin untuk section itu sendiri + // console.log(section.permission); + return hasPermission(section.permission) ? section : null; + }).filter(section => section !== null); + + // console.log(filteredNavConfig); + + const formattedNavConfig = filteredNavConfig.map(item => ({ + + items: [{ + title: item.title, + path: item.path, + icon: ICONS[item.icon] + }] + })); + + setNavConfig(formattedNavConfig); + + } catch (error) { + console.error('Gagal mengambil konfigurasi navigasi:', error); + } + }; + + fetchNavConfig(); + }, [user]); + console.log(navConfig); + useEffect(() => { if (isOpenSidebar) { onCloseSidebar(); @@ -76,10 +157,10 @@ export default function NavbarVertical({ isOpenSidebar, onCloseSidebar }: Props) - Hospital Portal + {formattedRoleName} - ) + ) : ( diff --git a/frontend/hospital-portal/src/pages/DashboardApotek.tsx b/frontend/hospital-portal/src/pages/DashboardApotek.tsx new file mode 100644 index 00000000..957ef386 --- /dev/null +++ b/frontend/hospital-portal/src/pages/DashboardApotek.tsx @@ -0,0 +1,119 @@ +// @mui +import { Typography, Container, Grid, Card } from '@mui/material'; +// hooks +import useSettings from '@/hooks/useSettings'; +// components +import Page from '@/components/Page'; +// theme +import CardNotification from '@/sections/dashboard/CardNotification'; +import CardSearchMember from '@/sections/dashboard/CardSearchMember' +import { useContext, useEffect, useState } from 'react'; +import axios from '@/utils/axios'; +import { Stack } from '@mui/system'; +import { Input } from '@mui/material'; +//sections +import TableList from '@/sections/dashboardApotek/TableList'; +import { fDate } from '@/utils/formatTime'; +import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim'; +import HeaderBreadcrumbs from "@/components/HeaderBreadcrumbs"; + +// ---------------------------------------------------------------------- + +// const [notifications, setNotifications] = useState([]) + +const itemList = [ + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '08:00 WIB' }, + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '09:00 WIB' }, + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '10:00 WIB' }, + { info: 'Mohon lengkapi dokumen Mahen sadarsa', date: 'Selasa, 20 April 22', time: '11:00 WIB' }, +]; + +// ---------------------------------------------------------------------- + +/* ---------------------------------- types --------------------------------- */ + +type PolicyProps = { + myLimit: { + balance: number; + total: number; + percentage: number; + }; + lockLimit: { + balance: number; + percentage: number; + }; +}; + +/* -------------------------------------------------------------------------- */ + +/* ------------------------------ default data ------------------------------ */ +const defaultPolicyData = { + myLimit: { + balance: 0, + total: 0, + percentage: 0, + }, + lockLimit: { + balance: 0, + percentage: 0, + }, +}; +/* -------------------------------------------------------------------------- */ + +export default function Claim() { + const { themeStretch } = useSettings(); + + // const [tableData, setTableData] = useState([]); + const [policyData, setPolicyData] = useState(defaultPolicyData); + + // TODO Remove This + //const [itemList, setItemList] = useState([]); + function handleDataLoaded(dataTable:any) { + let dummyData = []; + dataTable.map(function(data:any) { + if (data.status == 'approved') { + dummyData.push({ + info: `LOG Approved for member ${data.member.full_name}`, + date: fDate(data.created_at, "dd MMMM"), + time: fDate(data.created_at, "HH:mm") + }) + } + }) + + //setItemList(dummyData); + } + + return ( + + + + {/* + + */} + {/* + + */} + + {/* */} + + + + + + + + + ); +} diff --git a/frontend/hospital-portal/src/routes/index.tsx b/frontend/hospital-portal/src/routes/index.tsx index 2bcb583b..66550bcc 100755 --- a/frontend/hospital-portal/src/routes/index.tsx +++ b/frontend/hospital-portal/src/routes/index.tsx @@ -11,12 +11,16 @@ import Register from '@/pages/auth/Register'; import VerifyCode from '@/pages/auth/VerifyCode'; import { AuthProvider } from '@/contexts/LaravelAuthContext'; import AuthGuard from '@/guards/AuthGuard'; +import useAuth from '@/hooks/useAuth'; +import RoleBasedGuard from '@/guards/RoleBasedGuard'; // ---------------------------------------------------------------------- const Loadable = (Component: ElementType) => (props: any) => { // eslint-disable-next-line react-hooks/rules-of-hooks const { pathname } = useLocation(); + const {user} = useAuth(); + const formattedRoleName = user?.role.name; return ( }> @@ -74,11 +78,19 @@ export default function Router() { { element: , index: true }, { path: 'dashboard', - element: , + element: ( + + + + ), }, { path: '/detail/:id', - element: , + element: ( + + + + ), }, ], }, @@ -95,18 +107,54 @@ export default function Router() { { element: , index: true }, { path: 'claim', - element: , + element: ( + + + + ), }, { path: '/claim/detail/:id', - element: , + element: ( + + + + ), }, ], }, + { + path: '/', + element: ( + + + + + + ), + children: [ + { element: , index: true }, + { + path: 'prescription-orders', + element: ( + + + + ), + }, + ], + }, + { path: '*', - element: , + element: ( + + + + + + ), children: [ { path: '404', element: }, { path: '*', element: }, @@ -123,6 +171,7 @@ const ForgetPassword = Loadable(lazy(() => import('@/pages/auth/ForgetPassword') // Dashboard const Dashboard = Loadable(lazy(() => import('@/pages/Dashboard'))); const Claim = Loadable(lazy(() => import('@/pages/Claim'))); +const DashboardApotek = Loadable(lazy(() => import('@/pages/DashboardApotek'))); const NotFound = Loadable(lazy(() => import('@/pages/Page404'))); const DetailClaimReport = Loadable(lazy(()=> import('@/sections/dashboard/Detail'))); diff --git a/frontend/hospital-portal/src/sections/dashboardApotek/TableList.tsx b/frontend/hospital-portal/src/sections/dashboardApotek/TableList.tsx new file mode 100644 index 00000000..c731b5ce --- /dev/null +++ b/frontend/hospital-portal/src/sections/dashboardApotek/TableList.tsx @@ -0,0 +1,450 @@ +/* ---------------------------------- @mui ---------------------------------- */ +import { Stack, Button, MenuItem, SelectChangeEvent, Tab, Tabs, Card, Box } from '@mui/material'; +/* ---------------------------------- axios --------------------------------- */ +// import axios from 'axios'; +import axios from '../../utils/axios'; +import { styled } from '@mui/material/styles'; +/* ---------------------------------- react --------------------------------- */ +import { useContext, useEffect, useState } from 'react'; + +/* -------------------------------- component ------------------------------- */ +import Iconify from '../../components/Iconify'; +import TableComponent from '../../components/Table'; +/* ---------------------------------- theme --------------------------------- */ +import palette from '../../theme/palette'; +//import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate'; +import { HeadCell, Order, PaginationTableProps } from '../../@types/table'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { fDate, fDateSuffix, fDateTime } from '../../utils/formatTime'; +import Typography from '@mui/material/Typography'; +import { format } from 'date-fns'; +import TableMoreMenu from '../../components/table/TableMoreMenu'; +import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; +import HistoryIcon from '@mui/icons-material/History'; +import SearchIcon from '@mui/icons-material/Search'; +import Label from '../../components/Label'; +import { enqueueSnackbar } from 'notistack'; +import { LoadingButton, TabPanel } from "@mui/lab"; +import { LanguageContext } from '@/contexts/LanguageContext'; +import MuiDialog from '@/components/MuiDialog'; +import DialogMember from '@/sections/dashboard/DialogMember'; +import DialogClaimSubmit from '@/sections/dashboard/DialogClaimSubmit'; +import { fPostFormat } from '@/utils/formatTime'; + +export default function TableList() { + const navigate = useNavigate(); + const { localeData }: any = useContext(LanguageContext); + + const [data, setData] = useState([]); + + // Download LOG + async function handleDownloadLog(claimRequest:any) { + return axios + .get(`claim-requests/${claimRequest}/log`, { + responseType: 'blob', + }) + .then((response) => { + window.open(URL.createObjectURL(response.data)); + // setLoadingLog(false); + }) + // .then((blobFile) => { + // new File([blobFile], 'asdads.pdf', { type: blobFile.type }) + // setLoadingLog(false); + // }) + .catch((response) => { + enqueueSnackbar(response.message, { variant: 'error' }); + // setLoadingLog(false); + }); + } + + /* -------------------------------------------------------------------------- */ + /* setting up for the table */ + /* -------------------------------------------------------------------------- */ + const [isLoading, setIsLoading] = useState(true); + + const loadings = { + isLoading: isLoading, + setIsLoading: setIsLoading, + }; + + /* ------------------------------ handle params ----------------------------- */ + const [searchParams, setSearchParams] = useSearchParams(); + const [appliedParams, setAppliedParams] = useState({}); + + const params = { + searchParams: searchParams, + setSearchParams: setSearchParams, + appliedParams: appliedParams, + setAppliedParams: setAppliedParams, + }; + /* -------------------------------------------------------------------------- */ + + /* ------------------------------ handle order ------------------------------ */ + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('dTanggalresep'); + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + /* -------------------------------------------------------------------------- */ + + /* ---------------------------- handle pagination --------------------------- */ + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + + const [paginationTable, setPaginationTable] = useState({ + current_page: 0, + from: 0, + last_page: 0, + links: [], + path: '', + per_page: 0, + to: 0, + total: 0, + }); + + const paginations = { + page: page, + setPage: setPage, + rowsPerPage: rowsPerPage, + setRowsPerPage: setRowsPerPage, + paginationTable: paginationTable, + setPaginationTable: setPaginationTable, + }; + + /* -------------------------------------------------------------------------- */ + + // ----------------------------------------- handle selected --------------------- + const [selectAll, setSelectAll] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + const [dataTableData, setDataTableData] = useState(); + const handleSelectAll = () => { + setSelectAll(!selectAll); + if (!selectAll) { + const requestedIds = dataTableData?.data + .filter((row: { status: string, check_claim:any }) => row.status === 'approved' && !row.check_claim) + .map((row: { id: any }) => row.id); + setSelectedRows(requestedIds); + } else { + setSelectedRows([]); + } + }; + + const handleCheckboxChange = (id: any) => { + setSelectedRows(prevSelectedRows => { + const isSelected = prevSelectedRows.includes(id); + if (isSelected) { + return prevSelectedRows.filter(rowId => rowId !== id); + } else { + return [...prevSelectedRows, id]; + } + }); + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + const [valDialog, setValDialog] = useState(''); + const [reasonDecline, setReasonDecline] = useState(''); + const handleReasonDeclineChange = (event: { target: { value: SetStateAction; }; }) => { + setReasonDecline(event.target.value); +}; + + const selected = { + useSelected: false, + selectAll: selectAll, + handleSelectAll: handleSelectAll, + selectedRows: selectedRows, + handleCheckboxChange : handleCheckboxChange, + totRows: 9, + useDecline: false, + txtDecline: 'Decline', + useApprove:true, + txtApprove: 'Submit Claim', + setOpenDialogSubmit: setOpenDialogSubmit, + setValDialog: setValDialog + }; + + /* ------------------------------ handle search ----------------------------- */ + const [searchText, setSearchText] = useState(''); + + const handleSearchSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + if (searchText === '') { + searchParams.delete('search'); + const params = Object.fromEntries([...searchParams.entries()]); + setAppliedParams(params); + } else { + const params = Object.fromEntries([...searchParams.entries(), ['search', searchText]]); + setAppliedParams(params); + } + }; + + const searchs = { + useSearchs: true, + searchText: searchText, + setSearchText: setSearchText, + handleSearchSubmit: handleSearchSubmit, + }; + + /* ------------------------------ handle filter ----------------------------- */ + const [statusValue, setStatusValue] = useState('all'); + const [filterData, setStatusData] = useState([]); + + // handle status + const handleStatusChanges = (event: SelectChangeEvent) => { + setStatusValue(event.target.value as string); + + if (event.target.value === 'all') { + searchParams.delete('status'); + const params = Object.fromEntries([...searchParams.entries()]); + setAppliedParams(params); + } else { + const params = Object.fromEntries([ + ...searchParams.entries(), + ['status', event.target.value as string], + ]); + setAppliedParams(params); + } + }; + + const filterStatus = { + useFilter: true, + config: { + label: 'Status', + statusValue: statusValue, + filterData: filterData, + handleStatusChange: handleStatusChanges, + }, + }; + + // handle start date + const [startDateValue, setStartDateValue] = useState(''); + + const handleStartDateChanges = async (event: React.FormEvent) => { + event.preventDefault(); + const newStartDateValue = event.currentTarget.elements['date-input'].value; + setStartDateValue(newStartDateValue); + if (newStartDateValue === '') { + searchParams.delete('start_date'); + const params = Object.fromEntries([...searchParams.entries()]); + setAppliedParams(params); + } else { + const params = Object.fromEntries([...searchParams.entries(), ['start_date', newStartDateValue]]); + setAppliedParams(params); + } + }; + + const filterStartDate = { + useFilter: true, + startDate: startDateValue, + setStartDate: setStartDateValue, + handleStartDateChange: handleStartDateChanges, + }; + + // handle end date + const [endDateValue, setEndDateValue] = useState(''); + + const handleEndDateChanges = async (event: React.FormEvent) => { + event.preventDefault(); + const newEndDateValue = event.currentTarget.elements['date-input'].value; + setEndDateValue(newEndDateValue); + if (newEndDateValue === '') { + searchParams.delete('end_date'); + const params = Object.fromEntries([...searchParams.entries()]); + setAppliedParams(params); + } else { + const params = Object.fromEntries([...searchParams.entries(), ['end_date', newEndDateValue]]); + setAppliedParams(params); + } + }; + + const filterEndDate = { + useFilter: true, + endDate: endDateValue, + setEndDate: setEndDateValue, + handleEndDateChange: handleEndDateChanges, + }; + + /* -------------------------------- headCell Claim -------------------------------- */ + const headCells: HeadCell[] = [ + { + id: 'no_resep', + align: 'left', + label: localeData.txtPrescriptionNumber, + isSort: true, + }, + { + id: 'tanggal', + align: 'left', + label: localeData.txtPrescriptionDate, + isSort: true, + }, + { + id: 'pasien', + align: 'center', + label: localeData.txtPrescriptionPatient, + isSort: true, + }, + { + id: 'apotek', + align: 'center', + label: localeData.txtPrescriptionPharmacy, + isSort: true, + }, + { + id: 'status', + align: 'center', + label: localeData.txtStatus, + isSort: true, + }, + { + id: 'action', + align: 'right', + label: '', + isSort: false, + }, + ]; + + + useEffect(() => { + getData(); + }, [appliedParams, searchParams, order, orderBy, setSearchParams]); + const [openRowId, setOpenRowId] = useState(null); + function getData() + { + (async () => { + setIsLoading(true); + + await new Promise((resolve) => setTimeout(resolve, 250)); + + const parameters = + Object.keys(appliedParams).length !== 0 + ? appliedParams + : Object.fromEntries([...searchParams.entries(), ['order', order], ['orderBy', orderBy]]); + + const response = await axios.get(`/get-prescription-orders`, { + params: { ...parameters, type: 'prescription-orders' }, + }); + setDataTableData(response.data); + setData( + response.data.data.map((obj: any) => ({ + ...obj, + status: + obj.status === 'waiting_pharmacy' ? ( + + ) : obj.status === 'order_prepared' ? ( + + ) : obj.status === 'ready' ? ( + + ) : obj.status === 'waiting_for_courir' ? ( + + ) : obj.status === 'package_picked_up' ? ( + + ) : obj.status === 'package_on_delivery' ? ( + + ) : obj.status === 'package_delivered' ? ( + + ) : obj.status === 'waiting_for_payment' ? ( + + ) : ( + + ), + tanggal: + + , + valid_tanggal: + + , + action: + + setOpenRowId(obj.id === openRowId ? null : obj.id)}> + + {localeData.txtPrescriptionDetail} + + {/* handleDownloadLog(obj.id, obj.service_code, obj.no_polis, obj.full_name, obj.provider, obj.approved_at)}> + + {localeData.txtPrescriptionDownload} + */} + + } /> + })) + ); + + setPaginationTable(response.data); + setRowsPerPage(response.data.per_page); + + if (searchParams.get('page')) { + //@ts-ignore + const currentPage = parseInt(searchParams.get('page')) - 1; + + paginationTable.current_page = currentPage; + setPage(currentPage); + } + + const status:any = [ + {"id": "waiting_for_payment", "name": localeData.txtLabelWaitingForPayment }, + {"id": "waiting_pharmacy", "name": localeData.txtLabelNew }, + {"id": "order_prepared", "name": localeData.txtLabelAccepted }, + {"id": "ready", "name": localeData.txtLabelReady }, + {"id": "waiting_for_courir", "name": localeData.txtLabelWaitingCourir }, + {"id": "package_picked_up", "name": localeData.txtLabelPackagePickedUp }, + {"id": "package_on_delivery", "name": localeData.txtLabelPackageOnDelivery }, + {"id": "package_delivered", "name": localeData.txtLabelPackageDelivered }, + ]; + setStatusData(status) + + setIsLoading(false); + })(); + } + + + return ( + <> + + + ); +} diff --git a/frontend/hospital-portal/src/utils/formatTime.ts b/frontend/hospital-portal/src/utils/formatTime.ts index a9fc9250..fea72e17 100755 --- a/frontend/hospital-portal/src/utils/formatTime.ts +++ b/frontend/hospital-portal/src/utils/formatTime.ts @@ -11,6 +11,40 @@ export function fDateTime(date: Date | string | number) { return format(new Date(date), 'dd MMM yyyy HH:mm'); } +// Function to calculate the age from the birthdate +function calculateAge(birthDate: Date): number { + const today = new Date(); + const age = today.getFullYear() - birthDate.getFullYear(); + const monthDifference = today.getMonth() - birthDate.getMonth(); + const dayDifference = today.getDate() - birthDate.getDate(); + + // Adjust age if birthday hasn't occurred yet this year + if (monthDifference < 0 || (monthDifference === 0 && dayDifference < 0)) { + return age - 1; + } + + return age; +} + +// Extended function to format the date and include the age if it's a birthdate +export function fDateTimeWithAge(date: Date | string | number): string { + const birthDate = new Date(date); + const formattedDate = format(birthDate, 'dd MMM yyyy'); + const age = calculateAge(birthDate); + + // If the age is 0, the output should be months instead of years + let ageText = age > 0 ? `${age} years old` : ''; + + if (age === 0) { + const monthsOld = new Date().getMonth() - birthDate.getMonth() + + (12 * (new Date().getFullYear() - birthDate.getFullYear())); + ageText = `${monthsOld} months old`; + } + + return `${formattedDate} / ${ageText}`; +} + + export function fTimestamp(date: Date | string | number) { return getTime(new Date(date)); }