diff --git a/Modules/Client/Http/Controllers/Api/ClaimController.php b/Modules/Client/Http/Controllers/Api/ClaimController.php index 22878143..607f8a8c 100644 --- a/Modules/Client/Http/Controllers/Api/ClaimController.php +++ b/Modules/Client/Http/Controllers/Api/ClaimController.php @@ -296,7 +296,9 @@ class ClaimController extends Controller 'Excess Paid', 'Diagnosis', 'Keterangan', - 'Catatan' + 'Catatan', + 'Invoice No', + 'Billing No' ]; $style = (new StyleBuilder()) ->setFontBold() @@ -336,7 +338,9 @@ class ClaimController extends Controller DB::raw(' (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits WHERE request_log_benefits.request_log_id = request_logs.id AND request_log_benefits.deleted_at IS NULL LIMIT 1) AS tot_bill - ') + '), + 'request_logs.invoice_no', + 'request_logs.billing_no', ) ->groupBy( 'request_logs.submission_date', @@ -509,6 +513,8 @@ class ClaimController extends Controller !empty($item->diagnosis) ? $item->diagnosis : '', !empty($item->keterangan) ? $item->keterangan : '', !empty($item->catatan) ? $item->catatan : '', + !empty($item->invoice_no) ? $item->invoice_no : '', + !empty($item->billing_no) ? $item->billing_no : '', ]; array_push($dataRow,$rowData); @@ -543,6 +549,8 @@ class ClaimController extends Controller '', '', '', + !empty($item->invoice_no) ? $item->invoice_no : '', + !empty($item->billing_no) ? $item->billing_no : '', ]; array_push($dataRow,$rowData); diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index 3e4513cc..6fb49e19 100644 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -14,6 +14,10 @@ use Modules\Internal\Http\Controllers\ClaimEncounterController; use Modules\Client\Http\Controllers\Api\ClaimReportController; use Modules\Client\Http\Controllers\Api\ClaimRequestController; use Modules\Client\Http\Controllers\Api\DataController; +use Modules\Internal\Http\Controllers\Api\FormulariumController; +use Modules\Internal\Http\Controllers\Api\FormulariumTemplateController; +use Modules\Internal\Http\Controllers\Api\AuditTrailController; +use Modules\Internal\Http\Controllers\Api\CorporateController; /* |-------------------------------------------------------------------------- @@ -71,5 +75,20 @@ Route::prefix('client')->group(function () { Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store'); Route::post('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show'); + + Route::get('master/formulariums/{formulariums_template_id}', [FormulariumController::class, 'index']); + Route::post('master/formulariums/{formulariums_template_id}', [FormulariumController::class, 'store']); + Route::post('master/formulariums/{formulariums_template_id}/import', [FormulariumController::class, 'import']); + Route::get('master/formulariums/{formulariums_template_id}/list', [FormulariumController::class, 'generateFormulariumList']); + + Route::get('master/formularium-template', [FormulariumTemplateController::class, 'index']); + Route::get('master/formularium-template/search', [FormulariumTemplateController::class, 'search']); + Route::post('master/formularium-template/store', [FormulariumTemplateController::class, 'store']); + Route::put('master/formularium-template/{id}/activation', [FormulariumTemplateController::class, 'activation']); + Route::get('master/formularium-template/{id}/edit', [FormulariumTemplateController::class, 'edit']); + Route::put('master/formularium-template/{id}/update', [FormulariumTemplateController::class, 'update']); + + Route::get('audittrail/{corporate_id}', [AuditTrailController::class, 'index']); + Route::get('corporates/import-document-example/{document_type}', [CorporateController::class, 'importDocumentExample']); }); }); diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php index 5b33b40c..a7e214b8 100644 --- a/Modules/Internal/Http/Controllers/Api/ClaimController.php +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -34,6 +34,9 @@ use Modules\Internal\Transformers\RequestLogResource; use App\Models\RequestLog; +use ZipArchive; +use File; + use PDF; class ClaimController extends Controller @@ -91,13 +94,13 @@ class ClaimController extends Controller // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code // '), DB::raw(' - (SELECT plans.code - FROM plans + (SELECT plans.code + FROM plans WHERE plans.id IN ( - SELECT member_plans.plan_id - FROM member_plans + SELECT member_plans.plan_id + FROM member_plans WHERE member_plans.member_id = claim_requests.member_id - ) + ) AND plans.service_code = claim_requests.service_code) AS plan_code '), DB::raw(' @@ -116,13 +119,103 @@ class ClaimController extends Controller 'claim_requests.status_claim_management as status', ) ->paginate($limit); - + return response()->json(Helper::paginateResources($results)); } - public function downloadTemplate() + public function filesProvider(Request $request) + { + $limit = $request->has('per_page') ? $request->input('per_page') : 50; + $results = DB::table('request_logs') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->join('files', 'request_logs.id', '=', 'files.fileable_id') + // ->leftJoin('member_plans', 'member_plans.member_id', '=', 'members.id') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('members.name', 'like', "%" . $search . "%"); + $query->orWhere('request_logs.code', 'like', "%" . $search . "%"); + $query->orWhere('members.member_id', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('request_logs.created_at', '>=', $start_date. ' 00:00:00'); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('request_logs.created_at', '<=', $end_date. ' 23:59:59'); + }); + }) + ->when($request->input('provider') , function ($query, $provider) { + $query->where(function ($query) use ($provider) { + $query->where('request_logs.organization_id', '=', $provider); + }); + }) + ->where('files.fileable_type', '=', 'App\Models\RequestLog') + ->where('request_logs.final_log', '=', '1') + ->where('request_logs.status_final_log', '=', 'approved') + ->select( + 'files.original_name as files', + 'files.id', + 'files.id AS id_log', + 'request_logs.code as code', + 'members.name', + 'request_logs.created_at', + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + 'request_logs.status_final_log as status', + DB::raw("CONCAT('" . env('APP_URL') . "/storage/', path) as path") + ) + ->paginate($limit); + + + + return response()->json(Helper::paginateResources($results)); + } + + public function downloadZip(Request $request) + { + $selectedRows = $request->selectedRows; // asumsi $selectedRows berisi array ID file yang dipilih + $files = []; + + // Ambil path file dari database atau sumber lain sesuai dengan $selectedRows + $data = DB::table('files') + ->whereIn('id', $selectedRows) + ->select('path') + ->get(); + + foreach ($data as $value) { + $files[] = storage_path('app/public/' . $value->path); + } + + $zipFileName = 'downloaded_files.zip'; + $zip = new ZipArchive(); + + if ($zip->open(storage_path('app/public/' . $zipFileName), ZipArchive::CREATE | ZipArchive::OVERWRITE)) { + foreach ($files as $file) { + $zip->addFile($file, basename($file)); + } + + $zip->close(); + + // Mengembalikan response berupa URL file zip + return response()->json(['file_url' => env('APP_URL').Storage::url($zipFileName)], 200); + } else { + return response()->json(['message' => 'Gagal membuat file ZIP.'], 500); + } + } + + public function downloadTemplate() { return Helper::responseJson([ 'file_name' => "Template - Claim - Management.xlsx", @@ -155,7 +248,7 @@ class ClaimController extends Controller 'approval_by_claim_management' => auth()->user()->id, 'approval_date_claim_management' => date('Y-m-d H:i:s'), ]); - + if ($affectedRows === 0) { $check_status = DB::table('claim_requests') ->where('code','=', $row['code']) @@ -183,16 +276,16 @@ class ClaimController extends Controller $row['error'] = $e->getMessage(); if(!$row['code']) { - $row['error'] = 'Kolom Code wajib isi'; + $row['error'] = 'Kolom Code wajib isi'; } if(!$row['qc']) { - $row['error'] = 'Kolom QC wajib isi'; + $row['error'] = 'Kolom QC wajib isi'; } $result_rows[] = $row; $failedRows[] = $row; } - } + } $response = [ 'message' => 'File uploaded and data saved to database', @@ -217,7 +310,7 @@ class ClaimController extends Controller $row[] = $data[0][$i]; $header[] = $data[0][0]; } - + $filed = []; foreach ($header[0] as $value) { @@ -228,18 +321,18 @@ class ClaimController extends Controller $filed[] = $modelColumn; } } - + $result = []; foreach ($row as $subarray) { $trimmedSubarray = []; for ($i = 0; $i < count($filed); $i++) { $trimmedSubarray[$filed[$i]] = $subarray[$i] ? $subarray[$i] : null; } - + $result[] = $trimmedSubarray; } return $result; - } + } public function exportClaimManagement(Request $request) { @@ -314,13 +407,13 @@ class ClaimController extends Controller '), 'claim_requests.created_at', DB::raw(' - (SELECT plans.code - FROM plans + (SELECT plans.code + FROM plans WHERE plans.id IN ( - SELECT member_plans.plan_id - FROM member_plans + SELECT member_plans.plan_id + FROM member_plans WHERE member_plans.member_id = claim_requests.member_id - ) + ) AND plans.service_code = claim_requests.service_code) AS plan_code '), // DB::raw(' @@ -391,7 +484,7 @@ class ClaimController extends Controller ->setCellAlignment(CellAlignment::LEFT) // ->setBackgroundColor(Color::YELLOW) ->build(); - + $footerRow = WriterEntityFactory::createRowFromArray($footer, $style); $writer->addRow($footerRow); @@ -426,10 +519,10 @@ class ClaimController extends Controller $headerRow = WriterEntityFactory::createRowFromArray($header, $style); $writer->addRow($headerRow); // ============================ - + foreach($request->params as $item) { - + $rowData = [ $item['code'], $item['qc'], @@ -461,7 +554,7 @@ class ClaimController extends Controller ->setCellAlignment(CellAlignment::LEFT) // ->setBackgroundColor(Color::YELLOW) ->build(); - + $footerRow = WriterEntityFactory::createRowFromArray($footer, $style); $writer->addRow($footerRow); diff --git a/Modules/Internal/Http/Controllers/Api/DrugController.php b/Modules/Internal/Http/Controllers/Api/DrugController.php index 97c7bd63..f5237e67 100644 --- a/Modules/Internal/Http/Controllers/Api/DrugController.php +++ b/Modules/Internal/Http/Controllers/Api/DrugController.php @@ -3,6 +3,7 @@ namespace Modules\Internal\Http\Controllers\Api; use App\Models\Drug; +use App\Models\Unit; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; @@ -25,6 +26,37 @@ class DrugController extends Controller return $drugs; } + public function drugList(Request $request){ + $drugs = Drug::query() + ->where([ + 'atc_code' => 'lms', // ini untuk menggunakan list obat yang baru + ]) + ->get(); + + $manipulatedDrugs = $drugs->map(function ($drug) { + // Contoh manipulasi, tambahkan atau ubah properti sesuai kebutuhan + return [ + 'value' => $drug->id, // Ganti dengan properti yang sesuai dari model Icd + 'label' => $drug->name, // Ganti dengan properti yang sesuai dari model Icd + ]; + }); + return Helper::responseJson(data: $manipulatedDrugs); + } + + public function unitList(Request $request){ + $units = Unit::query() + ->get(); + + $manipulatedUnits = $units->map(function ($unit) { + // Contoh manipulasi, tambahkan atau ubah properti sesuai kebutuhan + return [ + 'value' => $unit->id, // Ganti dengan properti yang sesuai dari model Icd + 'label' => $unit->name, // Ganti dengan properti yang sesuai dari model Icd + ]; + }); + return Helper::responseJson(data: $manipulatedUnits); + } + /** * Show the form for creating a new resource. * @return Renderable @@ -123,20 +155,22 @@ class DrugController extends Controller foreach ($processedData as $row) { try { - Drug::create( - [ - 'name' => $row['name'], - 'code' => $row['code'], - 'generic_name' => $row['generic_name'], - 'description' => $row['description'], - 'mims_class' => $row['mims_class'], - 'indications' => $row['indications'], - 'atc_code' => $row['atc_code'], - 'segmentation' => $row['segmentation'], - 'type' => $row['type'], - 'dosage' => $row['dosage'], - 'remark' => $row['remark'], - ] + Drug::updateOrCreate([ + 'code' => $row['code'], + ], + [ + 'name' => $row['name'], + 'code' => $row['code'], + 'generic_name' => $row['generic_name'], + 'description' => $row['description'], + 'mims_class' => $row['mims_class'], + 'indications' => $row['indications'], + 'atc_code' => $row['atc_code'], + 'segmentation' => $row['segmentation'], + 'type' => $row['type'], + 'dosage' => $row['dosage'], + 'remark' => $row['remark'], + ] ); $importedRows++; } catch (\Exception $e) { diff --git a/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php b/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php index 96a37f18..1f3e8236 100644 --- a/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php +++ b/Modules/Internal/Http/Controllers/Api/FormulariumTemplateController.php @@ -24,20 +24,44 @@ use Modules\Internal\Services\IcdService; class FormulariumTemplateController extends Controller { public function index(Request $request) - { - if ($request->search){ - return FormulariumTemplate::when($request->search ?? null, function($icd, $search) { - $icd->where('name', 'LIKE', '%'.$search.'%') - ->orWhere('description', 'LIKE', '%'.$search.'%'); - })->paginate(15); - } else { - $diagnosisTemplate = FormulariumTemplate::query() - // ->filter($request->toArray()) - ->orderBy('name', 'ASC') - ->paginate(15); - return $diagnosisTemplate; + { if($request->corporate_id) + { + if ($request->search){ + return FormulariumTemplate::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('description', 'LIKE', '%'.$search.'%'); + }) + ->join('corporate_formulariums', 'formularium_templates.id', '=', 'corporate_formulariums.formularium_template_id') + ->where('corporate_formulariums.corporate_id', '=', $request->corporate_id) + ->select('formularium_templates.*', 'corporate_formulariums.corporate_id') + ->orderBy('formularium_templates.id', 'ASC') + ->paginate(15); + } else { + $diagnosisTemplate = FormulariumTemplate::query() + // ->filter($request->toArray()) + ->join('corporate_formulariums', 'formularium_templates.id', '=', 'corporate_formulariums.formularium_template_id') + ->where('corporate_formulariums.corporate_id', '=', $request->corporate_id) + ->select('formularium_templates.*', 'corporate_formulariums.corporate_id') + ->orderBy('formularium_templates.id', 'ASC') + ->paginate(15); + return $diagnosisTemplate; + } + } + else + { + if ($request->search){ + return FormulariumTemplate::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('description', 'LIKE', '%'.$search.'%'); + })->paginate(15); + } else { + $diagnosisTemplate = FormulariumTemplate::query() + // ->filter($request->toArray()) + ->orderBy('name', 'ASC') + ->paginate(15); + return $diagnosisTemplate; + } } - } /** @@ -127,7 +151,7 @@ class FormulariumTemplateController extends Controller })->limit(10)->get(); } - public function import(Request $request) + public function import(Request $request) { $request->validate([ 'file' => 'required|file|mimes:xls,xlsx,csv,txt', @@ -171,7 +195,7 @@ class FormulariumTemplateController extends Controller 6 => 'version', 7 => 'active', ]; - + foreach ($row->getCells() as $header_index => $cell) { if (isset($row_map[$header_index])) { $value = $cell->getValue(); @@ -246,7 +270,7 @@ class FormulariumTemplateController extends Controller public function activation(Request $request, $id) { - + $request->validate([ 'active' => 'required' ]); @@ -268,20 +292,20 @@ class FormulariumTemplateController extends Controller // Membuat penulis entitas Spout $writer = WriterEntityFactory::createXLSXWriter(); - + // Membuka penulis untuk menulis ke file $writer->openToFile(public_path('files/CorporateMembershipList.xlsx')); /** Create a style with the StyleBuilder */ $style = (new StyleBuilder()) ->setFontBold() ->build(); - + // Menulis header kolom $headers_map_to_table_fields = $this->icdService->listing_doc_headers; $headerRow = WriterEntityFactory::createRowFromArray($headers_map_to_table_fields, $style); - + $writer->addRow($headerRow); - + // Menulis data if (!empty($data)) { foreach ($data as $item) { @@ -295,22 +319,22 @@ class FormulariumTemplateController extends Controller $item['active'] == 1 ? 'Active' : 'Inactive', // Status $item['type'], // Type ]; - + $row = WriterEntityFactory::createRowFromArray($rowData); $writer->addRow($row); } } - + // Menutup penulis $writer->close(); - + // Mengembalikan response untuk mengunduh file $filePath = public_path('files/CorporateMembershipList.xlsx'); - + return Helper::responseJson([ 'file_name' => "Diagnosis ICD List " . date('Y-m-d h:i:s'), "file_url" => url('files/CorporateMembershipList.xlsx') ]); - + } } diff --git a/Modules/Internal/Http/Controllers/Api/PrescriptionController.php b/Modules/Internal/Http/Controllers/Api/PrescriptionController.php index a7127855..f4e32185 100644 --- a/Modules/Internal/Http/Controllers/Api/PrescriptionController.php +++ b/Modules/Internal/Http/Controllers/Api/PrescriptionController.php @@ -2,11 +2,36 @@ namespace Modules\Internal\Http\Controllers\Api; - +use App\Helpers\Helper; +use App\Models\OLDLMS\Livechat; +use App\Models\OLDLMS\LivechatSummary; +use App\Models\OLDLMS\Appointment; +use App\Models\OLDLMS\Dokter; +use App\Models\OLDLMS\User; +use App\Models\OLDLMS\UserDetail; use App\Models\OLDLMS\Prescription; +use App\Models\OLDLMS\PrescriptionItem; + +use App\Models\Prescription as PrescriptionAso; +use App\Models\PrescriptionItem as PrescriptionItemAso; +use App\Models\Icd; +use App\Models\Organization; +use App\Models\Drug; +use App\Models\Unit; +use Illuminate\Contracts\Support\Renderable; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Storage; +use Modules\Internal\Transformers\LivechatResource; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\Controller; +use Illuminate\Support\Facades\Validator; +use Dompdf\Dompdf; +use Dompdf\Options; +use DB; class PrescriptionController extends Controller { @@ -15,21 +40,31 @@ class PrescriptionController extends Controller * @param int|null $id * @return \Illuminate\Http\JsonResponse */ - public function index($id = null) + public function index(Request $request) { - $query = Prescription::query(); - if ($id !== null) { - $query->where('nID', $id); + $startDate = $request->startDate; + $endDate = $request->endDate; + + $livechat = Livechat::with('doctor.user', 'doctor.speciality', 'appointment.appointmentDetail', 'healthCare', 'summary') + ->where('nTebusObat', '=', 1); + // ->where('nIDAppointment', '!=', null) + // ->where('nIDAppointment', '!=', ''); + if ($startDate) { + $livechat = $livechat->where('dCreateOn', '>=', $startDate); } - - $prescriptions = $query->select('nID','nIDLiveChat', 'nIDLiveChatSummary', 'nIDDokter', 'sDokterName', 'dTanggalResep', 'sSource', 'nIDUser', 'sKodeResep', 'sDiagnose', 'sStatus') - ->get(); - // $prescriptions->toArray(); - // dd($prescriptions); + if ($endDate) { + $endDate = date('Y-m-d', strtotime($endDate . ' +1 day')); + $livechat = $livechat->where('dCreateOn', '<', $endDate); + } - return response()->json($prescriptions); - // return response()->json(Helper::paginateResources(LivechatResource::collection($livechat))); + $livechat = $livechat->whereHas('summary', function ($query) { + $query->whereNotNull('nIDLiveChat'); + }); + + $livechat = $livechat->latest()->paginate(15); + + return response()->json(Helper::paginateResources(LivechatResource::collection($livechat))); } @@ -51,6 +86,128 @@ class PrescriptionController extends Controller */ public function store(Request $request) { + // Insert atau Update ke table prescription di ASO + $data = [ + 'livechat_id' => $request->id, + 'organization_id' => $request->hospital, + 'icd_code' => $request->diagnosis, + ]; + $prescriptionAso = PrescriptionAso::updateOrCreate([ + 'livechat_id' => $request->id + ], $data); + + // Insert ke table tx_prescription di Linksehat + $livechat = Livechat::where('nID', $request->id)->first(); + $livechatSummary = LivechatSummary::where('nIDLivechat', $request->id)->first(); + + $dokterData = Dokter::where('nIDUser', $livechat->nIDDokter)->first(); + $nIDDokter = $dokterData ? $dokterData->nID : $livechat->nIDDokter; + $userDokter = User::where('nID', $livechat->nIDDokter)->first(); + $userDetailDokter = UserDetail::where('nIDUser', $userDokter->nID)->first(); + + $dokter = $userDetailDokter->sTitlePrefix . ' ' . $userDokter->sFirstName . ' ' . $userDokter->sLastName . ' ' . $userDetailDokter->sTitleSuffix; + + $kodeResep = 'LMS' . date('ymd') . rand(1,100); + $diagnosis = explode(",",$request->diagnosis); + + if(isset($request->diagnosis) && is_array($diagnosis) && count($diagnosis) > 0) { + foreach($diagnosis as $data){ + $icd = Icd::where('code', $data)->first(); + array_push($diagnosis, $icd->name); + }; + } + $sDiagnosis = implode(", ",$diagnosis); + $hospitalData = Organization::where('id', $request->hospital)->first(); + $hospital = ''; + if ($hospitalData) { + $hospital = $hospitalData->code; + } + + $data = [ + 'nIDLivechat' => $request->id, + 'nIDLivechatSummary' => $livechatSummary->nID, + 'nIDDokter' => $nIDDokter, + 'sDokterName' => $dokter, + 'dTanggalResep' => date('Y-m-d H:i:s'), + 'sSource' => 'lms', + 'nIDUser' => $livechat->nIDUser, + 'sRegID' => '', + 'sKodeResep' => $kodeResep, + 'sDiagnose' => $sDiagnosis, + 'sKodeRS' => $hospital, + ]; + + $prescription = Prescription::updateOrCreate([ + 'nIDLivechat' => $request->id + ],$data); + + $medicine = $request->medicine; + $customMessages = [ + 'required' => 'Kolom :attribute wajib diisi.', + 'numeric' => 'Kolom :attribute harus berupa angka.', + ]; + + $validator = Validator::make($request->all(), [ + 'medicine' => 'required|array', + 'medicine.*' => 'required', + ], $customMessages); + + if ($validator->fails()) { + return Helper::responseJson([$request->all()],'error', 400, $validator->errors()); + } else { + // BeginTransaction + // delete item + DB::beginTransaction(); + PrescriptionItemAso::where('prescription_id', $prescriptionAso->id)->delete(); + PrescriptionItem::where('nIDPrescription', $prescription->nID)->delete(); + foreach($medicine as $key => $value){ + $drugData = Drug::where('id', $value['drug_id'])->first(); + $drug = ''; + $drugCode = ''; + if ($drugData){ + $drug = $drugData->name; + $drugCode = $drugData->code; + } + $unitData = Unit::where('id', $value['unit_id'])->first(); + $unit = ''; + if ($unitData) { + $unit = $unitData->name; + } + + // Insert Data + $dataAso = [ + 'prescription_id' => $prescriptionAso->id, + 'drug_id' => $value['drug_id'], + 'qty' => $value['qty'], + 'unit_id' => $value['unit_id'], + 'signa' => $value['signa'], + 'note' => $value['note'] + ]; + $data = [ + 'nIDPrescription' => $prescription->nID, + 'sItemName' => $drug, + 'sItemCode' => $drug, + 'sOriginCode' => $drugCode, + 'nQty' => $value['qty'], + 'sSatuan' => $unit, + 'sSigna' => $value['signa'], + 'sNote' => $value['note'], + ]; + try { + // Insert to ASO + PrescriptionItemAso::create($dataAso); + // Insert to Linksehat + PrescriptionItem::create($data); + } catch (\Throwable $th) { + DB::rollBack(); + return Helper::responseJson(status: 'failed', statusCode: 500, message: $th->getMessage()); + } + } + DB::commit(); + return Helper::responseJson(status: 'success', statusCode: 201, message: 'success', data: $request->toArray()); + } + + return Helper::responseJson(status: 'success', statusCode: 200, message: 'Resep Online berhasil ajukan!', data: $prescription); } /** @@ -93,4 +250,58 @@ class PrescriptionController extends Controller { // } + + public function downloadPrescription($id){ + $pdf = new Dompdf(); + + $options = new Options(); + $options->set('isHtml5ParserEnabled', true); + $options->set('isPhpEnabled', true); + $options->set(['isRemoteEnabled' => true]); + $pdf->setOptions($options); + + $pdf->setPaper('A4', 'portrait'); + + + $livechat = Livechat::with('doctor.user', 'doctor.speciality', 'appointment.appointmentDetail', 'healthCare') + ->where('nIDAppointment', '!=', null)->where('nIDAppointment', '!=', '') + ->where('nID', $id) + ->first(); + + $prescription = Prescription::where('nIDLivechat', $id)->first(); + $valid_date = date('d-m-Y', strtotime($prescription->dTanggalResep . ' +3 days')); + $prescriptionItem = PrescriptionItem::where('nIDPrescription', $prescription->nID)->get(); + + $user = User::where('nID', $livechat->nIDUser)->first(); + $doctor = Dokter::where('nIDUser', $livechat->nIDDokter)->first(); + + $patient = [ + 'name' => $user->sFirstName. ' '. $user->sMiddleName. ' '. $user->sLastName, + 'tgl_lahir' => date('d-m-Y', strtotime($user->dTanggalLahir)), + 'kelamin' => $user->nIDJenisKelamin == 1 ? 'M' : 'F', + 'umur' => Helper::calculateAge($user->dTanggalLahir) + ]; + + // Memuat view pdf_view.php ke dalam variabel + $data = [ + 'doctor' => $doctor, + 'items' => $prescriptionItem, + 'tanggal_resep' => date('d-m-Y', strtotime($prescription->dTanggalResep)), + 'pasien' => $patient, + 'valid_date' => $valid_date, + ]; + // Halaman 1 + $html1 = view('pdf.prescription', $data); + $htmlCombined = $html1 ; + + $pdf->loadHtml($htmlCombined); + $pdf->render(); + + $headers = [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'inline; filename="file.pdf"', + ]; + + return response($pdf->output(), 200, $headers); + } } diff --git a/Modules/Internal/Http/Controllers/Api/RequestLogController.php b/Modules/Internal/Http/Controllers/Api/RequestLogController.php index 3c382daf..f7587603 100644 --- a/Modules/Internal/Http/Controllers/Api/RequestLogController.php +++ b/Modules/Internal/Http/Controllers/Api/RequestLogController.php @@ -215,6 +215,24 @@ class RequestLogController extends Controller return Helper::responseJson(data: $manipulatedIcds); } + public function hospitals(){ + $organizations = Organization::query() + ->where([ + 'type' => 'hospital', + 'status' => 'active', + ]) + ->get(); + + $manipulatedOrganizations = $organizations->map(function ($organization) { + // Contoh manipulasi, tambahkan atau ubah properti sesuai kebutuhan + return [ + 'value' => $organization->id, // Ganti dengan properti yang sesuai dari model Icd + 'label' => $organization->name, // Ganti dengan properti yang sesuai dari model Icd + ]; + }); + return Helper::responseJson(data: $manipulatedOrganizations); + } + /** * Show the form for editing the specified resource. * @param int $id @@ -255,6 +273,14 @@ class RequestLogController extends Controller $requestLog->catatan = $request->catatan; } + if (!empty($request->billing_no)) { + $requestLog->billing_no = $request->billing_no; + } + + if (!empty($request->invoice_no)) { + $requestLog->invoice_no = $request->invoice_no; + } + if (!empty($request->reason)) { $requestLog->reason = $request->reason; } @@ -301,10 +327,12 @@ class RequestLogController extends Controller $requestLog = RequestLog::findOrFail($id); $requestLog->status_final_log = null; $requestLog->final_log = 0; - $requestLog->reason_final = 'Reason Delete ' .$request->reason; + $requestLog->reason_final = 'Reason Delete Final LOG' .$request->reason; $requestLog->save(); + // Hapus semua manfaat log permintaan terkait RequestLogBenefit::where('request_log_id', $id)->delete(); + return response()->json([ 'error' => false, 'message' => 'Delete Final LOG', @@ -406,7 +434,13 @@ class RequestLogController extends Controller // Update Request LOG untuk lanjut ke Final LOG // if (!empty($request->catatan)) { $requestLog->catatan = $request->catatan; - // } + } + if (!empty($request->billing_no)) { + $requestLog->billing_no = $request->billing_no; + } + if (!empty($request->invoice_no)) { + $requestLog->invoice_no = $request->invoice_no; + } if ($request->discharge_date) { $requestLog->discharge_date = $request->discharge_date; } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index e7f5cd19..e7b29f1e 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -238,6 +238,8 @@ Route::prefix('internal')->group(function () { Route::post('claims/{claim_id}/set-final-encounter', [ClaimEncounterController::class, 'setFinalEncounter']); Route::get('claims', [ClaimController::class, 'index']); + Route::get('claims-files-provider', [ClaimController::class, 'filesProvider']); + Route::post('download-zip', [ClaimController::class, 'downloadZip']); Route::get('claims/download-template', [ClaimController::class, 'downloadTemplate']); Route::post('claims/import', [ClaimController::class, 'import']); Route::post('claims/exportFiled/', [ClaimController::class, 'exportFiled']); @@ -285,6 +287,10 @@ Route::prefix('internal')->group(function () { // search diagnosis Route::get('diagnosis', [RequestLogController::class, 'diagnosis']); + Route::get('hospitals', [RequestLogController::class, 'hospitals']); + Route::get('drugs', [DrugController::class, 'drugList']); + Route::get('units', [DrugController::class, 'unitList']); + // insert benefit Route::post('customer-service/request/insert-benefit', [RequestLogBenefitController::class, 'store']); Route::post('customer-service/request/benefit_data/{id}', [RequestLogBenefitController::class, 'destroy']); @@ -301,7 +307,13 @@ Route::prefix('internal')->group(function () { Route::resource('appointments', AppointmentController::class); Route::get('live-chat/export', [LivechatController::class, 'export']); Route::resource('live-chat', LivechatController::class); + Route::get('prescription', [PrescriptionController::class, 'index']); + + Route::post('prescription', [PrescriptionController::class, 'store']); + Route::get('prescription-download/{id}', [PrescriptionController::class, 'downloadPrescription']); + + Route::get('prescription/{id}', [PrescriptionController::class, 'index']); Route::get('doctorrating', [DoctorRatingController::class, 'index']); Route::get('doctorrating/{id}', [PrescriptionController::class, 'index']); diff --git a/Modules/Internal/Transformers/DoctorResource.php b/Modules/Internal/Transformers/DoctorResource.php index 5889b3a5..b49477d1 100644 --- a/Modules/Internal/Transformers/DoctorResource.php +++ b/Modules/Internal/Transformers/DoctorResource.php @@ -61,6 +61,8 @@ class DoctorResource extends JsonResource 'speciality_id' => $item->speciality->id, ]; }), + 'period_start' => $items->pluck('period_start')->first(), + 'period_end' => $items->pluck('period_end')->first(), ]; }); diff --git a/Modules/Internal/Transformers/LivechatResource.php b/Modules/Internal/Transformers/LivechatResource.php index 1dd3954e..28018765 100644 --- a/Modules/Internal/Transformers/LivechatResource.php +++ b/Modules/Internal/Transformers/LivechatResource.php @@ -5,6 +5,8 @@ namespace Modules\Internal\Transformers; use Carbon\Carbon; use Illuminate\Http\Resources\Json\JsonResource; use App\Helpers\Helper; +use App\Models\Prescription; +use App\Models\PrescriptionItem; class LivechatResource extends JsonResource { @@ -16,6 +18,20 @@ class LivechatResource extends JsonResource */ public function toArray($request) { + $prescription = Prescription::where('livechat_id', $this->nID)->first(); + $diagnosis = $prescription ? $prescription->icd_code : ''; + $hospital = $prescription ? $prescription->organization_id : ''; + + $prescriptionItem = $prescription ? PrescriptionItem::where('prescription_id', $prescription->id)->get() : [ + [ + 'drug_id' => 0, + 'qty' => 0, + 'signa' => '', + 'unit_id' => 0, + 'note' => '', // input to database + ] + ]; + $livechat = [ 'id' => $this->nID, 'doctor_name' => isset($this->doctor->user->sFirstName) ? $this->doctor->user->detail->sTitlePrefix . '. ' . $this->doctor->user->sFirstName . ' ' . $this->doctor->user->sLastName . ' ' . $this->doctor->user->detail->sTitleSuffix : null, @@ -36,6 +52,9 @@ class LivechatResource extends JsonResource 'appointment_media' => $this->appointment->sMedia ?? null, 'status_chat' => $this->status_name ?? null, 'payment_method' => $this->appointment->payment_method ?? null, + 'diagnosis' => $diagnosis, + 'hospital' => $hospital, + 'medicine' => $prescriptionItem ]; $start_time = $this->dStartTime; diff --git a/Modules/Internal/Transformers/RequestLogShowResource.php b/Modules/Internal/Transformers/RequestLogShowResource.php index 1591701a..88bd4c54 100644 --- a/Modules/Internal/Transformers/RequestLogShowResource.php +++ b/Modules/Internal/Transformers/RequestLogShowResource.php @@ -42,7 +42,7 @@ class RequestLogShowResource extends JsonResource $claimCode = $claimRequest->code; $isReversal = false; $isRole = auth()->user()->role_id; - if ($requestLog['status'] == 'approved' && + if ($requestLog['status'] == 'approved' && $requestLog['status_final_log'] == 'approved' && $claimRequest->status == 'approved' && $claimRequest->status_claim_management == 'approved' && @@ -108,11 +108,14 @@ class RequestLogShowResource extends JsonResource ->whereIn('code', $diagnosis) ->select('code', 'name') ->get(); - } + } $data = [ 'id' => $requestLog['id'], 'code' => $requestLog['code'], + 'invoice_no' => $requestLog['invoice_no'], + 'billing_no' => $requestLog['billing_no'], + 'code' => $requestLog['code'], 'code_claim' => $claimCode, 'member_id' => $requestLog['member']['member_id'], 'corporate_id' => $corporateId, diff --git a/Modules/Linksehat/Helpers/Doctor/ApiResponse.php b/Modules/Linksehat/Helpers/Doctor/ApiResponse.php new file mode 100644 index 00000000..56029c67 --- /dev/null +++ b/Modules/Linksehat/Helpers/Doctor/ApiResponse.php @@ -0,0 +1,21 @@ +first(); + } + return response()->json([ + 'meta' => [ + 'status' => $status, + 'code' => $statusCode, + 'message' => $message + ], + 'data' => $data, + ], $statusCode); + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/AutocompleteController.php b/Modules/Linksehat/Http/Controllers/Api/AutocompleteController.php index 3af9e00b..633d50e8 100644 --- a/Modules/Linksehat/Http/Controllers/Api/AutocompleteController.php +++ b/Modules/Linksehat/Http/Controllers/Api/AutocompleteController.php @@ -3,6 +3,9 @@ namespace Modules\Linksehat\Http\Controllers\Api; use App\Helpers\Helper; use App\Models\OLDLMS\User; +use App\Models\Icd; +use App\Models\Drug; +use App\Models\Unit; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; @@ -93,5 +96,51 @@ class AutocompleteController extends Controller { } return Helper::responseJson($data); } + + public function diagnosis(){ + $icds = Icd::query() + ->get(); + + $manipulatedIcds = $icds->map(function ($icd) { + // Contoh manipulasi, tambahkan atau ubah properti sesuai kebutuhan + return [ + 'value' => $icd->code, // Ganti dengan properti yang sesuai dari model Icd + 'label' => $icd->code . ' - ' .$icd->name, // Ganti dengan properti yang sesuai dari model Icd + ]; + }); + return Helper::responseJson(data: $manipulatedIcds); + } + + public function drugList(Request $request){ + $drugs = Drug::query() + ->where([ + 'atc_code' => 'lms', // ini untuk menggunakan list obat yang baru + ]) + ->get(); + + $manipulatedDrugs = $drugs->map(function ($drug) { + // Contoh manipulasi, tambahkan atau ubah properti sesuai kebutuhan + return [ + 'value' => $drug->id, // Ganti dengan properti yang sesuai dari model Icd + 'label' => $drug->name, // Ganti dengan properti yang sesuai dari model Icd + ]; + }); + return Helper::responseJson(data: $manipulatedDrugs); + } + + public function unitList(Request $request){ + $units = Unit::query() + ->get(); + + $manipulatedUnits = $units->map(function ($unit) { + // Contoh manipulasi, tambahkan atau ubah properti sesuai kebutuhan + return [ + 'value' => $unit->id, // Ganti dengan properti yang sesuai dari model Icd + 'label' => $unit->name, // Ganti dengan properti yang sesuai dari model Icd + ]; + }); + return Helper::responseJson(data: $manipulatedUnits); + } + } diff --git a/Modules/Linksehat/Http/Controllers/Api/ChatController.php b/Modules/Linksehat/Http/Controllers/Api/ChatController.php new file mode 100644 index 00000000..a6a3307b --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/ChatController.php @@ -0,0 +1,323 @@ +validate([ + 'member_id' => 'required', + 'doctor_id' => 'required', + ], [ + 'member_id.required' => 'Member ID harus diisi.', + 'doctor_id.required' => 'Doctor ID harus diisi.', + ]); + + // Buat dan simpan data channel ke dalam tabel + $channel = Channel::updateOrCreate([ + 'name' => $request->member_id .'_' . $request->doctor_id, + ], + [ + 'name' => $request->member_id .'_' . $request->doctor_id, + 'type' => $request->type, + 'member_id' => $request->member_id, + 'doctor_id' => $request->doctor_id, + ]); + + // Menggunakan updateOrCreate untuk menambahkan data UserChannel untuk member_id + $userChannelMember = UserChannel::updateOrCreate( + [ + 'user_id' => $request->member_id, + 'channel_id' => $channel->id + ], + [ + 'user_id' => $request->member_id, + 'channel_id' => $channel->id + ] + ); + + // Menggunakan updateOrCreate untuk menambahkan data UserChannel untuk doctor_id + $userChannelDoctor = UserChannel::updateOrCreate( + [ + 'user_id' => $request->doctor_id, + 'channel_id' => $channel->id + ], + [ + 'user_id' => $request->doctor_id, + 'channel_id' => $channel->id + ] + ); + + // Berikan respons yang sesuai ke klien + return response()->json(['message' => 'Channel created successfully', 'channel' => $channel]); + } + + public function listChannel(Request $request){ + // Validasi request jika diperlukan + $channel = Channel::where('member_id',$request->user_id)->get()->toArray(); + + if (!$channel) { + $dataChannel = Channel::where('doctor_id',$request->user_id)->get()->toArray(); + $data = []; + if ($dataChannel){ + foreach($dataChannel as $d){ + $user = User::with('detail')->where('nID', $d['member_id'])->first(); + $lastMessage = Message::where('channel_id', $d['id']) + ->latest('created_at') + ->first(); + $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $user->detail->sImage ?? $urlAvatarDefault; + + $arr['id'] = $d['id']; + $arr['avatar'] = $avatarMember; + $arr['name'] = $user->sFirstName .' '.$user->sLastName; + $arr['last_message'] = $lastMessage; + + array_push($data, $arr); + } + } + $channel = $data; + } + + + return response()->json(['message' => 'Get List Channel successfully', 'channel' => $channel]); + } + + public function sendMessage(Request $request) + { + // Validasi request jika diperlukan + $validatedData = $request->validate([ + 'user_id' => 'required' + ]); + + // Ambil data dari request + $message = Message::create([ + 'content' => $request->message, + 'from_user' => $request->user_id, + 'channel_id' => $request->channel_id, + 'type' => $request->message ? 'text' : 'file' + ]); + + $pathFile = null; + if ($request->hasFile('file_chat')) { + foreach ($request->file_chat as $file) { + $pathFile = File::storeFile('chat', $message->id, $file); + File::updateOrCreate([ + 'fileable_type'=>'App\Models\Message', + 'fileable_id' => $message->id, + 'type' => 'chat', + 'name' => File::getFileName('chat', $message->id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + + $message->update([ + 'content' => env('LMS_APP_STORAGE') . 'storage/' . $pathFile, + 'from_user' => $request->user_id, + 'channel_id' => $request->channel_id, + 'type' => 'file', + ]); + } + // Berikan respons yang sesuai ke klien + + $channel = Channel::where('id',$request->channel_id)->first(); + if($channel->member_id == $request->user_id){ + // Get nama dokter + $person = Person::where('id', $channel->doctor_id)->first(); + $name = $person->name; + } else { + // Get nama pasien + $person = User::where('nID', $channel->member_id)->first(); + $name = $person->sFirstName . ' ' . $person->sLastName; + } + + ChatMessageSent::dispatch($message); + + return response()->json([ + 'message' => 'Message sent successfully', + 'data' => [ + 'header' => $name, + ] + ]); + } + + public function getMessage(Request $request) + { + // Buat instance Pusher dengan konfigurasi yang sesuai + $channel = Channel::where('id',$request->channel_id)->first(); + $livechat = Livechat::where([ + 'doctor_id' => $channel->doctor_id, + 'patient_id' => $channel->member_id, + ])->latest('created_at')->first(); + + if($channel->member_id == $request->user_id){ + // Get nama dokter + $person = Person::where('id', $channel->doctor_id)->first(); + $name = $person->name; + $avatar = ''; + $age = ''; + $gender = ''; + $question = ''; + + $consultationStart = $livechat->start_date; + $consultationEnd = $livechat->end_date; + $work = ''; + $address = ''; + } else { + // Get nama pasien + $user = User::where('nID', $channel->member_id)->with('detail')->first(); + $name = $user->sFirstName . ' ' . $user->sLastName; + $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatar = $user->detail->sImage ?? $urlAvatarDefault; + $gender = DB::connection('oldlms')->table('tm_jenis_kelamin')->where('nID', $user->detail->nIDJenisKelamin)->first('sJenisKelamin'); + if ($gender){ + $gender = $gender->sJenisKelamin; + } + $age = Helper::calculateAge($user->detail->dTanggalLahir); + $question = $livechat->descriptions; + $consultationStart = $livechat->start_date; + $consultationEnd = $livechat->end_date; + $work = DB::connection('oldlms')->table('tm_pekerjaan')->where('nID', $user->detail->nIDPekerjaan)->first('sPekerjaan'); + + if($work){ + $work = $work->sPekerjaan; + } + $address = DB::connection('oldlms')->table('tm_users_address')->where('nIDUser', $user->nID)->first('sAlamat'); + if($address){ + $address = $address->sAlamat; + } + + } + + // Ini Untul Chat + $perPage = $request->input('per_page', 10); // Default 10 pesan per halaman + $page = $request->input('page', 1); // Default halaman 1 + + $data = Message::where('channel_id', $request->channel_id) + ->where('type', '!=', 'trigger') + ->orderBy('created_at', 'desc') // Urutkan berdasarkan created_at secara descending + ->paginate($perPage, ['*'], 'page', $page); + + // Data Consultation Summary + $consultationSummary = [ + 'subject' => $livechat->subject, + 'object' => $livechat->object, + 'assessment' => $livechat->assessment, + 'plan' => $livechat->plan, + ]; + + $healthSertificate = false; + if ($livechat->health_certificate_start && $livechat->health_certificate_end){ + $healthSertificate = True; + } + // Berikan respons yang sesuai ke klien + return response()->json([ + 'message' => 'Message sent successfully', + 'data' => [ + 'header' => $name, + 'avatar' => $avatar, + 'gender' => $gender, + 'age' => $age, + 'question' => $question, + 'start' => $consultationStart, + 'end' => $consultationEnd, + 'work' => $work, + 'address' => $address, + 'chat' => $data->items(), + 'pagination' => [ + 'total' => $data->total(), + 'per_page' => $data->perPage(), + 'current_page' => $data->currentPage(), + 'last_page' => $data->lastPage(), + 'from' => $data->firstItem(), + 'to' => $data->lastItem(), + ], + 'summary' => $consultationSummary, + 'livechat_id' => $livechat->id, + 'health_sertificate' => $healthSertificate, + ] + ]); + } + + public function downloadHealtcare($id){ + $pdf = new Dompdf(); + + $options = new Options(); + $options->set('isHtml5ParserEnabled', true); + $options->set('isPhpEnabled', true); + $options->set(['isRemoteEnabled' => true]); + $pdf->setOptions($options); + + $pdf->setPaper('A4', 'portrait'); + + $livechat = Livechat::where([ + 'id' => $id + ])->latest('created_at')->first(); + + $user = User::where('nID', $livechat->patient_id)->with('detail')->first(); + $name = $user->sFirstName . ' ' . $user->sLastName; + $age = Helper::calculateAge($user->detail->dTanggalLahir); + + $person = Person::where('id', $livechat->doctor_id)->first(); + $doctorName = $person->name; + + $work = DB::connection('oldlms')->table('tm_pekerjaan')->where('nID', $user->detail->nIDPekerjaan)->first('sPekerjaan'); + if($work){ + $work = $work->sPekerjaan; + } + $address = DB::connection('oldlms')->table('tm_users_address')->where('nIDUser', $user->nID)->first('sAlamat'); + if($address){ + $address = $address->sAlamat; + } + // Memuat view pdf_view.php ke dalam variabel + $calculateDate = Helper::calculateDateDifference($livechat->health_certificate_start, $livechat->health_certificate_end); + + $data = [ + 'name' => $name, + 'age' => $age, + 'work' => $work, + 'address' => $address, + 'doctor_name' => $doctorName, + 'date' => $livechat->created_at, + 'start_date' => $livechat->health_certificate_start, + 'end_date' => $livechat->health_certificate_end, + 'calculate_date' => $calculateDate + ]; + // Halaman 1 + $html1 = view('pdf.health_sertificate', $data); + $htmlCombined = $html1 ; + + $pdf->loadHtml($htmlCombined); + $pdf->render(); + + $headers = [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'inline; filename="file.pdf"', + ]; + + return response($pdf->output(), 200, $headers); + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php b/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php new file mode 100644 index 00000000..4bcc60f1 --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php @@ -0,0 +1,264 @@ + $request->email, + 'password' => $request->password + ]; + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + 'password' => 'required' + ], [ + 'email.required' => trans('Validation.required',['attribute' => 'Email']), + 'email.email' => trans('Validation.email'), + 'password.required' => trans('Validation.required',['attribute' => 'Password']), + ]); + + if ($validator->fails()) + { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); + } + else + { + $user = User::where('email', $request->email)->first(); + if (!$user) { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404); + } + + if (!Hash::check($request->password, $user->password)) { + return ApiResponse::apiResponse('Bad Request', $data, trans('Message.password'), 400); + } + + $res_data = [ + // 'user' => $user, + 'token' => $user->createToken('app')->plainTextToken + ]; + + return ApiResponse::apiResponse("Success", $res_data, trans('Message.success'), 200); + } + } + + public function logout(Request $request) + { + $request->user()->tokens()->delete(); + + return ApiResponse::apiResponse('Success', [], trans('Message.logout'), 200); + } + + public function forgotPassword(Request $request) + { + $data = [ + 'email' => $request->email, + ]; + + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + ], [ + 'email.required' => trans('Validation.required',['attribute' => 'Email']), + 'email.email' => trans('Validation.email'), + ]); + + if ($validator->fails()) + { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); + } + else + { + $user = User::where('email', $request->email)->first(); + if (!$user) { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404); + } + + //send email + // Insert data notifications + $emailTo = $request->email; + $dataNotif = [ + 'user_id' => $user->id, + 'email' => $emailTo, + 'title' => 'Forgot Password', + 'description' => 'Request forgot password from App Doctor', + 'type' => 1, + 'isUnRead' => true, + 'created_by' => auth()->check() ? auth()->user()->id : null, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]; + $sendNotif = Helper::insertNotification($dataNotif); + //Insert data password reset + $token = mt_rand(100000, 999999); // Menghasilkan angka acak antara 100000 dan 999999 + $p_resets = DB::table('password_resets') + ->insert([ + 'email' => $request->email, + 'token' => $token, + 'created_at' => date('Y-m-d H:i:s'), + ]); + // Send Email after insert notifications + if($sendNotif && $p_resets) + { + //send to alarm + $nameTo = 'User'; + $dataEmail = [ + 'email' => $emailTo, + 'name' => $nameTo, + 'subject' => 'Request Forgot Password from App Doctor Date '. date('Y-m-d H:i:s'), + 'body' => View::make('email/forgot_password', ['token' => $token])->render(), + ]; + Helper::sendEmail($dataEmail); + + $res = DB::table('password_resets') + ->where('email', '=', $request->email) + ->where('token', '=', $token) + ->get(); + + return ApiResponse::apiResponse("Success", $res, trans('Message.success'), 200); + } + else + { + return ApiResponse::apiResponse("Internal Server Error", $data, trans('Message.server_error'), 500); + } + } + } + + public function verifCode(Request $request) + { + $data = [ + 'email' => $request->email, + 'token' => $request->token, + ]; + + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + 'token' => 'required|numeric', + ], [ + 'email.required' => trans('Validation.required',['attribute' => 'Email']), + 'email.email' => trans('Validation.email'), + 'token.required' => trans('Validation.required',['attribute' => 'Token']), + ]); + + if ($validator->fails()) + { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); + } + else + { + //Check Time + $check = DB::table('password_resets') + ->where('email', '=', $request->email) + ->where('token', '=', $request->token) + ->select('created_at') + ->first(); + + if($check) + { + $created_at = strtotime($check->created_at); // Konversi string waktu ke UNIX timestamp + $now = time(); // Waktu sekarang dalam UNIX timestamp + + // Hitung selisih waktu dalam menit + $diffInMinutes = ($now - $created_at) / 60; + + if ($diffInMinutes > 60) { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404); + } else { + // Lanjutkan dengan proses pemulihan kata sandi + return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200); + } + } + else + { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404); + } + } + } + + public function resetPassword(Request $request) + { + $data = [ + 'email' => $request->email, + 'token' => $request->token, + 'new_password' => $request->new_password + ]; + + $validator = Validator::make($request->all(), [ + 'email' => 'required|email', + 'token' => 'required|numeric', + 'new_password' => [ + 'required', + 'min:8', + 'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/' + ] + ], [ + 'email.required' => trans('Validation.required',['attribute' => 'Email']), + 'email.email' => trans('Validation.email'), + 'token.required' => trans('Validation.required',['attribute' => 'Token']), + 'new_password.required' => trans('Validation.required',['attribute' => 'New Password']), + 'new_password.min' => trans('Validation.min',['attribute' => 'New Password']), + 'new_password.regex' => trans('Validation.regex',['attribute' => 'New Password']), + ]); + + if ($validator->fails()) + { + return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400); + } + else + { + //Check Time + $check = DB::table('password_resets') + ->where('email', '=', $request->email) + ->where('token', '=', $request->token) + ->select('created_at') + ->first(); + + if($check) + { + $created_at = strtotime($check->created_at); // Konversi string waktu ke UNIX timestamp + $now = time(); // Waktu sekarang dalam UNIX timestamp + + // Hitung selisih waktu dalam menit + $diffInMinutes = ($now - $created_at) / 60; + + if ($diffInMinutes > 60) { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404); + } else { + // Lanjutkan dengan proses pemulihan kata sandi + $user = User::where('email', $request->email)->first(); + if ($user) + { + $newPassword = Hash::make($request->new_password); + $user->password = $newPassword; + $user->save(); + return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200); + } + else + { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404); + } + } + } + else + { + return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404); + } + } + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php b/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php new file mode 100644 index 00000000..1809f0ac --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php @@ -0,0 +1,205 @@ + auth()->check() ? auth()->user()->id : null, + ]; + $user_id = auth()->check() ? auth()->user()->id : null; + //Get data Chat + $user = User::where('id',$user_id)->with('person')->first(); + $chat = Livechat::where([ + 'doctor_id'=> $user->person_id, + 'accept_date'=> null, + 'status' => 1 + ])->get(); + + $dataIncomingChat = []; + if($chat) { + foreach($chat as $c){ + $patient = UserLMS::where('nID',$c->patient_id)->with('detail')->first(); + $urlAvatarDefault = $patient->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $patient->detail->sImage ?? $urlAvatarDefault; + $arr['id'] = $c->id; + $arr['patient_id'] = $patient->nID; + $arr['avatar'] = $avatarMember; + $arr['name'] = $patient->sFirstName .' '.$patient->sLastName; ; + array_push($dataIncomingChat, $arr); + } + } + + $dataChannel = Channel::where('doctor_id',$user->person_id)->get()->toArray(); + $dataOnGoing = []; + if ($dataChannel){ + foreach($dataChannel as $d){ + $user = UserLMS::with('detail')->where('nID', $d['member_id'])->first(); + $lastMessage = Message::where('channel_id', $d['id']) + ->latest('created_at') + ->first(); + if ($user->detail){ + $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $user->detail->sImage ?? $urlAvatarDefault; + } else { + $avatarMember = 'https://linksehat.dev/assets/img/users/male-avatar.png'; + } + + $arr['id'] = $d['id']; + $arr['avatar'] = $avatarMember; + $arr['name'] = $user->sFirstName .' '.$user->sLastName; + $arr['last_message'] = $lastMessage; + + array_push($dataOnGoing, $arr); + } + } + $channel = $data; + + $data = [ + 'incoming_chat' => $dataIncomingChat, + 'ongoing_chat' => $dataOnGoing + ]; + + return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200); + + } + + public function getChatDetail($id){ + $livechat = Livechat::find($id); + $user = UserLMS::with('detail')->where('nID', $livechat->patient_id)->first(); + $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $user->detail->sImage ?? $urlAvatarDefault; + $gender = DB::connection('oldlms')->table('tm_jenis_kelamin')->where('nID', $user->detail->nIDJenisKelamin)->first('sJenisKelamin'); + $maritalStaus = DB::connection('oldlms')->table('tm_status_pernikahan')->where('nID', $user->detail->sMartialStatus)->first('sStatusPernikahan'); + $data = []; + if ($livechat->status != 2){ + $data = [ + 'id' => $user->nID, + 'name' => $user->sFirstName . ' ' . $user->sLastName, + 'avatar' => $avatarMember, + 'gender' => $gender->sJenisKelamin, + 'marital_status' => $maritalStaus->sStatusPernikahan, + 'age' => Helper::calculateAge($user->detail->dTanggalLahir), + 'weight' => $user->detail->sWeight, + 'height' => $user->detail->sHeight, + 'question' => $livechat->descriptions, + 'diseases' => [], + 'medications' => [], + 'allergy' => [], + 'family_history' => [] + ]; + } else if ($livechat->status == 2){ // sudah accept, tinggal tunggu bayar pasient + $data = [ + 'message' => 'waiting payment' + ]; + } + return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200); + } + + public function declineChat(Request $request) + { + $livechat = Livechat::find($request->id); + if ($livechat) { + // Memperbarui atribut model + $livechat->status = 3; // Decline + // Menyimpan perubahan ke database + $livechat->save(); + return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); + } else { + return response()->json(['message' => 'Livechat not found'], 404); + } + } + + public function approveChat(Request $request) + { + $livechat = Livechat::find($request->id); + if ($livechat) { + // Memperbarui atribut model + $livechat->status = 2; // Accept + $livechat->accept_date = date('Y-m-d H:i:s'); // Accept + // Menyimpan perubahan ke database + $livechat->save(); + return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); + } else { + return response()->json(['message' => 'Livechat not found'], 404); + } + } + + public function endChat(Request $request) + { + $livechat = Livechat::find($request->id); + if ($livechat) { + // Memperbarui atribut model + $livechat->status = 6; // End Chat + $livechat->end_date = date('Y-m-d H:i:s'); // Accept + // Menyimpan perubahan ke database + $livechat->save(); + return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); + } else { + return response()->json(['message' => 'Livechat not found'], 404); + } + } + + public function summaryChat(Request $request) + { + + $livechat = Livechat::find($request->id); + if ($livechat) { + // Memperbarui atribut model + $livechat->subject = $request->subject; // Subject + $livechat->object = $request->object; // Object + $livechat->assessment = $request->assessment; // Assessment + $livechat->plan = $request->plan; // Plan + + $livechat->health_certificate_start = $request->health_certificate_start; // start + $livechat->health_certificate_end = $request->health_certificate_end; // end + // Menyimpan perubahan ke database + $livechat->save(); + + $prescriptions = Prescription::create([ + 'livechat_id' => $livechat->id, + 'organization_id' => $livechat->organization_id, + ]); + + if ($request->prescriptions) { + foreach ($request->prescriptions as $prescription) { + $prescriptionItem = PrescriptionItem::create([ + 'prescription_id' => $prescriptions->id, + 'drug_id' => $prescription['medicine'], + 'signa' => $prescription['dosis'], + 'direction' => $prescription['direction'], + 'note' => $prescription['note'], + ]); + } + } + + return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); + } else { + return response()->json(['message' => 'Livechat not found'], 404); + } + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/Doctor/ProfileDoctorController.php b/Modules/Linksehat/Http/Controllers/Api/Doctor/ProfileDoctorController.php new file mode 100644 index 00000000..28fdf245 --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/Doctor/ProfileDoctorController.php @@ -0,0 +1,175 @@ + auth()->check() ? auth()->user()->id : null, + ]; + $user_id = auth()->check() ? auth()->user()->id : null; + //Get data Profile + $dataProfile = DB::table('users') + ->join('persons','persons.id', '=', 'users.person_id') + ->leftJoin('person_educations','person_educations.person_id', '=', 'persons.id') + ->leftJoin('practitioners','practitioners.person_id', '=', 'persons.id') + ->leftJoin('practitioner_roles','practitioner_roles.practitioner_id', '=', 'practitioners.id') + ->where('users.id', '=', $user_id) + ->select( + 'persons.name', + DB::raw(' + "Pediatrics" AS specialist + '), + DB::raw(' + "4" AS rating + '), + 'persons.name AS full_name', + 'persons.birth_date as date_of_birth', + 'persons.gender', + 'persons.phone AS mobile_number', + 'persons.email', + 'practitioners.str_number', + 'practitioners.exp_date_str', + 'practitioner_roles.sip_number', + 'practitioner_roles.exp_date_sip' + ) + ->first(); + + //Name + $dataName = [ + 'name' => $dataProfile->name, + 'specialist' => $dataProfile->specialist, + 'rating' => $dataProfile->rating + ]; + $res_data['dataName'] = $dataName; + + // Basic + $dataProfileBasic = [ + 'full_name' => $dataProfile->full_name, + 'date_of_birth' => $dataProfile->date_of_birth ? date('d M Y', strtotime($dataProfile->date_of_birth)) : '', + 'gender' => $dataProfile->gender + ]; + $res_data['dataProfileBasic'] = $dataProfileBasic; + + //Contact + $dataProfileContact = [ + 'mobile_number' => $dataProfile->mobile_number, + 'email' => $dataProfile->email + ]; + $res_data['dataProfileContact'] = $dataProfileContact; + + //Education + $dataEdu = DB::table('users') + ->join('persons','persons.id', '=', 'users.person_id') + ->leftJoin('person_educations','person_educations.person_id', '=', 'persons.id') + ->where('users.id', '=', $user_id) + ->select( + 'person_educations.level_id', + 'person_educations.name', + 'person_educations.start_date', + 'person_educations.end_date', + ) + ->get(); + $dataEducations = []; + foreach($dataEdu as $val) + { + $dataEducations[] = [ + 'level_id' => $val->level_id, + 'name' => $val->name, + 'start_date' => date('d/m/Y', strtotime($val->start_date)), + 'end_date' => date('d/m/Y', strtotime($val->end_date)), + ]; + } + $res_data['dataEducations'] = $dataEducations; + + //Work Experience + $dataWork = DB::table('users') + ->join('persons','persons.id', '=', 'users.person_id') + ->leftJoin('practitioners','practitioners.person_id', '=', 'persons.id') + ->leftJoin('practitioner_roles','practitioner_roles.practitioner_id', '=', 'practitioners.id') + ->leftJoin('organizations','organizations.id', '=', 'practitioner_roles.organization_id') + ->where('users.id', '=', $user_id) + ->select( + 'organizations.name', + 'practitioner_roles.period_start', + 'practitioner_roles.period_end', + ) + ->get(); + $dataWorkExperience = []; + foreach ($dataWork as $val) + { + $dataWorkExperience[] = [ + 'name' => $val->name ? $val->name : '', + 'period' => $this->fWorkExperience($val->period_start, $val->period_end) + ]; + } + $res_data['dataWorkExperience'] = $dataWorkExperience; + + //STR + $dataStr = [ + 'str_number' => $dataProfile->str_number, + 'exp_date_str' => $dataProfile->exp_date_str ? date('d M Y', strtotime($dataProfile->exp_date_str)) : '' + ]; + $res_data['dataStr'] = $dataStr; + + //SIP + $dataSip = [ + 'sip_number' => $dataProfile->sip_number, + 'exp_date_sip' => $dataProfile->exp_date_sip ? date('d M Y', strtotime($dataProfile->exp_date_sip)) : '' + ]; + $res_data['dataSip'] = $dataSip; + + return ApiResponse::apiResponse("Success", $res_data, trans('Message.success'), 200); + + } + public function fWorkExperience($start, $end) + { + $startDateString = $start; // Tanggal dan waktu awal + $endDateString = $end ; // Tanggal dan waktu akhir + + // Mengubah string tanggal ke timestamp UNIX + $startTime = strtotime($startDateString); + $endTime = strtotime($endDateString); + + // Menghitung selisih waktu dalam detik + $timeDifference = $endTime - $startTime; + + // Menghitung jumlah tahun, bulan, dan hari dari selisih waktu + $years = floor($timeDifference / (365 * 24 * 60 * 60)); + $months = floor(($timeDifference - ($years * 365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60)); + $days = floor(($timeDifference - ($years * 365 * 24 * 60 * 60) - ($months * 30 * 24 * 60 * 60)) / (24 * 60 * 60)); + + // Formatkan hasilnya + $experience = ''; + if ($years > 0) { + $experience .= $years . ' years '; + } + if ($months > 0) { + $experience .= $months . ' months '; + } + if ($days > 0) { + $experience .= $days . ' days'; + } + + return $experience; + + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/DuitkuController.php b/Modules/Linksehat/Http/Controllers/Api/DuitkuController.php new file mode 100644 index 00000000..4f5159b5 --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/DuitkuController.php @@ -0,0 +1,360 @@ +setSandboxMode(true); + // set sanitizer (default : true) + $duitkuConfig->setSanitizedMode(false); + // set log parameter (default : true) + $duitkuConfig->setDuitkuLogs(false); + return $duitkuConfig; + } + + public function createInvoice(Request $request) + { + $data = [ + 'paymentMethod' => $request->paymentMethod, + 'paymentAmount' => $request->paymentAmount, + 'email' => $request->email, + 'phoneNumber' => $request->phoneNumber, + 'productDetails' => $request->productDetails, + 'merchantOrderId' => $request->merchantOrderId, + 'additionalParam' => $request->additionalParam, + 'merchantUserInfo' => $request->merchantUserInfo, + 'customerVaName' => $request->customerVaName, + // 'callbackUrl' => $request->callbackUrl, + // 'returnUrl' => $request->returnUrl, + // 'expiryPeriod' => $request->expiryPeriod, + 'firstName' => $request->firstName, + 'lastName' => $request->lastName, + 'alamat' => $request->alamat, + 'city' => $request->city, + 'postalCode' => $request->postalCode, + // 'countryCode' => $request->countryCode + ]; + $validator = Validator::make($request->all(), [ + 'paymentMethod' => 'nullable', + 'paymentAmount' => 'required', + 'email' => 'required|email', + 'phoneNumber' => 'nullable', + 'productDetails' => 'required', + 'merchantOrderId' => 'required', + 'additionalParam' => 'nullable', + 'merchantUserInfo' => 'nullable', + 'customerVaName' => 'required', + // 'callbackUrl' => 'required', + // 'returnUrl' => 'nullable', + // 'expiryPeriod' => 'required', + 'firstName' => 'required', + 'lastName' => 'required', + 'alamat' => 'required', + 'city' => 'required', + 'postalCode' => 'required', + // 'countryCode' => 'required' + + ], [ + 'paymentAmount.required' => 'Jumlah pembayaran harus diisi', + 'email.required' => 'Email harus diisi', + 'email.email' => 'Format email salah', + 'productDetails.required' => 'Judul pembayaran harus diisi', + 'merchantOrderId.required' => 'Order ID harus diisi', + 'customerVaName.required' => 'Nama panggilan pelanggan harus diisi', + 'firstName.required' => 'Nama depan pelanggan harus diisi', + 'lastName.required' => 'Nama belakang pelanggan harus diisi', + 'alamat.required' => 'Alamat pelanggan harus diisi', + 'city.required' => 'Kota pelanggan harus diisi', + 'postalCode.required' => 'Kode pos pelanggan harus diisi', + ]); + + if ($validator->fails()) + { + return Helper::responseJson( + data: $data, + status: 'Bad Request', + statusCode: 400, + message: $validator->errors() + ); + } + else + { + #CONTOH DARI DUITKU + // $paymentMethod = ""; // PaymentMethod list => https://docs.duitku.com/pop/id/#payment-method + // $paymentAmount = 10000; // Amount + // $email = "customer@gmail.com"; // your customer email + // $phoneNumber = "081234567890"; // your customer phone number (optional) + // $productDetails = "Test Payment"; + // $merchantOrderId = "2"; // from merchant, unique + // $additionalParam = ''; // optional + // $merchantUserInfo = ''; // optional + // $customerVaName = 'John Doe'; // display name on bank confirmation display + // $callbackUrl = 'http://YOUR_SERVER/callback'; // url for callback + // $returnUrl = 'http://YOUR_SERVER/return'; // url for redirect + // $expiryPeriod = 60; // set the expired time in minutes + + // // Customer Detail + // $firstName = "John"; + // $lastName = "Doe"; + + // // Address + // $alamat = "Jl. Kembangan Raya"; + // $city = "Jakarta"; + // $postalCode = "11530"; + // $countryCode = "ID"; + + $paymentMethod = $request->paymentMethod; // PaymentMethod list => https://docs.duitku.com/pop/id/#payment-method + $paymentAmount = $request->paymentAmount; // Amount + $email = $request->email; // your customer email + $phoneNumber = $request->phoneNumber; // your customer phone number (optional) + $productDetails = $request->productDetails; + $merchantOrderId = $request->merchantOrderId; // from merchant, unique + $additionalParam = $request->additionalParam; // optional + $merchantUserInfo = $request->merchantUserInfo; // optional + $customerVaName = $request->customerVaName; // display name on bank confirmation display + $callbackUrl = env('APP_URL').'/api/linksehat/callback-duitku'; // url for callback + $returnUrl = env('APP_URL').'/api/linksehat/redirect-duitku';; // url for redirect + $expiryPeriod = 60; // set the expired time in minutes + + // Customer Detail + $firstName = $request->firstName; + $lastName = $request->lastName; + + // Address + $alamat = $request->alamat; + $city = $request->city; + $postalCode = $request->postalCode; + $countryCode = "ID"; + + $address = array( + 'firstName' => $firstName, + 'lastName' => $lastName, + 'address' => $alamat, + 'city' => $city, + 'postalCode' => $postalCode, + 'phone' => $phoneNumber, + 'countryCode' => $countryCode + ); + + $customerDetail = array( + 'firstName' => $firstName, + 'lastName' => $lastName, + 'email' => $email, + 'phoneNumber' => $phoneNumber, + 'billingAddress' => $address, + 'shippingAddress' => $address + ); + + // Item Details + $item1 = array( + 'name' => $productDetails, + 'price' => $paymentAmount, + 'quantity' => 1 + ); + + $itemDetails = array( + $item1 + ); + + $params = array( + 'paymentAmount' => $paymentAmount, + 'merchantOrderId' => $merchantOrderId, + 'productDetails' => $productDetails, + 'additionalParam' => $additionalParam, + 'merchantUserInfo' => $merchantUserInfo, + 'customerVaName' => $customerVaName, + 'email' => $email, + 'phoneNumber' => $phoneNumber, + 'itemDetails' => $itemDetails, + 'customerDetail' => $customerDetail, + 'callbackUrl' => $callbackUrl, + 'returnUrl' => $returnUrl, + 'expiryPeriod' => $expiryPeriod + ); + $duitkuConfig = $this->configuration(); + try { + // createInvoice Request + $responseDuitkuPop = \Duitku\Pop::createInvoice($params, $duitkuConfig); + + header('Content-Type: application/json'); + echo $responseDuitkuPop; + } catch (Exception $e) { + echo $e->getMessage(); + + } + } + } + + public function paymentMethod(Request $request) + { + $duitkuConfig = $this->configuration(); + try { + $paymentAmount = "10000"; //"YOUR_AMOUNT"; + $paymentMethodList = \Duitku\Pop::getPaymentMethod($paymentAmount, $duitkuConfig); + + header('Content-Type: application/json'); + echo $paymentMethodList; + } catch (Exception $e) { + echo $e->getMessage(); + } + } + + public function checkStatus(Request $request) + { + $duitkuConfig = $this->configuration(); + $data = [ + 'merchantOrderId' => $request->merchantOrderId + ]; + $validator = Validator::make($request->all(), [ + 'merchantOrderId' => 'required', + ], [ + 'merchantOrderId.required' => 'Order ID harus diisi', + ]); + + if ($validator->fails()) + { + return Helper::responseJson( + data: $data, + status: 'Bad Request', + statusCode: 400, + message: $validator->errors() + ); + } + else + { + try { + $merchantOrderId = $request->merchantOrderId; + $transactionList = \Duitku\Pop::transactionStatus($merchantOrderId, $duitkuConfig); + + header('Content-Type: application/json'); + $transaction = json_decode($transactionList); + + // var_dump($transactionList); + + if ($transaction->statusCode == "00") { + // Action Success + } else if ($transaction->statusCode == "01") { + // Action Pending + } else { + // Action Failed Or Expired + } + echo $transaction->statusCode; + } catch (Exception $e) { + echo $e->getMessage(); + } + } + } + + public function callback(Request $request) + { + $duitkuConfig = $this->configuration(); + try { + $callback = \Duitku\Pop::callback($duitkuConfig); + + header('Content-Type: application/json'); + $notif = json_decode($callback); + + // $notif = $request; ini untuk di local + + DB::table('api_logs') + ->insert([ + 'type' => 'in', + 'target' => env('APP_URL').'/api/linksehat/callback-duitku', + 'request' => $callback, + 'created_by' => auth()->check() ? auth()->user()->id : null, + 'created_at' => date('Y-m-d H:i:s') + ]); + + if ($notif->resultCode == "00") { + // Action Success + $livechat = Livechat::where('uuid', $notif->merchantOrderId)->first(); + // Update status pembayaran + $livechat->payment_method = $notif->paymentCode; + $livechat->status = 5; // success payment + $livechat->save(); + + // Update start chat + $livechat->start_date = date('Y-m-d H:i:s'); + // Buat dan simpan data channel ke dalam tabel + $channel = Channel::updateOrCreate([ + 'name' => $livechat->patient_id .'_' . $request->doctor_id, + ], + [ + 'name' => $livechat->patient_id .'_' . $livechat->doctor_id, + 'type' => 'Private', + 'member_id' => $livechat->patient_id, + 'doctor_id' => $livechat->doctor_id, + ]); + + // Menggunakan updateOrCreate untuk menambahkan data UserChannel untuk member_id + $userChannelMember = UserChannel::updateOrCreate( + [ + 'user_id' => $livechat->patient_id, + 'channel_id' => $channel->id + ], + [ + 'user_id' => $livechat->patient_id, + 'channel_id' => $channel->id + ] + ); + + // Menggunakan updateOrCreate untuk menambahkan data UserChannel untuk doctor_id + $userChannelDoctor = UserChannel::updateOrCreate( + [ + 'user_id' => $livechat->doctor_id, + 'channel_id' => $channel->id + ], + [ + 'user_id' => $livechat->doctor_id, + 'channel_id' => $channel->id + ] + ); + + // Berikan respons yang sesuai ke klien + return response()->json(['message' => 'Channel created successfully', 'channel' => $channel]); + + } else if ($notif->resultCode == "01") { + // Action Failed + $livechat = Livechat::where('uuid', $notif->merchantOrderId)->first(); + // Update status pembayaran + $livechat->payment_method = $notif->paymentCode; + $livechat->status = 7; // failed payment + $livechat->save(); + + return response()->json(['message' => 'User Gagal melakukan pembayaran']); + } + + } catch (Exception $e) { + http_response_code(400); + echo $e->getMessage(); + } + } + + public function redirect(Request $request) + { + $resultCode = $request->input('resultCode'); + $merchantOrderId = $request->input('merchantOrderId'); + $reference = $request->input('reference'); + return Redirect::to('https://linksehat.com/'); + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/HomeController.php b/Modules/Linksehat/Http/Controllers/Api/HomeController.php new file mode 100644 index 00000000..a405e697 --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/HomeController.php @@ -0,0 +1,75 @@ +where('nId', $request->id) + ->first(); + return Helper::responseJson([ + 'home' => HomeResource::make($user, $request), + ]); + } + + public function listHospital(Request $request){ + // Hospital List + $hospitalList = []; + $hospitals = Organization::where([ + 'type' => 'hospital', + 'status' => 'active', + ]) + ->with('currentAddress') + ->get()->toArray(); + foreach($hospitals as $hospital){ + $lat = 0; + $lang = 0; + if ($hospital['current_address']['lat']){ + $lat = $hospital['current_address']['lat']; + } + if ($hospital['current_address']['lng']){ + $lang = $hospital['current_address']['lng']; + } + + $address = ''; + if ($hospital['current_address']['text']){ + $address = $hospital['current_address']['text']; + } + + $radius = 0; + if ($lat && $lang && $request->longitude && $request->latitude){ + $radius = round(Helper::calculateDistance($lat, $lang, $request->latitude, $request->longitude), 2); + } + + $data = [ + 'name' => $hospital['name'], + 'radius' => $radius, + 'image' => '', + 'address' => $address + ]; + + array_push($hospitalList, $data); + } + usort($hospitalList, function($a, $b) { + return $a['radius'] <=> $b['radius']; + }); + return Helper::responseJson([ + 'hospital' => $hospitalList + ]); + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/LinkingController.php b/Modules/Linksehat/Http/Controllers/Api/LinkingController.php index 899dbfe2..331eeb68 100644 --- a/Modules/Linksehat/Http/Controllers/Api/LinkingController.php +++ b/Modules/Linksehat/Http/Controllers/Api/LinkingController.php @@ -5,6 +5,8 @@ namespace Modules\Linksehat\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Person; +use App\Models\RequestLogBenefit; +use App\Models\RequestLog; use App\Models\Corporate; use App\Models\Member; use App\Models\OLDLMS\User; @@ -135,4 +137,39 @@ class LinkingController extends Controller $message = $member->currentPolicy->corporate->welcome_message; return Helper::responseJson(data: MemberResource::make($member), message: $message); } + + public function card_detail($member_id, $id){ + $member = Member::where('member_id', $member_id)->get()->toArray(); + $requestLogBenefits = RequestLogBenefit::where('request_log_id', $id)->with('benefit')->get()->toArray(); + $requestLog = RequestLog::find($id)->first(); + $benefitItem = []; + $dataRequestLog = [ + 'code' => $requestLog['code'], + 'diagnosis' => Helper::diagnosisName($requestLog['diagnosis']), + 'service_type' => Helper::serviceName($requestLog['service_code']), + + ]; + foreach($requestLogBenefits as $requestLogBenefit) { + $data = [ + 'benefit_item' => $requestLogBenefit['benefit']['description'], + 'amount_incurred' => $requestLogBenefit['amount_incurred'], + 'amount_approved' => $requestLogBenefit['amount_approved'], + 'amount_not_approved' => $requestLogBenefit['amount_not_approved'], + 'excess_paid' => $requestLogBenefit['excess_paid'], + ]; + + $benefitItem[] = $data; + }; + $dataRequestLog['benefit_item'] = $benefitItem; + + // dd($dataRequestLog); + // $data = [ + // 'id' => $requestLog['id'], + // 'code' => $requestLog['code'], + // 'submission_date' => Carbon::parse($requestLog['submission_date'])->format('d M Y H:i:s'), + // 'provider_name' => $requestLog['organization']['name'], + // 'service' => Helper::serviceName($requestLog['service_code']) + // ]; + return Helper::responseJson(data:$dataRequestLog); + } } diff --git a/Modules/Linksehat/Http/Controllers/Api/LivechatController.php b/Modules/Linksehat/Http/Controllers/Api/LivechatController.php new file mode 100644 index 00000000..b1e2d7e0 --- /dev/null +++ b/Modules/Linksehat/Http/Controllers/Api/LivechatController.php @@ -0,0 +1,308 @@ +where('nId', $request->id) + ->first(); + return Helper::responseJson([ + 'livechat' => LivechatResource::make($user, $request), + ]); + } + + public function consultation(Request $request) + { + $dataMemberProfile = []; + $user = User::with('detail') + ->where('nId', $request->member_id) + ->first(); + $memberProfile = User::with('detail')->where('nIDUser', $request->member_id)->get()->toArray(); + if (count($memberProfile) > 0){ + $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $user->detail->sImage ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $user->nIDHubunganKeluarga)->first('sHubunganKeluarga'); + + $dataUser = [ + 'id' => $user->nID, + 'name' => $user->sFirstName . ' ' . $user->sLastName, + 'relationship' => $relationship ? $relationship->sHubunganKeluarga : '-', + 'avatar' => $avatarMember + ]; + + array_push($dataMemberProfile, $dataUser); + + foreach($memberProfile as $m){ + $urlAvatarDefault = $m['detail']['nIDJenisKelamin'] == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $m['detail']['sImage'] ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $m['nIDHubunganKeluarga'])->first('sHubunganKeluarga'); + + $data = [ + 'id' => $m['nID'], + 'name' => $m['full_name'], + 'relationship' => $relationship ? $relationship->sHubunganKeluarga : '-', + 'avatar' => $avatarMember, + ]; + + array_push($dataMemberProfile, $data); + } + } else { + $nID = $user->nIDUser ? $user->nIDUser : $user->nID; + if ($nID){ + $memberProfile = User::with('detail')->where('nIDUser', $nID)->get()->toArray(); + + $dataMember = User::with('detail')->where('nID', $nID)->get()->first(); + + if ($user->detail){ + $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + } else { + $urlAvatarDefault = 'https://linksehat.dev/assets/img/users/male-avatar.png'; + } + $avatar = $user->detail->sImage ?? $urlAvatarDefault; + + $avatarMember = $dataMember->detail->sImage ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $user->detail->nIDHubunganKeluarga)->first('sHubunganKeluarga'); + + $dataUser = [ + 'id' => $dataMember->nID, + 'name' => $dataMember->sFirstName . ' ' . $dataMember->sLastName, + 'relationship' => 'Me', + 'avatar' => $avatarMember + ]; + array_push($dataMemberProfile, $dataUser); + + if (count($memberProfile) > 0){ + foreach($memberProfile as $m){ + $urlAvatarDefault = $m['detail']['nIDJenisKelamin'] == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $m['detail']['sImage'] ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $m['nIDHubunganKeluarga'])->first('sHubunganKeluarga'); + + $data = [ + 'id' => $m['nID'], + 'name' => $m['full_name'], + 'relationship' => $relationship->sHubunganKeluarga, + 'avatar' => $avatarMember, + ]; + + array_push( $dataMemberProfile, $data); + } + } + } + } + + return Helper::responseJson([ + 'member' => $dataMemberProfile + ]); + } + + public function consultation_request(Request $request) + { + $data = [ + 'doctor_id' => $request->doctor_id, + 'patient_id' => $request->patient_id, + 'organization_id' => $request->organization_id, + 'descriptions' => $request->descriptions + ]; + + $validator = Validator::make($request->all(), [ + 'doctor_id' => 'required', + 'patient_id' => 'required', + 'descriptions' => 'required', + ], [ + 'doctor_id.required' => 'ID Dokter harus diisi', + 'patient_id.required' => 'ID Dokter harus diisi', + 'descriptions.required' => 'Description harus diisi', + ]); + + if ($validator->fails()){ + return Helper::responseJson( + status: 'Bad Request', + statusCode: 400, + message: $validator->errors() + ); + } else { + // insert table livechat + + /** + * Status Livechat + * 1=Request, 2=Accept, 3=Decline, 4=Waiting Payment, 5=Success Payment, 6 = End Chat + */ + + $timezone = date_default_timezone_get(); + $data['request_date'] = date('Y-m-d H:i:s'); + $data['timezone'] = $timezone; + $data['uuid'] = (string) Str::orderedUuid(); + $data['status'] = 1; + $livechat = Livechat::create($data); + $doctor = $livechat->doctor; + $data = [ + 'id' => $livechat->id, + 'request_date' => $livechat->request_date, + 'image_path' =>'https' + ]; + + + return Helper::responseJson(data: $data); + } + } + + public function consultation_request_show($id){ + $livechat = Livechat::where('id', $id)->with(['doctor', 'practitioner'])->first(); + $practitionerRole = PractitionerRole::where('id',$livechat->practitioner->id)->first(); + + $price = $practitionerRole->price ? $practitionerRole->price : 30000; + $discount = 0; + $adminFee = 5000; + $totalPay = $price + $adminFee - $discount; + $data = [ + 'id' => $livechat->id, + 'code_transaksi' => $livechat->uuid, + 'doctor_id' => $livechat->doctor_id, + 'doctor_name' => $livechat->doctor->name, + 'doctor_specialist' => 'Umum', + 'price' => $price, + 'admin_price' => $adminFee, + 'promo' => [ + [ + 'id' => 1, + 'code' => 'SEHATBERSAMA', + 'discount_percent' => 20 + ], + [ + 'id' => 2, + 'code' => 'MARET MERIAH', + 'discount_percent' => 5 + ], + ], + 'total' => $totalPay + + ]; + return Helper::responseJson(data: $data); + } + + public function consultation_payment_choose($id){ + $livechat = Livechat::where('id', $id)->with(['doctor', 'practitioner'])->first(); + $practitionerRole = PractitionerRole::where('id',$livechat->practitioner->id)->first(); + $eWallet = PaymentsMethods::where( + [ + 'active' => 1, + 'config_pmc_id' => 3, + ])->get()->toArray(); + $va = PaymentsMethods::where( + [ + 'active' => 1, + 'config_pmc_id' => 2, + ])->get()->toArray(); + + $payment = DuitkuHelper::paymentMethod(); + + $price = $practitionerRole->price ? $practitionerRole->price : 30000; + $discount = 0; + $adminFee = 5000; + $totalPay = $price + $adminFee - $discount; + $data = [ + 'id' => $livechat->id, + 'code_transaksi' => $livechat->uuid, + 'price' => $price, + 'admin_price' => $adminFee, + 'total' => $totalPay, + 'payment_method' => [ + 'ewallet' => $eWallet, + 'va' => $va + ] + // 'payment_method' => json_decode($payment) + + ]; + return Helper::responseJson(data: $data); + } + + public function consultation_payment(Request $request) + { + try { + // Mengambil data Livechat dengan relasi doctor dan practitioner + $livechat = Livechat::with(['doctor', 'practitioner'])->find($request->consultation_id); + + if (!$livechat) { + return response()->json(['success' => false, 'message' => 'Consultation not found'], 404); + } + + // Update status + $livechat->status = 4; + $livechat->save(); + + $practitionerRole = PractitionerRole::find($livechat->practitioner->id); + $price = $practitionerRole->price ?? 30000; // Gunakan null coalescing operator + $adminFee = 5000; + $discount = 0; + $totalPay = $price + $adminFee - $discount; + + // Mengambil user dari database + $user = User::with('detail')->where('nId', $livechat->patient_id)->first(); + $address = DB::connection('oldlms')->table('tm_users_address')->where('nIDUser', $user->nID)->first('sAlamat'); + if($address){ + $address = $address->sAlamat; + } + if (!$user) { + return response()->json(['success' => false, 'message' => 'User not found'], 404); + } + + // Menyiapkan data untuk invoice + $data = [ + 'paymentMethod' => $request->payment_code, + 'paymentAmount' => $totalPay, + 'email' => $user->sEmail, + 'phoneNumber' => $user->sPhone, + 'productDetails' => 'INV-' . date('Ymd') . '-' . rand(100, 999), + 'merchantOrderId' => $livechat->uuid, + 'additionalParam' => '', + 'merchantUserInfo' => '', + 'customerVaName' => $user->sFirstName . ' ' . $user->sLastName, + 'firstName' => $user->sFirstName, + 'lastName' => $user->sLastName, + 'alamat' => $address, + 'city' => '', + 'postalCode' => '' + ]; + + // Membuat invoice menggunakan DuitkuHelper + $duitku = DuitkuHelper::createInvoice($data); + + + return response()->json(['success' => true, 'data' => $duitku], 200); + } catch (Exception $e) { + // Menangkap error dan mengembalikan respon error + return response()->json(['success' => false, 'message' => $e->getMessage()], 500); + } + } + + public function consultation_check_payment($id){ + $livechat = Livechat::where('id',$id)->with(['doctor', 'practitioner'])->first(); + $duitku = DuitkuHelper::checkStatus($livechat->uuid); + + return $duitku; + + } +} diff --git a/Modules/Linksehat/Http/Controllers/Api/ProfileController.php b/Modules/Linksehat/Http/Controllers/Api/ProfileController.php index f4be989a..d334dfbf 100644 --- a/Modules/Linksehat/Http/Controllers/Api/ProfileController.php +++ b/Modules/Linksehat/Http/Controllers/Api/ProfileController.php @@ -43,7 +43,46 @@ class ProfileController extends Controller */ public function store(Request $request) { - // + $validator = Validator::make($request->all(), [ + 'id_user' => 'required', + 'first_name' => 'required', + 'last_name' => 'required', + 'date_of_birth' => 'required', + // 'email' => 'required', + + ]); + + if ($validator->fails()) { + return response()->json(['errors' => $validator->errors()], 422); + } else { + $value = [ + 'nIDuser' => $request->id_user, + 'sIPAddress' => $request->ip(), + 'sPassword' => null, + 'sFirstName' => $request->first_name, + 'sLastName' => $request->last_name, + ]; + + $user = User::create($value); + $dataDetail = [ + 'nIDUser' => $user->nID, + 'dTanggalLahir' => $request->date_of_birth, + 'nIDJenisKelamin' => $request->gender + ]; + + $userDetail = UserDetail::create( + $dataDetail + ); + + $data['data'] = [ + 'status' => 200, + 'message' => 'data berhasil di tambahkan', + 'error' => 'false' + + ]; + + return response()->json($data); + } } /** diff --git a/Modules/Linksehat/Http/Middleware/Doctor/Authentication.php b/Modules/Linksehat/Http/Middleware/Doctor/Authentication.php new file mode 100644 index 00000000..9f1f9c26 --- /dev/null +++ b/Modules/Linksehat/Http/Middleware/Doctor/Authentication.php @@ -0,0 +1,65 @@ +header('Accept'); + $contentType = $request->header('Content-Type'); + $locale = $request->header('Accept-Language'); + + // Add language + if(!$locale) + { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Accept-Language']), 401); + } + if($locale !== 'en-US' && $locale !== 'id-ID') + { + return ApiResponse::apiResponse('Bad Request', null, trans('Validation.invalid', ['attribute' => 'Accept-Language']), 400); + } + if ($locale === 'en-US') + { + App::setLocale('en'); + } elseif ($locale === 'id-ID') + { + App::setLocale('id'); + } else + { + App::setLocale('en'); + } + + // Validate type accept & content type + if (!$acceptHeader) + { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Accept']), 401); + } + if (!$contentType) + { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Content-Type']), 401); + } + if ($acceptHeader !== 'application/json') + { + return ApiResponse::apiResponse('Bad Request', null, trans('Validation.invalid', ['attribute' => 'Accept']), 400); + } + if($contentType !== 'application/json') + { + return ApiResponse::apiResponse('Bad Request', null, trans('Validation.invalid', ['attribute' => 'Content-Type']), 400); + } + return $next($request); + } +} diff --git a/Modules/Linksehat/Http/Middleware/Doctor/Authorization.php b/Modules/Linksehat/Http/Middleware/Doctor/Authorization.php new file mode 100644 index 00000000..6d29b24f --- /dev/null +++ b/Modules/Linksehat/Http/Middleware/Doctor/Authorization.php @@ -0,0 +1,71 @@ +header('Accept'); + $contentType = $request->header('Content-Type'); + $locale = $request->header('Accept-Language'); + $authorization = $request->header('Authorization'); + + // Add language + if(!$locale) + { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Accept-Language']), 401); + } + if($locale !== 'en-US' && $locale !== 'id-ID') + { + return ApiResponse::apiResponse('Bad Request', null, trans('Validation.invalid', ['attribute' => 'Accept-Language']), 400); + } + if ($locale === 'en-US') + { + App::setLocale('en'); + } elseif ($locale === 'id-ID') + { + App::setLocale('id'); + } else + { + App::setLocale('en'); + } + + // Validate authorization + if (empty($authorization) || strpos($authorization, 'Bearer ') !== 0) { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Authorization']), 401); + } + + // Validate type accept & content type + if (!$acceptHeader) + { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Accept']), 401); + } + if (!$contentType && $request->isMethod('post')) + { + return ApiResponse::apiResponse('Unauthorized', null, trans('Validation.required', ['attribute' => 'Content-Type']), 401); + } + if ($acceptHeader !== 'application/json') + { + return ApiResponse::apiResponse('Bad Request', null, trans('Validation.invalid', ['attribute' => 'Accept']), 400); + } + if($contentType !== 'application/json' && $request->isMethod('post')) + { + return ApiResponse::apiResponse('Bad Request', null, trans('Validation.invalid', ['attribute' => 'Content-Type']), 400); + } + return $next($request); + } +} diff --git a/Modules/Linksehat/Routes/api.php b/Modules/Linksehat/Routes/api.php index 07fe7532..9344de6f 100644 --- a/Modules/Linksehat/Routes/api.php +++ b/Modules/Linksehat/Routes/api.php @@ -5,6 +5,8 @@ use Modules\Linksehat\Http\Controllers\Api\AuthController; use Modules\Linksehat\Http\Controllers\Api\DashboardController; use Modules\Linksehat\Http\Controllers\Api\AutocompleteController; use Modules\Linksehat\Http\Controllers\Api\DoctorController; +use Modules\Linksehat\Http\Controllers\Api\DuitkuController; +use Modules\Linksehat\Http\Controllers\Api\ChatController; use Modules\Linksehat\Http\Controllers\Api\HospitalController; use Modules\Linksehat\Http\Controllers\Api\NotificationTokenController; use Modules\Linksehat\Http\Controllers\Api\PersonController; @@ -12,7 +14,13 @@ use Modules\Linksehat\Http\Controllers\Api\ProfileController; use Modules\Linksehat\Http\Controllers\Api\SearchController; use Modules\Linksehat\Http\Controllers\Api\SpecialityController; use Modules\Linksehat\Http\Controllers\Api\LinkingController; - +use Modules\Linksehat\Http\Controllers\Api\HomeController; +use Modules\Linksehat\Http\Controllers\Api\LivechatController; +use Modules\Linksehat\Http\Middleware\Doctor\Authentication; +use Modules\Linksehat\Http\Middleware\Doctor\Authorization; +use Modules\Linksehat\Http\Controllers\Api\Doctor\AuthDoctorController; +use Modules\Linksehat\Http\Controllers\Api\Doctor\ProfileDoctorController; +use Modules\Linksehat\Http\Controllers\Api\Doctor\ChatDoctorController; /* |-------------------------------------------------------------------------- | API Routes @@ -24,8 +32,9 @@ use Modules\Linksehat\Http\Controllers\Api\LinkingController; | */ +Broadcast::routes(['middleware' => ['auth:sanctum']]); Route::prefix('linksehat')->group(function () { - + Route::get('dashboard/{query}/{limit?}', [DashboardController::class, 'index']); Route::controller(SearchController::class)->group(function () { @@ -63,11 +72,11 @@ Route::prefix('linksehat')->group(function () { Route::get('doctors/{id}', 'show')->name('doctors.show'); }); - Route::middleware('auth:sanctum')->group(function () { Route::get('profile/{id}', [ProfileController::class, 'index'])->name('profile'); Route::get('change-profile/{id}', [ProfileController::class, 'changeProfile'])->name('change-profile'); Route::post('profile', [ProfileController::class, 'update'])->name('profile.update'); + Route::post('profile-add', [ProfileController::class, 'store'])->name('profile.store'); Route::post('notification-tokens/delete/{id}', [NotificationTokenController::class, 'destroy'])->name('profile.delete.token'); Route::post('notification-tokens', [NotificationTokenController::class, 'store'])->name('profile.store.token'); Route::apiResource('appointment', AppointmentController::class); @@ -77,10 +86,84 @@ Route::prefix('linksehat')->group(function () { Route::get('autocomplete/blood_type', [AutocompleteController::class, 'bloodType']); Route::get('autocomplete/relationship', [AutocompleteController::class, 'relationship']); Route::get('autocomplete/corporate', [AutocompleteController::class, 'corporate']); + Route::get('autocomplete/drugs', [AutocompleteController::class, 'drugList']); + Route::get('autocomplete/units', [AutocompleteController::class, 'unitList']); + Route::get('autocomplete/diagnosis', [AutocompleteController::class, 'diagnosis']); Route::post('manual-linking', [LinkingController::class, 'linkingValidate']); Route::get('card/{member_id}', [LinkingController::class, 'card']); + Route::get('card/{member_id}/{log_id}', [LinkingController::class, 'card_detail']); + + Route::controller(HomeController::class)->group(function () { + Route::get('home', 'index')->name('homes.index'); + Route::get('home/hospital', 'listHospital')->name('homes.listHospital'); + }); + + Route::controller(LivechatController::class)->group(function () { + Route::get('livechat', 'index')->name('livechats.index'); + Route::get('livechat/consultation', 'consultation')->name('livechats.consultation'); + Route::post('livechat/consultation-request', 'consultation_request')->name('livechats.consultation-request'); + Route::get('livechat/consultation-request/{id}', 'consultation_request_show'); + Route::get('livechat/consultation-request/consultation-payment-choose/{id}', 'consultation_payment_choose'); + Route::get('livechat/consultation-request/consultation-payment-check/{id}', 'consultation_check_payment'); + + Route::post('livechat/consultation-payment', 'consultation_payment'); + }); + + Route::controller(ChatController::class)->group(function () { + Route::post('livechat/send-message', 'sendMessage'); + Route::get('livechat/get-message', 'getMessage'); + Route::post('livechat/channel','createChannel'); + Route::get('livechat/channel','listChannel'); + Route::get('livechat/{id}/health-sertificate','downloadHealtcare'); + }); + + + Route::post('create-invoice-duitku', [DuitkuController::class, 'createInvoice']); + Route::post('check-status-duitku', [DuitkuController::class, 'checkStatus']); }); -}); + + Route::post('payment-method-duitku', [DuitkuController::class, 'paymentMethod']); + Route::post('callback-duitku', [DuitkuController::class, 'callback']); + Route::get('redirect-duitku', [DuitkuController::class, 'redirect']); + + //DOCTOR API + Route::prefix('doctor')->group(function() { + //Version 1.0 + Route::prefix('v1')->group(function() { + Route::middleware(Authentication::class)->group(function () { + Route::controller(AuthDoctorController::class)->group(function () { + Route::post('login', 'login'); + }); + }); + Route::middleware('auth:sanctum')->group(function () { + Route::middleware(Authorization::class)->group(function () { + Route::controller(AuthDoctorController::class)->group(function () { + Route::post('logout', 'logout'); + Route::post('forgot-password', 'forgotPassword'); + }); + Route::controller(ProfileDoctorController::class)->group(function () { + Route::get('get-profile', 'getProfile'); + }); + Route::controller(ChatDoctorController::class)->group(function () { + Route::get('chat', 'getChat'); + Route::post('decline', 'declineChat'); + Route::post('approve', 'approveChat'); + Route::post('end', 'endChat'); + Route::post('summary', 'summaryChat'); + Route::get('chat/{id}', 'getChatDetail'); + }); + }); + }); + Route::controller(AuthDoctorController::class)->group(function () { + Route::post('forgot-password', 'forgotPassword'); + Route::post('verif-code', 'verifCode'); + Route::post('resend-code', 'forgotPassword'); + Route::post('reset-password', 'resetPassword'); + }); + + }); + }); +;}); diff --git a/Modules/Linksehat/Transformers/Home/HomeResource.php b/Modules/Linksehat/Transformers/Home/HomeResource.php new file mode 100644 index 00000000..274ca362 --- /dev/null +++ b/Modules/Linksehat/Transformers/Home/HomeResource.php @@ -0,0 +1,280 @@ +request = $request; + parent::__construct($resource); + } + + public function toArray($request) + { + $memberProfile = User::with('detail')->where('nIDUser', $this->nID)->get()->toArray(); + $dataMemberProfile = []; + + $userInsurance = UserInsurance::where('nIDUser', $this->nID)->get()->first(); + $memberId = null; + $linking = false; + + $jam = date('G'); // Ambil jam dalam format 24 jam + + if ($jam < 12) { + $wellcome = "Good Morning!"; + } elseif ($jam < 18) { + $wellcome = "Good Afternoon!"; + } else { + $wellcome = "Good Evening!"; + } + + if($userInsurance){ + $memberId = $userInsurance->sNoPolis; + $linking = true; + } else { + $member = Member::where('email', $this->sEmail)->get()->first(); + $person = Person::where('phone', $this->sPhone)->get()->first(); + if ($member || $person){ // Autolinking + $corporateEmployee = CorporateEmployee::where('member_id', $member->id)->get()->first(); // cek corporate id empolyee/member + if ($corporateEmployee){ + $corporate = Corporate::findOrFail($corporateEmployee->corporate_id)->automatic_linking; // cek autocomplete + if ($corporate){ + if($member) { + // Insert into database linksehat + $insurance = UserInsurance::updateOrCreate( + [ + 'nIDUser' => $this->nID, + ], + [ + 'nIDUser' => $this->nID, + 'nIDInsurance' => $_ENV['LINKSEHAT_ASO_INSURANCE_ID'], + 'sNoPolis' => $member->member_id, + 'sNamaPeserta' => $member->fullName, + 'sKartuPeserta' => '', + 'sLayanan' => 'RJ,TC', + 'dStartDate' => $member->members_effective_date, + 'dExpireDate' => $member->members_expire_date, + 'dTanggalLahir' => $member->birth_date, + 'nNoKTP' => $member->nric != '' ? $member->nric : 0 , + 'sIsConfrimed' => 1, + 'sStatus' => 1, + ]); + $message = $member->currentPolicy->corporate->welcome_message; + $linking = true; + + $memberId = $member->member_id; + } + } + } + } + }; + + if (count($memberProfile) > 0){ + + $urlAvatarDefault = $this->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $this->detail->sImage ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $this->nIDHubunganKeluarga)->first('sHubunganKeluarga'); + + $dataUser = [ + 'id' => $this->nID, + 'name' => $this->sFirstName . ' ' . $this->sLastName, + 'relationship' => $relationship ? $relationship->sHubunganKeluarga : '-', + 'avatar' => $avatarMember + ]; + + array_push($dataMemberProfile, $dataUser); + + foreach($memberProfile as $m){ + $urlAvatarDefault = $m['detail']['nIDJenisKelamin'] == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $m['detail']['sImage'] ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $m['nIDHubunganKeluarga'])->first('sHubunganKeluarga'); + + $data = [ + 'id' => $m['nID'], + 'name' => $m['full_name'], + 'relationship' => $relationship ? $relationship->sHubunganKeluarga : '-', + 'avatar' => $avatarMember, + ]; + + array_push( $dataMemberProfile, $data); + } + + + } else { + $nID = $this->nIDUser ? $this->nIDUser : $this->nID; + if ($nID){ + $memberProfile = User::with('detail')->where('nIDUser', $nID)->get()->toArray(); + + $dataMember = User::with('detail')->where('nID', $nID)->get()->first(); + + if ($this->detail){ + $urlAvatarDefault = $this->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + } else { + $urlAvatarDefault = 'https://linksehat.dev/assets/img/users/male-avatar.png'; + } + $avatar = $this->detail->sImage ?? $urlAvatarDefault; + + $avatarMember = $dataMember->detail->sImage ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $this->nIDHubunganKeluarga)->first('sHubunganKeluarga'); + + $dataUser = [ + 'id' => $dataMember->nID, + 'name' => $dataMember->sFirstName . ' ' . $dataMember->sLastName, + 'relationship' => 'Me', + 'avatar' => $avatarMember + ]; + array_push($dataMemberProfile, $dataUser); + + if (count($memberProfile) > 0){ + foreach($memberProfile as $m){ + $urlAvatarDefault = $m['detail']['nIDJenisKelamin'] == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + $avatarMember = $m['detail']['sImage'] ?? $urlAvatarDefault; + $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $m['nIDHubunganKeluarga'])->first('sHubunganKeluarga'); + + $data = [ + 'id' => $m['nID'], + 'name' => $m['full_name'], + 'relationship' => $relationship->sHubunganKeluarga, + 'avatar' => $avatarMember, + ]; + + array_push( $dataMemberProfile, $data); + } + } + } else { + + } + } + + // Principal + if ($this->detail){ + $urlAvatarDefault = $this->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; + } else { + $urlAvatarDefault = 'https://linksehat.dev/assets/img/users/male-avatar.png'; + } + $avatar = $this->detail->sImage ?? $urlAvatarDefault; + + // Doctor livechat + $doctors = Practitioner::with('person', 'practitionerRoles.organization', 'practitionerRoles.speciality') + ->whereHas('person', function ($query) { + $query->where('isOnline', 1); // hanya online + }) + ->get() + ->toArray(); + $doctorsLivechat = []; + foreach($doctors as $doctor){ + $specialist = 'Umum'; + $year = 0; + $price = 0; + if (!empty($doctor['person']['start_date_work'])) { + $starExperience = Carbon::parse($doctor['person']['start_date_work'])->format('Y-m-d'); + $experience = Carbon::createFromFormat('Y-m-d', $starExperience); + $year = $experience->diffInYears(Carbon::now()); + } + if ($doctor['practitioner_roles']) { + if ($doctor['practitioner_roles'][0]['speciality']){ + $specialist = $doctor['practitioner_roles'][0]['speciality']['name']; + } + if ($doctor['practitioner_roles'][0]['price']){ + $price = $doctor['practitioner_roles'][0]['price']; + } + } + $data = [ + 'id' => $doctor['id'], + 'full_name' => $doctor['person']['name'], + 'specialist' => $specialist, + 'experience' => $year, + 'review' => $doctor['person']['review'], + 'price' => $price, + 'price_real' => $price + ]; + array_push($doctorsLivechat, $data); + } + + // Hospital List + $hospitalList = []; + + $hospitals = Organization::where([ + 'type' => 'hospital', + 'status' => 'active', + ]) + ->with('currentAddress') + ->get()->toArray(); + foreach($hospitals as $hospital){ + $lat = 0; + $lang = 0; + if ($hospital['current_address']['lat']){ + $lat = $hospital['current_address']['lat']; + } + if ($hospital['current_address']['lng']){ + $lang = $hospital['current_address']['lng']; + } + + $address = ''; + if ($hospital['current_address']['text']){ + $address = $hospital['current_address']['text']; + } + + $radius = 0; + if ($lat && $lang && $request->longitude && $request->latitude){ + $radius = round(Helper::calculateDistance($lat, $lang, $request->latitude, $request->longitude), 2); + } + + $data = [ + 'name' => $hospital['name'], + 'radius' => $radius, + 'image' => '', + 'address' => $address + ]; + + array_push($hospitalList, $data); + } + + + usort($hospitalList, function($a, $b) { + return $a['radius'] <=> $b['radius']; + }); + + $hospitalList = array_slice($hospitalList, 0, 5); + + return [ + 'id' => $this->nID, + 'message' => $wellcome, + 'full_name' => $this->sFirstName . ' '. $this->sLastName, + 'avatar' => $avatar, + 'member_id' => $memberId, + 'linking' => $linking, + 'doctors_livechat' => $doctorsLivechat, + 'hospital' => $hospitalList + ]; + } +} diff --git a/Modules/Linksehat/Transformers/Livechat/LivechatResource.php b/Modules/Linksehat/Transformers/Livechat/LivechatResource.php new file mode 100644 index 00000000..e1d974d1 --- /dev/null +++ b/Modules/Linksehat/Transformers/Livechat/LivechatResource.php @@ -0,0 +1,85 @@ +request = $request; + parent::__construct($resource); + } + + public function toArray($request) + { + // Specialis + $specialists = Speciality::all(); + + // Doctor livechat + $doctors = Practitioner::with('person', 'practitionerRoles.organization', 'practitionerRoles.speciality') + ->whereHas('person', function ($query) { + $query->where('isOnline', 1); // hanya online + }) + ->get() + ->toArray(); + $doctorsLivechat = []; + foreach($doctors as $doctor){ + $specialist = 'Umum'; + $year = 0; + $price = 0; + if (!empty($doctor['person']['start_date_work'])) { + $starExperience = Carbon::parse($doctor['person']['start_date_work'])->format('Y-m-d'); + $experience = Carbon::createFromFormat('Y-m-d', $starExperience); + $year = $experience->diffInYears(Carbon::now()); + } + if ($doctor['practitioner_roles']) { + if ($doctor['practitioner_roles'][0]['speciality']){ + $specialist = $doctor['practitioner_roles'][0]['speciality']['name']; + } + if ($doctor['practitioner_roles'][0]['price']){ + $price = $doctor['practitioner_roles'][0]['price']; + } + } + $data = [ + 'id' => $doctor['id'], + 'full_name' => $doctor['person']['name'], + 'specialist' => $specialist, + 'experience' => $year, + 'review' => $doctor['person']['review'], + 'price' => $price, + 'price_real' => $price + ]; + array_push($doctorsLivechat, $data); + } + + return [ + 'jadwal_weekday' => 'Senin - Jumat (08:00 - 17:30)', + 'jadwal_weekend' => 'Sabtu (08:00 - 12:00)', + 'doctors_livechat' => + $doctorsLivechat + , + 'specialist' => $specialists + ]; + } +} diff --git a/app/Events/ChatMessageSent.php b/app/Events/ChatMessageSent.php new file mode 100644 index 00000000..75426384 --- /dev/null +++ b/app/Events/ChatMessageSent.php @@ -0,0 +1,44 @@ +message = $message; + $this->dontBroadcastToCurrentUser(); + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('chat.' . $this->message->channel_id); + } + + public function broadcastAs(): string + { + return 'chat.sent'; + } +} diff --git a/app/Helpers/DuitkuHelper.php b/app/Helpers/DuitkuHelper.php new file mode 100644 index 00000000..68237430 --- /dev/null +++ b/app/Helpers/DuitkuHelper.php @@ -0,0 +1,167 @@ +setSandboxMode(true); + // set sanitizer (default : true) + $duitkuConfig->setSanitizedMode(false); + // set log parameter (default : true) + $duitkuConfig->setDuitkuLogs(false); + return $duitkuConfig; + } + + public static function paymentMethod() + { + $duitkuConfig = self::configuration(); + try { + $paymentAmount = "10000"; //"YOUR_AMOUNT"; + $paymentMethodList = \Duitku\Pop::getPaymentMethod($paymentAmount, $duitkuConfig); + + header('Content-Type: application/json'); + return $paymentMethodList; + } catch (Exception $e) { + return $e->getMessage(); + } + } + + public static function checkStatus($merchantOrderId) + { + $duitkuConfig = self::configuration(); + $data = [ + 'merchantOrderId' => $merchantOrderId + ]; + try { + $transactionList = \Duitku\Pop::transactionStatus($merchantOrderId, $duitkuConfig); + + $transaction = json_decode($transactionList); + + if ($transaction->statusCode == "00" || $transaction->statusCode == "01") { + // Transaksi berhasil atau dalam proses + return $transaction; + } else { + // Transaksi gagal atau kedaluwarsa + return ['error' => true]; + } + } catch (\Duitku\Exceptions\DuitkuException $e) { + // Tangani pengecualian yang terkait dengan Duitku + return ['error' => true, 'message' => $e->getMessage()]; + } catch (Exception $e) { + // Tangani pengecualian umum + return ['error' => true, 'message' => $e->getMessage()]; + } + } + + public static function createInvoice($data) + { + #CONTOH DARI DUITKU + // $paymentMethod = ""; // PaymentMethod list => https://docs.duitku.com/pop/id/#payment-method + // $paymentAmount = 10000; // Amount + // $email = "customer@gmail.com"; // your customer email + // $phoneNumber = "081234567890"; // your customer phone number (optional) + // $productDetails = "Test Payment"; + // $merchantOrderId = "2"; // from merchant, unique + // $additionalParam = ''; // optional + // $merchantUserInfo = ''; // optional + // $customerVaName = 'John Doe'; // display name on bank confirmation display + // $callbackUrl = 'http://YOUR_SERVER/callback'; // url for callback + // $returnUrl = 'http://YOUR_SERVER/return'; // url for redirect + // $expiryPeriod = 60; // set the expired time in minutes + + // // Customer Detail + // $firstName = "John"; + // $lastName = "Doe"; + + // // Address + // $alamat = "Jl. Kembangan Raya"; + // $city = "Jakarta"; + // $postalCode = "11530"; + // $countryCode = "ID"; + + $paymentMethod = $data['paymentMethod']; // PaymentMethod list => https://docs.duitku.com/pop/id/#payment-method + $paymentAmount = $data['paymentAmount']; // Amount + $email = $data['email']; // your customer email + $phoneNumber = $data['phoneNumber']; // your customer phone number (optional) + $productDetails = $data['productDetails']; + $merchantOrderId = $data['merchantOrderId']; // from merchant, unique + $additionalParam = $data['additionalParam']; // optional + $merchantUserInfo = $data['merchantUserInfo']; // optional + $customerVaName = $data['customerVaName']; // display name on bank confirmation display + $callbackUrl = env('DUITKU_PAYMENT_CALLBACK_URL'); // url for callback + $returnUrl = env('APP_URL').'/api/linksehat/redirect-duitku';; // url for redirect + $expiryPeriod = 60; // set the expired time in minutes + + // Customer Detail + $firstName = $data['firstName']; + $lastName = $data['lastName']; + + // Address + $alamat = $data['alamat']; + $city = $data['city']; + $postalCode = $data['postalCode']; + $countryCode = "ID"; + + $address = array( + 'firstName' => $firstName, + 'lastName' => $lastName, + 'address' => $alamat, + 'city' => $city, + 'postalCode' => $postalCode, + 'phone' => $phoneNumber, + 'countryCode' => $countryCode + ); + + $customerDetail = array( + 'firstName' => $firstName, + 'lastName' => $lastName, + 'email' => $email, + 'phoneNumber' => $phoneNumber, + 'billingAddress' => $address, + 'shippingAddress' => $address + ); + + // Item Details + $item1 = array( + 'name' => $productDetails, + 'price' => $paymentAmount, + 'quantity' => 1 + ); + + $itemDetails = array( + $item1 + ); + + $params = array( + 'paymentAmount' => $paymentAmount, + 'merchantOrderId' => $merchantOrderId, + 'productDetails' => $productDetails, + 'additionalParam' => $additionalParam, + 'merchantUserInfo' => $merchantUserInfo, + 'customerVaName' => $customerVaName, + 'email' => $email, + 'phoneNumber' => $phoneNumber, + 'itemDetails' => $itemDetails, + 'customerDetail' => $customerDetail, + 'callbackUrl' => $callbackUrl, + 'returnUrl' => $returnUrl, + 'expiryPeriod' => $expiryPeriod + ); + $duitkuConfig = self::configuration(); + try { + // createInvoice Request + $responseDuitkuPop = \Duitku\Pop::createInvoice($params, $duitkuConfig); + header('Content-Type: application/json'); + return json_decode($responseDuitkuPop); + } catch (Exception $e) { + return json_decode($e->getMessage()); + + } + } + +} \ No newline at end of file diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index b188b9dc..dae72916 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\DB; use App\Models\Member; use App\Models\User; use App\Models\Service; +use App\Models\Icd; use DateTime; class Helper @@ -117,6 +118,16 @@ class Helper } } + public static function diagnosisName($code) + { + $icd = Icd::where('code', $code)->get()->first(); + if ($icd){ + return $icd->name; + } else { + return '-'; + } + } + public static function paginateResources($resource) { return [ @@ -258,11 +269,14 @@ class Helper * * @param array|object $data * @param int $statusCode - * @param string $message + * @param string|array|object $message * @return JsonResponse */ - public static function responseJson(array|object $data = [], string $status = 'success', int $statusCode = Response::HTTP_OK, string $message = 'Data berhasil di ambil'): JsonResponse + public static function responseJson(array|object $data = [], string $status = 'success', int $statusCode = Response::HTTP_OK, string|array|object $message = 'Data berhasil di ambil'): JsonResponse { + if ($message instanceof \Illuminate\Support\MessageBag) { + $message = $message->first(); + } return response()->json([ 'status' => $status, 'statusCode' => $statusCode, @@ -377,9 +391,7 @@ class Helper $mail->send(); return true; } catch (\Exception $e) { - dd($e); - return ($mail->ErrorInfo); - return false; + return $mail->ErrorInfo; } } @@ -439,8 +451,52 @@ class Helper } } else { // throw new ImportRowException(__('Format Date Invalid'), 0, null, $date_from_row); - return null; + return null; } } + public static function calculateDistance($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371) { + // Convert degrees to radians + $latFrom = deg2rad($latitudeFrom); + $lonFrom = deg2rad($longitudeFrom); + $latTo = deg2rad($latitudeTo); + $lonTo = deg2rad($longitudeTo); + + // Calculate the change in coordinates + $deltaLat = $latTo - $latFrom; + $deltaLon = $lonTo - $lonFrom; + + // Apply Haversine formula + $a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($latFrom) * cos($latTo) * sin($deltaLon / 2) * sin($deltaLon / 2); + $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); + $distance = $earthRadius * $c; + + return $distance; + } + + public static function calculateAge($date_brith_day){ + // Konversi tanggal lahir ke dalam format UNIX timestamp + $dob = strtotime($date_brith_day); + + // Hitung umur berdasarkan tanggal lahir + $umur = date('Y') - date('Y', $dob); + + // Periksa apakah ulang tahun sudah lewat atau belum + if (date('md', $dob) > date('md')) { + $umur--; + } + + // Mengembalikan umur dalam format "x years old" + return $umur . ' years old'; + } + + public static function calculateDateDifference($startDate, $endDate) + { + $start = Carbon::parse($startDate); + $end = Carbon::parse($endDate); + + return $start->diffInDays($end) + 1; + } + + } diff --git a/app/Http/Resources/OLDLMS/MemberResource.php b/app/Http/Resources/OLDLMS/MemberResource.php index 610ce9cf..14d5f04d 100644 --- a/app/Http/Resources/OLDLMS/MemberResource.php +++ b/app/Http/Resources/OLDLMS/MemberResource.php @@ -4,6 +4,12 @@ namespace App\Http\Resources\OLDLMS; use App\Services\ClaimService; use App\Models\Corporate; +use App\Models\CorporateBenefit; +use App\Models\RequestLog; +use App\Models\RequestLogBenefit; +use App\Models\MemberPlan; +use App\Helpers\Helper; +use Carbon\Carbon; use Illuminate\Http\Resources\Json\JsonResource; @@ -21,7 +27,46 @@ class MemberResource extends JsonResource $currentMemberPlan = $this->memberPlans?->first(); $limitTelecon = $currentMemberPlan->plan->limit_telecon ?? null; $limitTelecon = $this->totalUsage >= 6 ? null : $limitTelecon; + $services = MemberPlan::where('member_id', $this->id)->with('plan')->get()->toArray(); + $dataServices = []; + if ($services) { + foreach($services as $service) { + $serviceName = Helper::serviceName($service['plan']['service_code']); + $benefits = CorporateBenefit::where('plan_id', $service['plan_id'])->with('benefit')->get()->toArray(); + $dataBenefit = []; + foreach($benefits as $benefit){ + $dataBenefitItem = $benefit['benefit']['description']; + array_push($dataBenefit, $dataBenefitItem); + } + $data = [ + 'name' => $serviceName, + 'benefit' => $dataBenefit + ]; + + array_push($dataServices, $data); + } + } + // LOG + $dataLog = []; + $requestLogs = RequestLog::where('member_id', $this->id)->with('organization')->get()->toArray(); + $totalBenefit = 0; + if ($requestLogs) { + foreach($requestLogs as $requestLog) { + $requestLogBenefit = RequestLogBenefit::where('request_log_id', $requestLog['id'])->sum('amount_approved'); + $totalBenefit += $requestLogBenefit; + $data = [ + 'id' => $requestLog['id'], + 'code' => $requestLog['code'], + 'submission_date' => Carbon::parse($requestLog['submission_date'])->format('d M Y H:i:s'), + 'provider_name' => $requestLog['organization']['name'], + 'service' => Helper::serviceName($requestLog['service_code']) + ]; + + array_push($dataLog, $data); + } + } + $data = [ 'id' => $this->id, 'member_name' => $this->full_name, @@ -33,6 +78,9 @@ class MemberResource extends JsonResource 'start_date' => $this->members_effective_date, 'corporate_logo' => $_ENV['LMS_APP_STORAGE'] . $this->corporateLogo, 'valid_until' => $this->members_expire_date, + 'total_benefit_usage' => $totalBenefit, + 'service' => $dataServices, + 'histor_log' => $dataLog ]; return $data; diff --git a/app/Listeners/ProcessChatMessage.php b/app/Listeners/ProcessChatMessage.php new file mode 100644 index 00000000..a0fc7afc --- /dev/null +++ b/app/Listeners/ProcessChatMessage.php @@ -0,0 +1,32 @@ +message; + } +} diff --git a/app/Models/Channel.php b/app/Models/Channel.php new file mode 100644 index 00000000..a318ca67 --- /dev/null +++ b/app/Models/Channel.php @@ -0,0 +1,17 @@ + 'final-log/', 'docs' => 'docs/', 'additional-files' => 'additional-files/', + 'chat' => 'chat/', ]; public function fileable() @@ -89,4 +90,15 @@ class File extends Model $file->storeAs('public/' . $directory, $fileName . '.' . $extension); return $path; } + + public static function storeFileChat($type, $id, $file) + { + // $fileName = self::getFileName($type, $id); + $fileName = $file->getClientOriginalName(); + $directory = self::getDirectory($type); + $extension = $file->getClientOriginalExtension(); + $path = $directory . $fileName . '.' . $extension; + $file->store('public/' .$path); + return $path; + } } diff --git a/app/Models/Livechat.php b/app/Models/Livechat.php new file mode 100644 index 00000000..82536b15 --- /dev/null +++ b/app/Models/Livechat.php @@ -0,0 +1,44 @@ +belongsTo(Person::class, 'doctor_id', 'id'); + } + + public function practitioner() { + return $this->belongsTo(Practitioner::class, 'doctor_id', 'id'); + } + + +} diff --git a/app/Models/Message.php b/app/Models/Message.php new file mode 100644 index 00000000..587cb957 --- /dev/null +++ b/app/Models/Message.php @@ -0,0 +1,25 @@ +morphMany(File::class, 'fileable')->whereNull('deleted_at'); + } + +} diff --git a/app/Models/OLDLMS/Livechat.php b/app/Models/OLDLMS/Livechat.php index 5c0ae646..19b35fa7 100644 --- a/app/Models/OLDLMS/Livechat.php +++ b/app/Models/OLDLMS/Livechat.php @@ -70,4 +70,8 @@ class Livechat extends Model { return $this->belongsTo(Healthcare::class, 'nIDHealthCare', 'nID'); } + + public function summary(){ + return $this->belongsTo(LivechatSummary::class, 'nID', 'nIDLivechat'); + } } diff --git a/app/Models/OLDLMS/LivechatSummary.php b/app/Models/OLDLMS/LivechatSummary.php new file mode 100644 index 00000000..076fbd58 --- /dev/null +++ b/app/Models/OLDLMS/LivechatSummary.php @@ -0,0 +1,40 @@ + 'Menunggu Konfirmasi', + // 1 => 'Diterima', + // 3 => 'Ditolak', + // 2 => 'Selesai', + // 4 => 'Expired', + // ]; + + public $sStatusNames = [ + 0 => 'Request TC', + 1 => 'Accepted by Doctor but User not Payment', + 3 => 'Decline by Doctor', + 2 => 'Payment Success', + 4 => 'Payment Expired', + 5 => 'Cancel by Patient' + ]; + + const CREATED_AT = 'dCreateOn'; + const UPDATED_AT = 'dUpdateOn'; + const DELETED_AT = 'dDeleteOn'; + + protected $connection = 'oldlms'; + + protected $table = 'tx_livechat_summary'; + + protected $primaryKey = 'nID'; +} diff --git a/app/Models/OLDLMS/Prescription.php b/app/Models/OLDLMS/Prescription.php index 8f75f528..5652aa73 100644 --- a/app/Models/OLDLMS/Prescription.php +++ b/app/Models/OLDLMS/Prescription.php @@ -10,6 +10,20 @@ class Prescription extends Model { use HasFactory; + protected $fillable = [ + 'nIDLivechat', + 'nIDLivechatSummary', + 'nIDDokter', + 'sDokterName', + 'dTanggalResep', + 'sSource', + 'nIDUser', + 'sRegID', + 'sKodeResep', + 'sDiagnose', + 'sKodeRS', + ]; + // public $sStatusNames = [ // 0 => 'Menunggu Konfirmasi', @@ -27,6 +41,8 @@ class Prescription extends Model protected $table = 'tx_prescriptions'; + protected $primaryKey = 'nID'; + // protected $appends = [ // 'status_name', // ]; diff --git a/app/Models/OLDLMS/PrescriptionItem.php b/app/Models/OLDLMS/PrescriptionItem.php new file mode 100644 index 00000000..4851e29c --- /dev/null +++ b/app/Models/OLDLMS/PrescriptionItem.php @@ -0,0 +1,52 @@ + 'Menunggu Konfirmasi', + // 1 => 'Diterima', + // 2 => 'Ditolak', + // 3 => 'Selesai', + // 4 => 'Expired', + // ]; + + const CREATED_AT = 'dCreateOn'; + const UPDATED_AT = 'dUpdateOn'; + const DELETED_AT = 'dDeleteOn'; + + protected $connection = 'oldlms'; + + protected $table = 'tx_prescription_items'; + + // protected $appends = [ + // 'status_name', + // ]; + + protected $casts = [ + 'dTanggalResep' => 'datetime', + ]; + + protected $primaryKey = 'nID'; +} diff --git a/app/Models/OLDLMS/User.php b/app/Models/OLDLMS/User.php index 47a49474..c67e03ee 100644 --- a/app/Models/OLDLMS/User.php +++ b/app/Models/OLDLMS/User.php @@ -38,6 +38,7 @@ class User extends Authenticatable 'sEmail', 'nIDHubunganKeluarga', 'dUpdateOn', + 'sIPAddress', ]; protected function fullName(): Attribute diff --git a/app/Models/PaymentsMethods.php b/app/Models/PaymentsMethods.php new file mode 100644 index 00000000..1db98639 --- /dev/null +++ b/app/Models/PaymentsMethods.php @@ -0,0 +1,20 @@ + [ LogClaimJournal::class, ], + + ChatMessageSent::class => [ + ProcessChatMessage::class, + ], ]; diff --git a/app/Services/Duitku.php b/app/Services/Duitku.php index da849ab9..4b60cc56 100644 --- a/app/Services/Duitku.php +++ b/app/Services/Duitku.php @@ -44,7 +44,7 @@ class Duitku $merchantUserInfo = ''; // optional $customerVaName = $patient->name ?? ''; // display name on bank confirmation display $callbackUrl = env('DUITKU_PAYMENT_CALLBACK_URL'); // url for callback - $returnUrl = 'https://dev-superapp.primaya.id'; + $returnUrl = env(''); $expiryPeriod = $paymentMethods->timeout; // set the expired time in minutes // Customer Detail @@ -53,9 +53,9 @@ class Duitku $lastName = $patient->last_name ?? ''; // Address - $alamat = $patient->address->first()->line ?? ''; + $alamat = $patient->address?? ''; // dd($alamat); - $city = $patient->address->first()->city->name ?? ''; + $city = $patient->city ?? ''; // dd($city); $postalCode = $patient->postal_code ?? ''; $countryCode = "ID"; diff --git a/config/broadcasting.php b/config/broadcasting.php index 9e4d4aa4..e2550c4c 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -40,7 +40,9 @@ return [ 'port' => env('PUSHER_PORT', 443), 'scheme' => env('PUSHER_SCHEME', 'https'), 'encrypted' => true, - 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + 'cluster' => 'ap1', + 'useTLS' => true + ], 'client_options' => [ // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html diff --git a/database/migrations/2024_02_12_133447_add_coloumn_to_benefits_table.php b/database/migrations/2024_02_12_133447_add_coloumn_to_benefits_table.php new file mode 100644 index 00000000..7a031803 --- /dev/null +++ b/database/migrations/2024_02_12_133447_add_coloumn_to_benefits_table.php @@ -0,0 +1,32 @@ +integer('type')->nullable()->default(1)->comment('1=Non COB, 2=Cob'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('benefits', function (Blueprint $table) { + $table->dropColumn('type'); + }); + } +}; diff --git a/database/migrations/2024_04_16_134401_add_column_to_persons_table.php b/database/migrations/2024_04_16_134401_add_column_to_persons_table.php new file mode 100644 index 00000000..06adb0d3 --- /dev/null +++ b/database/migrations/2024_04_16_134401_add_column_to_persons_table.php @@ -0,0 +1,39 @@ +dateTime('start_date_work')->nullable()->after('birth_place'); + $table->integer('isOnline') + ->default(0) + ->comment('0=offline, 1=online') + ->after('start_date_work'); + $table->double('review')->after('isOnline'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('persons', function (Blueprint $table) { + $table->dropColumn('start_date_work'); + $table->dropColumn('isOnline'); + $table->dropColumn('review'); + }); + } +}; diff --git a/database/migrations/2024_04_16_170457_add_column_to_practitioner_roles_table.php b/database/migrations/2024_04_16_170457_add_column_to_practitioner_roles_table.php new file mode 100644 index 00000000..46ce4053 --- /dev/null +++ b/database/migrations/2024_04_16_170457_add_column_to_practitioner_roles_table.php @@ -0,0 +1,32 @@ +integer('price')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('practitioner_roles', function (Blueprint $table) { + $table->dropColumn('price'); + }); + } +}; diff --git a/database/migrations/2024_04_19_100246_create_livechat_table.php b/database/migrations/2024_04_19_100246_create_livechat_table.php new file mode 100644 index 00000000..3b6bf5e7 --- /dev/null +++ b/database/migrations/2024_04_19_100246_create_livechat_table.php @@ -0,0 +1,46 @@ +id(); + $table->foreignId('doctor_id'); + $table->foreignId('patient_id'); + $table->foreignId('organization_id'); + $table->string('timezone')->nullable()->default(null); + $table->text('descriptions'); + $table->string('payment_method'); + $table->dateTime('request_date')->nullable(); + $table->dateTime('accept_date')->nullable(); + $table->dateTime('start_date')->nullable(); + $table->dateTime('end_date')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->foreignId('created_by')->nullable(); + $table->foreignId('updated_by')->nullable(); + $table->foreignId('deleted_by')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('livechats'); + } +}; diff --git a/database/migrations/2024_04_22_084927_add_column_to_livechats_table.php b/database/migrations/2024_04_22_084927_add_column_to_livechats_table.php new file mode 100644 index 00000000..8cfb806b --- /dev/null +++ b/database/migrations/2024_04_22_084927_add_column_to_livechats_table.php @@ -0,0 +1,32 @@ +string('uuid')->after('id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('livechats', function (Blueprint $table) { + $table->dropColumn('uuid'); + }); + } +}; diff --git a/database/migrations/2024_04_23_145224_create_person_educations_table.php b/database/migrations/2024_04_23_145224_create_person_educations_table.php new file mode 100644 index 00000000..86faea77 --- /dev/null +++ b/database/migrations/2024_04_23_145224_create_person_educations_table.php @@ -0,0 +1,38 @@ +id(); + $table->bigInteger('person_id'); + $table->integer('level_id')->comment('1=SD, 2= SMP, 3=SMA dst.'); + $table->string('name', 255); + $table->dateTime('start_date'); + $table->dateTime('end_date'); + $table->bigInteger('created_by')->nullable(); + $table->bigInteger('updated_by')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('person_educations'); + } +}; diff --git a/database/migrations/2024_04_23_160705_add_column_to_table_practitioners.php b/database/migrations/2024_04_23_160705_add_column_to_table_practitioners.php new file mode 100644 index 00000000..a6c7a6b6 --- /dev/null +++ b/database/migrations/2024_04_23_160705_add_column_to_table_practitioners.php @@ -0,0 +1,34 @@ +string('str_number', 255)->after('person_id')->nullable(); + $table->dateTime('exp_date_str')->after('str_number')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('practitioners', function (Blueprint $table) { + $table->dropColumn('str_number'); + $table->dropColumn('exp_date_str'); + }); + } +}; diff --git a/database/migrations/2024_04_23_162213_add_column_to_table_practitioner_roles.php b/database/migrations/2024_04_23_162213_add_column_to_table_practitioner_roles.php new file mode 100644 index 00000000..d80eb1b4 --- /dev/null +++ b/database/migrations/2024_04_23_162213_add_column_to_table_practitioner_roles.php @@ -0,0 +1,34 @@ +string('sip_number', 255)->after('period_end')->nullable(); + $table->dateTime('exp_date_sip')->after('sip_number')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('practitioner_roles', function (Blueprint $table) { + $table->dropColumn('sip_number'); + $table->dropColumn('exp_date_sip'); + }); + } +}; diff --git a/database/migrations/2024_04_25_160416_add_column_to_request_logs_table.php b/database/migrations/2024_04_25_160416_add_column_to_request_logs_table.php new file mode 100644 index 00000000..46452d52 --- /dev/null +++ b/database/migrations/2024_04_25_160416_add_column_to_request_logs_table.php @@ -0,0 +1,34 @@ +string('invoice_no')->after('code')->nullable(); + $table->string('billing_no')->after('invoice_no')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('request_logs', function (Blueprint $table) { + $table->dropColumn('invoice_no'); + $table->dropColumn('billing_no'); + }); + } +}; diff --git a/database/migrations/2024_04_30_132417_create_prescription_table.php b/database/migrations/2024_04_30_132417_create_prescription_table.php new file mode 100644 index 00000000..69b52c24 --- /dev/null +++ b/database/migrations/2024_04_30_132417_create_prescription_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('livechat_id'); + $table->foreignId('organization_id'); + $table->string('icd_code'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('prescriptions'); + } +}; diff --git a/database/migrations/2024_04_30_132747_create_prescription_items_table.php b/database/migrations/2024_04_30_132747_create_prescription_items_table.php new file mode 100644 index 00000000..57585342 --- /dev/null +++ b/database/migrations/2024_04_30_132747_create_prescription_items_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('prescription_id'); + $table->foreignId('drug_id'); + $table->integer('qty'); + $table->foreignId('unit_id'); + $table->string('note'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('prescription_items'); + } +}; diff --git a/database/migrations/2024_04_30_150511_add_column_to_prescription_items_table.php b/database/migrations/2024_04_30_150511_add_column_to_prescription_items_table.php new file mode 100644 index 00000000..ec8ec019 --- /dev/null +++ b/database/migrations/2024_04_30_150511_add_column_to_prescription_items_table.php @@ -0,0 +1,32 @@ +string('signa'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('prescription_items', function (Blueprint $table) { + $table->dropColumn('signa'); + }); + } +}; diff --git a/database/migrations/2024_05_08_143444_create_user_channels_table.php b/database/migrations/2024_05_08_143444_create_user_channels_table.php new file mode 100644 index 00000000..a1709558 --- /dev/null +++ b/database/migrations/2024_05_08_143444_create_user_channels_table.php @@ -0,0 +1,33 @@ +id(); + $table->timestamps(); + $table->foreignId('user_id'); + $table->foreignId('channel_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_channels'); + } +}; diff --git a/database/migrations/2024_05_08_143558_create_channels_table.php b/database/migrations/2024_05_08_143558_create_channels_table.php new file mode 100644 index 00000000..2f65aeec --- /dev/null +++ b/database/migrations/2024_05_08_143558_create_channels_table.php @@ -0,0 +1,33 @@ +id(); + $table->timestamps(); + $table->string('name'); + $table->string('type'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('channels'); + } +}; diff --git a/database/migrations/2024_05_08_143716_create_messages_table.php b/database/migrations/2024_05_08_143716_create_messages_table.php new file mode 100644 index 00000000..fe9cd3d4 --- /dev/null +++ b/database/migrations/2024_05_08_143716_create_messages_table.php @@ -0,0 +1,36 @@ +id(); + $table->timestamps(); + $table->foreignId('from_user'); + $table->foreignId('channel_id'); + $table->text('content'); + $table->string('type'); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('messages'); + } +}; diff --git a/database/migrations/2024_05_14_100058_add_column_to_channels_table.php b/database/migrations/2024_05_14_100058_add_column_to_channels_table.php new file mode 100644 index 00000000..3043fdaa --- /dev/null +++ b/database/migrations/2024_05_14_100058_add_column_to_channels_table.php @@ -0,0 +1,34 @@ +foreignId('member_id'); + $table->foreignId('doctor_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('channels', function (Blueprint $table) { + $table->dropColumn('member_id'); + $table->dropColumn('doctor_id'); + }); + } +}; diff --git a/database/migrations/2024_05_14_150646_add_column_to_table_drugs.php b/database/migrations/2024_05_14_150646_add_column_to_table_drugs.php new file mode 100644 index 00000000..6ae79552 --- /dev/null +++ b/database/migrations/2024_05_14_150646_add_column_to_table_drugs.php @@ -0,0 +1,32 @@ +decimal('price', 8, 2)->nullable()->after('active'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('drugs', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2024_05_15_111627_add_column_to_livechats_table.php b/database/migrations/2024_05_15_111627_add_column_to_livechats_table.php new file mode 100644 index 00000000..da35e42a --- /dev/null +++ b/database/migrations/2024_05_15_111627_add_column_to_livechats_table.php @@ -0,0 +1,44 @@ +integer('status')->after('end_date')->comment('1=Request, 2=Accept, 3=Decline, 4=Waiting Payment, 5=Success Payment, 6 = End Chat'); + $table->string('subject')->after('status')->nullable(); + $table->string('object')->after('subject')->nullable(); + $table->string('assessment')->after('object')->nullable(); + $table->text('plan')->after('assessment')->nullable(); + $table->date('health_certificate_start')->after('end_date')->nullable(); + $table->date('health_certificate_end')->after('health_certificate_start')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('livechats', function (Blueprint $table) { + $table->dropColumn('status'); + $table->dropColumn('subject'); + $table->dropColumn('object'); + $table->dropColumn('assessment'); + $table->dropColumn('plan'); + $table->dropColumn('health_certificate_start'); + $table->dropColumn('health_certificate_end'); + }); + } +}; diff --git a/database/migrations/2024_05_16_133343_add_column_to_prescription_items_table.php b/database/migrations/2024_05_16_133343_add_column_to_prescription_items_table.php new file mode 100644 index 00000000..1c6a55b5 --- /dev/null +++ b/database/migrations/2024_05_16_133343_add_column_to_prescription_items_table.php @@ -0,0 +1,32 @@ +string('direction')->after('signa')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('prescription_items', function (Blueprint $table) { + $table->dropColumn('direction'); + }); + } +}; diff --git a/frontend/client-portal/src/components/DialogUpdateStatus.tsx b/frontend/client-portal/src/components/DialogUpdateStatus.tsx new file mode 100644 index 00000000..71b8f908 --- /dev/null +++ b/frontend/client-portal/src/components/DialogUpdateStatus.tsx @@ -0,0 +1,208 @@ +import * as Yup from 'yup'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Dialog, DialogTitle, DialogContent, Stack, Typography, IconButton, Grid } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { ReactElement } from 'react'; +import Iconify from './Iconify'; +import { Card } from '@mui/material'; +import { FormProvider, RHFTextField, RHFSwitch, RHFSelect } from './hook-form'; +import { Button } from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import axios from '@/utils/axios'; +import { enqueueSnackbar } from 'notistack'; + +// ---------------------------------------------------------------------- + +type DataContent = { + code: string; + name: string; + id: number; + status: string +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + maxWidth?: string; + data?: DataContent | undefined; + description: string; +}; + +type FormValuesProps = { + value: string; + active: boolean; +}; + + +// ---------------------------------------------------------------------- + +const DialogUpdateStatus = ({ title, openDialog, setOpenDialog, data, maxWidth, content }: MuiDialogProps) => { + const NewCorporateSchema = Yup.object().shape({ + reason: Yup.string().required('Corporate Status is required'), + }); + + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + }); + + const { + reset, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + + useEffect(() => { + if (openDialog === false) { + reset(); + } + }, [openDialog, reset]); + + const handleClose = () => { + setOpenDialog(false); + }; + + const handleUpdate = (id: number, status: number) => { + axios + .put(`/corporates/${id}/activation`, { + // service_code: service.service_code, + active: status, + }) + .then((res) => { + handleClose() + window.location.reload(); + }) + .catch((error) => { + // console.log('asdasd', error.response.data.message) + enqueueSnackbar( + error.response.data.message ?? error.message ?? 'Failed Processing Request', + { variant: 'error' } + ); + }); + } + let maxWidthDialog = 'md'; + + if (maxWidth) { + maxWidthDialog = maxWidth; + } + + const onSubmit = async (row : any) => { + console.log('test') + }; + + return ( + + + + {title?.icon ? ( + + + {title?.name} + + ) : ( + {title?.name ? title?.name : ''} + )} + + + + + + + + {/* + {description} + + + + + Code + + + {data?.code} + + + Corporate Name + + + {data?.name} + + + + + + Reason for update* + + + + + + + + + + + + + + + {data?.status == 1 ? + + : } + + */} + {content} + + + + ); +}; + +export default DialogUpdateStatus; diff --git a/frontend/client-portal/src/contexts/ConfiguredCorporateContext.tsx b/frontend/client-portal/src/contexts/ConfiguredCorporateContext.tsx new file mode 100644 index 00000000..59270cf0 --- /dev/null +++ b/frontend/client-portal/src/contexts/ConfiguredCorporateContext.tsx @@ -0,0 +1,46 @@ +import { Corporate } from '@/@types/corporates'; +import axios from '@/utils/axios'; +import { ReactNode, createContext, useState, useEffect } from 'react'; +import { useParams } from 'react-router'; +// hooks +import useResponsive from '../hooks/useResponsive'; + +// ---------------------------------------------------------------------- + +export type ConfiguredCorporateContextProps = { + currentCorporate?: Corporate | null; +}; + +const initialState: ConfiguredCorporateContextProps = { + currentCorporate: null, +}; + +const ConfiguredCorporateContext = createContext(initialState); + +type ConfiguredCorporateProviderProps = { + children: ReactNode; +}; + +function ConfiguredCorporateProvider({ children }: ConfiguredCorporateProviderProps) { + const { corporate_id } = useParams(); + const [corporate, setCorporate] = useState(null); + + useEffect(() => { + axios.get(`corporates/${corporate_id}`) + .then((res) => { + setCorporate(res.data) + }) + }, []); + + return ( + + {children} + + ); +} + +export { ConfiguredCorporateProvider, ConfiguredCorporateContext }; diff --git a/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx index b910d105..4ea27c39 100644 --- a/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/client-portal/src/layouts/dashboard/navbar/NavConfig.tsx @@ -32,6 +32,10 @@ const navConfig = [ title: 'Alarm Center', path: '/alarm-center', }, + { + title: 'Formularium', + path: '/master/formularium-template-v2', + } // { // title: 'Claim Submit', // path: '/claim-submit', diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdate.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdate.tsx new file mode 100644 index 00000000..2e1aa7f7 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdate.tsx @@ -0,0 +1,69 @@ +import { useNavigate, useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import {useContext, useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { useSnackbar } from 'notistack'; +import { ConfiguredCorporateContext } from "@/contexts/ConfiguredCorporateContext"; +import { Corporate, CorporatePlan } from "@/@types/corporates"; +import { MasterFormularium } from "./Type"; +import CreateUpdateForm from "./CreateUpdateForm"; +import Typography from "@/theme/overrides/Typography"; + +export default function CreateUpdate() { + const navigate = useNavigate(); + const { corporate_id, id } = useParams(); + const [corporate, setCorporate] = useState(); + const configuredCorporateContext = useContext(ConfiguredCorporateContext) + + useEffect(() => { + setCorporate(configuredCorporateContext.currentCorporate); + }, [ConfiguredCorporateContext]) + + const [ currentCorporatePlan, setCurrentCorporatePlan ] = useState(); + const [ currentMasterForm, setCurrentMasterForm ] = useState(); + const isEdit = !!id; + + useEffect(() => { + if (isEdit) { + axios.get(`/master/formularium-template/${id}/edit`) + .then((res) => { + // setCurrentCorporatePlan(res.data); + setCurrentMasterForm(res.data); + }) + .catch((err) => { + if (err.response.status === 404) { + navigate('/404'); + } + }) + } + }, [corporate_id, id]) + + const pageType = !isEdit ? "Create" : "Edit" + const pageTitle = `Master Formularium ${pageType}` + return ( + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdateForm.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdateForm.tsx new file mode 100644 index 00000000..b93d3bfd --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/CreateUpdateForm.tsx @@ -0,0 +1,143 @@ +import * as Yup from 'yup'; +import { LoadingButton } from "@mui/lab"; +import { Button, Card, Grid, Stack, Typography } from "@mui/material"; +import { CorporatePlan } from "../../../@types/corporates"; +import { FormProvider, RHFSwitch, RHFTextField } from "../../../components/hook-form"; +import { useEffect, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate, useParams } from 'react-router-dom'; +import axios from '../../../utils/axios'; +import { MasterFormularium } from "./Type"; + + +type Props = { + isEdit: boolean; + currentMasterForm?: MasterFormularium; +}; + +export default function CreateUpdateForm({ isEdit, currentMasterForm }: Props) { + const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporatePlanSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + }); + + const defaultValues = useMemo( + () => ({ + name: currentMasterForm?.name || '', + description: currentMasterForm?.description || '', + }), + [currentMasterForm] + ) + + useEffect(() => { + if (isEdit && currentMasterForm) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + }, [isEdit, currentMasterForm]) + + const methods = useForm({ + resolver: yupResolver(NewCorporatePlanSchema), + defaultValues, + }) + + const { + reset, + setError, + handleSubmit, + formState: { isSubmitting } + } = methods; + + const onSubmit = async (data: any) => { + if (!isEdit) { + await axios + .post('/master/formularium-template/store', data) + .then((res) => { + enqueueSnackbar('Formularium created succesfully', {variant: 'success'}); + }) + .then((res) => { + navigate('/master/formularium-template-v2', { replace: true }); + }) + .catch(({ response }) => { + if (response.status === 422) { + for (const [key, value] of Object.entries(response.data.errors)) { + setError(key, { message: value[0] }); + enqueueSnackbar(value[0] ?? 'Failed Processing Reques', { variant: 'error' }); + } + } + else { + enqueueSnackbar('Create Failed : ' + response.data.message, {variant: 'error'}); + } + }); + } else { + await axios + .put(`/master/formularium-template/${currentMasterForm?.id}/update`, data) + .then((res) => { + enqueueSnackbar('Formularium updated successfully', { variant: 'success' }); + }) + .then((res) => { + navigate('/master/formularium-template-v2', { replace: true }); + }) + .catch(({ response }) => { + enqueueSnackbar(`Update Failed : ${response.data.message}`, { variant: 'error' }) + }) + } + } + + return ( + + + + + + Detail + Name* + + Description* + + + + + + + + { isEdit? 'Update' : 'Create' } + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/DetailFormularium.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/DetailFormularium.tsx new file mode 100644 index 00000000..049b3c3a --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/DetailFormularium.tsx @@ -0,0 +1,102 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Grid, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import { LoadingButton } from "@mui/lab"; +import { CachedOutlined, FindInPageOutlined } from '@mui/icons-material'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../../hooks/useSettings'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; +import { RHFSelect, FormProvider } from '@/components/hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useForm } from 'react-hook-form'; +// components +import axios from '../../../../utils/axios'; +import Label from '@/components/Label'; +import TableMoreMenu from '@/components/table/TableMoreMenu'; +import DialogUpdateStatus from '@/components/DialogUpdateStatus' +import * as Yup from 'yup'; +import { FormulariumData } from "../Type"; + + +type Props = { + props: FormulariumData, + isActive: number, +} + +export default function DetailFormularium({props, isActive} : Props) { + const [open, setOpen] = React.useState(false); + + + return ( + + + + {props.code} + {props.atc_code} + {props.name} + {props.category_name} + {props.uom} + + {isActive == 1 ? ( + + ) : ( + + )} + + + + setOpen(!open)}> + + Detail + + + } /> + + + + + + + + + + Detail + + Description + : {props.description} + + General Indication + : {props.general_indication} + + Composition + : {props.composition} + + Kategori Obat + : {props.kategori_obat} + + BPOM Registration + : {props.bpom_registration} + + Classification + : {props.classifications} + + Cat For + : {props.cat_for} + + Class + : {props.class} + + Manufacturer + : {props.manufacturer} + + + + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Formularium.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Formularium.tsx new file mode 100644 index 00000000..13e09315 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Formularium.tsx @@ -0,0 +1,316 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Grid, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../../hooks/useSettings'; +import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../../../utils/axios'; +import { LaravelPaginatedData } from '../../../../@types/paginated-data'; +import { Icd } from '../../../../@types/diagnosis'; +import BasePagination from '../../../../components/BasePagination'; +import { enqueueSnackbar } from 'notistack'; +import DetailFormularium from "./DetailFormularium"; +import { FormulariumData } from '../Type'; + + +export default function List() { + const navigate = useNavigate(); + const { id: formularium_template_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + const { isActive } = useLocation().state as { isActive: number } + + function SearchInput(props: any) { + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? '' + setSearchText(newSearchText); + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); + } + + useEffect(() => { + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ); + } + + function ImportForm( props: any ) { + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null) + const [currentImportFileName, setCurrentImportFileName] = useState(null) + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleImportButton = () => { + if (importForm?.current) {; + handleClose(); + importForm.current ? importForm.current.click() : console.log('No File selected'); + } else { + alert('No file selected') + } + } + + const handleCancelImportButton = () => { + importForm.current.value = ''; + importForm.current?.dispatchEvent(new Event('change', { bubbles: true })) + } + + const handleGetTemplate = (type: string) => { + axios.get('corporates/import-document-example/' + type) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }) + } + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name) + } else { + setCurrentImportFileName(null); + } + } + + const handleFormulariumList = async (appliedFilter = null) => { + axios.get(`master/formulariums/${formularium_template_id}/list`) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + enqueueSnackbar('Download Success', { variant: 'success' }) + }) + .catch(response => { + enqueueSnackbar('Looks like something went wrong. Please check your data and try again. ' + response.message, { variant: 'error' }) + }); + } + + const handleUpload = () => { + if (importForm.current?.files?.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]) + axios.post(`master/formulariums/${formularium_template_id}/import`, formData ) + .then(response => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + }) + .catch(response => { + enqueueSnackbar('Looks like something went wrong. Please check your data and try again. ' + response.message, { variant: 'error' }) + }) + } else { + enqueueSnackbar(`No File Selected`, { variant: 'warning' }) + } + } + + return ( +
+ + {(!currentImportFileName && + + + + Import + handleGetTemplate('master-formularium')}>Download Template + handleFormulariumList()}>Download Formularium + + + )} + {( currentImportFileName && + + + + + + + )} + {( importResult && + + Last Import Result Report : {importResult.result_file?.name ?? "-"} + + )} +
+ ); + } + + // Default data + const [dataTableRow, setDataTableRow] = useState(null) + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 + }); + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get(`/master/formulariums/${formularium_template_id}`, { params: filter }); + setDataTableLoading(false); + + console.log(formularium_template_id) + console.log(response) + setDataTableData(response.data) + setDataTableRow(response.data.data) + } + + const applyFilter = async (searchFilter: string) => { + await loadDataTableData({ 'search': searchFilter }); + setSearchParams({ 'search' : searchFilter }); + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + useEffect(() => { + loadDataTableData(); + }, []) + + const headStyle = { + fontWeight: 'bold', + }; + + return ( + + + + + + + + + Code + ATC Code + Name + Category Name + UOM + Status + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + dataTableRow?.map(item => ( + + )) + )} +
+
+ + +
+
+ ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Index.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Index.tsx new file mode 100644 index 00000000..381283ef --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Detail/Index.tsx @@ -0,0 +1,37 @@ +import { Card, Grid } from "@mui/material"; +import { useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../../components/HeaderBreadcrumbs"; +import Page from "../../../../components/Page"; +import useSettings from "../../../../hooks/useSettings"; +import List from "./Formularium"; + + +export default function Formularium() { + const pageTitle = "Formularium" + const { id: formularium_template_id } = useParams(); + + return ( + + + + + + + ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/FormulariumRow.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/FormulariumRow.tsx new file mode 100644 index 00000000..47c90524 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/FormulariumRow.tsx @@ -0,0 +1,211 @@ +import * as Yup from 'yup'; +import TableMoreMenu from "@/components/table/TableMoreMenu" +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined'; +import HistoryIcon from '@mui/icons-material/History'; +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; +import { Button, Card, Collapse, Grid, MenuItem, Paper, Stack, Table, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material" +import React, { Fragment, useEffect, useState } from "react"; +import axios from "@/utils/axios"; +import { useNavigate, useParams } from "react-router-dom"; +import { RHFSelect, FormProvider } from '@/components/hook-form'; +import DialogUpdateStatus from '@/components/DialogUpdateStatus' +import { MasterFormularium } from "./Type"; +import { LoadingButton } from "@mui/lab"; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; + +type Props = { + props: MasterFormularium +} + +export default function FormulariumRow({props} : Props) { + const navigate = useNavigate(); + + const [isDialogOpen, setDialogOpen] = useState(false); + const [dataValue, setDataValue] = useState(null); + const [dataDescription, setDescriptionValue] = useState(''); + const [url, setUrl] = useState(''); + let titles = { + name: 'Update Status', + icon: '-' + } + type FormValuesProps = { + value: string; + active: boolean; + }; + + const handleActivate = (isOpen: boolean, dataValue: MasterFormularium) => { + setDialogOpen(isOpen); + setDataValue(dataValue); + setDescriptionValue("Are you sure to inactive this formularium ? "); + setUrl(url); + }; + + const NewMasterFormSchema = Yup.object().shape({ + reason: Yup.string().required('Reason edit is required') + }); + + const methods = useForm({ + resolver: yupResolver(NewMasterFormSchema) + }); + + const onSubmit = async (row: any) => { + try { + handleUpdate(dataValue?.active, dataValue?.id, row.reason) + } catch (error: any) { + console.log('data gagal'); + } + + const ascent = document?.querySelector('ascent'); + if (ascent != null) { + ascent.innerHTML = ''; + } + }; + + const handleUpdate = (active: number, id: number, reason: string) => { + + if (active == 1) { + active = 0 + } else { + active = 1 + } + + axios + .put(`/master/formularium-template/${id}/activation`, { + active: active, + }) + .then((res) => { + window.location.reload(); + }) + } + + const { + reset, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const getContent = () => ( + <> + + Are you sure to Change this Formularium + + + + + Formularium name + + + {dataValue?.name} + + + + + Reason for update + + + + + + + + + + + + {dataValue?.active == 1 ? + + Inactive + : + + Active + + } + + + + + + + ) + + return ( + + + + {props.name} + {props.description} + + + navigate(`/master/formularium-template-v2/${props.id}/detail`, {state: { isActive: props.active }})}> + + Detail + + navigate(`/master/formularium-template-v2/${props.id}/edit`)}> + + Edit + + handleActivate(true, { + id: props.id, + name: props.name, + description: props.description, + active: props.active + }) + }> + + Update Status + + navigate(`/master/formularium-template-v2/${props.id}/history`)}> + + History + + + } /> + + + + + ) +} diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/History.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/History.tsx new file mode 100644 index 00000000..ca991be6 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/History.tsx @@ -0,0 +1,211 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + Container, + FormControl, + Grid, + IconButton, + InputLabel, + MenuItem, + OutlinedInput, + Paper, + Select, + SelectChangeEvent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Badge, + Stack, + } from '@mui/material'; + import * as React from 'react'; +import { useParams } from 'react-router-dom'; +import { styled } from '@mui/material/styles'; +import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'; +import MuiAccordion, { AccordionProps } from '@mui/material/Accordion'; +import { useContext, useEffect, useState } from 'react'; +import MuiAccordionSummary, { + AccordionSummaryProps, +} from '@mui/material/AccordionSummary'; +import useSettings from '../../../hooks/useSettings'; +import axios from '../../../utils/axios'; +import { ConfiguredCorporateContext } from '@/contexts/ConfiguredCorporateContext'; +import MuiAccordionDetails from '@mui/material/AccordionDetails'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import { Corporate } from '@/@types/corporates'; +import { fDate, fDateTime } from '@/utils/formatTime'; + + +const Accordion = styled((props: AccordionProps) => ( + +))(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + "&:not(:last-child)": { + borderBottom: 0, + }, + "&:before": { + display: 'none' + }, +})); + +const AccordionSummary = styled((props: AccordionSummaryProps) => ( + } + {...props} + /> +))(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' + ? 'rgba(255,255,255,0.5)' + : 'rgba(0,0,0,.03)', + flexDirection: 'row-reverse', + "& .MuiAccordionSummary-expandIconWrapper.Mui-expanded": { + transform: 'rotate(90dg)', + }, + "& .MuiAccordionSummary=content": { + marginLeft: theme.spacing(1), + }, +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + padding: theme.spacing(2), + borderTop: '1px solid rgba(0,0,0,.125)', +})); + +export default function MasterFormulariumHistory() { + const [expanded, setExpanded] = React.useState('panel1'); + + const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { + setExpanded(newExpanded ? panel : false); + }; + const pageTitle = 'Master Formularium History' + const { id } = useParams(); + const { corporate_id } = useParams(); + const [corporate, setCorporate] = useState(); + const [currentCorporate, setCurrentCorporate] = useState(); + const configuredCorporateContext = useContext(ConfiguredCorporateContext); + + useEffect(() => { + setCorporate(configuredCorporateContext.currentCorporate); + const model = 'App\\Models\\FormulariumTemplate'; + const url = `/audittrail/${id}?model=${model}`; + axios.get(url) + .then((res) => { + setCurrentCorporate(res.data); + }) + .catch((error) => { + console.error('Terjadi kesalahan: ', error); + }); + }, [configuredCorporateContext]) + + return ( +
+ + {currentCorporate?.data.map((item, index) => ( + + + {`Data has ${item.action} by ${item.user_id} on ${fDateTime(item.updated_at)}`} + + + + + + + Field + Old Value + New Values + + + + {Object.entries(item.old_values).map(([key, value]) => { + let renderedValue; + if (key === 'deleted_by' || + key === 'deleted_at' || + key === 'created_by' || + key === 'created_at' || + key === 'updated_by' || + key === 'description' + ) { + return null; // Melewati iterasi saat key adalah 'deleted_by' + } + switch (key) { + case 'welcome_message': + renderedValue = item.new_values[key].replace(/<[^>]*>/g, ''); + value = value.replace(/<[^>]*>/g, ''); + break; + case 'help_text': + renderedValue = item.new_values[key].replace(/<[^>]*>/g, ''); + value = value.replace(/<[^>]*>/g, ''); + break; + case 'active': + renderedValue = item.new_values[key] == 1 ? 'Active' : 'Inactive'; + value = value == 1 ? 'Active' : 'Inactive'; + break; + case 'created_at': + renderedValue = fDateTime(item.new_values[key]); + value = fDateTime(value); + break; + case 'updated_at': + renderedValue = fDateTime(item.new_values[key]); + value = fDateTime(value); + break; + case 'delete_at': + renderedValue = fDateTime(item.new_values[key]); + value = fDateTime(value); + break; + default: + renderedValue = item.new_values[key]; + break; + } + + const field = key.charAt(0).toUpperCase() + key.slice(1); + if (value == renderedValue) { + return null + } else { + return ( + + {`${field}`} + {`${value}`} + {renderedValue} + + ); + } + })} + +
+
+
+
+ ))} +
+ ) +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Index.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/Index.tsx new file mode 100644 index 00000000..2a25563b --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Index.tsx @@ -0,0 +1,33 @@ +import { Card, Grid } from "@mui/material"; +import { useParams } from "react-router-dom"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import useSettings from "../../../hooks/useSettings"; +import List from "./List"; + +export default function MasterFormularium() { + const pageTitle = "Master Formularium" + + return ( + + + + + + + + + ) +} diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/List.tsx b/frontend/client-portal/src/pages/Master/FormulariumV2/List.tsx new file mode 100644 index 00000000..25dae159 --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/List.tsx @@ -0,0 +1,179 @@ +// @mui +import { Box, Button, Card, Collapse, IconButton, InputLabel, MenuItem, OutlinedInput, Paper, Grid, Select, SelectChangeEvent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, Badge, Tab, Tabs, CardHeader, Stack, Menu, ButtonGroup, Pagination } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +// hooks +import React, { ChangeEvent, Component, useEffect, useContext, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom'; +// components +import axios from '../../../utils/axios'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import BasePagination from '../../../components/BasePagination'; +import FormulariumRow from "./FormulariumRow"; +import { MasterFormularium } from "./Type"; +import { UserCurrentCorporateContext } from '../../../contexts/UserCurrentCorporate'; + +export default function List() { + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + + // Default data + const [dataTableRow, setDataTableRow] = useState(null) + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const { corporateValue } = useContext(UserCurrentCorporateContext); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: "", + first_page_url: "", + last_page: 1, + last_page_url: "", + next_page_url: "", + prev_page_url: "", + per_page: 10, + from: 0, + to: 0, + total: 0 + }); + + const loadDataTableData =async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + + const filter = { + ...Object.fromEntries([...searchParams.entries()]), // Mengambil parameter dari searchParams + corporate_id: corporateValue, // Menambahkan corporate_id + ...(appliedFilter ? appliedFilter : {}) // Menggabungkan dengan appliedFilter jika ada + }; + const response = await axios.get('/master/formularium-template', { params: filter }); + console.log(response.data); + console.log(response.data.data) + setDataTableLoading(false); + + setDataTableData(response.data); + setDataTableRow(response.data.data); + } + + const applyFilter = async (searchFilter: string) => { + await loadDataTableData({'search' : searchFilter }); + setSearchParams({ 'search' : searchFilter }) + } + + const handlePageChange = (event : ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + } + + useEffect(() => { + loadDataTableData(); + }, []) + + function SearchInput(props: any) { + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? "" + setSearchText(newSearchText) + } + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch(searchText); + } + + useEffect(() => { + setSearchText(searchParams.get('search') ?? ''); + }, [searchParams]) + + return ( +
+ + + ) + } + + function SearchCreate(props: any) { + return ( +
+ + + {/* */} + +
+ ) + } + + const headStyle = { + fontWeight: 'bold' + } + + return ( + + + + + + + + + + Name + Description + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableRow?.map(item => ( + + ))} + + )} +
+
+ +
+
+
+ ) +} diff --git a/frontend/client-portal/src/pages/Master/FormulariumV2/Type.ts b/frontend/client-portal/src/pages/Master/FormulariumV2/Type.ts new file mode 100644 index 00000000..3e16e32e --- /dev/null +++ b/frontend/client-portal/src/pages/Master/FormulariumV2/Type.ts @@ -0,0 +1,32 @@ +export type MasterFormularium = { + id: number + name: string + description: string + active: number +} + + +export type FormulariumData = { + id: number + code: string + name: string + description: string + manufacturer: string + category_name: string + kategori_obat: string + uom: string + general_indication: string + composition: string + atc_code: string + class: string + bpom_registration: string + classifications: string + cat_for: string + created_at: string + updated_at: string + deleted_at: any + created_by: number + updated_by: number + deleted_by: any + formularium_template_id: number + } \ No newline at end of file diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index a43eba44..4bf282dd 100644 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -234,6 +234,86 @@ export default function Router() { { path: '*', element: }, ], }, + { + path: 'master/formularium-template-v2', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/:id/detail', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/create', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/:id/edit', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, + { + path: 'master/formularium-template-v2/:id/history', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, { path: '*', element: }, ]); } @@ -275,3 +355,9 @@ const ClaimRequest = Loadable(lazy(() => import('../pages/ClaimSubmit/DialogDeta const Corporate = Loadable(lazy(() => import('../pages/Corporate/Index'))); const CorporateEdit = Loadable(lazy(() => import('../pages/Corporate/Form'))); const CorporateShow = Loadable(lazy(() => import('../pages/Corporate/Show'))); + +// Formularium +const MasterFormulariumTemplateV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/Index'))); +const MasterFormulariumTemplateCreateV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/CreateUpdate'))); +const MasterFormulariumTemplateHistoriesV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/History'))); +const MasterFormulariumTemplateDetailV2 = Loadable(lazy(() => import('../pages/Master/FormulariumV2/Detail/Index'))); diff --git a/frontend/dashboard/src/@types/doctor.tsx b/frontend/dashboard/src/@types/doctor.tsx index a635e68c..4a1dd106 100644 --- a/frontend/dashboard/src/@types/doctor.tsx +++ b/frontend/dashboard/src/@types/doctor.tsx @@ -30,6 +30,13 @@ export type Specialities = { name: string; }; +export type PeriodStart = { + date: string; +}; +export type PeriodEnd = { + date: string; +}; + export type Practitioner = { id: number; name: string; @@ -50,6 +57,35 @@ export type Practitioner = { active: boolean | number; organizations?: Organizations[]; specialities?: Specialities[]; + period_start?: PeriodStart[]; + period_end?: PeriodEnd[]; }; +export type Medicine = { + id: number; + drug_id: number; + unit_id: number; + qty: number; + signa: string; + note: string; +} + +export type Appointment = { + id:number|undefined; + name:string; + address:string; + birth_date:string; + gender:string; + description:string; + birth_place:string; + active:number; + avatar_url:string; + doctor_id:number; + organizations:Organizations[]; + specialities:Specialities[]; + diagnosis:string; + hospital:string; + medicine: Medicine[]; +} + export type PractitionerType = Practitioner; \ No newline at end of file diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index 17334caf..df8ffd5d 100644 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -19,6 +19,8 @@ export const accessGroup = { admin: ["/report/logs"] } + + const navConfig = [ // GENERAL // ---------------------------------------------------------------------- @@ -95,11 +97,12 @@ const navConfig = [ { title: 'REPORT', children: [ + { title: 'Files Provider', path: 'report/files-provider'}, { title: 'Letter of Guarantee', path: '/report/logs' }, { title: 'Appointment', path: '/report/appointments' }, { title: 'Live Chat', path: '/report/live-chat' }, { title: 'Linksehat Payment', path: '/report/linksehat-payments' }, - { title: 'Prescription', path: '/report/prescription' }, + // { title: 'Prescription', path: '/report/prescription' }, { title: 'Doctor Rating', path: '/report/doctorrating' }, ], @@ -122,6 +125,10 @@ const navConfig = [ title: 'LINKING TOOLS', path: '/linking', }, + { + title: 'E-PRESCRIPTION', + path: '/e-prescription/live-chat', + }, ], }, ]; diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavbarVertical.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavbarVertical.tsx index a356edf3..6f7bd107 100644 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavbarVertical.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavbarVertical.tsx @@ -19,6 +19,7 @@ import navConfig from './NavConfig'; import NavbarDocs from './NavbarDocs'; import NavbarAccount from './NavbarAccount'; import CollapseButton from './CollapseButton'; +import useAuth from '@/hooks/useAuth'; // ---------------------------------------------------------------------- @@ -42,6 +43,7 @@ export default function NavbarVertical({ isOpenSidebar, onCloseSidebar }: Props) const theme = useTheme(); const { pathname } = useLocation(); + const {user} = useAuth() const isDesktop = useResponsive('up', 'lg'); @@ -55,6 +57,16 @@ export default function NavbarVertical({ isOpenSidebar, onCloseSidebar }: Props) // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]); + const filteredItems = navConfig.filter(section => { + return section.items.some(item => { + return item.title === 'E-PRESCRIPTION'; + }); + }); + + const filteredData = filteredItems.map(section => ({ + items: section.items.filter(item => item.title === "E-PRESCRIPTION") + })); + const renderContent = ( - + diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx index ce60261b..88959ff9 100644 --- a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx @@ -21,6 +21,8 @@ export default function DialogEditFinalLOG({requestLog, setOpenDialog, openDialo const navigate = useNavigate(); const [formData, setFormData] = useState({ + billing_no: requestLog?.billing_no, + invoice_no: requestLog?.invoice_no, discharge_date: requestLog?.discharge_date, id: requestLog?.id, catatan: requestLog?.catatan, @@ -49,6 +51,8 @@ export default function DialogEditFinalLOG({requestLog, setOpenDialog, openDialo if (requestLog) { setFormData({ discharge_date: requestLog.discharge_date, + billing_no: requestLog.billing_no, + invoice_no: requestLog.invoice_no, id: requestLog.id, catatan: requestLog.catatan, icdCodes: requestLog.diagnosis @@ -116,6 +120,8 @@ export default function DialogEditFinalLOG({requestLog, setOpenDialog, openDialo setFormData({ discharge_date: requestLog?.discharge_date, id: requestLog?.id, + billing_no: requestLog?.billing_no, + invoice_no: requestLog?.invoice_no, catatan: requestLog?.catatan, icdCodes: requestLog?.diagnosis ? requestLog?.diagnosis.map(diagnosis => diagnosis.code) @@ -173,6 +179,26 @@ export default function DialogEditFinalLOG({requestLog, setOpenDialog, openDialo + + Invoice Number + handleChange('invoice_no', e.target.value)} + /> + + + Billing Number + handleChange('billing_no', e.target.value)} + /> + Discharge Date ) : null } + + Invoice Number + {requestLog?.invoice_no ? requestLog?.invoice_no : '-'} + + + Billing Number + {requestLog?.billing_no ? requestLog?.billing_no : '-'} + Provider {requestLog?.provider} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx index b00a049b..3ce0da8d 100644 --- a/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx @@ -31,6 +31,8 @@ export type DetailFinalLogType = { id : number, code : string, member_id : string, + invoice_no : string, + billing_no : string, provider : string, policy_number : string, name : string|any, diff --git a/frontend/dashboard/src/pages/EPrescription/Livechat/Create.tsx b/frontend/dashboard/src/pages/EPrescription/Livechat/Create.tsx new file mode 100644 index 00000000..efb7a395 --- /dev/null +++ b/frontend/dashboard/src/pages/EPrescription/Livechat/Create.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import Form from './Form'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Practitioner } from '../../../@types/doctor'; +import ButtonBack from '../../../components/ButtonBack'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentPractitioner, setCurrentPractitioner] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/doctors/' + id).then((res) => { + setCurrentPractitioner(res.data); + }); + } + }, [id]); + + return ( + + + + {/* */} + + + +
+ + + ); +} +// const pageTitle = 'Create Data Dokter'; +// return ( +// +// +// + +// +// +// +// +// +// +// +// +// +// ); +// } diff --git a/frontend/dashboard/src/pages/EPrescription/Livechat/Form.tsx b/frontend/dashboard/src/pages/EPrescription/Livechat/Form.tsx new file mode 100644 index 00000000..39885db8 --- /dev/null +++ b/frontend/dashboard/src/pages/EPrescription/Livechat/Form.tsx @@ -0,0 +1,260 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Practitioner } from '../../../@types/doctor'; + +import { Label, Rowing } from '@mui/icons-material'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentPractitioner?: Practitioner; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function PractitionerForm({ isEdit, currentPractitioner }: Props) { + const navigate = useNavigate(); + const [practitioner_group, setPractitionerGroups] = useState([]); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentPractitioner?.id, + name: currentPractitioner?.name || '', + address: currentPractitioner?.address || '', + birth_date: currentPractitioner?.birth_date || '', + gender: currentPractitioner?.gender || '', + description: currentPractitioner?.description || '', + birth_place: currentPractitioner?.birth_place || '', + active: currentPractitioner?.active === 1 ? true : false, + avatar_url: currentPractitioner?.avatar_url || '', + doctor_id: currentPractitioner?.doctor_id || '', + organizations: currentPractitioner?.organizations || [], + specialities: currentPractitioner?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentPractitioner] + ); + + console.log('defaultValues', defaultValues); + + function StatusLabel({ value }: { value: boolean }) { + return ( + + ); + } + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentPractitioner) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentPractitioner]); + + const handleActivate = (event: React.ChangeEvent) => { + setValue('active', event.target.checked); + + console.log('event.target.checked', event.target.checked); + + const formData = new FormData(); + formData.append('active', event.target.checked ? '1' : '0'); + formData.append('_method', 'PUT'); + axios.post('/doctors/' + currentPractitioner?.id ?? '', formData); + + enqueueSnackbar('active Updated Successfully!', { variant: 'success' }); + }; + + return ( + + + + {/* */} + + + + Data Dokter + + + {/* Status Rumah Sakit */} + + + + + Informasi Umum + + + + + Nama Dokter + {currentPractitioner?.name ? currentPractitioner?.name : '-'} + No Telp + {currentPractitioner?.phone ? currentPractitioner?.phone : '-'} + Tempat Lahir + + {currentPractitioner?.birth_place ? currentPractitioner?.birth_place : '-'} + + Alamat + {currentPractitioner?.address ? currentPractitioner?.address : '-'} + + + Jenis Kelamin + {currentPractitioner?.gender ? currentPractitioner?.gender : '-'} + Email + {currentPractitioner?.email ? currentPractitioner?.email : '-'} + Tanggal Lahir + + {currentPractitioner?.birth_date ? currentPractitioner?.birth_date : '-'} + + + + + + Tempat Praktik + {currentPractitioner?.organizations?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + Spesialisasi + {currentPractitioner?.specialities?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + + + ); +} diff --git a/frontend/dashboard/src/pages/EPrescription/Livechat/Index.tsx b/frontend/dashboard/src/pages/EPrescription/Livechat/Index.tsx new file mode 100644 index 00000000..c5dfb515 --- /dev/null +++ b/frontend/dashboard/src/pages/EPrescription/Livechat/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Grid, Container } from '@mui/material'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import Page from '../../../components/Page'; +import useSettings from '../../../hooks/useSettings'; +import List from './List'; + +export default function Doctor() { + const { themeStretch } = useSettings(); + + const { id } = useParams(); + + const pageTitle = 'E-Prescription'; + return ( + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/EPrescription/Livechat/List.tsx b/frontend/dashboard/src/pages/EPrescription/Livechat/List.tsx new file mode 100644 index 00000000..c768e17a --- /dev/null +++ b/frontend/dashboard/src/pages/EPrescription/Livechat/List.tsx @@ -0,0 +1,573 @@ +import { + Box, + Button, + Card, + Collapse, + Paper, + Select, + SelectChangeEvent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Stack, + ButtonGroup, + Grid, + Chip, + Dialog, + DialogContent, + DialogContentText, + DialogActions, + FormControl, + Autocomplete, + InputAdornment, + IconButton, +} from '@mui/material'; + +import { + Link, + NavLink as RouterLink, + useSearchParams, + useNavigate, + useParams, +} from 'react-router-dom'; +// hooks +import React, { ChangeEvent, Component, useEffect, useRef, useState } from 'react'; +import useSettings from '../../../hooks/useSettings'; +// components +import axios from '../../../utils/axios'; +import { LaravelPaginatedData } from '../../../@types/paginated-data'; +import { Icd } from '../../../@types/diagnosis'; +import BasePagination from '../../../components/BasePagination'; +import { Practitioner } from '../../../@types/doctor'; +import CreateIcon from '@mui/icons-material/Create'; +import { Props } from '../../../components/editor/index'; +import { red } from '@mui/material/colors'; +import { margin, padding } from '@mui/system'; +import { enqueueSnackbar } from 'notistack'; +import { Controller } from 'react-hook-form'; + +import SvgIconStyle from '../../../components/SvgIconStyle'; +import { GridSearchIcon } from '@mui/x-data-grid'; +import { Add, Search } from '@mui/icons-material'; +import { Icon } from '@iconify/react'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import { RHFDatepicker } from '@/components/hook-form'; +import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; +import { fDateOnly } from '@/utils/formatTime'; + +// ---------------------------------------------------------------------- + +export default function List() { + // Generate the every row of the table + + const navigate = useNavigate(); + const { organization_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const [searchParamsOrganizations, setSearchParamsOrganizations] = useSearchParams(); + const [searchParamsSpecialities, setSearchParamsSpecialities] = useSearchParams(); + const [searchParamsFilter, setSearchParamsFilter] = useSearchParams(); + + function Filter(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(''); + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + + props.onSearch(searchText); + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + return ( + + + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + value={searchText} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search', + }} + /> + + + + { + try { + if (value && !!Date.parse(value)) { + const date = value ? fDateOnly(value) : ''; + var entries = [...searchParams.entries(), ['startDate', date ?? '']]; + if (!searchParams.get('endDate')) { + entries = [...entries, ['endDate', date ?? '']]; + } + const filter = Object.fromEntries(entries); + + setSearchParams(filter); + loadDataTableData(filter); + } + } catch (e) {} + }} + renderInput={(params) => } + /> + + + + + { + try { + if (value && !!Date.parse(value)) { + const date = fDateOnly(value); + var entries = [...searchParams.entries(), ['endDate', date ?? '']]; + if (!searchParams.get('startDate')) { + entries = [...entries, ['startDate', date ?? '']]; + } + const filter = Object.fromEntries(entries); + + setSearchParams(filter); + loadDataTableData(filter); + } + } catch (e) {} + }} + renderInput={(params) => ( + + )} + /> + + + + + + + + ); + } + + function FilterForm(props: any) { + // IMPORT + return ( + + + + + + ); + } + + function createData(doctor: Practitioner): Practitioner { + return { + ...doctor, + }; + } + + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + const [openDialog, setOpenDialog] = React.useState(false); + + const handleDelete = (model: any) => { + axios + .delete(`/doctors/${row.id}`) + .then((res) => { + setDataTableData({ + ...dataTableData, + data: dataTableData.data.filter((model) => model.id != row.id), + }); + enqueueSnackbar('Data berhasil dihapus', { variant: 'success' }); + }) + .catch((error) => { + enqueueSnackbar( + error.response.data.message ?? error.message ?? 'Failed Processing Request', + { variant: 'error' } + ); + }); + }; + + return ( + + + + setOpen(!open)}> + {open ? : } + + + {row.date_created ? row.date_created : '-'} + {row.time_request ? row.time_request : '-'} + {row.health_care ? row.health_care : '-'} + {row.doctor_name ? row.doctor_name : '-'} + {row.speciality ? row.speciality : '-'} + {row.pasien_name ? row.pasien_name : '-'} + {row.pasien_phone ? row.pasien_phone : '-'} + {/* {row.appointment_media ? row.appointment_media : '-'} */} + {row.patient_media ? row.patient_media : '-'} + {row.doctor_media ? row.doctor_media : '-'} + {/* + {row.status_appointment ? row.status_appointment : '-'} + */} + {row.status_chat ? row.status_chat : '-'} + + + + + + + + + {/* COLLAPSIBLE ROW */} + + + + + + + + + Metode Pembayaran + + + : {row.payment_method ? row.payment_method : '-'} + + + + Jenis Benefit + + + : - + + + Durasi + + + : {row.duration ? row.duration : '-'} + + + Kode + + + : {row.id ? row.id : '-'} + + + + + + + + + + {/* END COLLAPSIBLE ROW */} + { + setOpenDialog(false); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + + + Apakah anda yakin ingin menghapus + + + {row.name}? + + + + + + + + + ); + } + + const headStyle = { + fontWeight: 'bold', + }; + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableLastRequest, setDataTableLastRequest] = useState(0); + const [dataTableResponseState, setDataTableResponseState] = useState('idle'); + const [dataTableData, setDataTableData] = useState({ + current_page: 1, + data: [], + path: '', + first_page_url: '', + last_page: 1, + last_page_url: '', + next_page_url: '', + prev_page_url: '', + per_page: 10, + from: 0, + to: 0, + total: 0, + }); + const [dataTablePage, setDataTablePage] = useState(5); + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/prescription', { + params: filter, + }); + setDataTableLoading(false); + setDataTableData(response.data); + }; + + const exportExcel = async () => { + var filter = Object.fromEntries([...searchParams.entries()]); + + await axios + .get('live-chat/export', { params: filter }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + ); + }; + + // const applyFilter = async (searchFilter: string) => { + // await loadDataTableData({ search: searchFilter }); + // setSearchParams({ search: searchFilter }); + // }; + + const applyItems = async ( + searchFilter: string, + searchFilterOrganization: string, + searchFilterSpecialities: string + ) => { + await loadDataTableData({ + search: searchFilter, + organization_id: searchFilterOrganization, + speciality_id: searchFilterSpecialities, + }); + setSearchParamsFilter({ + search: searchFilter, + organization_id: searchFilterOrganization, + speciality_id: searchFilterSpecialities, + }); + }; + + const handlePageChange = (event: ChangeEvent, value: number) => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; + + useEffect(() => { + loadDataTableData(); + }, []); + + return ( + + {/* */} + + + + + {/* The Main Table */} + + + + + {/* */} + + + Tanggal + + + Waktu + + + Faskes + + + Nama Dokter + + + Spesialisasi + + + Nama Pasien + + + No Telepon Pasien + + {/* + Appointment Via App/Website + */} + + Chat Via App/Website + + {/* + Status Appointment + */} + + Status + + + + + {/* + Tanggal Booking + + + Tanggal Appointment + + + Faskes + + + Nama Dokter + + + Spesialisasi + */} + + Pasien + + + Dokter + + + + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length == 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} +
+
+ + +
+
+ ); +} diff --git a/frontend/dashboard/src/pages/EPrescription/Livechat/Show.tsx b/frontend/dashboard/src/pages/EPrescription/Livechat/Show.tsx new file mode 100644 index 00000000..25cb9c15 --- /dev/null +++ b/frontend/dashboard/src/pages/EPrescription/Livechat/Show.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import View from './View'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Appointment } from '../../../@types/doctor'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentAppointment, setCurrentAppointment] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/live-chat/' + id).then((res) => { + setCurrentAppointment(res.data); + }); + } + }, [id]); + + return ( + + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/EPrescription/Livechat/View.tsx b/frontend/dashboard/src/pages/EPrescription/Livechat/View.tsx new file mode 100644 index 00000000..6438ed27 --- /dev/null +++ b/frontend/dashboard/src/pages/EPrescription/Livechat/View.tsx @@ -0,0 +1,687 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import {useFieldArray, useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Autocomplete, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, + Badge, + Divider, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Appointment } from '../../../@types/doctor'; +import RHFTextFieldMoney from "@/components/hook-form/v2/RHFTextFieldMoney"; + +import { Label, Rowing, Spa } from '@mui/icons-material'; +import { border, padding } from '@mui/system'; +import { IconButton } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + id: number; + currentAppointment?: Appointment; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function AppointmentForm({ isEdit, id, currentAppointment }: Props) { + const navigate = useNavigate(); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + // name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentAppointment?.id, + name: currentAppointment?.name || '', + address: currentAppointment?.address || '', + birth_date: currentAppointment?.birth_date || '', + gender: currentAppointment?.gender || '', + description: currentAppointment?.description || '', + birth_place: currentAppointment?.birth_place || '', + active: currentAppointment?.active === 1 ? true : false, + avatar_url: currentAppointment?.avatar_url || '', + doctor_id: currentAppointment?.doctor_id || '', + organizations: currentAppointment?.organizations || [], + specialities: currentAppointment?.specialities || [], + diagnosis: currentAppointment?.diagnosis || '', + hospital: currentAppointment?.hospital || null, + medicine : currentAppointment?.medicine || [ + { + drug_id: 0, + qty: 0, + signa: '', + unit_id: 0, + note: '', // input to database + } + ], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentAppointment] + ); + + console.log() + + const methods = useForm({ + // resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const {fields, append, remove} = useFieldArray({name: 'medicine',control: methods.control}) + + // Autocomplite ICD + const [icdOptions, setIcdOptions] = useState([]); + useEffect(() => { + // Ambil data dari API dan atur opsi ICD + axios.get('diagnosis') + .then((response) => { + setIcdOptions(response.data.data); + }) + .catch((error) => { + console.error('Error fetching ICD options:', error); + }); + }, []); // useEffect dijalankan hanya sekali saat komponen dimount + + // Menggunakan selectedIcdOptions sebagai state untuk nilai default + const [selectedIcdOptions, setSelectedIcdOptions] = useState([]); + const codes = defaultValues.diagnosis.split(','); + useEffect(() => { + // Pastikan bahwa icdOptions sudah terisi sebelum memfilter + if (icdOptions.length > 0) { + const selectedCodes = icdOptions.filter((icd) => { + return codes.includes(icd.value); + }); + setSelectedIcdOptions(selectedCodes); + // setValue('diagnosis', selectedCodes); // Ini bisa Anda hilangkan jika tidak diperlukan + } + }, [icdOptions, defaultValues.diagnosis]); + + + // Autocomplite Rumah Sakit + const [hospitalOptions, setHospitalOptions] = useState([]); + const [selectedHospitalOption, setSelectedHospitalOption] = useState(null); + + // Ambil data dari API dan atur opsi rumah sakit + useEffect(() => { + axios.get('hospitals') + .then((response) => { + setHospitalOptions(response.data.data); + }) + .catch((error) => { + console.error('Error fetching hospital options:', error); + }); + }, []); // useEffect dijalankan hanya sekali saat komponen dimount + + // Set default value saat hospitalOptions berubah + useEffect(() => { + const selectedId = hospitalOptions.find((hospital) => { + + return hospital.value == defaultValues.hospital + }); + setSelectedHospitalOption(selectedId); + setValue('hospital', defaultValues.hospital) + + }, [hospitalOptions, defaultValues]); + + // Autocomplete drugs + const [drugOptions, setDrugsOptions] = useState([]); + const [selectedDrugsOptions, setSelectedDrugsOptions] = useState({}); + + const handleAutocompleteChange = (newValue, index) => { + setSelectedDrugsOptions((prevState) => ({ + ...prevState, + [index]: newValue + })); + setValue(`medicine.${index}.drug_id`, newValue ? newValue.value : ''); + }; + + // Ambil data dari API dan atur opsi obat + useEffect(() => { + axios.get('drugs') + .then((response) => { + const data = response.data.data; + // Set nilai default jika data tidak kosong + if (data.length > 0) { + // Pilih nilai default pertama + setSelectedDrugsOptions({ 0: data[0] }); + setValue('medicine.0.drug_id', data[0] ? data[0].value : ''); + } + setDrugsOptions(data); + }) + .catch((error) => { + console.error('Error fetching drug options:', error); + }); + }, []); // useEffect dijalankan hanya sekali saat komponen dimount + + // Autocomplete unit + const [unitOptions, setUnitsOptions] = useState([]); + const [selectedUnitsOptions, setSelectedUnitsOptions] = useState({}); + + // Ambil data dari API dan atur opsi satuan + useEffect(() => { + axios.get('units') + .then((response) => { + const data = response.data.data; + // Set nilai default jika data tidak kosong + if (data.length > 0) { + // Pilih nilai default pertama + setSelectedUnitsOptions({ 0: data[0] }); + setValue('medicine.0.unit_id', data[0] ? data[0].value : ''); + } + setUnitsOptions(data); + }) + .catch((error) => { + console.error('Error fetching unit options:', error); + }); + }, []); // useEffect dijalankan hanya sekali saat komponen dimount + + const handleAutocompleteChangeUnit = (newValue, index) => { + setSelectedUnitsOptions((prevState) => ({ + ...prevState, + [index]: newValue + })); + setValue(`medicine.${index}.unit_id`, newValue ? newValue.value : ''); + }; + + useEffect(() => { + if (defaultValues.medicine.length > 0) { + defaultValues.medicine.forEach((med, index) => { + const selectedDrugId = drugOptions.find((drug) => drug.value === med.drug_id); + handleAutocompleteChange(selectedDrugId, index); + + const selectedUnitId = unitOptions.find((unit) => unit.value === med.unit_id); + handleAutocompleteChangeUnit(selectedUnitId, index); + + // Lakukan tindakan lainnya sesuai kebutuhan Anda + }); + } else { + console.log('Medicine is empty'); + } + }, [defaultValues.medicine]); + + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentAppointment) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentAppointment]); + + const onSubmit = async (data: FormValuesProps) => { + try { + const formData = new FormData(); + formData.append('id', data.id); + formData.append('diagnosis', data.diagnosis); + formData.append('hospital', data.hospital); + + // Iterasi melalui setiap objek dalam array medicine dan menambahkannya ke FormData + data.medicine.forEach((medicineObj, index) => { + // Anda dapat menambahkan setiap properti dari objek medicine ke FormData + formData.append(`medicine[${index}][drug_id]`, medicineObj.drug_id); + formData.append(`medicine[${index}][qty]`, medicineObj.qty); + formData.append(`medicine[${index}][unit_id]`, medicineObj.unit_id); + formData.append(`medicine[${index}][signa]`, medicineObj.signa); + formData.append(`medicine[${index}][note]`, medicineObj.note); + }); + + + const response = await axios.post('/prescription', formData); + reset(); + enqueueSnackbar('Berhasil menambahkan resep', { + variant: 'success', + }); + navigate('/e-prescription/live-chat'); + } catch (error: any) { + console.log(error, 'submit') + enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); + } + + const ascent = document?.querySelector('ascent'); + if (ascent != null) { + ascent.innerHTML = ''; + } + }; + + const handleDownloadEPrescription = (id: number) => { + axios + .get(`prescription-download/${id}`, { + responseType: 'blob', + }) + .then((response) => { + window.open(URL.createObjectURL(response.data)); + }) + .catch((response) => { + enqueueSnackbar(response.message, { variant: 'error' }); + }); + } + + return ( + + + + {/* */} + + + + } + spacing={2} + > + Data Live Chat + + + + + + + + + + + Status Appointment : + + + + + + + + Status Chat : + + + + + + + + + + + Tanggal Booking : + + {currentAppointment?.date_created ? currentAppointment?.date_created : '-'} + + + + + + Tanggal Appointment : + + {currentAppointment?.date_appointment + ? currentAppointment?.date_appointment + : '-'} + + + + + + + + Nama Dokter + + {currentAppointment?.doctor_name ? currentAppointment?.doctor_name : '-'} + + Faskes + + {currentAppointment?.health_care ? currentAppointment?.health_care : '-'} + + Durasi + {currentAppointment?.duration ? currentAppointment?.duration : '-'} + + + Spesialis + {currentAppointment?.speciality ? currentAppointment?.speciality : '-'} + Appointment Via Web/App + + {currentAppointment?.appointment_media + ? currentAppointment?.appointment_media + : '-'} + + + + + + + + Data Pembayaran + + + + {currentAppointment?.payment_detail !== null ? ( + + + Metode Pembayaran + + {currentAppointment?.payment_method ? currentAppointment?.payment_method : '-'} + + Harga + + {currentAppointment?.payment_detail?.gross_amount + ? currentAppointment?.payment_detail?.gross_amount + : '-'} + + Mata Uang + + {currentAppointment?.payment_detail?.currency + ? currentAppointment?.payment_detail?.currency + : '-'} + + + + Tipe Pembayaran + + {currentAppointment?.payment_detail?.payment_type + ? currentAppointment?.payment_detail?.payment_type + : '-'} + + Waktu Transaksi + + {currentAppointment?.payment_detail?.transaction_time + ? currentAppointment?.payment_detail?.transaction_time + : '-'} + + Status + + {currentAppointment?.payment_detail?.status_message + ? currentAppointment?.payment_detail?.status_message + : '-'} + + + + ) : ( + Belum ada pembayaran + )} + + + + + + E-Prescription + + + + + + Diagnosa + option.label} + fullWidth + value={selectedIcdOptions} + onChange={(e, newValues) => { + const selectedCodes = newValues.map((value) => value.value); + setValue('diagnosis', selectedCodes); + setSelectedIcdOptions(newValues); + }} + renderInput={(params) => ( + + )} + /> + + + {/* + Rumah Sakit + option.label} + fullWidth + value={selectedHospitalOption} + onChange={(e, newValue) => { + setSelectedHospitalOption(newValue); + setValue('hospital', newValue ? newValue.value : ''); // Simpan nilai rumah sakit + }} + renderInput={(params) => ( + + )} + /> + + */} + + + + Obat + + + + + {fields.map((field, index) => ( + + + option.label} + fullWidth + value={selectedDrugsOptions[index] || null} + onChange={(e, newValue) => handleAutocompleteChange(newValue, index)} + renderInput={(params) => ( + + )} + /> + + + + + + option.label} + fullWidth + value={selectedUnitsOptions[index] || null} + onChange={(e, newValue) => handleAutocompleteChangeUnit(newValue, index)} + renderInput={(params) => ( + + )} + /> + + + + + + + + { + index === 0 ? ( + + append({medicine_name: '', medicine_price: 0, request_log_id: 1 })}> + + + + ) : ( + index == (fields.length-1) ? ( + + remove(index)}> + + + + ) : ( + + append({medicine_name: '', medicine_price: 0, request_log_id: 1 })}> + + + + ) + ) + } + + ))} + + + + + {!isEdit ? 'Save' : 'Update'} + + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Master/Doctors/Form.tsx b/frontend/dashboard/src/pages/Master/Doctors/Form.tsx index 55d27f88..22ab0467 100644 --- a/frontend/dashboard/src/pages/Master/Doctors/Form.tsx +++ b/frontend/dashboard/src/pages/Master/Doctors/Form.tsx @@ -188,6 +188,8 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) // formData.append('active', data.active ? '1' : '0'); forms.forEach((form, index) => { formData.append(`practices[${index}][organization_id]`, form.organizationId); + formData.append(`practices[${index}][period_start]`, form.periodStart); + formData.append(`practices[${index}][period_end]`, form.periodEnd); form.specialities.forEach((speciality, i) => { formData.append(`practices[${index}][specialities][${i}][speciality_id]`, speciality); }); @@ -225,6 +227,7 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) const [organizations, setOrganizations] = useState([]); const [specialities, setSpecialities] = useState([]); + const [price, setPrice] = useState([]); useEffect(() => { axios.get(`/search-organizations`).then((response) => { @@ -281,6 +284,8 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) return { organizationId: practice.organization_id, specialities: practice.specialities.map((s) => s.speciality_id), + periodStart: practice.period_start, + periodEnd: practice.period_end, }; }); setForms(newForms); @@ -289,6 +294,9 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) { organizationId: '', specialities: [], + periodStart: '', + periodEnd: '', + }, ]); } @@ -333,6 +341,8 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) ...forms, { organizationId: '', + periodStart: '', + periodEnd: '', specialities: [], }, ]); @@ -397,6 +407,8 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) setForms(updatedForms); }; + console.log(forms, 'forms') + return ( @@ -492,7 +504,6 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) value={findValueOrganization(form.organizationId) ?? ''} getOptionLabel={(option) => option.name} isOptionEqualToValue={(option, value) => { - console.log(value, option, 'test') return option.value === value.value } } @@ -541,6 +552,23 @@ export default function PractitionerForm({ isEdit, currentPractitioner }: Props) )} + + + Period Start + + + + + + Period End + + +
))} diff --git a/frontend/dashboard/src/pages/Report/FilesProvider/Index.tsx b/frontend/dashboard/src/pages/Report/FilesProvider/Index.tsx new file mode 100644 index 00000000..d3de2a44 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/FilesProvider/Index.tsx @@ -0,0 +1,35 @@ +import { Card, Grid, Container } from '@mui/material'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import Page from '../../../components/Page'; +import useSettings from '../../../hooks/useSettings'; +import List from './List'; + +export default function Index() { + const { themeStretch } = useSettings(); + + const { id } = useParams(); + + const pageTitle = 'Files Provider'; + return ( + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/FilesProvider/List.tsx b/frontend/dashboard/src/pages/Report/FilesProvider/List.tsx new file mode 100644 index 00000000..6dffa607 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/FilesProvider/List.tsx @@ -0,0 +1,999 @@ +// @mui +import { + Box, + Grid, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Tooltip, + TableHead, + Checkbox, + InputAdornment, + TableSortLabel, + FormControl + } from '@mui/material'; + import { visuallyHidden } from '@mui/utils'; + + import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; + import { fDateOnly } from '@/utils/formatTime'; + + import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + import AssessmentIcon from '@mui/icons-material/Assessment'; + // hooks + import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; + import { Link, Navigate, useNavigate, useSearchParams } from 'react-router-dom'; + + import { LoadingButton } from '@mui/lab'; + // components + import axios from '../../../utils/axios'; + import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../../@types/paginated-data'; + import DataTable from '../../../components/LaravelTable'; + import { fCurrency } from '../../utils/formatNumber'; + import EditRoundedIcon from '@mui/icons-material/EditRounded'; + import { Chip } from '@mui/material'; + import Iconify from '@/components/Iconify'; + import { enqueueSnackbar } from 'notistack'; + import { fDate, fDateTime } from '../../../utils/formatTime'; + import { Claims } from '@/@types/claims'; + import Label from '@/components/Label'; + import { capitalizeFirstLetter } from '@/utils/formatString'; + import TableMoreMenu from '@/components/table/TableMoreMenu'; + import Edit from '@mui/icons-material/Edit'; + import { Download } from '@mui/icons-material'; + import { Add, Search } from '@mui/icons-material'; + import Autocomplete from '@mui/material/Autocomplete'; + + import DownloadIcon from '@mui/icons-material/Download'; + + import UploadIcon from '@mui/icons-material/Upload'; + import CancelIcon from '@mui/icons-material/Cancel'; + import CheckCircleIcon from '@mui/icons-material/CheckCircle'; + + import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; + import CloseIcon from '@mui/icons-material/Close'; + + + export default function List() { + const [selectAll, setSelectAll] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + const [providers, setProviders] = useState(null); + // const [searchText, setSearchText] = useState(''); + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('created_at'); + const [perPage, setPerPage] = useState(0); + + const handleChange = (event, newValue) => { + // Jika newValue tidak undefined, atur nilai dataProvider + if (newValue !== undefined) { + setDataProvider(newValue.service_code); + } else { + // Jika tidak ada yang dipilih, set dataProvider menjadi string kosong + setDataProvider(null); + } + }; + // Dummy data + const dummyServices = [ + { service_code: '1', name: 'Service 1' }, + { service_code: '2', name: 'Service 2' }, + { service_code: '3', name: 'Service 3' }, + // tambahkan data lain sesuai kebutuhan + ]; + + + + const handleSelectAll = () => { + setSelectAll(!selectAll); + if (!selectAll) { + const requestedIds = dataTableData.data + .filter(row => row.status === 'approved') // Memfilter baris dengan status 'requested' + .map(row => row.id); // Mengambil hanya ID dari baris-baris yang memenuhi kondisi + setSelectedRows(requestedIds); + } else { + setSelectedRows([]); + } + }; + + const handleRowSelect = (id) => { + if (selectedRows.includes(id)) { + setSelectedRows(selectedRows.filter(rowId => rowId !== id)); + } else { + setSelectedRows([...selectedRows, id]); + } + }; + + const [searchParams, setSearchParams] = useSearchParams(); + const [startDate, setStartDate] = useState(null); + const [searchText, setSearchText] = useState(''); + const [endDate, setEndDate] = useState(null); + const navigate = useNavigate(); + const [dataProvider, setDataProvider] = useState(null); + + useEffect(() => { + if (startDate !== null || endDate !== null || dataProvider !== null + || order !== null || orderBy !== null || perPage !== 0) { + loadDataTableData(); + getProvider(); + } + }, [startDate, endDate, dataProvider, order, orderBy, perPage]); + + const [isLoading, setIsLoading] = useState(false); + const [isLoadingImport, setIsLoadingImport] = useState(false); + const handleExportReport = async () => { + + + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + + + var filter = Object.fromEntries([...searchParams.entries()]); + setIsLoading(true) + await axios + .get('/claims/export-claim-management',{ + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + order: order, + orderBy: orderBy, + page: perPage, + } + }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + + + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({ search: searchText }); // Trigger to Parent + }; + + const handleGetData = (type :string) => { + axios.get(`claims/1/data-claim`) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + }) + } + + useEffect(() => { + // Trigger First Search + // setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + + +
+ ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + + return ( +
+ + + {/* */} + +
+ ); + } + + const searchInput = useRef(null); + + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + loadDataTableData(); + }; + + + + useEffect(() => { + // Trigger First Search + //setSearchText(searchText); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + // const handleClick = () => { + + // } + + + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/claims-files-provider', { + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + order: order, + orderBy: orderBy, + page: perPage, + } + }); + + setDataTableLoading(false); + + setDataTableData(response.data); + }; + + const getProvider = async () => { + const response = await axios.get('/claims/get-provider'); + setProviders(response.data) + } + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + setPerPage(value); + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [approve, setApprove] = useState(''); + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const handleSubmitData = async () => { + try { + const response = await axios.post('download-zip', { selectedRows: selectedRows }); + const fileUrl = response.data.file_url; // Perbaikan disini + enqueueSnackbar('Data berhasil di download', { variant: 'success' }); + window.open(fileUrl, '_blank'); + setOpenDialogSubmit(false); + setTimeout(() => { + window.location.reload(); + }, 5000); // Reload the page after 5 seconds + } catch (error) { + enqueueSnackbar('Data Gagal di download', { variant: 'error' }); + } + }; + + const handleSubmitData1 = () => { + //approve or decline + if (!reasonDecline && approve == 'decline') { + enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + return false; + } + Promise.all(selectedRows.map(send_bulk)) + .then(() => { + enqueueSnackbar('All requests processed successfully', { variant: 'success' }); + setOpenDialogSubmit(false); + setTimeout(() => { + window.location.reload(); + }, 5000); // Reload the page after 5 seconds + }) + .catch((error) => { + enqueueSnackbar(error.response?.data?.message ?? 'Something went wrong!', { variant: 'error' }); + }); + }; + + function send_bulk(id) { + return axios.post(`claims/${id}/${approve}`, { reasonDecline: reasonDecline }); + } + + + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importClaimManagement = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + const [importResult, setImportResult] = useState(null); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const handleImportButton = () => { + if (importClaimManagement?.current) { + handleClose(); + importClaimManagement.current ? importClaimManagement.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + const handleCancelImportButton = () => { + if(importClaimManagement.current) + { + importClaimManagement.current.value = ''; + importClaimManagement.current.dispatchEvent(new Event('change', { bubbles: true })); + } + }; + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + const handleUpload = () => { + if(importClaimManagement.current && importClaimManagement.current.files) + { + if (importClaimManagement.current?.files.length) { + const formData = new FormData(); + formData.append('file', importClaimManagement.current?.files[0]); + setImportLoading(true); + axios + .post('claims/import', formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + setImportLoading(false); + enqueueSnackbar('Success Import Claim Managemenet', { variant: 'success' }); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + } + }; + const handleGetTemplate = () => { + axios.get('claims/download-template').then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }); + + + }; + + const handleExportReportFiled = async () => { + + await axios + .post('claims/exportFiled', { params: importResult?.data.result_rows }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + // useEffect(() => { + // loadDataTableData(); + // getProvider(); + // }, []); + + const headStyle = { + fontWeight: 'bold', + }; + const headCells = [ + { + id: 'created_at', + align: 'left', + label: 'Date Submission', + isSort: true, + }, + { + id: 'code', + align: 'left', + label: 'Code', + isSort: true, + }, + { + id: 'name', + align: 'left', + label: 'Name', + isSort: false, + }, + { + id: 'provider', + align: 'left', + label: 'Provider', + isSort: false, + }, + { + id: 'files', + align: 'left', + label: 'Nama File', + isSort: false, + }, + ]; + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + }; + // Called on every row to map the data to the columns + function createData(data: Claims): Claims { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType, isSelected: boolean, onSelect: (id: string) => void }) { + const { row, isSelected, onSelect } = props; + // Memperbaiki destrukturisasi props + + const handleRowCheckboxChange = () => { + onSelect(row.id); // Panggil fungsi onSelect dari komponen induk dengan id baris saat checkbox di baris diklik + }; + + const [open, setOpen] = React.useState(false); + + const test = 1000; + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */} + + + + {row?.created_at ? fDateTime(row?.created_at) : ''} + {row?.code} + {/* {row.code} */} + {row?.name} + {row?.provider} + + + {row.files ? row.files : '-'} + + + + {/* COLLAPSIBLE ROW */} + + + + {/* + + Description : {row.description} + + */} + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {selectedRows.length > 0 ? ( + <> + + + + {selectedRows.length > 0 ? selectedRows.length : '0'}  Selected + + + + + + {/* + + */} + + + + + + ) : ( + <> + + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + )} + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + return ( + + + +
+ + + {!currentImportFileName && ( + <> + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search Code or Name', + }} + /> + + + + { + + // loadDataTableData(); + setStartDate(value); + }} + renderInput={(params) => } + /> + + + + { + setEndDate(value); + }} + renderInput={(params) => ( + + )} + /> + + + + { + providers ? ( + option.name || ''} + value={providers.find((item) => item.id === dataProvider) || null} + onChange={(event, value) => { + if (value) { + setDataProvider(value.id); + } else { + setDataProvider(null); + } + }} + renderInput={(params) => ( + + )} + /> + ):( + <> + Loading... + + ) + } + + {/* + + } + sx={{ p: 1.8 }} + loading={isLoadingImport} + onClick={handleClick} + > + + Import + + + + + Import + + { + handleGetTemplate(); + }} + > + Download Template + + + + + } + sx={{ p: 1.8 }} + onClick={handleExportReport} + loading={isLoading} + > + + Export + + + + */} + + )} + {currentImportFileName && ( + + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + + )} + {importResult && ( + + + Last Import Result :{' '} + + {importResult.data.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.data.total_failed_row} + {' '} + Failed, + {/* {importResult.data.failed_rows.map((row, index) => ( + [Code={row.code ? row.code : 'Required'}] + ))} */} +  Report: +  Download Data Result Import + + + )} + +
+
+
+ + } + /> + + + + + Confirmation + + + + + + + + + + Are you sure to Download this files selected ? + {approve == "decline" ? ( + + + + ): ''} + + + + + + + +
+ ); + } diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index c6534bfa..2e1a40f0 100644 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -418,6 +418,10 @@ export default function Router() { path: 'report/appointments/:id/edit', element: , }, + { + path: 'report/files-provider', + element: , + }, { path: 'report/live-chat', element: , @@ -507,6 +511,18 @@ export default function Router() { path: 'custormer-service/final-log/detail/:id', element: , }, + { + path: 'e-prescription/live-chat', + element: , + }, + { + path: 'e-prescription/live-chat/:id', + element: , + }, + { + path: 'e-prescription/live-chat/:id/show', + element: , + }, ], }, // { @@ -630,7 +646,7 @@ const InpatientMonitoring = Loadable(lazy(() => import('../pages/CaseManagement/ /** * Customer Service */ -// Request +// Request const RequestLog = Loadable(lazy(() => import('../pages/CustomerService/Request/Index'))) const RequestLogDetail = Loadable(lazy(() => import('../pages/CustomerService/Request/Detail'))) // Final LOG @@ -662,10 +678,17 @@ const Appointment = Loadable(lazy(() => import('../pages/Report/Appointments/Ind const AppointmentCreate = Loadable(lazy(() => import('../pages/Report/Appointments/Create'))); const AppointmentShow = Loadable(lazy(() => import('../pages/Report/Appointments/Show'))); +const FilesProvider = Loadable(lazy(() => import('../pages/Report/FilesProvider/Index'))); + const Livechat = Loadable(lazy(() => import('../pages/Report/Livechat/Index'))); const LivechatCreate = Loadable(lazy(() => import('../pages/Report/Livechat/Create'))); const LivechatShow = Loadable(lazy(() => import('../pages/Report/Livechat/Show'))); + +const EPrescription = Loadable(lazy(() => import('../pages/EPrescription/Livechat/Index'))); +const EPrescriptionCreate = Loadable(lazy(() => import('../pages/EPrescription/Livechat/Create'))); +const EPrescriptionShow = Loadable(lazy(() => import('../pages/EPrescription/Livechat/Show'))); + const LinksehatPayment = Loadable(lazy(() => import('../pages/Report/LinksehatPayments/Index'))); const MasterDrug = Loadable(lazy(() => import('../pages/Master/Drug/Index'))); diff --git a/resources/lang/en/Message.php b/resources/lang/en/Message.php index 7b8a6c6d..f439e71e 100644 --- a/resources/lang/en/Message.php +++ b/resources/lang/en/Message.php @@ -7,4 +7,6 @@ return [ 'password' => 'Password wrong. Please try again.', 'read_notification' => 'Notification has been read.', 'already_exists' => 'Data already exists.', + 'logout' => 'User logged out successfully.', + 'token_expired' => 'Token has expired. Please re-request the token.' ]; diff --git a/resources/lang/en/Validation.php b/resources/lang/en/Validation.php index 8f242065..ba9e9e01 100644 --- a/resources/lang/en/Validation.php +++ b/resources/lang/en/Validation.php @@ -6,5 +6,7 @@ return [ 'max' => [ 'file' => ':attribute max size is :max.', ], - 'email' => 'Invalid email format.' -]; \ No newline at end of file + 'email' => 'Invalid email format.', + 'regex' => 'The :attribute must contain at least one uppercase letter, one lowercase letter, and one numeric digit.', + 'min' => 'The :attribute must be at least 8 characters.' +]; diff --git a/resources/lang/id/Message.php b/resources/lang/id/Message.php index 475a5731..6589d023 100644 --- a/resources/lang/id/Message.php +++ b/resources/lang/id/Message.php @@ -7,4 +7,6 @@ return [ 'password' => 'Password salah. Silakan coba lagi.', 'read_notification' => 'Notifikasi telah dibaca.', 'already_exists' => 'Data sudah ada.', + 'logout' => 'User berhasil logout.', + 'token_expired' => 'Token telah kedaluwarsa. Silakan minta ulang token.' ]; diff --git a/resources/lang/id/Validation.php b/resources/lang/id/Validation.php index 5a8fdb02..a564bf1e 100644 --- a/resources/lang/id/Validation.php +++ b/resources/lang/id/Validation.php @@ -6,5 +6,7 @@ return [ 'max' => [ 'file' => ':attribute tidak boleh melebihi :max.', ], - 'email' => 'Format email salah.' -]; \ No newline at end of file + 'email' => 'Format email salah.', + 'regex' => ':attribute harus berisi setidaknya satu huruf besar, satu huruf kecil, dan satu digit angka.', + 'min' => ':attribute setidaknya harus terdiri dari 8 karakter.' +]; diff --git a/resources/views/email/forgot_password.blade.php b/resources/views/email/forgot_password.blade.php new file mode 100644 index 00000000..9f484c66 --- /dev/null +++ b/resources/views/email/forgot_password.blade.php @@ -0,0 +1,62 @@ + + + + + + + Verification Code + + + + +
+

