diff --git a/Modules/Internal/Http/Controllers/Api/RequestLogController.php b/Modules/Internal/Http/Controllers/Api/RequestLogController.php new file mode 100644 index 00000000..6f9078bc --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/RequestLogController.php @@ -0,0 +1,727 @@ +when($request->search, function ($q, $search) { + $q->where('code', 'LIKE', "%".$search."%"); + $q->orWhereHas('member', function ($subQuery) use ($search) { + $subQuery->where('name', 'LIKE', "%".$search.""); + }); + }) + ->when($request->orderBy, function ($q, $orderBy) use ($request) { + if (in_array($orderBy, ['submission_date', 'code'])) { + $q->orderBy($orderBy, $request->order); + } + }) + ->when(empty($request->orderBy), function ($q) { + $q->orderBy('created_at', 'desc'); + }) + ->when($request->status, function($q, $status) { + $q->where('status', $status); + }) + // ->where('status', $request->status) + ->with(['member', 'files', 'service', 'member.currentPolicy']) + ->paginate(); + + return Helper::paginateResources(RequestLogResource::collection($requestLog)); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Create New Request Request + * + * Tb, BSD 28 November 2023 + */ + public function createNew(Request $request) + { + $request->validate([ + 'member_id' => 'required', + 'service_code' => 'required', + ]); + + if ($request->member_id){ + $code = $this->getNextCode($request); + $member = Member::find($request->member_id); + + $memberValid = false; + if ($member){ + if (($member->members_effective_date <= date('Y-m-d')) && + ($member->members_expire_date >= date('Y-m-d')) && + ($member->active == 1) && + ($member->suspended == 'N') + ){ + DB::beginTransaction(); + try { + $newRequestLog = RequestLogService::storeRequestLog( + row: [], + code: $code, + member: $member, + paymentType: 'cashless', + serviceCode: $request->service_code, + submissionDate: null, + status: 'approved', + organization_id: $request->organization_id, + source: $request->source + ); + + DB::commit(); + } + catch (\Throwable $th) { + DB::rollBack(); + + return Helper::responseJson(status: 'failed', statusCode: 500, message: $th->getMessage()); + } + } else { + DB::beginTransaction(); + try { + $newRequestLog = RequestLogService::storeRequestLog( + row: [], + code: $code, + member: $member, + paymentType: 'cashless', + serviceCode: $request->service_code, + submissionDate: null, + status: 'requested', + organization_id: $request->organization_id, + source: $request->source + ); + + DB::commit(); + } + catch (\Throwable $th) { + DB::rollBack(); + + return Helper::responseJson(status: 'failed', statusCode: 500, message: $th->getMessage()); + } + // return Helper::responseJson(status: 'failed', statusCode: 500, message: 'Member Not Valid'); + } + } + + } + + return Helper::responseJson(status: 'success', statusCode: 200, message: 'Request LOG berhasil ajukan!', data: $request->toArray()); + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + $claimRequest = RequestLog::findOrFail($id); + $claimRequest->load([ + 'histories' => function ($history) { + $history->latest(); + }, + 'files', + 'member', + 'claim', + 'organization', + ]); + + return Helper::responseJson(data: RequestLogShowResource::make($claimRequest)); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('internal::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + $requestLog = RequestLog::findOrFail($id); + $requestLog->status = $request->status; + $requestLog->save(); + + return response()->json([ + 'error' => false, + 'message' => 'Update succses', + 'data' => $requestLog], + 200); + + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } + + /** + * Generate Request LOG + */ + public function generateRequestLog($id) + { + $requestLog = RequestLog::find($id); + $service_code = $requestLog->service_code; + if ($requestLog->status != 'approved') { + return response()->json([ + 'error' => true, + 'message' => 'LOG Belum Terverifikasi', + 'data' => $requestLog], + 200); + } + + $member = Member::findOrFail($requestLog->member_id) + ->load([ + // 'currentPlan', + 'currentPlan' => function ($plan) use ($id, $service_code) { + $plan->where('plans.service_code', $service_code); + }, + 'currentPolicy', + 'currentPlan.corporateBenefits', + 'currentPlan.corporateBenefits.benefit' + ]); + + $pdf = PDF::loadView('pdf.guaranted_leter', compact('member', 'requestLog')); + return $pdf->download('Guaranted Letter - '.$member->full_name.'.pdf'); + + return $requestLog; + } + + public function updateFinalLog(Request $request, $id) + { + $requestLog = RequestLog::findOrFail($id); + + if ($request->hasFile('result_files')) { + foreach ($request->result_files as $file) { + $pathFile = File::storeFile('final-log-result', $id, $file); + $requestLog->files()->updateOrCreate([ + 'type' => 'final-log-result', + 'name' => File::getFileName('final-log-result', $id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + if ($request->hasFile('diagnosa_files')) { + foreach ($request->diagnosa_files as $file) { + $pathFile = File::storeFile('final-log-diagnosis', $id, $file); + $requestLog->files()->updateOrCreate([ + 'type' => 'final-log-diagnosis', + 'name' => File::getFileName('final-log-diagnosis', $id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + if ($request->hasFile('kondisi_files')) { + foreach ($request->kondisi_files as $file) { + $pathFile = File::storeFile('final-log-kondisi', $id, $file); + $requestLog->files()->updateOrCreate([ + 'type' => 'final-log-kondisi', + 'name' => File::getFileName('final-log-kondisi', $id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + return response()->json([ + 'error' => false, + 'message' => 'Update succses', + 'data' => $updateClaimRequest], + 200); + + } + + + public function updateStatus($id) + { + $requestLog = RequestLog::findOrFail($id); + $member = $requestLog->member; + + try { + + // Update Request LOG Status & Link with Claim + $requestLog->status = 'approved'; + $requestLog->save(); + + // Store Generated Documents LOG + $logContent = view('pdf.guaranted_leter', compact('member', 'requestLog')); + $requestLog->generatedDocuments()->create([ + 'type' => 'guarantee_letter', + 'title' => 'Guarantee Letter for '. $member->full_name, + 'document_type' => 'type', + 'html_content' => $logContent, + 'system_origin' => 'primecenter' + ]); + + + } catch (\Exception $e) { + return $e->getMessage(); + } + + return $claimRequest; + } + + public function filesMcu(Request $request) + { + $request->validate([ + 'id' => 'required', + 'memberid' => 'required' + ]); + if ($request->hasFile('result_files')) { + $pathFile = File::storeFile('claim-result', $request->id, $request->result_files); + $data = [ + 'memberid' => $request->id, + 'original_name' => $request->result_files->getClientOriginalName(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id + ]; + FilesMcu::create($data); + return Helper::responseJson(data: $request->toArray(), message: 'Berhasil tambah file MemberID '.$request->memberid.', silahkan lihat dilaporan'); + } + else + { + return Helper::responseJson(data: $request->toArray(), message: 'Tidak ada file member yang ditambahkan'); + } + + } + + public function importClaim(Request $request) + { + + $request->validate([ + 'file' => 'required|file|mimes:xls,xlsx,csv,txt', + ]); + $file_name = now()->getPreciseTimestamp(3) . '-' . $request->file('file')->getClientOriginalName(); + $file = $request->file('file')->storeAs('temp', $file_name); + $fileWrite = Storage::disk('public')->path('temp/result-' . $file_name); + $fileRead = Storage::path('temp/' . $file_name); + $import = new ImportService(); + $import->read($fileRead); + $import->write($fileWrite, 'xsls'); + foreach ($import->sheetsIterator() as $sheetIndex => $sheet) { + if ($sheetIndex == 1) { // Rename First Sheet to Writer + $firstWriterSheet = $import->writer->getCurrentSheet(); + $firstWriterSheet->setName($sheet->getName()); + } else { // Add New Sheet to Writer + $nextWriterSheet = $import->writer->addNewSheetAndMakeItCurrent(); + $nextWriterSheet->setName($sheet->getName()); + } + + $headers_map_to_table_fields = RequestLog::$doc_headers_to_field_map; + + // Write Header to File + $result_headers = array_keys($headers_map_to_table_fields); + $result_headers = array_merge($result_headers, ['Ingest Code', 'Ingest Note']); + + $import->addArrayToRow($result_headers); + $doc_headers_indexes = []; + foreach ($sheet->getRowIterator() as $index => $row) { + if ($index == 1) { // First Row Must be Header + foreach ($row->getCells() as $index => $cell) { + $title = $cell->getValue(); + $title = preg_replace("/\r|\n/", " ", $title); + $title = preg_replace('/\xc2\xa0/', " ", $title); + $title = rtrim($title); + $title = ltrim($title); + $doc_headers_indexes[$index] = $title; + } + // TODO Validate if First Row not Header + } else { // Next Row Should be Data + $row_data = []; + foreach ($row->getCells() as $header_index => $cell) { + if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) + $row_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); + } + try { // Process the Row Data + $claimRequestService = new RequestLogService(); + + $claimRequestService->handleRequestLogRow($row_data); + + // Write Success Result to File + // $import->read($fileRead); + // $import->write($fileWrite, 'xsls'); + $result_headers = array_merge($row_data, ['Ingest Code' =>200, 'Ingest Note' => 'Success']); + + $import->addArrayToRow($result_headers, $sheet->getName()); + + } catch (ImportRowException $e) { + // Write Data Validation Error to File + // $import->read($fileRead); + // $import->write($fileWrite, 'xsls'); + + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => $e->getCode(), + 'Ingest Note' => $e->getMessage(), + ]), $sheet->getName()); + } + // catch (\Exception $e) { + // // throw new \Exception($e); + // // Write Server Error to File + // // $import->read($fileRead); + // // $import->write($fileWrite, 'xsls'); + // dd( $e->getMessage()); + // $import->addArrayToRow(array_merge($row_data, [ + // 'Ingest Code' => 500, + // 'Ingest Note' => env('APP_DEBUG') ? $e->getMessage() : 'Server Error', + // ]), $sheet->getName()); + // } + } + } + } + $import->reader->close(); + Storage::delete('temp/' . $file_name); + $import->writer->close(); + + return [ + // 'total_successed_row' => $imported_plan_data, + // 'total_failed_row' => count($failed_plan_data), + // 'failed_row' => $failed_plan_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-' . $file_name), + 'name' => 'result-' . $file_name, + ] + ]; + } + + public function claimRequestDetail($claimRequestId) + { + $status = DB::table('claim_requests') + ->leftJoin('claims', 'claim_requests.id', '=', 'claims.claim_request_id') + ->leftJoin('members', 'claim_requests.member_id', '=', 'members.id') + ->leftJoin('corporate_employees', 'members.id', '=', 'corporate_employees.member_id') + ->leftJoin('corporate_divisions', 'corporate_employees.division_id', '=', 'corporate_divisions.id') + ->where('claim_requests.id', '=', $claimRequestId) + ->select( + 'claim_requests.submission_date', + 'claim_requests.code', + DB::raw(' + CASE + WHEN claim_requests.status = "requested" THEN "requested" + WHEN claim_requests.status = "approved" AND claims.status = "approved" THEN "approved" + WHEN claim_requests.status = "approved" AND claims.status = "declined" THEN "declined" + WHEN claim_requests.status = "approved" AND claims.status = "disbrusmented" THEN "disbrusmented" + /*WHEN claim_requests.status = "approved" AND claims.status = "received" THEN "pending"*/ + WHEN claim_requests.status = "approved" AND claims.status = "received" THEN "reviewed" + ELSE "" + END AS status + ') + ) + ->first(); + $results['status'] = $status; + $timeline = DB::table('claim_logs') + ->where('claim_logs.claim_request_id', '=', $claimRequestId) + ->select( + DB::raw(' + CASE + WHEN claim_logs.status = "requested" THEN "Request" + WHEN claim_logs.status = "reviewed" THEN "Review" + WHEN claim_logs.status = "approved" THEN "Approval" + WHEN claim_logs.status = "declined" THEN "Decline" + ELSE "-" + END AS txt_status + '), + DB::raw(' + CASE + WHEN claim_logs.status = "requested" THEN "#159C9C" + WHEN claim_logs.status = "reviewed" THEN "#0C53B7" + WHEN claim_logs.status = "approved" THEN "#229A16" + WHEN claim_logs.status = "declined" THEN "#FF4842" + ELSE "-" + END AS txt_status_color + '), + DB::raw(' + CASE + WHEN claim_logs.status = "requested" THEN "#00AB5529" + WHEN claim_logs.status = "reviewed" THEN "#1890FF29" + WHEN claim_logs.status = "approved" THEN "#54D62C29" + WHEN claim_logs.status = "declined" THEN "#FF48427A" + ELSE "-" + END AS txt_status_backgroundColor + '), + 'claim_logs.date', + 'claim_logs.description', + 'claim_logs.status' + ) + ->orderBy('claim_logs.id', 'desc') + ->get(); + $results['timeline'] = $timeline; + $request_files = DB::table('claim_request_files') + ->where('claim_request_files.claim_request_id', '=', $claimRequestId) + ->select( + 'claim_request_files.*', + DB::raw('(SELECT files.fileable_id FROM files WHERE files.fileable_id = claim_request_files.claim_request_id AND files.type = claim_request_files.type LIMIT 1) AS check_files'), + ) + ->get(); + $results['request_files'] = $request_files; + $documents = DB::table('files') + ->where('fileable_type', 'App\Models\RequestLog') + ->where('fileable_id', $claimRequestId) + ->select('original_name', \DB::raw("CONCAT('" . env('APP_URL') . "/storage/', path) as path"), 'type') + ->orderBy('id', 'desc') + ->get(); + $results['documents'] = $documents; + $dialog_submits = DB::table('claim_requests') + ->leftJoin('members', 'claim_requests.member_id','=', 'members.id') + ->where('claim_requests.id', $claimRequestId) + ->select('claim_requests.code', 'members.name', 'claim_requests.submission_date', 'claim_requests.service_code','claim_requests.status') + ->first(); + $results['dialog_submits'] = $dialog_submits; + + return Helper::responseJson($results); + } + + public function invoiceFiles(Request $request, $claim_id) + { + if ($request->hasFile('invoice_files')) { + foreach ($request->invoice_files as $file) { + $pathFile = File::storeFile('claim-invoice', $claim_id, $file); + File::updateOrCreate([ + 'fileable_type'=>'App\Models\RequestLog', + 'fileable_id' => $claim_id, + 'type' => 'claim-invoice', + 'name' => File::getFileName('claim-invoice', $claim_id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + if($request->date) + { + DB::table('claim_requests') + ->where('id', $claim_id) + ->update(['invoice_date' => $request->date]); + + } + return Helper::responseJson(data: $request->toArray(), message: 'Invoice Success Uploaded'); + } + + /** + * Get Claim Member - Infinite Scroll + * + * Bagaskoro, BSD 31 Oktober 2023 + */ + public function getClaimMemberInfiniteScroll(Request $request) + { + $offset = 0; + $limit = 10; + $page = $request->get('page'); + $keyword = $request->get('keyword'); + + if ($page > 1) { + $offset = ($page*$limit)-$limit; + } + + $memberList = DB::table('members') + ->select('id','member_id','name') + ->where("name", "like", "%$keyword%") + ->orWhere("member_id", "like", "%$keyword%") + ->orderBy('created_at', 'asc') + ->offset($offset) + ->limit($limit) + ->get(); + + $data = []; + if(count($memberList)>0){ + $temp = []; + foreach($memberList as $d){ + $serviceType = $this->getServiceMember($d->id); + $temp['id'] = $d->id; + $temp['member_id'] = $d->member_id; + $temp['name'] = $d->name; + $temp['service_type'] = $serviceType; + + + array_push($data, $temp); + } + + } + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [ + 'member_list'=> $data, + ] + ],200); + } + + public function getServiceMember($id){ + $service = DB::table('member_plans') + ->select('plans.service_code as code', 'services.name') + ->join('plans', 'member_plans.plan_id', '=', 'plans.id') + ->join('services', 'plans.service_code', '=', 'services.code') + ->where('member_id', $id) + ->get() + ->toArray(); + return $service; + } + + public static function getNextCode(Request $request) + { + // $last_number = RequestLog::max('code'); + // $next_number = empty($last_number) ? 1 : ((int) explode('-', $last_number)[2] + 1); + // return self::makeCode($next_number); + + $source = $request->source == 'client-portal' ? 'C' : 'H'; + $organization = Organization::where(['id' => $request->organization_id, 'type' => 'hospital'])->first('code'); + $provideCode = $organization ? $organization->code : ''; + $member = Member::with('currentCorporate')->where(['id' => $request->member_id])->first(); + + $data = [ + 'source' => $source, + 'provideCode' => $provideCode, + 'date' => date('ymd'), + 'policy' => $member->currentPolicy->code, + 'member_code' => $member->member_id, + ]; + + $last_numeric_code = RequestLog::select(DB::raw('MAX(CAST(SUBSTRING_INDEX(code, ".", -1) AS SIGNED)) as max_numeric_code')) + ->whereRaw('SUBSTRING_INDEX(code, ".", -1) REGEXP "^[0-9]+$"') + ->value('max_numeric_code'); + // $next_number = 1; + + if ($last_numeric_code) { + // // Jika ada kode sebelumnya, pecah kode dan tambahkan 1 ke angka terakhir + // $parts = explode('-', $last_code); + // $last_number = (int) end($parts); + $next_number = $last_numeric_code + 1; + } else { + $next_number = 1; + } + return self::makeCode($next_number, $data); + } + + + public static function makeCode($next_number, $data) + { + $sparator = '.'; + // Pastikan $next_number adalah integer positif + $next_number = max(1, (int) $next_number); + // Menghasilkan kode dengan format yang diinginkan + return self::$code_prefix . $sparator. $data['source'] . $sparator. $data['provideCode'] . $sparator. $data['date'] . $sparator . $data['policy'] . $sparator. $data['member_code'] . $sparator. str_pad($next_number, 3, '0', STR_PAD_LEFT); + } + + public function requestFiles(Request $request, $claim_id) + { + + if ($request->hasFile('fileDiagnosis')) { + foreach ($request->fileDiagnosis as $file) { + $pathFile = File::storeFile('claim-diagnosis', $claim_id, $file); + File::updateOrCreate([ + 'fileable_type'=>'App\Models\RequestLog', + 'fileable_id' => $claim_id, + 'type' => 'claim-diagnosis', + 'name' => File::getFileName('claim-diagnosis', $claim_id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + if ($request->hasFile('fileKondisis')) { + foreach ($request->fileKondisis as $file) { + $pathFile = File::storeFile('claim-kondisi', $claim_id, $file); + File::updateOrCreate([ + 'fileable_type'=>'App\Models\RequestLog', + 'fileable_id' => $claim_id, + 'type' => 'claim-kondisi', + 'name' => File::getFileName('claim-kondisi', $claim_id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + if ($request->hasFile('fileResults')) { + foreach ($request->fileResults as $file) { + $pathFile = File::storeFile('claim-result', $claim_id, $file); + File::updateOrCreate([ + 'fileable_type'=>'App\Models\RequestLog', + 'fileable_id' => $claim_id, + 'type' => 'claim-result', + 'name' => File::getFileName('claim-result', $claim_id, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + + return Helper::responseJson(data: $request->toArray(), message: 'Invoice Success Uploaded'); + } +} diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index b723b07e..a2f2821a 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -7,6 +7,7 @@ use Modules\Internal\Http\Controllers\Api\AppointmentController; use Modules\Internal\Http\Controllers\Api\BenefitController; use Modules\Internal\Http\Controllers\Api\CityController; use Modules\Internal\Http\Controllers\Api\ClaimController; +use Modules\Internal\Http\Controllers\Api\RequestLogController; use Modules\Internal\Http\Controllers\Api\ClaimRequestController; use Modules\Internal\Http\Controllers\Api\CorporateBenefitController; use Modules\Internal\Http\Controllers\Api\CorporateController; @@ -247,6 +248,11 @@ Route::prefix('internal')->group(function () { Route::get('claims/{id}/benefit-configuration', [ClaimController::class, 'getBenefitConfiguration']); // Bagaskoro, BSD 03 November 2023 Route::put('claims/benefit-configuration/edit/{id}', [ClaimController::class, 'editBenefitConfiguration']); // Bagaskoro, BSD 03 November 2023 + Route::get('customer-service/request', [RequestLogController::class, 'index']); + Route::post('customer-service/request', [RequestLogController::class, 'createNew']); + Route::put('customer-service/request/{id}', [RequestLogController::class, 'update']); + Route::get('customer-service/request/{id}/download', [RequestLogController::class, 'generateRequestLog']); + Route::get('search-organizations', [OrganizationController::class, 'searchOrganization']); Route::get('search-specialities', [SpecialityController::class, 'searchSpeciality']); Route::resource('organizations', OrganizationController::class); @@ -264,6 +270,8 @@ Route::prefix('internal')->group(function () { Route::controller(ClaimRequestController::class)->group(function () { Route::post('files-mcu', 'filesMcu'); }); + + Route::get('claim-requests', [ClaimRequestController::class, 'index'])->name('claim-requests.index'); Route::get('claim-requests/list-member', [ClaimRequestController::class, 'getClaimMemberInfiniteScroll']); // Bagaskoro, BSD 31 Oktober 2023 Route::post('claim-requests/{id}/approve', [ClaimRequestController::class, 'approve'])->name('claim-requests.approve'); diff --git a/Modules/Internal/Services/MemberEnrollmentService.php b/Modules/Internal/Services/MemberEnrollmentService.php index dca06def..278e7720 100644 --- a/Modules/Internal/Services/MemberEnrollmentService.php +++ b/Modules/Internal/Services/MemberEnrollmentService.php @@ -581,6 +581,7 @@ class MemberEnrollmentService "telephone_mobile" => $row['telephone_mobile'] ?? null, "telephone_res" => $row['telephone_res'] ?? null, "telephone_office" => $row['telephone_office'] ?? null, + "suspended" => $row['member_suspended'] ?? null, ]; // $this->validateRow($row); if (!isset($corporate->currentPolicy) || $corporate->currentPolicy->code != $row['policy_number']) { @@ -895,46 +896,46 @@ class MemberEnrollmentService // } // Update plan - $plans = explode(",",$row['plan_id']); - if (count($plans) > 0) { - foreach($plans as $d){ - $plan = Plan::query() - ->where('code', $d) - ->where('corporate_id', $corporate->id) - ->first(); - if (!$plan) { - throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row); - } - $member->memberPlans()->updateOrCreate([ - 'member_id' => $member->id, - 'plan_id' => $plan->id, - ], - [ - 'plan_id' => $plan->id, - 'status' => 'active', - 'start' => $this->dateParser($row['member_effective_date']), - 'end' => $this->dateParser($row['member_expiry_date']), - ]); - } - } else { - $plan = Plan::query() - ->where('code', $row['plan_id']) - ->where('corporate_id', $corporate->id) - ->first(); - if (!$plan) { - throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row); - } - $member->memberPlans()->updateOrCreate([ - 'member_id' => $member->id, - 'plan_id' => $plan->id, - ], - [ - 'plan_id' => $plan->id, - 'status' => 'active', - 'start' => $this->dateParser($row['member_effective_date']), - 'end' => $this->dateParser($row['member_expiry_date']), - ]); - } + // $plans = explode(",",$row['plan_id']); + // if (count($plans) > 0) { + // foreach($plans as $d){ + // $plan = Plan::query() + // ->where('code', $d) + // ->where('corporate_id', $corporate->id) + // ->first(); + // if (!$plan) { + // throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row); + // } + // $member->memberPlans()->updateOrCreate([ + // 'member_id' => $member->id, + // 'plan_id' => $plan->id, + // ], + // [ + // 'plan_id' => $plan->id, + // 'status' => 'active', + // 'start' => $this->dateParser($row['member_effective_date']), + // 'end' => $this->dateParser($row['member_expiry_date']), + // ]); + // } + // } else { + // $plan = Plan::query() + // ->where('code', $row['plan_id']) + // ->where('corporate_id', $corporate->id) + // ->first(); + // if (!$plan) { + // throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row); + // } + // $member->memberPlans()->updateOrCreate([ + // 'member_id' => $member->id, + // 'plan_id' => $plan->id, + // ], + // [ + // 'plan_id' => $plan->id, + // 'status' => 'active', + // 'start' => $this->dateParser($row['member_effective_date']), + // 'end' => $this->dateParser($row['member_expiry_date']), + // ]); + // } // end update plan diff --git a/Modules/Internal/Transformers/RequestLogResource.php b/Modules/Internal/Transformers/RequestLogResource.php new file mode 100644 index 00000000..bb900cfd --- /dev/null +++ b/Modules/Internal/Transformers/RequestLogResource.php @@ -0,0 +1,36 @@ +files->mapToGroups(function($file) { + return [Str::slug($file->type, '_') => $file]; + }); + + $data = [ + 'id' => $this->id, + 'code' => $this->code, + 'submission_date' => $this->submission_date, + 'member' => $this->member, + 'status' => $this->status ?? 'unknown', + 'service_name' => $this->service ? $this->service->name : '', + 'payment_type' => $this->payment_type, + 'payment_type_name' => $this->payment_type_name, + 'files_by_type' => $filesGroupByType + ]; + + return $data; + } +} diff --git a/app/Models/Member.php b/app/Models/Member.php index 5b5363ed..1d0435a3 100644 --- a/app/Models/Member.php +++ b/app/Models/Member.php @@ -63,6 +63,7 @@ class Member extends Model "policy_in_force", "start_no_claim", "end_no_claim", + "suspended" ]; protected $appends = [ diff --git a/app/Models/RequestLog.php b/app/Models/RequestLog.php new file mode 100644 index 00000000..7e89fa94 --- /dev/null +++ b/app/Models/RequestLog.php @@ -0,0 +1,264 @@ + "payor_id", + "CORPORATE ID" => "corporate_id", + "POLICY NUMBER" => "policy_number", + "MEMBER ID" => "member_id", + "MEMBER NAME" => "member_name", + "RECORD TYPE (P/D)" => "record_type", + "BENEFIT CODE" => "benefit_code", + "BENEFIT DESC" => "benefit_desc", + "CLAIM TYPE" => "claim_type", + "CLAIM PROCESS STATUS" => "status", + "CLIENT CLAIM ID" => "client_claim_id", + "REFERENCE NO" => "reference_no", + "ADMEDIKA CLAIM ID" => "admika_claim_id", + "PROVIDER CODE" => "provider_code", + "ADMISSION DATE" => "admission_date", + "DISCUTRGE DATE" => "discutrge_date", + "DURATION DAYS" => "duration_days", + "COVERAGE TYPE" => "coverage_type", + "PLAN ID" => "plan_id", + "DIAGNOSIS CODE" => "diagnosis_code", + "DIAGNOSIS DESC" => "diagnosis_desc", + "TOT AMT INCURRED" => "tot_amt_insurred", + "TOT AMT APPROVED" => "tot_amt_approved", + "TOT AMT NOT APPROVED" => "tot_amt_not_approved", + "TOT EXCESS PAID" => "tot_excess_paid", + "REMARKS" => "remarks", + "SECONDARY DIAGNOSIS CODE" => "secondary_diagnosis", + "APPROVED DATE" => "approved_date", + "APPROVED BY" => "approved_by", + "DATE RECEIVED" => "data_received", + "HOSPITAL INVOICE DATE" => "hospital_invoice_date", + ]; + + public static $listing_doc_headers = [ + "PAYOR ID", + "CORPORATE ID", + "POLICY NUMBER", + "MEMBER ID", + "MEMBER NAME", + "RECORD TYPE (P/D)", + "CLAIM TYPE", + "CLAIM PROCESS STATUS", + "CLIENT CLAIM ID", + "REFERENCE NO", + "ADMEDIKA CLAIM ID", + "PROVIDER CODE", + "ADMISSION DATE", + "DISCUTRGE DATE", + "DURATION DAYS", + "COVERAGE TYPE", + "PLAN ID", + "DIAGNOSIS CODE", + "DIAGNOSIS DESC", + "TOT AMT INCURRED", + "TOT AMT APPROVED", + "TOT AMT NOT APPROVED", + "TOT EXCESS PAID", + "REMARKS", + "SECONDARY DIAGNOSIS CODE", + "APPROVED DATE", + "APPROVED BY", + "DATE RECEIVED", + "HOSPITAL INVOICE DATE", + ]; + + + public static $status = [ + 'draft' => 'Draft', + 'requested' => 'Requested', + 'approved' => 'Approved', + 'declined' => 'Declined' + ]; + + public static $payment_types = [ + 'cashless' => 'Cashless', + 'reimbursement' => 'Reimbursement' + ]; + + public $appends = [ + 'payment_type_name' + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($model) { + try { + $model->uuid = (string) Str::orderedUuid(); // generate uuid + // $model->code = self::getNextCode(); + } catch (\Exception $e) { + abort(500, $e->getMessage()); + } + }); + + static::created(function ($model) { + // try { + // if (!empty($model->status) && $model->status == 'requested') { + // $model->histories()->create([ + // 'title' => 'New Claim Requested', + // 'description' => "Claim Requested for Member : {$model->member->member_id} - ({$model->member->full_name})", + // 'type' => 'info' + // ]); + // } + // } catch (\Exception $e) { + // abort(500, $e->getMessage()); + // } + }); + + static::updated(function ($model) { + if ($model->hasChanges(['status'])) { + + // if ($model->status == 'requested') { + // $model->histories()->create([ + // 'title' => 'New Claim Requested', + // 'description' => "Claim Requested for Member : {$model->member->member_id} - ({$model->member->full_name})", + // 'type' => 'info' + // ]); + // } + + // if ($model->status == 'received') { + // ClaimReceived::dispatch($model); + // } + + // if ($model->status == 'approved') { + // ClaimApproved::dispatch($model); + // } + + // if ($model->status == 'postpone') { + // ClaimPostpone::dispatch($model); + // } + + // if ($model->status == 'paid') { + // ClaimPaid::dispatch($model); + // } + + // if ($model->status == 'declined') { + // ClaimDeclined::dispatch($model); + // } + } + }); + } + + // public static function getNextCode() + // { + // $last_number = self::withTrashed()->max('code'); + // $next_number = empty($last_number) ? 1 : ((int) explode('-', $last_number)[1] + 1); + + // return self::makeCode($next_number); + // } + + // public static function makeCode($next_number) + // { + // return (string) self::$code_prefix .'-'. str_pad($next_number, 5, 0, STR_PAD_LEFT); + // } + + public function claim() + { + return $this->belongsTo(Claim::class, 'claim_id'); + } + + public function claims() + { + return $this->hasMany(Claim::class, 'claim_request_id'); + } + + public function files() + { + return $this->morphMany(File::class, 'fileable'); + } + + public function claimResults() + { + return $this->files()->where('type', 'claim-result')->whereNull('deleted_at'); + } + + public function claimConditions() + { + return $this->files()->where('type', 'claim-kondisi')->whereNull('deleted_at'); + } + + public function claimDiagnosis() + { + return $this->files()->where('type', 'claim-diagnosis')->whereNull('deleted_at'); + } + + public function generatedDocuments() + { + return $this->morphMany(GeneratedDocument::class, 'generated_documentable'); + } + + public function histories() + { + return $this->morphMany(ClaimHistory::class, 'historiable'); + } + + public function organization() + { + return $this->belongsTo(Organization::class, 'organization_id'); + } + + public function member() + { + return $this->belongsTo(Member::class, 'member_id', 'id'); + } + + public function service() + { + return $this->belongsTo(Service::class, 'service_code', 'code'); + } + + public function getPaymentTypeNameAttribute() + { + return self::$payment_types[$this->payment_type] ?? $this->payment_type; + } + + public function getStatusAttribute($value) + { + return self::$payment_types[$value] ?? $value; + } +} diff --git a/app/Services/RequestLogService.php b/app/Services/RequestLogService.php new file mode 100644 index 00000000..1256fa56 --- /dev/null +++ b/app/Services/RequestLogService.php @@ -0,0 +1,189 @@ +first(); + if (!$organization){ + throw new ImportRowException(__('Code Provider Tidak ditemukan', [ + 'attribute' => 'provider_code', + 'code' => $row['provider_code'] + ]), 403, null, $row); + } + }; + + DB::beginTransaction(); + + $requestLogData = [ + 'code' => $code, + 'member_id' => $member->id, + 'submission_date' => $submissionDate ?? now(), + 'status' => $status, + 'payment_type' => $paymentType, + 'service_code' => $serviceCode, + 'policy_id' => $member->currentPolicy->id ?? null, + 'organization_id' => $organization ? $organization->id : 0, + 'source' => $source, + ]; + + $requestLog = RequestLog::create($requestLogData); + + DB::commit(); + + return $requestLog; + } catch (\Exception $error) { + DB::rollBack(); + + throw new \Exception($error); + } + } + + public static function storeClaimManagement($row, $member, $claim_request_id){ + try { + $organization = 0; + if($row['provider_code']){ + $organization = Organization::where('code', $row['provider_code'])->first(); + if (!$organization){ + throw new ImportRowException(__('Provider Tidak ditemukan'), 0, null, $row); + } + }; + if(!$member){ + throw new ImportRowException(__('Member Tidak ditemukan'), 0, null, $row); + }; + DB::beginTransaction(); + $data = [ + 'member_id' => $member->id, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'total_claim' => $row['tot_amt_insurred'] ? $row['tot_amt_insurred'] : 0, + 'benefit_code' => $row['benefit_code'] ? $row['benefit_code'] : '-', + 'benefit_desc' => $row['benefit_desc'] ? $row['benefit_desc'] : '-', + 'amount_incurred' => $row['tot_amt_insurred'] ? $row['tot_amt_insurred'] : 0, + 'amount_approved' => $row['tot_amt_approved'] ? $row['tot_amt_approved'] : 0, + 'amount_not_approved' => $row['tot_amt_not_approved'] ? $row['tot_amt_not_approved'] :0, + 'excess_paid' => $row['tot_excess_paid'] ? $row['tot_excess_paid'] : 0, + 'claim_request_id' => $claim_request_id, + 'organization_id' => $organization ? $organization->id : NULL, + 'status' => 'requested' + ]; + + + $claimManagement = Claim::create($data); + + // update client id di claim request + ClaimRequest::where('id', $claim_request_id)->update([ + 'claim_id' => $claimManagement->id, + ]); + + DB::commit(); + return $claimManagement; + + + } catch (\Exception $error) { + DB::rollBack(); + + throw new \Exception($error); + } + } + + public static function updateClaimRequest($organization_id, $claim_request_id){ + try { + $data = [ + 'organization_id' => $organization_id + ]; + DB::commit(); + $update = ClaimRequest::where('id', $claim_request_id)->update($data); + return ClaimRequest::find($claim_request_id); + + } catch (\Exception $error) { + DB::rollBack(); + + throw new \Exception($error); + } + } + + protected function validatePlanRow($row) + { + if (empty($row['member_id'])) { + throw new ImportRowException(__('Member ID Required'), 0, null, $row); + } + } + + public function handleClaimRequestRow($row) + { + try { + $member = Member::where('member_id', $row['member_id'])->with(['currentPlan'])->first(); + if(!$member){ + throw new ImportRowException(__('Member Tidak ditemukan'), 0, null, $row); + }; + $code = $row['client_claim_id']; + $organization_id = $row['provider_code']; + $submissionDate = Helper::formatDateDB($row['admission_date']); + $paymentType = $row['claim_type']; + $status = $row['status']; + $serviceCode = $row['coverage_type']; + + $newClaimRequest = $this->storeClaimRequest( + row: $row, + code: $code, + member: $member, + paymentType: $paymentType, + serviceCode: $serviceCode, + submissionDate: $submissionDate, + status: $status, + organization_code: $organization_id + ); + + $newlyCreatedID = $newClaimRequest->id; + + $newClaimManangement = $this->storeClaimManagement($row, $member, $newlyCreatedID); + ClaimRequested::dispatch($newClaimRequest); + // Log History + $newClaimRequest->histories()->create([ + 'title' => 'New Claim Requested', + 'description' => "Claim Requested for Member : {$member->member_id} - ({$member->full_name})", + 'type' => 'info', + 'system_origin' => 'import-internal-aso' + ]); + + $claim_request_data = $row; + $this->validatePlanRow($claim_request_data); + + return $newClaimRequest; + } catch (\Exception $e) { + throw $e; + } + } + +} \ No newline at end of file diff --git a/database/migrations/2023_11_27_155931_create_request_log_table.php b/database/migrations/2023_11_27_155931_create_request_log_table.php new file mode 100644 index 00000000..a61d01e8 --- /dev/null +++ b/database/migrations/2023_11_27_155931_create_request_log_table.php @@ -0,0 +1,45 @@ +id(); + $table->uuid('uuid'); + $table->string('code')->index(); + $table->dateTime('submission_date')->nullable(); + $table->foreignId('member_id'); + $table->string('payment_type'); + $table->string('service_code'); + $table->foreignId('policy_id'); + $table->string('status')->nullable(); + $table->string('source')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + $table->unsignedBigInteger('created_by')->nullable()->index(); + $table->unsignedBigInteger('updated_by')->nullable()->index(); + $table->unsignedBigInteger('deleted_by')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('request_logs'); + } +}; diff --git a/database/migrations/2023_11_28_092019_add_columns_organization_id_to_table_request_logs.php b/database/migrations/2023_11_28_092019_add_columns_organization_id_to_table_request_logs.php new file mode 100644 index 00000000..a64cd329 --- /dev/null +++ b/database/migrations/2023_11_28_092019_add_columns_organization_id_to_table_request_logs.php @@ -0,0 +1,32 @@ +integer('organization_id')->default(0)->after('policy_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('request_logs', function (Blueprint $table) { + $table->dropColumn('organization_id'); + }); + } +}; diff --git a/database/migrations/2023_11_28_104608_add_columns_suspended_to_members_table.php b/database/migrations/2023_11_28_104608_add_columns_suspended_to_members_table.php new file mode 100644 index 00000000..57502a1f --- /dev/null +++ b/database/migrations/2023_11_28_104608_add_columns_suspended_to_members_table.php @@ -0,0 +1,32 @@ +string('suspended')->after('active')->default('N'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('members', function (Blueprint $table) { + $table->dropColumn('suspended'); + }); + } +}; diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index 153a78c9..3ca34a56 100644 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -82,8 +82,9 @@ const navConfig = [ { title: 'CUSTOMER SERVICES', children: [ - { title: 'Request', path: '/cs-request' }, - { title: 'Membership', path: '/cs-membership' }, + { title: 'Request', path: '/custormer-service/request' }, + // { title: 'Membership', path: '/cs-membership' }, + { title: 'Final LOG', path: '/custormer-service/final-request' }, ], }, { diff --git a/frontend/dashboard/src/pages/Corporates/Plan/List.tsx b/frontend/dashboard/src/pages/Corporates/Plan/List.tsx index 5c2963f6..2e3a8813 100644 --- a/frontend/dashboard/src/pages/Corporates/Plan/List.tsx +++ b/frontend/dashboard/src/pages/Corporates/Plan/List.tsx @@ -38,7 +38,7 @@ import UploadIcon from '@mui/icons-material/Upload'; import CancelIcon from '@mui/icons-material/Cancel'; import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; import CachedOutlinedIcon from '@mui/icons-material/CachedOutlined'; -import { useForm } from 'react-hook-form'; +import { Controller, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import HistoryIcon from '@mui/icons-material/History'; // hooks @@ -113,7 +113,6 @@ export default function CorporatePlanList() { }; useEffect(() => { - // console.log('Search Input: useEffect') setSearchText(searchParams.get('search') ?? ''); }, [searchParams]); @@ -141,6 +140,8 @@ export default function CorporatePlanList() { const [currentImportFileName, setCurrentImportFileName] = useState(null); const [importLoading, setImportLoading] = useState(false); + const { control } = useForm(); + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -223,6 +224,40 @@ export default function CorporatePlanList() { }) } + const handleFilter = (event: React.SyntheticEvent, newValue: { value: string, label: string }[], name:string) => { // + const serviceCodeArray :string[] = []; + const codeArray :string[] = []; + const typeArray :string[] = []; + const planArray :string[] = []; + if (name == 'service_code'){ + newValue.map(row => { + serviceCodeArray.push(row.value); + }) + } + if (name == 'code'){ + newValue.map(row => { + codeArray.push(row.value); + }) + } + if (name == 'type'){ + newValue.map(row => { + typeArray.push(row.value); + }) + } + if (name == 'plan'){ + newValue.map(row => { + planArray.push(row.value); + }) + } + let data = { + service_code : serviceCodeArray, + code : codeArray, + type : typeArray, + plan : planArray, + } + loadDataTableDataFilter(data) + } + return (
handleFilter(event, newValue, 'service_code')} fullWidth getOptionLabel={(option) => option.label} isOptionEqualToValue={(option, value) => option.value === value.value } + renderInput={(params) => ( )} - /> + /> handleFilter(event, newValue, 'plan')} multiple limitTags={1} fullWidth @@ -276,6 +314,7 @@ export default function CorporatePlanList() { handleFilter(event, newValue, 'code')} multiple limitTags={1} fullWidth @@ -299,6 +338,7 @@ export default function CorporatePlanList() { isOptionEqualToValue={(option, value) => option.value === value.value } + onChange={(event, newValue) => handleFilter(event, newValue, 'type')} renderInput={(params) => ( )} @@ -827,8 +867,6 @@ export default function CorporatePlanList() { }; }); - - console.log(optionCode) setServiceCode(optionServiceCode) setType(optionType) setCode(optionCode) @@ -836,6 +874,73 @@ export default function CorporatePlanList() { }; + const loadDataTableDataFilter = async (appliedFilter = null) => { + setDataTableLoading(true); + let filter = appliedFilter + + const response = await axios.post('/corporates/' + corporate_id + '/corporate-plans/filter',filter); + + setDataTableLoading(false); + + setDataTableData(response.data); + + const data = response.data.data; + const serviceCodeArray :string[] = []; + const codeArray :string[] = []; + const typeArray :string[] = []; + const planArray :string[] = []; + + // data.forEach((row: any) => { + // if (!serviceCodeArray.includes(row.service_code)) { + // serviceCodeArray.push(row.service_code); + // } + + // if (!codeArray.includes(row.code)) { + // codeArray.push(row.code); + // } + + // if (!typeArray.includes(row.type)) { + // typeArray.push(row.type); + // } + + // if (!planArray.includes(row.corporate_plan_id)) { + // planArray.push(row.corporate_plan_id); + // } + + // }); + + // const optionServiceCode = serviceCodeArray.map((value) => { + // return { + // value: value, + // label: value + // }; + // }); + // const optionCode = codeArray.map((value) => { + // return { + // value: value, + // label: value + // }; + // }); + // const optionType = typeArray.map((value) => { + // return { + // value: value, + // label: value + // }; + // }); + // const optionPlan = planArray.map((value) => { + // return { + // value: value, + // label: value + // }; + // }); + + // setServiceCode(optionServiceCode) + // setType(optionType) + // setCode(optionCode) + // setCodePlan(optionPlan) + + }; + const headStyle = { fontWeight: 'bold', }; diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreate.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreate.tsx new file mode 100644 index 00000000..d952f5e3 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreate.tsx @@ -0,0 +1,355 @@ +/** + * Core + * ============================================ + */ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; +import { Box, FormControlLabel, Grid, Checkbox, Typography, CircularProgress , Button, styled, Stack, IconButton, Card} from '@mui/material'; +import { LoadingButton } from '@mui/lab'; + +/** + * Components + * ============================================ +*/ +// - Global - +import Label from '@/components/Label'; +// - Local - +import FormCreateSearch from './FormCreateSearch'; +import FormCreateListChoose from './FormCreateListChoose'; +import FormCreateBtnUpload from './FormCreateBtnUpload'; + +/** + * Icon, Utils, Types, Functions, theme, hook + * ============================================ + */ +import { ArrowBackIosNew } from '@mui/icons-material'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { MemberListType } from '../Model/Types'; +import { addClaimRequest, getMemberList } from '../Model/Functions'; +import palette from '@/theme/palette'; +import FormCreateFilesUpload from './FormCreateFilesUpload'; +import useLoadOnScroll from '@/hooks/useLoadOnScroll'; +import useCollapseDrawer from '@/hooks/useCollapseDrawer'; +import FormCreateBtnChoose from './FormCreateBtnChoose'; +import axios from '../../../utils/axios'; + +export default function FormCreate() { + const navigate = useNavigate() + const defaultListChoosed:MemberListType[] = []; + + // State + // ------------------------- + const [keyword, setKeyword] = useState(''); + const [listChoosed, setListChoosed] = useState([]); + const [isChoosed, setIsChoosed] = useState(false); + const [formIsLoading, setFormIsLoading] = useState(false); + + // List Choose - auto Scroll + // ------------------------- + const fetchFunction = async (page: number): Promise => getMemberList(page, keyword) + + const {data: MemberList, isLoading: scrollIsLoading, setData, resetLastPage, refetchData} = useLoadOnScroll(fetchFunction); + + // List Choose - Search + // ------------------------- + const handleSearch = (keyword: string) => { + setData([]) + resetLastPage() + setKeyword(keyword) + refetchData() + } + + // Function - Clear Form + // ----------------------------- + const clearForm = () => { + setListChoosed(defaultListChoosed); + setIsChoosed(false); + } + + // Function - Choose Patien Type + // ----------------------------- + const handleChoosePatienType = (data: MemberListType, type: string) => { + let newListChoosed = listChoosed.map((list) => { + if (data.id == list.id) { + list.patien_type = type + } + + return list; + }) + setListChoosed(newListChoosed) + } + + // Function - Handle Btn Upload + // ----------------------------- + const handleChangeInput = (data: MemberListType, type_file: 'kondisi'|'diagnosa'|'penunjang', file: any) => { + let newListChoosed = listChoosed.map((list) => { + if (data.id == list.id) { + if (type_file == 'kondisi') { + if (list.file_kondisi == undefined) { + list.file_kondisi = [file]; + } + else { + list.file_kondisi.push(file); + } + } + + if (type_file == 'diagnosa') { + if (list.file_diagnosa == undefined) { + list.file_diagnosa = [file]; + } + else { + list.file_diagnosa.push(file); + } + } + + if (type_file == 'penunjang') { + if (list.file_penunjang == undefined) { + list.file_penunjang = [file]; + } + else { + list.file_penunjang.push(file); + } + } + } + + return list; + }) + + setListChoosed(newListChoosed) + } + + // Function - Handle Remove Fle + // ----------------------------- + const handleRemoveFile = (data: MemberListType, type_file: 'kondisi'|'diagnosa'|'penunjang', target_index: number) => { + let newListChoosed = listChoosed.map((list) => { + if (data.id == list.id) { + if (type_file == 'kondisi') { + list.file_kondisi = list.file_kondisi?.filter((file: any, index: number) =>{ + if (target_index !== index) { + return file; + } + }); + } + + if (type_file == 'diagnosa') { + list.file_diagnosa = list.file_diagnosa?.filter((file: any, index: number) =>{ + if (target_index !== index) { + return file; + } + }); + } + + if (type_file == 'penunjang') { + list.file_penunjang = list.file_penunjang?.filter((file: any, index: number) =>{ + if (target_index !== index) { + return file; + } + }); + } + } + + return list; + }) + + setListChoosed(newListChoosed) + } + + // Function - Handle Submit Form + // ----------------------------- + const handleSubmit = async () => { + setFormIsLoading(true) + let response = await addClaimRequest(listChoosed) + setFormIsLoading(false) + + if (response == true) { + clearForm() + } + } + + + + let isDirty = listChoosed.some((row) => { + if (row.patien_type == undefined) { + return true + } + }) + + return ( + + {/* Back Button */} + + isChoosed==false ? navigate(`/claim-requests`) : setIsChoosed(false)} > + + + + + {'Create Claim Requests'} + + + + {/* Choose Section */} + + {/* Search */} + + handleSearch('')} onSubmit={(keyword) => handleSearch(keyword)} /> + + + + + {/* List */} + + + { + MemberList.map((row, index) => { + return ( + { + checked ? setListChoosed((prevData) => [...prevData, data]) : setListChoosed((items) => items.filter(item => item.id != data.id)) + }} + /> + ) + }) + } + + + + {/* Loading */} + + + + + {/* Submit List */} + + setIsChoosed(true)} /> + + + + + + {/* Input Section */} + + { + listChoosed.map((row, index) => { + return ( + + + {/* Patien Name */} + + + + + {row.name} + + + {row.member_id} + + + + + + + {/* Patien Type */} + + + {row.service_type.map((r,i) => { + const code = r.code + return ( + + + + ) + })} + + + + {/* File Kondisi */} + + + + Condition Document + + + {row.file_kondisi && row.file_kondisi.map((file, index) => ( + + handleRemoveFile(row, 'kondisi', index)} /> + + ))} + + + handleChangeInput(row, 'kondisi', file)} /> + + + + + {/* File Diagnosa */} + + + + Diagnosis Document + + + {row.file_diagnosa && row.file_diagnosa.map((file, index) => ( + + handleRemoveFile(row, 'diagnosa', index)} /> + + ))} + + + handleChangeInput(row, 'diagnosa', file)} /> + + + + + {/* File Penunjang */} + + + + Supporting Result Document + + + {row.file_penunjang && row.file_penunjang.map((file, index) => ( + + handleRemoveFile(row, 'penunjang', index)} /> + + ))} + + + handleChangeInput(row, 'penunjang', file)} /> + + + + + + + ) + }) + } + + + + + handleSubmit()}> + Save Changes + + + + + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateBtnChoose.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateBtnChoose.tsx new file mode 100644 index 00000000..cffd3bc3 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateBtnChoose.tsx @@ -0,0 +1,39 @@ +import { styled, Button } from "@mui/material"; +import useCollapseDrawer from "@/hooks/useCollapseDrawer"; + +/** + * Custom Style + * ============================================ +*/ +const DivCustom1 = styled('div')(({ theme }) => ({ + background: 'white', + position: 'fixed', + left: '350px', + right: 0, + bottom: 0, + paddingLeft: '32px', + paddingRight: '32px', + paddingTop: '32px', + paddingBottom: '48px', + [theme.breakpoints.between('sm', 'lg')]: { + left: '0px', + }, +})); + +type Props = { + disabled: boolean, + title : string, + handleClickProp: () => void +} + +export default function FormCreateBtnChoose ({disabled, title, handleClickProp}: Props) { + const { collapseClick } = useCollapseDrawer(); + + return ( + + + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateBtnUpload.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateBtnUpload.tsx new file mode 100644 index 00000000..23df5ed5 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateBtnUpload.tsx @@ -0,0 +1,39 @@ +import { useRef } from "react"; +import { Box, ButtonBase, Typography } from "@mui/material"; +import Iconify from "@/components/Iconify"; + +type Props = { + handleChangeInputProp: (event: any) => void +} + +export default function FormCreateBtnUpload ({handleChangeInputProp}: Props) { + const fileInput = useRef(null); + + return ( + fileInput.current?.click()}> + + + + Upload Result + + + handleChangeInputProp(event.target.files ? event.target.files[0] : {})} + accept="application/pdf" + /> + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateFilesUpload.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateFilesUpload.tsx new file mode 100644 index 00000000..7ab9d7bd --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateFilesUpload.tsx @@ -0,0 +1,25 @@ +import Iconify from "@/components/Iconify"; +import { ArrowBackIosNew, InsertDriveFile } from '@mui/icons-material'; +import { Stack, Typography } from "@mui/material"; + +type Props = { + file: any, + handleRemoveFileProp: () => void, +} + +export default function FormCreateFilesUpload({ file, handleRemoveFileProp }: Props) { + return ( + + + + {file.name ? file.name : '-'} + + {handleRemoveFileProp()}} + sx={{cursor: 'pointer'}} + > + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateListChoose.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateListChoose.tsx new file mode 100644 index 00000000..03c8d0b1 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateListChoose.tsx @@ -0,0 +1,78 @@ +/** + * Core + * ============================================ + */ +import { useEffect, useState } from 'react'; +import { Box, FormControlLabel, Grid, Checkbox, Typography, Card} from '@mui/material'; + +/** + * Components + * ============================================ +*/ +// - Global - +import Label from '@/components/Label'; +// - Local - + +/** + * Icon, Utils, Types, Functions, theme, hook + * ============================================ + */ +import { fDateTimesecond } from '@/utils/formatTime'; +import { MemberListType } from '../Model/Types'; +import palette from '@/theme/palette'; + +/** + * Props + * ===================================================== + */ +type Props = { + data: MemberListType, + ListChoosed: MemberListType[], + handleCheckedProp: (checked: boolean, data: MemberListType) => void, +}; + +export default function FormCreateListChoose({data, ListChoosed, handleCheckedProp}: Props) { + const [isChoosed, setIsChoosed] = useState(false) + + useEffect(() => { + setIsChoosed(false); + + ListChoosed.forEach(list => { + if (list.id == data.id) { + setIsChoosed(true); + } + }) + }, [ListChoosed]) + + return ( + + { + return isChoosed ? palette.light.primary.lighter : palette.light.background.default + } + }}> + + handleCheckedProp(checked, data)} />} + checked={isChoosed} + /> + + + + {data.name} + + + {data.member_id} + + + + + + + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateSearch.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateSearch.tsx new file mode 100644 index 00000000..026b886b --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormCreateSearch.tsx @@ -0,0 +1,70 @@ +/** + * Core + * ============================================ + */ +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { Grid } from '@mui/material'; + +/** + * Components + * ============================================ +*/ +// - Global - +import { FormProvider, RHFTextField } from '@/components/hook-form'; +// - Local - + +/** + * Icon, Utils, Types, Functions + * ============================================ + */ +import { Search } from '@mui/icons-material'; +import { SearchType } from '../Model/Types'; + +type Props = { + onSubmit: (keyword: string) => void, + onEmpty: () => void, +}; + +const FormCreateSearch = ({ onSubmit, onEmpty }: Props) => { + const defaultValuesSearchForm = { + keyword: '' + }; + + const methodsSearchForm = useForm({ + defaultValues: defaultValuesSearchForm + }); + + const { handleSubmit, formState: { isDirty } } = methodsSearchForm; + + // search on submit + const onSubmitSearch = (data: SearchType ) => { + onSubmit(data.keyword); + } + + // search on empty + useEffect(() => { + if (isDirty === false) { + onEmpty() + } + },[isDirty]) + + return ( + + + + }} + sx={{ input: { paddingLeft: '14px' } }} + /> + + + + ) +} + +export default FormCreateSearch diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormEdit.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormEdit.tsx new file mode 100644 index 00000000..faa95bf0 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/FormEdit.tsx @@ -0,0 +1,456 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Controller, useForm } from 'react-hook-form'; +import React, { useRef, useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { FormProvider, RHFTextField } from '../../../components/hook-form'; + +import { makeFormData } from '@/utils/jsonToFormData'; +import { + Autocomplete, + Button, + Grid, + Stack, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + useTheme, + List, + ListItem, + IconButton, + ListItemAvatar, + Avatar, + ListItemText, + Card, + InputAdornment, + Divider, + ButtonBase, + Box, +} from '@mui/material'; +import Iconify from '../../../components/Iconify'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import { LoadingButton } from '@mui/lab'; +import { fCurrency } from '../../../utils/formatNumber'; +import MemberSelectDialog from '../../../components/dialogs/MemberSelectDialog'; +import { Add, ArrowBackIosNew, DeleteOutline } from '@mui/icons-material'; +import { ClaimRequest, Files } from '@/@types/claims'; +import { fDateTimesecond } from '@/utils/formatTime'; + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentClaim?: ClaimRequest; +}; + +export default function FormEdit({ isEdit, currentClaim }: Props) { + const navigate = useNavigate(); + + const { enqueueSnackbar } = useSnackbar(); + + const EditClaimSchema = Yup.object().shape({ + organization_id: Yup.string().required('Code Provider is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentClaim?.id || '-', + code: currentClaim?.code || '-', + member_name: currentClaim?.member?.name || '-', + date: currentClaim?.submission_date ? fDateTimesecond(currentClaim?.submission_date) : '-', + claim_method: currentClaim?.payment_type || '-', + service_type: currentClaim?.service_code || '-', + organization_id: currentClaim?.organization?.code || '-', + }), + [currentClaim] + ); + + useEffect(() => { + if (isEdit && currentClaim) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + // setFileKondisis(currentClaim?.files_by_type?.claim_diagnosis); + // setFileDiagnosas(currentClaim?.files_by_type?.claim_diagnosis); + setFileHasilPenunjangCurrent(currentClaim?.files_by_type?.claim_result); + }, [isEdit, currentClaim]); + + + const methods = useForm({ + resolver: yupResolver(EditClaimSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + const [isCheckingLimit, setIsCheckingLimit] = useState(false); + const [isEligible, setIsEligible] = useState(false); + const [memberBenefits, setMemberBenefits] = useState([]); + const [diagnosisOption, setDiagnosisOption] = useState([]); + const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); + const [member, setMember] = useState({}) + + // ---------------------------------------------------------------------- + + // Files Result Kondisi + const fileKondisiInput = useRef(null); + const [fileKondisis, setFileKondisis] = useState([]); + + const handleKondisiInputChange = (event) => { + if (event.target.files[0]) { + setFileKondisis([...fileKondisis, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeKondisiFiles = (filesState, index) => { + setFileKondisis( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + // Files Result Diagnosa + const fileDiagnosaInput = useRef(null); + const [fileDiagnosas, setFileDiagnosas] = useState([]); + + const handleDiagnosaInputChange = (event) => { + if (event.target.files[0]) { + setFileDiagnosas([...fileDiagnosas, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeDiagnosaFiles = (filesState, index) => { + setFileDiagnosas( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + // Files Result Hasil Penunjang + const fileHasilPenunjangInput = useRef(null); + const [fileHasilPenunjangs, setFileHasilPenunjangs] = useState([]); + const [fileHasilPenunjangsCurrent, setFileHasilPenunjangCurrent] = useState([]); + + const handleResultInputChange = (event) => { + if (event.target.files[0]) { + setFileHasilPenunjangs([...fileHasilPenunjangs, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeFiles = (filesState, index) => { + setFileHasilPenunjangs( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + + const onSubmit = async (data: FormValuesProps) => { + try { + // const formData = new FormData(); + // formData.append('result_files', fileHasilPenunjangs); + // formData.append('diagnosa_files', fileDiagnosaInput); + // formData.append('kondisi_files', fileKondisiInput); + // formData.append('provider_code', data.organization_id); + // formData.append('_method', 'PUT'); + const formData = makeFormData({ + result_files: fileHasilPenunjangs, + diagnosa_files: fileDiagnosas, + kondisi_files: fileKondisis, + provider_code: data.organization_id, + _method: 'PUT' + }); + + const response = await axios.put(`/claim-requests/${data.id}`, formData); + + reset(); + enqueueSnackbar('Claim Request Updated Successfully!', { variant: 'success' }); + navigate('/claim-requests'); + } catch (error: any) { + if (error && error.response.status === 422) { + for (const [key, value] of Object.entries(error.response.data.errors)) { + // setError(key, { message: value[0] }); + enqueueSnackbar('Failed Processing Request', { variant: 'error' }); + } + } else { + enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); + } + } + }; + + + return ( + + + + navigate(`/claim-requests`)} > + + + + + {'Edit Claim Requests'} + + + + + + + + Code* + + + Name* + + + + + + + + {/* */} + + + + + + Date of Submission* + + + Claim Method* + + + Service Type* + + + Code Provider* + + + + + + + + ), }} + name="date" label="Date of Submission" disabled/> + + + + + + + + + + + + + {/* -------------------------------Upload Dokumen Kondisi------------------------------- */} + + + Condition Document + + + {fileKondisis && + fileKondisis.map((file, index) => ( + + {file.name} + { + removeKondisiFiles(fileKondisis, index); + }} + > + + ))} + + + fileKondisiInput.current?.click()}> + + + + Add File + + + + + + + {/* -------------------------------Upload Dokumen Diagnosa------------------------------- */} + + + Diagnosis Document + + + {fileDiagnosas && + fileDiagnosas.map((file, index) => ( + + {file.name} + { + removeDiagnosaFiles(fileDiagnosas, index); + }} + > + + ))} + + + fileDiagnosaInput.current?.click()}> + + + + Add Result + + + + + + + {/* -------------------------------Upload Result Hasil Penunjang------------------------------- */} + + + Supporting Result Document + + + {fileHasilPenunjangs && + fileHasilPenunjangs.map((file, index) => ( + + {file.name} + { + removeFiles(fileHasilPenunjangs, index); + }} + > + + ))} + + + fileHasilPenunjangInput.current?.click()}> + + + + Add Result + + + + + + + + + + + + + + + Update + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/CreateUpdate.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/CreateUpdate.tsx new file mode 100644 index 00000000..c710deaa --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/CreateUpdate.tsx @@ -0,0 +1,63 @@ +import * as Yup from 'yup'; +import { Box, IconButton } from '@mui/material'; +import { ArrowBackIosNew } from '@mui/icons-material'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Autocomplete, Button, Card, Collapse, Container, Divider, Grid, Stack, Table, TableBody, TableCell, TableRow, TextField, Typography } from '@mui/material'; +import { Controller, useForm } from 'react-hook-form'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs'; +import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form'; +import Page from '../../components/Page'; +import useSettings from '../../hooks/useSettings'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog'; +import { styled } from '@mui/system'; +import axios from '../../utils/axios'; +import { enqueueSnackbar } from 'notistack'; +import { LoadingButton } from '@mui/lab'; +import { fCurrency } from '../../utils/formatNumber'; +import Iconify from '../../components/Iconify'; +import { ClaimRequest } from '@/@types/claims'; +import FormEdit from './Components/FormEdit'; +import FormCreate from './Components/FormCreate'; + +export default function ClaimsCreateUpdate() { + + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentClaim, setCurrentClaim] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/claim-requests/' + id).then((res) => { + console.log('Yeet', res.data); + setCurrentClaim(res.data.data); + }); + + console.log(currentClaim) + } + }, [id]); + + + return ( + + + { + id == undefined + ? + ( + + ) + : + ( + + ) + } + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx new file mode 100644 index 00000000..39aa66ab --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx @@ -0,0 +1,306 @@ +// mui +import { Container, Grid, Stack, Typography, Card, TextField, Divider, ButtonBase, Box, IconButton } from '@mui/material'; +// components +import Page from '../../components/Page'; +// utils +import useSettings from '../../hooks/useSettings'; +// react +import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useEffect, useState, useRef } from 'react'; +import axios from '../../utils/axios'; +// pages +import DetailTimeline from '../../pages/ClaimRequests/DetailTimeline'; +import DetailStepper from '../../pages/ClaimRequests/DetailStepper'; +import { format } from 'date-fns'; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; +import Button from '@mui/material/Button'; +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; +import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; +import Iconify from '@/components/Iconify'; +import { fPostFormat } from '@/utils/formatTime'; +import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import DownloadIcon from '@mui/icons-material/Download'; +import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { makeFormData } from '@/utils/jsonToFormData'; + +import { enqueueSnackbar } from 'notistack'; + +// ---------------------------------------------------------------------- + +export default function Detail() { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + const code = queryParams.get('code'); + + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const [data, setData] = useState(); + const [dataDialog, setDataDialog] = useState(); + const [document, setDocument] = useState(null); + + const { id } = useParams(); + + useEffect(() => { + axios + .get('/claim-requests/detail/'+id) + .then((response) => { + setData(response.data); + setDataDialog(response.data.data.dialog_submits); + setDocument(response.data.data.documents); + + }) + .catch((error) => { + console.error(error); + }); + + }, []); + + const [isInvoiceVisible, setInvoiceVisibility] = useState(false); + + const handleInvoice = () => { + setInvoiceVisibility(!isInvoiceVisible); + } + const currentDate = new Date(); + const formattedCurrentDate = format(currentDate, 'dd MMM yyyy'); + const [dateInvoice, setDateInvoice] = useState(currentDate); + + const fileInvoiceInput = useRef(null); + const [fileInvoices, setFileInvoices] = useState([]); + + const handleInvoiceInputChange = (event) => { + if (event.target.files[0]) { + setFileInvoices([...fileInvoices, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeInvoiceFiles = (filesState, index) => { + setFileInvoices( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + const date = dateInvoice ? fPostFormat(dateInvoice, 'yyyy-MM-dd') : null; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + const handleSubmitData = () => { + // if(fileInvoices.length > 0) + // { + //submit data + axios + .post('claim-requests/'+id+'/approve') + .then((response) => { + enqueueSnackbar('Success Submit Claim Request', { variant: 'success' }); + setOpenDialogSubmit(false); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' }); + }); + //Upload file invoices + const formData = makeFormData({ + date:date, + invoice_files: fileInvoices, + }); + axios + .post('claim-requests/'+id+'/invoice-files', formData) + .then((response) => { + enqueueSnackbar(response.data.message ?? 'Success upload invoice', { variant: 'success' }); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something Went Wrong', { variant: 'error' }); + }); + // } + // else + // { + // enqueueSnackbar('Please upload file invoice, before submit', { variant: 'warning' }); + // } + + setTimeout(() => + { + window.location.reload(); + }, 5000); + + }; + + const check_invoice = document?.find((dataInvoice) => dataInvoice.type === 'claim-invoice'); + + return ( + + + + navigate(-1)} sx={{cursor:'pointer'}}/> + {(data && data.data) ? data.data.status.code : ''} + {data ? ( + + Submission Date + {(data && data.data) ? format(new Date(data.data.status.submission_date), "d MMM yyyy") : ''} + + ) : ''} + + {data ? ( + + + + + + + Format Claim + + + + {check_invoice ? ( + + + Request Claim + + + + ) : ''} + + + + + { + setDateInvoice(newValue); + }} + inputFormat="dd MMM yyyy" + renderInput={(params) => } + /> + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileInvoices && + fileInvoices.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeInvoiceFiles(fileInvoices, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileInvoiceInput.current?.click()}> + + + + Upload Invoice + + + + + + + + + + + + + {dataDialog && dataDialog.status === 'requested' ? ( + <> + + + + ) : ''} + {/* Dialog Submits */} + + + + + Confirmation + + + + + + + + {dataDialog ? ( + + Are you sure to submit this claim ? + + + Code + {dataDialog.code} + + + Name + {dataDialog.name} + + + Date Submission + {fDateTimesecond(dataDialog.submission_date)} + + + Claim Method + Service Type + + + Service Type + + {dataDialog.service_code === 'IP' ? 'Inpatient' : 'Outpatient'} + + + + + ) : ''} + + + + + + + + + + ) : ''} + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/DetailStepper.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/DetailStepper.tsx new file mode 100644 index 00000000..b788e29f --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/DetailStepper.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stepper from '@mui/material/Stepper'; +import Step from '@mui/material/Step'; +import StepLabel from '@mui/material/StepLabel'; +import { useEffect, useState } from 'react'; +import ClearIcon from '@mui/icons-material/Clear'; + +const steps = [ + 'Request', + 'Review', + 'Approval', + 'Decline', + ]; + + export default function HorizontalLinearAlternativeLabelStepper({data}) { + const [active, setActive] = useState(0); + const [status, SetStatus] = useState(null); + let updatedSteps = [...steps]; + useEffect(() => { + if (data && data.data) { + if (data.data.status.status === 'requested') { + setActive(1); + updatedSteps = updatedSteps.filter(step => step !== 'Decline'); + } + else if (data.data.status.status === 'reviewed') { + setActive(2); + updatedSteps = updatedSteps.filter(step => step !== 'Decline'); + } + else if (data.data.status.status === 'approved') + { + setActive(3); + updatedSteps = updatedSteps.filter(step => step !== 'Decline'); + } + else if(data.data.status.status === 'declined') + { + setActive(4) + updatedSteps = updatedSteps.filter(step => step !== 'Approval'); + } + } + SetStatus(updatedSteps); + }, [data]); + + + + + return ( + + + {status?.map((label) => ( + + : ''}>{label} + + ))} + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/DetailTimeline.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/DetailTimeline.tsx new file mode 100644 index 00000000..f62a706d --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/DetailTimeline.tsx @@ -0,0 +1,426 @@ +import * as React from 'react'; +import Timeline from '@mui/lab/Timeline'; +import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; +import TimelineSeparator from '@mui/lab/TimelineSeparator'; +import TimelineConnector from '@mui/lab/TimelineConnector'; +import TimelineContent from '@mui/lab/TimelineContent'; +import TimelineDot from '@mui/lab/TimelineDot'; +import {Typography, Card, Stack, ButtonBase, Box, Divider} from '@mui/material'; +import { styled } from '@mui/material/styles'; +import Paper from '@mui/material/Paper'; +import Button from '@mui/material/Button'; +import AddIcon from '@mui/icons-material/Add'; +import Iconify from '../../components/Iconify'; +import { useEffect, useState, useRef } from 'react'; +import { format } from 'date-fns'; +import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import DescriptionIcon from '@mui/icons-material/Description'; +import { LoadingButton } from '@mui/lab'; +import axios from '../../utils/axios'; +import { makeFormData } from '@/utils/jsonToFormData'; +import { enqueueSnackbar } from 'notistack'; +import { useParams} from 'react-router-dom'; + +const Item1 = styled(Paper)(({ theme }) => ({ + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + backgroundColor: '#919EAB29', + color: '#637381', + width: 'fit-content', + marginRight: 'auto', +})); + +const Item2 = styled(Paper)(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, + width: 'fit-content', + marginLeft: 'auto', +})); + +export default function NoOppositeContent({data}) { + const [timeline, setTimeline] = useState(null); + const [requestFile, setRequestFile] = useState(null); + const [document, setDocument] = useState(null); + useEffect(() => { + if (data && data.data) { + setTimeline(data.data.timeline); + setRequestFile(data.data.request_files); + setDocument(data.data.documents); + } + + }, [data]); + + // Diagnosis + const fileRequestDocumentInputDiagnosis = useRef(null); + const [fileDiagnosis, setFileDiagnosis] = useState([]); + const handleRequestDocumentInputChangeDiagnosis = (event) => { + if (event.target.files[0]) { + setFileDiagnosis([...fileDiagnosis, ...event.target.files]); + } + }; + const removeFileDiagnois = (filesState, index) => { + setFileDiagnosis( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + // Kondisi + const fileRequestDocumentInputKondisi = useRef(null); + const [fileKondisi, setFileKondisi] = useState([]); + const handleRequestDocumentInputChangeKondisi = (event) => { + if (event.target.files[0]) { + setFileKondisi([...fileKondisi, ...event.target.files]); + } + }; + const removeFileKondisi = (filesState, index) => { + setFileKondisi( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + // Result + const fileRequestDocumentInputResult = useRef(null); + const [fileResult, setFileResult] = useState([]); + const handleRequestDocumentInputChangeResult = (event) => { + if (event.target.files[0]) { + setFileResult([...fileResult, ...event.target.files]); + } + }; + const removeFileResult = (filesState, index) => { + setFileResult( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + const { id } = useParams(); + const [submitLoading, setSubmitLoading] = useState(false); + const submitRequestFiles = () => { + setSubmitLoading(true); + const formData = makeFormData({ + fileDiagnosis: fileDiagnosis, + fileKondisis: fileKondisi, + fileResults: fileResult + }); + axios + .post('claim-requests/'+id+'/request-files', formData) + .then((response) => { + window.location.reload(); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something Went Wrong', { variant: 'error' }); + }); + } + const submitButton = requestFile?.find((dataRequestFile) => dataRequestFile.check_files === null); + return ( + <> + {timeline?.map((dataTimeline, index) => ( + + {dataTimeline.date ? format(new Date(dataTimeline.date), "d MMM yyyy") : ''} + + + + + + + + + + {dataTimeline.date ? format(new Date(dataTimeline.date), "HH : mm") : ''} + {dataTimeline.txt_status} + + + Detail: + {dataTimeline.description} + + {dataTimeline.status === 'reviewed' && requestFile ? ( + <> + {submitButton ? ( + Request Document + ) : ( + Request Document Success Uploaded + )} + {/* Diagnosis */} + {requestFile?.map((dataRequestFile, index) => { + if(dataRequestFile.type !== 'claim-diagnosis' || dataRequestFile.check_files !== null){ + return null; + } + return ( + + + Diagnosis + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileDiagnosis && + fileDiagnosis.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeFileDiagnois(fileDiagnosis, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileRequestDocumentInputDiagnosis.current?.click()} + > + + + + Add Result + + + handleRequestDocumentInputChangeDiagnosis(event)} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain, application/pdf" + /> + + + ); + })} + {/* Kondisi */} + {requestFile?.map((dataRequestFile, index) => { + if(dataRequestFile.type !== 'claim-kondisi' || dataRequestFile.check_files !== null){ + return null; + } + return ( + + + Condition + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileKondisi && + fileKondisi.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeFileKondisi(fileKondisi, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileRequestDocumentInputKondisi.current?.click()} + > + + + + Add Result + + + handleRequestDocumentInputChangeKondisi(event)} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain, application/pdf" + /> + + + ); + })} + {/* Supporting Result */} + {requestFile?.map((dataRequestFile, index) => { + if(dataRequestFile.type !== 'claim-result' || dataRequestFile.check_files !== null){ + return null; + } + return ( + + + Supporting Result + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileResult && + fileResult.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeFileResult(fileResult, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileRequestDocumentInputResult.current?.click()} + > + + + + Add Result + + + handleRequestDocumentInputChangeResult(event)} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain, application/pdf" + /> + + + ); + })} + {submitButton ? ( + { + submitRequestFiles(); + }} + loading={submitLoading} + > + Submit + + ) : ''} + + ) : ''} + + + {dataTimeline.status === 'requested' ? ( + + + + + Documents + + + {document?.map((dataDocument, index) => ( + + + {dataDocument.type === 'claim-diagnosis' ? + 'Diagnosis' + : dataDocument.type === 'claim-kondisi' ? + 'Condition' + : dataDocument.type === 'claim-result' ? + 'Supporting Result' + : dataDocument.type === 'claim-invoice' ? + 'Invoice' + : ''} + + + + + {dataDocument.original_name ? dataDocument.original_name : '-'} + + + + ))} + + + + ) : ''} + + + + ))} + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Index.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Index.tsx new file mode 100644 index 00000000..d26903b0 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import List from "./List"; + + + +export default function Claims() { + + const pageTitle = 'Claim Request'; + return ( + + + + + {/* */} + + {/* */} + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/List.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/List.tsx new file mode 100644 index 00000000..24645d01 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/List.tsx @@ -0,0 +1,573 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Link, + Chip, + TableHead, + Grid, +} 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'; + +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +// hooks +import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; +import useSettings from '@/hooks/useSettings'; +// 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 { LoadingButton } from '@mui/lab'; +import { enqueueSnackbar } from 'notistack'; +import { Divider } from '@mui/material'; +import Iconify from '@/components/Iconify'; +import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { capitalizeFirstLetter } from '@/utils/formatString'; +import Label from '@/components/Label'; +import TableMoreMenu from '@/components/table/TableMoreMenu'; +import { Import } from '@/@types/claims'; +// import LoadingButton from '@/theme/overrides/LoadingButton'; + +export default function List() { + const { themeColorPresets } = useSettings(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + const navigate = useNavigate() + + function SearchInput(props: any) { + // SEARCH + 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({ search: searchText }); // Trigger to Parent + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + + 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 handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]); + + setImportLoading(true); + axios + .post(`claim-requests/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + setImportLoading(false); + }) + .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 = (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 handleGetData = (type :string) => { + axios.get(`corporates/${corporate_id}/data-plan-benefit`) + .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(); + }) + } + + return ( +
+ + {!currentImportFileName && ( + + + + + Import + {handleGetTemplate('claim-request')}}>Download Template + {handleGetData('data-plan-benefit')}}>Download Claim Request + + + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )} + {importResult && ( + + + Last Import Result Report :{' '} + + {importResult.result_file?.name ?? '-'} + + + + )} +
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/customer-service/request?status=approved', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + }; + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; + + const handleApprove = (claimRequest) => { + axios + .post(`claim-requests/${claimRequest.id}/approve`) + .then((response) => { + enqueueSnackbar('Success Approve', { variant: 'success' }); + loadDataTableData(); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' }); + }); + }; + + useEffect(() => { + loadDataTableData(); + }, []); + + const headStyle = { + fontWeight: 'bold', + }; + + // Called on every row to map the data to the columns + function createData(data: any): any { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + const [loadingApprove, setLoadingApprove] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */ } + + { + // handleShowClaim(row); + // }} + > + {row.code} + + + {row.member?.full_name} + + {row.service_name} + {row.payment_type_name} + + { row.status == "requested" ? + () : + () + } + + + + navigate(`/claim-requests/edit/${row.id}`)}> + + Edit + + navigate ('/claim-requests/detail/'+row.id+'')}> + + Detail + + + } /> + + {/* + + { + handleShowClaim(row); + }} + > + + + */} + + {/* COLLAPSIBLE ROW */} + + + + + } + spacing={1} + sx={{ marginY: 2 }} + > + + Berkas Hasil Penunjang + {/* {row.files_by_type?.claim_kondisi && + row.files_by_type?.claim_kondisi.map((file, index) => ( + + -{' '} + + {file.name} + + + ))} */} + + {row.files_by_type?.claim_kondisi && ( + <> + - Kondisi + {row.files_by_type?.claim_kondisi.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + + {row.files_by_type?.claim_diagnosis && ( + <> + - Diagnosa + {row.files_by_type?.claim_diagnosis.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + + {row.files_by_type?.claim_result && ( + <> + - Hasil + {row.files_by_type?.claim_result.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + {(!row.files_by_type?.claim_result && !row.files_by_type?.claim_diagnosis && !row.files_by_type?.claim_kondisi)&& Tidak ada berkas} + + + + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {/* */} + + Code + + + Name + + + Date of Submission + + + Service Type + + + Claim Method + + + Status + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + // --------------------------------------------------------- + // Dialog Detail Claim Request + const [openDialogDetailClaim, setOpenDialogDetailClaim] = useState(false); + const [loadingClaimDetail, setLoadingClaimDetail] = useState(true); + const [currentClaim, setCurrentClaim] = useState(null); + + function handleShowClaim(claimRequest) { + setLoadingClaimDetail(true); + setOpenDialogDetailClaim(true); + + axios + .get(`/claim-requests/${claimRequest.id}`) + .then(({ data }) => { + setCurrentClaim(data.data); + setLoadingClaimDetail(false); + }) + .catch((err) => { + enqueueSnackbar(err.message, { variant: 'error' }); + }); + } + + function handleDownloadLog() {} + + return ( + + + + + + + } + /> + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Functions.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Functions.tsx new file mode 100644 index 00000000..da72ff60 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Functions.tsx @@ -0,0 +1,79 @@ +import axios from '@/utils/axios'; +import { enqueueSnackbar } from 'notistack'; +import { MemberListType } from './Types'; +import { makeFormData } from '@/utils/jsonToFormData'; + +/** + * Listing Member + */ +export const getMemberList = async ( page: number, keyword: string ): Promise => { + const response = await axios.get(`/claim-requests/list-member?page=${page}&keyword=${keyword}`) + .then((res) =>{ + return res.data.data.member_list; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return []; + }); + + return response; +}; + +/** + * Add Claim Request + */ +export const addClaimRequest = async ( data: MemberListType[] ): Promise => { + // Mapping + const formData = new FormData(); + + data.map((row, index) => { + formData.append(`member_id[${index}]`, row.id.toString()); + formData.append(`service_code[${index}]`, row.patien_type??''); + + if (row.file_kondisi != undefined) { + row.file_kondisi.forEach((file, file_index) => { + console.log(file); + + formData.append(`file_kondisi[member_${row.id}][${file_index}]`, file); + }); + } + + if (row.file_diagnosa != undefined) { + row.file_diagnosa.forEach((file, file_index) => { + console.log(file); + + formData.append(`file_diagnosa[member_${row.id}][${file_index}]`, file); + }); + } + + if (row.file_penunjang != undefined) { + row.file_penunjang.forEach((file, file_index) => { + console.log(file); + + formData.append(`file_penunjang[member_${row.id}][${file_index}]`, file); + }); + } + }) + + // Axios + const response = await axios.post(`/claim-requests`, formData) + .then((res) =>{ + enqueueSnackbar("Berhasil membuat data !", { + variant: 'success', + }); + + return true; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return false; + }); + + return response; +}; diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx new file mode 100644 index 00000000..81b7da49 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx @@ -0,0 +1,25 @@ +/** + * Search Type + */ +export type SearchType = { + keyword: string, +} + +/** + * Member List + */ +export type MemberListType = { + id : string, + member_id : string, + name : string, + service_type : ServiceType[], + patien_type? : string, + file_kondisi? : any[], + file_diagnosa? : any[], + file_penunjang? : any[], +} + +export type ServiceType = { + code : string + name : string +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreate.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreate.tsx new file mode 100644 index 00000000..d952f5e3 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreate.tsx @@ -0,0 +1,355 @@ +/** + * Core + * ============================================ + */ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; +import { Box, FormControlLabel, Grid, Checkbox, Typography, CircularProgress , Button, styled, Stack, IconButton, Card} from '@mui/material'; +import { LoadingButton } from '@mui/lab'; + +/** + * Components + * ============================================ +*/ +// - Global - +import Label from '@/components/Label'; +// - Local - +import FormCreateSearch from './FormCreateSearch'; +import FormCreateListChoose from './FormCreateListChoose'; +import FormCreateBtnUpload from './FormCreateBtnUpload'; + +/** + * Icon, Utils, Types, Functions, theme, hook + * ============================================ + */ +import { ArrowBackIosNew } from '@mui/icons-material'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { MemberListType } from '../Model/Types'; +import { addClaimRequest, getMemberList } from '../Model/Functions'; +import palette from '@/theme/palette'; +import FormCreateFilesUpload from './FormCreateFilesUpload'; +import useLoadOnScroll from '@/hooks/useLoadOnScroll'; +import useCollapseDrawer from '@/hooks/useCollapseDrawer'; +import FormCreateBtnChoose from './FormCreateBtnChoose'; +import axios from '../../../utils/axios'; + +export default function FormCreate() { + const navigate = useNavigate() + const defaultListChoosed:MemberListType[] = []; + + // State + // ------------------------- + const [keyword, setKeyword] = useState(''); + const [listChoosed, setListChoosed] = useState([]); + const [isChoosed, setIsChoosed] = useState(false); + const [formIsLoading, setFormIsLoading] = useState(false); + + // List Choose - auto Scroll + // ------------------------- + const fetchFunction = async (page: number): Promise => getMemberList(page, keyword) + + const {data: MemberList, isLoading: scrollIsLoading, setData, resetLastPage, refetchData} = useLoadOnScroll(fetchFunction); + + // List Choose - Search + // ------------------------- + const handleSearch = (keyword: string) => { + setData([]) + resetLastPage() + setKeyword(keyword) + refetchData() + } + + // Function - Clear Form + // ----------------------------- + const clearForm = () => { + setListChoosed(defaultListChoosed); + setIsChoosed(false); + } + + // Function - Choose Patien Type + // ----------------------------- + const handleChoosePatienType = (data: MemberListType, type: string) => { + let newListChoosed = listChoosed.map((list) => { + if (data.id == list.id) { + list.patien_type = type + } + + return list; + }) + setListChoosed(newListChoosed) + } + + // Function - Handle Btn Upload + // ----------------------------- + const handleChangeInput = (data: MemberListType, type_file: 'kondisi'|'diagnosa'|'penunjang', file: any) => { + let newListChoosed = listChoosed.map((list) => { + if (data.id == list.id) { + if (type_file == 'kondisi') { + if (list.file_kondisi == undefined) { + list.file_kondisi = [file]; + } + else { + list.file_kondisi.push(file); + } + } + + if (type_file == 'diagnosa') { + if (list.file_diagnosa == undefined) { + list.file_diagnosa = [file]; + } + else { + list.file_diagnosa.push(file); + } + } + + if (type_file == 'penunjang') { + if (list.file_penunjang == undefined) { + list.file_penunjang = [file]; + } + else { + list.file_penunjang.push(file); + } + } + } + + return list; + }) + + setListChoosed(newListChoosed) + } + + // Function - Handle Remove Fle + // ----------------------------- + const handleRemoveFile = (data: MemberListType, type_file: 'kondisi'|'diagnosa'|'penunjang', target_index: number) => { + let newListChoosed = listChoosed.map((list) => { + if (data.id == list.id) { + if (type_file == 'kondisi') { + list.file_kondisi = list.file_kondisi?.filter((file: any, index: number) =>{ + if (target_index !== index) { + return file; + } + }); + } + + if (type_file == 'diagnosa') { + list.file_diagnosa = list.file_diagnosa?.filter((file: any, index: number) =>{ + if (target_index !== index) { + return file; + } + }); + } + + if (type_file == 'penunjang') { + list.file_penunjang = list.file_penunjang?.filter((file: any, index: number) =>{ + if (target_index !== index) { + return file; + } + }); + } + } + + return list; + }) + + setListChoosed(newListChoosed) + } + + // Function - Handle Submit Form + // ----------------------------- + const handleSubmit = async () => { + setFormIsLoading(true) + let response = await addClaimRequest(listChoosed) + setFormIsLoading(false) + + if (response == true) { + clearForm() + } + } + + + + let isDirty = listChoosed.some((row) => { + if (row.patien_type == undefined) { + return true + } + }) + + return ( + + {/* Back Button */} + + isChoosed==false ? navigate(`/claim-requests`) : setIsChoosed(false)} > + + + + + {'Create Claim Requests'} + + + + {/* Choose Section */} + + {/* Search */} + + handleSearch('')} onSubmit={(keyword) => handleSearch(keyword)} /> + + + + + {/* List */} + + + { + MemberList.map((row, index) => { + return ( + { + checked ? setListChoosed((prevData) => [...prevData, data]) : setListChoosed((items) => items.filter(item => item.id != data.id)) + }} + /> + ) + }) + } + + + + {/* Loading */} + + + + + {/* Submit List */} + + setIsChoosed(true)} /> + + + + + + {/* Input Section */} + + { + listChoosed.map((row, index) => { + return ( + + + {/* Patien Name */} + + + + + {row.name} + + + {row.member_id} + + + + + + + {/* Patien Type */} + + + {row.service_type.map((r,i) => { + const code = r.code + return ( + + + + ) + })} + + + + {/* File Kondisi */} + + + + Condition Document + + + {row.file_kondisi && row.file_kondisi.map((file, index) => ( + + handleRemoveFile(row, 'kondisi', index)} /> + + ))} + + + handleChangeInput(row, 'kondisi', file)} /> + + + + + {/* File Diagnosa */} + + + + Diagnosis Document + + + {row.file_diagnosa && row.file_diagnosa.map((file, index) => ( + + handleRemoveFile(row, 'diagnosa', index)} /> + + ))} + + + handleChangeInput(row, 'diagnosa', file)} /> + + + + + {/* File Penunjang */} + + + + Supporting Result Document + + + {row.file_penunjang && row.file_penunjang.map((file, index) => ( + + handleRemoveFile(row, 'penunjang', index)} /> + + ))} + + + handleChangeInput(row, 'penunjang', file)} /> + + + + + + + ) + }) + } + + + + + handleSubmit()}> + Save Changes + + + + + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateBtnChoose.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateBtnChoose.tsx new file mode 100644 index 00000000..cffd3bc3 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateBtnChoose.tsx @@ -0,0 +1,39 @@ +import { styled, Button } from "@mui/material"; +import useCollapseDrawer from "@/hooks/useCollapseDrawer"; + +/** + * Custom Style + * ============================================ +*/ +const DivCustom1 = styled('div')(({ theme }) => ({ + background: 'white', + position: 'fixed', + left: '350px', + right: 0, + bottom: 0, + paddingLeft: '32px', + paddingRight: '32px', + paddingTop: '32px', + paddingBottom: '48px', + [theme.breakpoints.between('sm', 'lg')]: { + left: '0px', + }, +})); + +type Props = { + disabled: boolean, + title : string, + handleClickProp: () => void +} + +export default function FormCreateBtnChoose ({disabled, title, handleClickProp}: Props) { + const { collapseClick } = useCollapseDrawer(); + + return ( + + + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateBtnUpload.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateBtnUpload.tsx new file mode 100644 index 00000000..23df5ed5 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateBtnUpload.tsx @@ -0,0 +1,39 @@ +import { useRef } from "react"; +import { Box, ButtonBase, Typography } from "@mui/material"; +import Iconify from "@/components/Iconify"; + +type Props = { + handleChangeInputProp: (event: any) => void +} + +export default function FormCreateBtnUpload ({handleChangeInputProp}: Props) { + const fileInput = useRef(null); + + return ( + fileInput.current?.click()}> + + + + Upload Result + + + handleChangeInputProp(event.target.files ? event.target.files[0] : {})} + accept="application/pdf" + /> + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateFilesUpload.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateFilesUpload.tsx new file mode 100644 index 00000000..7ab9d7bd --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateFilesUpload.tsx @@ -0,0 +1,25 @@ +import Iconify from "@/components/Iconify"; +import { ArrowBackIosNew, InsertDriveFile } from '@mui/icons-material'; +import { Stack, Typography } from "@mui/material"; + +type Props = { + file: any, + handleRemoveFileProp: () => void, +} + +export default function FormCreateFilesUpload({ file, handleRemoveFileProp }: Props) { + return ( + + + + {file.name ? file.name : '-'} + + {handleRemoveFileProp()}} + sx={{cursor: 'pointer'}} + > + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateListChoose.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateListChoose.tsx new file mode 100644 index 00000000..03c8d0b1 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateListChoose.tsx @@ -0,0 +1,78 @@ +/** + * Core + * ============================================ + */ +import { useEffect, useState } from 'react'; +import { Box, FormControlLabel, Grid, Checkbox, Typography, Card} from '@mui/material'; + +/** + * Components + * ============================================ +*/ +// - Global - +import Label from '@/components/Label'; +// - Local - + +/** + * Icon, Utils, Types, Functions, theme, hook + * ============================================ + */ +import { fDateTimesecond } from '@/utils/formatTime'; +import { MemberListType } from '../Model/Types'; +import palette from '@/theme/palette'; + +/** + * Props + * ===================================================== + */ +type Props = { + data: MemberListType, + ListChoosed: MemberListType[], + handleCheckedProp: (checked: boolean, data: MemberListType) => void, +}; + +export default function FormCreateListChoose({data, ListChoosed, handleCheckedProp}: Props) { + const [isChoosed, setIsChoosed] = useState(false) + + useEffect(() => { + setIsChoosed(false); + + ListChoosed.forEach(list => { + if (list.id == data.id) { + setIsChoosed(true); + } + }) + }, [ListChoosed]) + + return ( + + { + return isChoosed ? palette.light.primary.lighter : palette.light.background.default + } + }}> + + handleCheckedProp(checked, data)} />} + checked={isChoosed} + /> + + + + {data.name} + + + {data.member_id} + + + + + + + + ) +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateSearch.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateSearch.tsx new file mode 100644 index 00000000..026b886b --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormCreateSearch.tsx @@ -0,0 +1,70 @@ +/** + * Core + * ============================================ + */ +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { Grid } from '@mui/material'; + +/** + * Components + * ============================================ +*/ +// - Global - +import { FormProvider, RHFTextField } from '@/components/hook-form'; +// - Local - + +/** + * Icon, Utils, Types, Functions + * ============================================ + */ +import { Search } from '@mui/icons-material'; +import { SearchType } from '../Model/Types'; + +type Props = { + onSubmit: (keyword: string) => void, + onEmpty: () => void, +}; + +const FormCreateSearch = ({ onSubmit, onEmpty }: Props) => { + const defaultValuesSearchForm = { + keyword: '' + }; + + const methodsSearchForm = useForm({ + defaultValues: defaultValuesSearchForm + }); + + const { handleSubmit, formState: { isDirty } } = methodsSearchForm; + + // search on submit + const onSubmitSearch = (data: SearchType ) => { + onSubmit(data.keyword); + } + + // search on empty + useEffect(() => { + if (isDirty === false) { + onEmpty() + } + },[isDirty]) + + return ( + + + + }} + sx={{ input: { paddingLeft: '14px' } }} + /> + + + + ) +} + +export default FormCreateSearch diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Components/FormEdit.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormEdit.tsx new file mode 100644 index 00000000..faa95bf0 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Components/FormEdit.tsx @@ -0,0 +1,456 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Controller, useForm } from 'react-hook-form'; +import React, { useRef, useEffect, useMemo, useState } from 'react'; +import axios from '../../../utils/axios'; +import { FormProvider, RHFTextField } from '../../../components/hook-form'; + +import { makeFormData } from '@/utils/jsonToFormData'; +import { + Autocomplete, + Button, + Grid, + Stack, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + useTheme, + List, + ListItem, + IconButton, + ListItemAvatar, + Avatar, + ListItemText, + Card, + InputAdornment, + Divider, + ButtonBase, + Box, +} from '@mui/material'; +import Iconify from '../../../components/Iconify'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import { LoadingButton } from '@mui/lab'; +import { fCurrency } from '../../../utils/formatNumber'; +import MemberSelectDialog from '../../../components/dialogs/MemberSelectDialog'; +import { Add, ArrowBackIosNew, DeleteOutline } from '@mui/icons-material'; +import { ClaimRequest, Files } from '@/@types/claims'; +import { fDateTimesecond } from '@/utils/formatTime'; + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentClaim?: ClaimRequest; +}; + +export default function FormEdit({ isEdit, currentClaim }: Props) { + const navigate = useNavigate(); + + const { enqueueSnackbar } = useSnackbar(); + + const EditClaimSchema = Yup.object().shape({ + organization_id: Yup.string().required('Code Provider is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentClaim?.id || '-', + code: currentClaim?.code || '-', + member_name: currentClaim?.member?.name || '-', + date: currentClaim?.submission_date ? fDateTimesecond(currentClaim?.submission_date) : '-', + claim_method: currentClaim?.payment_type || '-', + service_type: currentClaim?.service_code || '-', + organization_id: currentClaim?.organization?.code || '-', + }), + [currentClaim] + ); + + useEffect(() => { + if (isEdit && currentClaim) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + // setFileKondisis(currentClaim?.files_by_type?.claim_diagnosis); + // setFileDiagnosas(currentClaim?.files_by_type?.claim_diagnosis); + setFileHasilPenunjangCurrent(currentClaim?.files_by_type?.claim_result); + }, [isEdit, currentClaim]); + + + const methods = useForm({ + resolver: yupResolver(EditClaimSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + const [isCheckingLimit, setIsCheckingLimit] = useState(false); + const [isEligible, setIsEligible] = useState(false); + const [memberBenefits, setMemberBenefits] = useState([]); + const [diagnosisOption, setDiagnosisOption] = useState([]); + const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); + const [member, setMember] = useState({}) + + // ---------------------------------------------------------------------- + + // Files Result Kondisi + const fileKondisiInput = useRef(null); + const [fileKondisis, setFileKondisis] = useState([]); + + const handleKondisiInputChange = (event) => { + if (event.target.files[0]) { + setFileKondisis([...fileKondisis, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeKondisiFiles = (filesState, index) => { + setFileKondisis( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + // Files Result Diagnosa + const fileDiagnosaInput = useRef(null); + const [fileDiagnosas, setFileDiagnosas] = useState([]); + + const handleDiagnosaInputChange = (event) => { + if (event.target.files[0]) { + setFileDiagnosas([...fileDiagnosas, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeDiagnosaFiles = (filesState, index) => { + setFileDiagnosas( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + // Files Result Hasil Penunjang + const fileHasilPenunjangInput = useRef(null); + const [fileHasilPenunjangs, setFileHasilPenunjangs] = useState([]); + const [fileHasilPenunjangsCurrent, setFileHasilPenunjangCurrent] = useState([]); + + const handleResultInputChange = (event) => { + if (event.target.files[0]) { + setFileHasilPenunjangs([...fileHasilPenunjangs, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeFiles = (filesState, index) => { + setFileHasilPenunjangs( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + + + const onSubmit = async (data: FormValuesProps) => { + try { + // const formData = new FormData(); + // formData.append('result_files', fileHasilPenunjangs); + // formData.append('diagnosa_files', fileDiagnosaInput); + // formData.append('kondisi_files', fileKondisiInput); + // formData.append('provider_code', data.organization_id); + // formData.append('_method', 'PUT'); + const formData = makeFormData({ + result_files: fileHasilPenunjangs, + diagnosa_files: fileDiagnosas, + kondisi_files: fileKondisis, + provider_code: data.organization_id, + _method: 'PUT' + }); + + const response = await axios.put(`/claim-requests/${data.id}`, formData); + + reset(); + enqueueSnackbar('Claim Request Updated Successfully!', { variant: 'success' }); + navigate('/claim-requests'); + } catch (error: any) { + if (error && error.response.status === 422) { + for (const [key, value] of Object.entries(error.response.data.errors)) { + // setError(key, { message: value[0] }); + enqueueSnackbar('Failed Processing Request', { variant: 'error' }); + } + } else { + enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); + } + } + }; + + + return ( + + + + navigate(`/claim-requests`)} > + + + + + {'Edit Claim Requests'} + + + + + + + + Code* + + + Name* + + + + + + + + {/* */} + + + + + + Date of Submission* + + + Claim Method* + + + Service Type* + + + Code Provider* + + + + + + + + ), }} + name="date" label="Date of Submission" disabled/> + + + + + + + + + + + + + {/* -------------------------------Upload Dokumen Kondisi------------------------------- */} + + + Condition Document + + + {fileKondisis && + fileKondisis.map((file, index) => ( + + {file.name} + { + removeKondisiFiles(fileKondisis, index); + }} + > + + ))} + + + fileKondisiInput.current?.click()}> + + + + Add File + + + + + + + {/* -------------------------------Upload Dokumen Diagnosa------------------------------- */} + + + Diagnosis Document + + + {fileDiagnosas && + fileDiagnosas.map((file, index) => ( + + {file.name} + { + removeDiagnosaFiles(fileDiagnosas, index); + }} + > + + ))} + + + fileDiagnosaInput.current?.click()}> + + + + Add Result + + + + + + + {/* -------------------------------Upload Result Hasil Penunjang------------------------------- */} + + + Supporting Result Document + + + {fileHasilPenunjangs && + fileHasilPenunjangs.map((file, index) => ( + + {file.name} + { + removeFiles(fileHasilPenunjangs, index); + }} + > + + ))} + + + fileHasilPenunjangInput.current?.click()}> + + + + Add Result + + + + + + + + + + + + + + + Update + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/CreateUpdate.tsx b/frontend/dashboard/src/pages/CustomerService/Request/CreateUpdate.tsx new file mode 100644 index 00000000..c710deaa --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/CreateUpdate.tsx @@ -0,0 +1,63 @@ +import * as Yup from 'yup'; +import { Box, IconButton } from '@mui/material'; +import { ArrowBackIosNew } from '@mui/icons-material'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Autocomplete, Button, Card, Collapse, Container, Divider, Grid, Stack, Table, TableBody, TableCell, TableRow, TextField, Typography } from '@mui/material'; +import { Controller, useForm } from 'react-hook-form'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs'; +import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form'; +import Page from '../../components/Page'; +import useSettings from '../../hooks/useSettings'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog'; +import { styled } from '@mui/system'; +import axios from '../../utils/axios'; +import { enqueueSnackbar } from 'notistack'; +import { LoadingButton } from '@mui/lab'; +import { fCurrency } from '../../utils/formatNumber'; +import Iconify from '../../components/Iconify'; +import { ClaimRequest } from '@/@types/claims'; +import FormEdit from './Components/FormEdit'; +import FormCreate from './Components/FormCreate'; + +export default function ClaimsCreateUpdate() { + + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentClaim, setCurrentClaim] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/claim-requests/' + id).then((res) => { + console.log('Yeet', res.data); + setCurrentClaim(res.data.data); + }); + + console.log(currentClaim) + } + }, [id]); + + + return ( + + + { + id == undefined + ? + ( + + ) + : + ( + + ) + } + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Detail.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Detail.tsx new file mode 100644 index 00000000..77f9e59e --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Detail.tsx @@ -0,0 +1,306 @@ +// mui +import { Container, Grid, Stack, Typography, Card, TextField, Divider, ButtonBase, Box, IconButton } from '@mui/material'; +// components +import Page from '../../../components/Page'; +// utils +import useSettings from '../../../hooks/useSettings'; +// react +import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useEffect, useState, useRef } from 'react'; +import axios from '../../../utils/axios'; +// pages +import DetailTimeline from '../../../pages/ClaimRequests/DetailTimeline'; +import DetailStepper from '../../../pages/ClaimRequests/DetailStepper'; +import { format } from 'date-fns'; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; +import Button from '@mui/material/Button'; +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; +import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; +import Iconify from '@/components/Iconify'; +import { fPostFormat } from '@/utils/formatTime'; +import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import DownloadIcon from '@mui/icons-material/Download'; +import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { makeFormData } from '@/utils/jsonToFormData'; + +import { enqueueSnackbar } from 'notistack'; + +// ---------------------------------------------------------------------- + +export default function Detail() { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + const code = queryParams.get('code'); + + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const [data, setData] = useState(); + const [dataDialog, setDataDialog] = useState(); + const [document, setDocument] = useState(null); + + const { id } = useParams(); + + useEffect(() => { + axios + .get('/claim-requests/detail/'+id) + .then((response) => { + setData(response.data); + setDataDialog(response.data.data.dialog_submits); + setDocument(response.data.data.documents); + + }) + .catch((error) => { + console.error(error); + }); + + }, []); + + const [isInvoiceVisible, setInvoiceVisibility] = useState(false); + + const handleInvoice = () => { + setInvoiceVisibility(!isInvoiceVisible); + } + const currentDate = new Date(); + const formattedCurrentDate = format(currentDate, 'dd MMM yyyy'); + const [dateInvoice, setDateInvoice] = useState(currentDate); + + const fileInvoiceInput = useRef(null); + const [fileInvoices, setFileInvoices] = useState([]); + + const handleInvoiceInputChange = (event) => { + if (event.target.files[0]) { + setFileInvoices([...fileInvoices, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeInvoiceFiles = (filesState, index) => { + setFileInvoices( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + const date = dateInvoice ? fPostFormat(dateInvoice, 'yyyy-MM-dd') : null; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + const handleSubmitData = () => { + // if(fileInvoices.length > 0) + // { + //submit data + axios + .post('claim-requests/'+id+'/approve') + .then((response) => { + enqueueSnackbar('Success Submit Claim Request', { variant: 'success' }); + setOpenDialogSubmit(false); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' }); + }); + //Upload file invoices + const formData = makeFormData({ + date:date, + invoice_files: fileInvoices, + }); + axios + .post('claim-requests/'+id+'/invoice-files', formData) + .then((response) => { + enqueueSnackbar(response.data.message ?? 'Success upload invoice', { variant: 'success' }); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something Went Wrong', { variant: 'error' }); + }); + // } + // else + // { + // enqueueSnackbar('Please upload file invoice, before submit', { variant: 'warning' }); + // } + + setTimeout(() => + { + window.location.reload(); + }, 5000); + + }; + + const check_invoice = document?.find((dataInvoice) => dataInvoice.type === 'claim-invoice'); + + return ( + + + + navigate(-1)} sx={{cursor:'pointer'}}/> + {(data && data.data) ? data.data.status.code : ''} + {data ? ( + + Submission Date + {(data && data.data) ? format(new Date(data.data.status.submission_date), "d MMM yyyy") : ''} + + ) : ''} + + {data ? ( + + + + + + + Format Claim + + + + {check_invoice ? ( + + + Request Claim + + + + ) : ''} + + + + + { + setDateInvoice(newValue); + }} + inputFormat="dd MMM yyyy" + renderInput={(params) => } + /> + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileInvoices && + fileInvoices.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeInvoiceFiles(fileInvoices, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileInvoiceInput.current?.click()}> + + + + Upload Invoice + + + + + + + + + + + + + {dataDialog && dataDialog.status === 'requested' ? ( + <> + + + + ) : ''} + {/* Dialog Submits */} + + + + + Confirmation + + + + + + + + {dataDialog ? ( + + Are you sure to submit this claim ? + + + Code + {dataDialog.code} + + + Name + {dataDialog.name} + + + Date Submission + {fDateTimesecond(dataDialog.submission_date)} + + + Claim Method + Service Type + + + Service Type + + {dataDialog.service_code === 'IP' ? 'Inpatient' : 'Outpatient'} + + + + + ) : ''} + + + + + + + + + + ) : ''} + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/DetailStepper.tsx b/frontend/dashboard/src/pages/CustomerService/Request/DetailStepper.tsx new file mode 100644 index 00000000..b788e29f --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/DetailStepper.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stepper from '@mui/material/Stepper'; +import Step from '@mui/material/Step'; +import StepLabel from '@mui/material/StepLabel'; +import { useEffect, useState } from 'react'; +import ClearIcon from '@mui/icons-material/Clear'; + +const steps = [ + 'Request', + 'Review', + 'Approval', + 'Decline', + ]; + + export default function HorizontalLinearAlternativeLabelStepper({data}) { + const [active, setActive] = useState(0); + const [status, SetStatus] = useState(null); + let updatedSteps = [...steps]; + useEffect(() => { + if (data && data.data) { + if (data.data.status.status === 'requested') { + setActive(1); + updatedSteps = updatedSteps.filter(step => step !== 'Decline'); + } + else if (data.data.status.status === 'reviewed') { + setActive(2); + updatedSteps = updatedSteps.filter(step => step !== 'Decline'); + } + else if (data.data.status.status === 'approved') + { + setActive(3); + updatedSteps = updatedSteps.filter(step => step !== 'Decline'); + } + else if(data.data.status.status === 'declined') + { + setActive(4) + updatedSteps = updatedSteps.filter(step => step !== 'Approval'); + } + } + SetStatus(updatedSteps); + }, [data]); + + + + + return ( + + + {status?.map((label) => ( + + : ''}>{label} + + ))} + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/DetailTimeline.tsx b/frontend/dashboard/src/pages/CustomerService/Request/DetailTimeline.tsx new file mode 100644 index 00000000..8b2d932c --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/DetailTimeline.tsx @@ -0,0 +1,426 @@ +import * as React from 'react'; +import Timeline from '@mui/lab/Timeline'; +import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem'; +import TimelineSeparator from '@mui/lab/TimelineSeparator'; +import TimelineConnector from '@mui/lab/TimelineConnector'; +import TimelineContent from '@mui/lab/TimelineContent'; +import TimelineDot from '@mui/lab/TimelineDot'; +import {Typography, Card, Stack, ButtonBase, Box, Divider} from '@mui/material'; +import { styled } from '@mui/material/styles'; +import Paper from '@mui/material/Paper'; +import Button from '@mui/material/Button'; +import AddIcon from '@mui/icons-material/Add'; +import Iconify from '../../../components/Iconify'; +import { useEffect, useState, useRef } from 'react'; +import { format } from 'date-fns'; +import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import DescriptionIcon from '@mui/icons-material/Description'; +import { LoadingButton } from '@mui/lab'; +import axios from '../../../utils/axios'; +import { makeFormData } from '@/utils/jsonToFormData'; +import { enqueueSnackbar } from 'notistack'; +import { useParams} from 'react-router-dom'; + +const Item1 = styled(Paper)(({ theme }) => ({ + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + backgroundColor: '#919EAB29', + color: '#637381', + width: 'fit-content', + marginRight: 'auto', +})); + +const Item2 = styled(Paper)(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, + width: 'fit-content', + marginLeft: 'auto', +})); + +export default function NoOppositeContent({data}) { + const [timeline, setTimeline] = useState(null); + const [requestFile, setRequestFile] = useState(null); + const [document, setDocument] = useState(null); + useEffect(() => { + if (data && data.data) { + setTimeline(data.data.timeline); + setRequestFile(data.data.request_files); + setDocument(data.data.documents); + } + + }, [data]); + + // Diagnosis + const fileRequestDocumentInputDiagnosis = useRef(null); + const [fileDiagnosis, setFileDiagnosis] = useState([]); + const handleRequestDocumentInputChangeDiagnosis = (event) => { + if (event.target.files[0]) { + setFileDiagnosis([...fileDiagnosis, ...event.target.files]); + } + }; + const removeFileDiagnois = (filesState, index) => { + setFileDiagnosis( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + // Kondisi + const fileRequestDocumentInputKondisi = useRef(null); + const [fileKondisi, setFileKondisi] = useState([]); + const handleRequestDocumentInputChangeKondisi = (event) => { + if (event.target.files[0]) { + setFileKondisi([...fileKondisi, ...event.target.files]); + } + }; + const removeFileKondisi = (filesState, index) => { + setFileKondisi( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + // Result + const fileRequestDocumentInputResult = useRef(null); + const [fileResult, setFileResult] = useState([]); + const handleRequestDocumentInputChangeResult = (event) => { + if (event.target.files[0]) { + setFileResult([...fileResult, ...event.target.files]); + } + }; + const removeFileResult = (filesState, index) => { + setFileResult( + filesState.filter((file, fileIndex) => { + return fileIndex != index; + }) + ); + }; + const { id } = useParams(); + const [submitLoading, setSubmitLoading] = useState(false); + const submitRequestFiles = () => { + setSubmitLoading(true); + const formData = makeFormData({ + fileDiagnosis: fileDiagnosis, + fileKondisis: fileKondisi, + fileResults: fileResult + }); + axios + .post('claim-requests/'+id+'/request-files', formData) + .then((response) => { + window.location.reload(); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something Went Wrong', { variant: 'error' }); + }); + } + const submitButton = requestFile?.find((dataRequestFile) => dataRequestFile.check_files === null); + return ( + <> + {timeline?.map((dataTimeline, index) => ( + + {dataTimeline.date ? format(new Date(dataTimeline.date), "d MMM yyyy") : ''} + + + + + + + + + + {dataTimeline.date ? format(new Date(dataTimeline.date), "HH : mm") : ''} + {dataTimeline.txt_status} + + + Detail: + {dataTimeline.description} + + {dataTimeline.status === 'reviewed' && requestFile ? ( + <> + {submitButton ? ( + Request Document + ) : ( + Request Document Success Uploaded + )} + {/* Diagnosis */} + {requestFile?.map((dataRequestFile, index) => { + if(dataRequestFile.type !== 'claim-diagnosis' || dataRequestFile.check_files !== null){ + return null; + } + return ( + + + Diagnosis + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileDiagnosis && + fileDiagnosis.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeFileDiagnois(fileDiagnosis, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileRequestDocumentInputDiagnosis.current?.click()} + > + + + + Add Result + + + handleRequestDocumentInputChangeDiagnosis(event)} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain, application/pdf" + /> + + + ); + })} + {/* Kondisi */} + {requestFile?.map((dataRequestFile, index) => { + if(dataRequestFile.type !== 'claim-kondisi' || dataRequestFile.check_files !== null){ + return null; + } + return ( + + + Condition + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileKondisi && + fileKondisi.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeFileKondisi(fileKondisi, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileRequestDocumentInputKondisi.current?.click()} + > + + + + Add Result + + + handleRequestDocumentInputChangeKondisi(event)} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain, application/pdf" + /> + + + ); + })} + {/* Supporting Result */} + {requestFile?.map((dataRequestFile, index) => { + if(dataRequestFile.type !== 'claim-result' || dataRequestFile.check_files !== null){ + return null; + } + return ( + + + Supporting Result + + } + spacing={1} + sx={{ marginY: 2 }} + > + {fileResult && + fileResult.map((file, index) => ( + + + + {file.name ? file.name : '-'} + + { + removeFileResult(fileResult, index); + }} + sx={{cursor: 'pointer'}} + > + + ))} + + fileRequestDocumentInputResult.current?.click()} + > + + + + Add Result + + + handleRequestDocumentInputChangeResult(event)} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain, application/pdf" + /> + + + ); + })} + {submitButton ? ( + { + submitRequestFiles(); + }} + loading={submitLoading} + > + Submit + + ) : ''} + + ) : ''} + + + {dataTimeline.status === 'requested' ? ( + + + + + Documents + + + {document?.map((dataDocument, index) => ( + + + {dataDocument.type === 'claim-diagnosis' ? + 'Diagnosis' + : dataDocument.type === 'claim-kondisi' ? + 'Condition' + : dataDocument.type === 'claim-result' ? + 'Supporting Result' + : dataDocument.type === 'claim-invoice' ? + 'Invoice' + : ''} + + + + + {dataDocument.original_name ? dataDocument.original_name : '-'} + + + + ))} + + + + ) : ''} + + + + ))} + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Index.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Index.tsx new file mode 100644 index 00000000..6b1e6fd4 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import List from "./List"; + + + +export default function RequestLog() { + + const pageTitle = 'Request LOG'; + return ( + + + + + {/* */} + + {/* */} + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/List.tsx b/frontend/dashboard/src/pages/CustomerService/Request/List.tsx new file mode 100644 index 00000000..887d67a2 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/List.tsx @@ -0,0 +1,581 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Link, + Chip, + TableHead, + Grid, + SvgIcon, +} 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'; + +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; +import ApprovalIcon from '../../../../build/icons/ic_approval.svg'; + + +// hooks +import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; +import useSettings from '@/hooks/useSettings'; +// 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 { LoadingButton } from '@mui/lab'; +import { enqueueSnackbar } from 'notistack'; +import { Divider } from '@mui/material'; +import Iconify from '@/components/Iconify'; +import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { capitalizeFirstLetter } from '@/utils/formatString'; +import Label from '@/components/Label'; +import TableMoreMenu from '@/components/table/TableMoreMenu'; +import { Import } from '@/@types/claims'; +// import SvgIconStyle from '@/components/SvgIconStyle'; +import SvgIconStyle from '../../../components/SvgIconStyle'; +// import LoadingButton from '@/theme/overrides/LoadingButton'; + +export default function List() { + const { themeColorPresets } = useSettings(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + const navigate = useNavigate() + + function SearchInput(props: any) { + // SEARCH + 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({ search: searchText }); // Trigger to Parent + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + + 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 handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]); + + setImportLoading(true); + axios + .post(`claim-requests/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + setImportLoading(false); + }) + .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 = (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 handleGetData = (type :string) => { + axios.get(`corporates/${corporate_id}/data-plan-benefit`) + .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(); + }) + } + + return ( +
+ + {!currentImportFileName && ( + + + + + Import + {handleGetTemplate('claim-request')}}>Download Template + {handleGetData('data-plan-benefit')}}>Download Claim Request + + + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )} + {importResult && ( + + + Last Import Result Report :{' '} + + {importResult.result_file?.name ?? '-'} + + + + )} +
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/customer-service/request', { params: filter }); + setDataTableLoading(false); + setDataTableData(response.data); + }; + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; + + const handleApprove = (claimRequest) => { + axios + .post(`claim-requests/${claimRequest.id}/approve`) + .then((response) => { + enqueueSnackbar('Success Approve', { variant: 'success' }); + loadDataTableData(); + }) + .catch(({ response }) => { + enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' }); + }); + }; + + useEffect(() => { + loadDataTableData(); + }, []); + + const headStyle = { + fontWeight: 'bold', + }; + + // Called on every row to map the data to the columns + function createData(data: any): any { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + const [loadingApprove, setLoadingApprove] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */ } + + { + // handleShowClaim(row); + // }} + > + {row.code} + + + {row.member?.full_name} + + {row.service_name} + {row.payment_type_name} + + { row.status == "requested" ? + () : + () + } + + + + navigate ('/claim-requests/detail/'+row.id+'')}> + + Detail + + navigate ('/claim-requests/detail/'+row.id+'')}> + + {/* credit: plus icon from https://heroicons.com/ */} + + + + + Konfirmasi + + + } /> + + {/* + + { + handleShowClaim(row); + }} + > + + + */} + + {/* COLLAPSIBLE ROW */} + + + + + } + spacing={1} + sx={{ marginY: 2 }} + > + + Berkas Hasil Penunjang + {/* {row.files_by_type?.claim_kondisi && + row.files_by_type?.claim_kondisi.map((file, index) => ( + + -{' '} + + {file.name} + + + ))} */} + + {row.files_by_type?.claim_kondisi && ( + <> + - Kondisi + {row.files_by_type?.claim_kondisi.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + + {row.files_by_type?.claim_diagnosis && ( + <> + - Diagnosa + {row.files_by_type?.claim_diagnosis.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + + {row.files_by_type?.claim_result && ( + <> + - Hasil + {row.files_by_type?.claim_result.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + {(!row.files_by_type?.claim_result && !row.files_by_type?.claim_diagnosis && !row.files_by_type?.claim_kondisi)&& Tidak ada berkas} + + + + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {/* */} + + Code + + + Name + + + Date of Submission + + + Service Type + + + Claim Method + + + Status + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + // --------------------------------------------------------- + // Dialog Detail Claim Request + const [openDialogDetailClaim, setOpenDialogDetailClaim] = useState(false); + const [loadingClaimDetail, setLoadingClaimDetail] = useState(true); + const [currentClaim, setCurrentClaim] = useState(null); + + function handleShowClaim(claimRequest) { + setLoadingClaimDetail(true); + setOpenDialogDetailClaim(true); + + axios + .get(`/claim-requests/${claimRequest.id}`) + .then(({ data }) => { + setCurrentClaim(data.data); + setLoadingClaimDetail(false); + }) + .catch((err) => { + enqueueSnackbar(err.message, { variant: 'error' }); + }); + } + + function handleDownloadLog() {} + + return ( + + + + + + + } + /> + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Model/Functions.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Model/Functions.tsx new file mode 100644 index 00000000..da72ff60 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Model/Functions.tsx @@ -0,0 +1,79 @@ +import axios from '@/utils/axios'; +import { enqueueSnackbar } from 'notistack'; +import { MemberListType } from './Types'; +import { makeFormData } from '@/utils/jsonToFormData'; + +/** + * Listing Member + */ +export const getMemberList = async ( page: number, keyword: string ): Promise => { + const response = await axios.get(`/claim-requests/list-member?page=${page}&keyword=${keyword}`) + .then((res) =>{ + return res.data.data.member_list; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return []; + }); + + return response; +}; + +/** + * Add Claim Request + */ +export const addClaimRequest = async ( data: MemberListType[] ): Promise => { + // Mapping + const formData = new FormData(); + + data.map((row, index) => { + formData.append(`member_id[${index}]`, row.id.toString()); + formData.append(`service_code[${index}]`, row.patien_type??''); + + if (row.file_kondisi != undefined) { + row.file_kondisi.forEach((file, file_index) => { + console.log(file); + + formData.append(`file_kondisi[member_${row.id}][${file_index}]`, file); + }); + } + + if (row.file_diagnosa != undefined) { + row.file_diagnosa.forEach((file, file_index) => { + console.log(file); + + formData.append(`file_diagnosa[member_${row.id}][${file_index}]`, file); + }); + } + + if (row.file_penunjang != undefined) { + row.file_penunjang.forEach((file, file_index) => { + console.log(file); + + formData.append(`file_penunjang[member_${row.id}][${file_index}]`, file); + }); + } + }) + + // Axios + const response = await axios.post(`/claim-requests`, formData) + .then((res) =>{ + enqueueSnackbar("Berhasil membuat data !", { + variant: 'success', + }); + + return true; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return false; + }); + + return response; +}; diff --git a/frontend/dashboard/src/pages/CustomerService/Request/Model/Types.tsx b/frontend/dashboard/src/pages/CustomerService/Request/Model/Types.tsx new file mode 100644 index 00000000..81b7da49 --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/Request/Model/Types.tsx @@ -0,0 +1,25 @@ +/** + * Search Type + */ +export type SearchType = { + keyword: string, +} + +/** + * Member List + */ +export type MemberListType = { + id : string, + member_id : string, + name : string, + service_type : ServiceType[], + patien_type? : string, + file_kondisi? : any[], + file_diagnosa? : any[], + file_penunjang? : any[], +} + +export type ServiceType = { + code : string + name : string +} diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 51910bb3..fd49827c 100644 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -470,6 +470,14 @@ export default function Router() { path: 'cs-membership', element: , }, + { + path: 'custormer-service/request', + element: , + }, + { + path: 'custormer-service/final-request', + element: , + }, ], }, // { @@ -587,6 +595,14 @@ const LaboratoriumResultClaims = Loadable(lazy(() => import('../pages/CaseManage const DetailLabResultForm = Loadable(lazy(() => import('../pages/CaseManagement/LaboratoriumResult/Components/DetailLabResultForm'))) const DetailLabResultList = Loadable(lazy(() => import('../pages/CaseManagement/LaboratoriumResult/Components/DetailLabResultList'))) + +/** + * Customer Service + */ +// Request +const RequestLog = Loadable(lazy(() => import('../pages/CustomerService/Request/Index'))) +const FinalLog = Loadable(lazy(() => import('../pages/CustomerService/FinalLog/Index'))) + const MasterDiagnosisTemplate = Loadable(lazy(() => import('../pages/Master/Diagnosis/Master/Index'))); const MasterDiagnosisTemplateCreate = Loadable(lazy(() => import('../pages/Master/Diagnosis/Master/CreateUpdate'))); const MasterDiagnosisTemplateHistories = Loadable(