Verification Code

+
{{$token}}
+

This code is valid for 60 minutes.

+ +
+ + + diff --git a/resources/views/pdf/health_sertificate.blade.php b/resources/views/pdf/health_sertificate.blade.php new file mode 100644 index 00000000..c45474a9 --- /dev/null +++ b/resources/views/pdf/health_sertificate.blade.php @@ -0,0 +1,297 @@ + + + + + + + @php + $dynamicVariableName = 'font_size_' . now()->timestamp; + $$dynamicVariableName = 10; + @endphp + + + + +
+
+
+
+ The Future Of Healthcare At Your Fingertips +
+ + +
+
+ SURAT KETERANGAN SAKIT +
+
+
+
+
+ Menerangkan Bahwa : +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Nama:{{ $name }}
Umur:{{ $age }}
Pekerjaan:{{ $work }}
Alamat:{{ $address }}
+
+
+
+ Setelah diperiksa kesehatannya ternyata pada saat ini dalam keadaan SAKIT dan memerlukan istirahat selama {{ $calculate_date }} hari, terhitung dari tanggal {{ \Carbon\Carbon::parse($start_date)->format('d M Y') }} sampai {{ \Carbon\Carbon::parse($end_date)->format('d M Y') }}. +
+ Demikianlah Surat Keterangan ini untuk dapat dipergunakan seperlunya. +
+ +
+ {{ \Carbon\Carbon::parse($date)->format('d M Y') }} +
+ Pemeriksa, +
+
+ +
+
+ ( {{ $doctor_name }} ) +
+
+ PT Link Medis Sehat
+ Primaya Hospital Corporate
+ Graha Cempaka Mas Blok D5-6
+ Jl. Let. Jend. Suprapto, Jakarta Pusat 10640, Indonesia
+ Telp (021) 4217746/47 +
+
+ + + diff --git a/resources/views/pdf/prescription.blade.php b/resources/views/pdf/prescription.blade.php new file mode 100644 index 00000000..40c40fff --- /dev/null +++ b/resources/views/pdf/prescription.blade.php @@ -0,0 +1,129 @@ + + + + E-Prescription + + + +
+ '; + ?> +
+

E-PRESCRIPTION

+
+

+

user->detail->sTitlePrefix . ' ' . $doctor->user->sFirstName . ' ' . $doctor->user->sLastName . ' ' . $doctor->user->detail->sTitleSuffix : $prescription->sDokterName; ?>

+

speciality->sSpesialis : '-'; ?>

+

Sip : jadwalDokter[0]->sSIP : '-'; ?>

+
+

Patient Details

+ + + + + + + + + + + + + + + +
(, )
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
R/sItemName;?>@nQty;?> Per sSatuan; ?>
sSigna ? '('.$item->sSigna.')' : ''; ?> per day
sNote; ?>
+ +
+

Valid Until

+
+

+

+

+
+

+

+

+ + + diff --git a/routes/channels.php b/routes/channels.php index 5d451e1f..fbcd48df 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -1,7 +1,9 @@ id === (int) $id; }); + +Broadcast::channel('chat.{room_id}', function ($user, $room_id) { + return Auth::check(); +});