From b3eb9b5f9d7a2bde8cf9d052ec5d320490d5ab3f Mon Sep 17 00:00:00 2001 From: R Date: Fri, 25 Nov 2022 05:14:40 +0700 Subject: [PATCH] [WIP] Claims --- .../Http/Controllers/Api/ClaimController.php | 93 +++++ .../Api/CorporateMemberController.php | 206 +++++++++++ .../Controllers/Api/DiagnosisController.php | 8 + .../Http/Controllers/Api/MemberController.php | 130 +------ Modules/Internal/Routes/api.php | 14 +- app/Models/Claim.php | 15 + app/Models/Corporate.php | 5 + app/Models/LimitJournal.php | 24 ++ app/Models/Member.php | 46 +++ app/Services/ClaimService.php | 27 ++ ..._23_140658_create_limit_journals_table.php | 41 +++ .../Claims => components}/LaravelTable.tsx | 10 +- .../dashboard/src/components/MuiDialog.tsx | 56 +++ .../components/dialogs/MemberSelectDialog.tsx | 275 ++++++++++++++ .../layouts/dashboard/navbar/NavConfig.tsx | 7 +- .../dashboard/src/pages/Claims/Create.tsx | 343 +++++++++--------- frontend/dashboard/src/pages/Claims/List.tsx | 81 ++--- frontend/dashboard/src/routes/index.tsx | 7 +- 18 files changed, 1012 insertions(+), 376 deletions(-) create mode 100644 Modules/Internal/Http/Controllers/Api/ClaimController.php create mode 100755 Modules/Internal/Http/Controllers/Api/CorporateMemberController.php mode change 100755 => 100644 Modules/Internal/Http/Controllers/Api/MemberController.php create mode 100644 app/Models/LimitJournal.php create mode 100644 app/Services/ClaimService.php create mode 100644 database/migrations/2022_11_23_140658_create_limit_journals_table.php rename frontend/dashboard/src/{pages/Claims => components}/LaravelTable.tsx (73%) create mode 100644 frontend/dashboard/src/components/MuiDialog.tsx create mode 100644 frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php new file mode 100644 index 00000000..4909f83d --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -0,0 +1,93 @@ +paginate(10); + + return response()->json($claims); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('internal::show'); + } + + /** + * 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) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } + + public function checkLimit(Request $request) + { + return true; + } +} diff --git a/Modules/Internal/Http/Controllers/Api/CorporateMemberController.php b/Modules/Internal/Http/Controllers/Api/CorporateMemberController.php new file mode 100755 index 00000000..e3573b98 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/CorporateMemberController.php @@ -0,0 +1,206 @@ +memberEnrollmentService = $memberEnrollmentService; + } + /** + * Display a listing of the resource. + * @return Renderable + */ + public function index(Request $request, $corporate_id) + { + $members = Member::query() + ->filter($request->all()) + // ->where('corporate_id', $corporate_id) + ->whereHas('employeds', function ($employeds) use ($corporate_id) { + $employeds->where('corporate_id', $corporate_id); + }) + ->with([ + 'employeds', + 'currentPolicy' => function ($policy) use ($corporate_id) { + $policy->whereHas('corporatePolicy', function($corporatePolicy) use ($corporate_id) { + $corporatePolicy->where('corporate_id', $corporate_id); + }); + } + ]) + ->with('currentPlan') + ->paginate() + ->appends($request->all()); + + return $members; + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('internal::show'); + } + + /** + * 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) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } + + + public function import(Request $request, $corporate_id) + { + $request->validate([ + 'file' => 'required|file|mimes:xls,xlsx,csv,txt', + ]); + $corporate = Corporate::findOrFail($corporate_id)->load('currentPolicy'); + + $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); + $file = $request->file('file')->storeAs('temp', $file_name); + + $reader = ReaderEntityFactory::createReaderFromFile(Storage::path('temp/'.$file_name)); + $reader->open(Storage::path('temp/'.$file_name)); + + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile(Storage::disk('public')->path('temp/result-'.$file_name)); + + $headers_map_to_table_fields = $this->memberEnrollmentService->doc_headers_to_field_map; + + // Write Header to File with certain Format from MemberEnrollmentService::$result_doc_headers + $result_headers = $this->memberEnrollmentService->result_doc_headers; + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($result_headers)); + $writer->addRow($singleRow); + + $imported_member_data = 0; + $failed_member_data = []; + foreach ($reader->getSheetIterator() as $sheet) { + $doc_headers_indexes = []; + foreach ($sheet->getRowIterator() as $index => $row) { + if ($index == 1) { // First Row Must be Header + foreach ($row->getCells() as $index => $cell) { + // Clear up the string and remove all spaces + $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; + } + } else { // Next Row Should be Data + // Collecting Values from table rows and map it to correct fields + $new_member_data = []; + foreach ($row->getCells() as $header_index => $cell) { + if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) { + $new_member_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); + } + } + + try { + // dd($new_member_data); + $rowResponse = $this->memberEnrollmentService->handleImportRow($corporate, $new_member_data); + + // Write Success Result to File + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($rowResponse)); + $writer->addRow($singleRow); + $imported_member_data++; + } catch (ImportRowException $e) { + // Write Data Validation Error to File + $new_member_data = array_merge($new_member_data, [ + 'ingestion_code' => $e->getCode(), + 'ingestion_status' => $e->getMessage(), + ]); + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); + $writer->addRow($singleRow); + $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; + } catch (\Exception $e) { + // Write Server Error to File + $new_member_data = array_merge($new_member_data, [ + 'ingestion_code' => $e->getCode(), + 'ingestion_status' => $e->getMessage(), + ]); + $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); + $writer->addRow($singleRow); + $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; + } + } + + } + + break; //only read first sheet + } + $reader->close(); + $writer->close(); + Storage::delete('temp/'.$file_name); + // throw(404); + + return [ + 'total_success_row' => $imported_member_data, + 'total_failed_row' => count($failed_member_data), + 'failed_row' => $failed_member_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-'.$file_name), + 'name' => 'result-'.$file_name, + ] + ]; + } +} diff --git a/Modules/Internal/Http/Controllers/Api/DiagnosisController.php b/Modules/Internal/Http/Controllers/Api/DiagnosisController.php index 1d49b710..f42a6b49 100755 --- a/Modules/Internal/Http/Controllers/Api/DiagnosisController.php +++ b/Modules/Internal/Http/Controllers/Api/DiagnosisController.php @@ -79,4 +79,12 @@ class DiagnosisController extends Controller { // } + + public function search(Request $request) + { + return Icd::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('code', 'LIKE', '%'.$search.'%'); + })->limit(10)->get(); + } } diff --git a/Modules/Internal/Http/Controllers/Api/MemberController.php b/Modules/Internal/Http/Controllers/Api/MemberController.php old mode 100755 new mode 100644 index c31d7c0b..f8defb31 --- a/Modules/Internal/Http/Controllers/Api/MemberController.php +++ b/Modules/Internal/Http/Controllers/Api/MemberController.php @@ -2,49 +2,20 @@ namespace Modules\Internal\Http\Controllers\Api; -use App\Exceptions\ImportRowException; -use App\Models\Corporate; use App\Models\Member; -use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; -use Box\Spout\Writer\Common\Creator\WriterEntityFactory; -use Box\Spout\Common\Entity\Row; use Illuminate\Contracts\Support\Renderable; use Illuminate\Http\Request; use Illuminate\Routing\Controller; -use Illuminate\Support\Facades\Storage; -use Modules\Internal\Services\MemberEnrollmentService; class MemberController extends Controller { - public function __construct(MemberEnrollmentService $memberEnrollmentService) - { - $this->memberEnrollmentService = $memberEnrollmentService; - } /** * Display a listing of the resource. * @return Renderable */ - public function index(Request $request, $corporate_id) + public function index() { - $members = Member::query() - ->filter($request->all()) - // ->where('corporate_id', $corporate_id) - ->whereHas('employeds', function ($employeds) use ($corporate_id) { - $employeds->where('corporate_id', $corporate_id); - }) - ->with([ - 'employeds', - 'currentPolicy' => function ($policy) use ($corporate_id) { - $policy->whereHas('corporatePolicy', function($corporatePolicy) use ($corporate_id) { - $corporatePolicy->where('corporate_id', $corporate_id); - }); - } - ]) - ->with('currentPlan') - ->paginate() - ->appends($request->all()); - - return $members; + return Member::paginate(); } /** @@ -106,101 +77,4 @@ class MemberController extends Controller { // } - - - public function import(Request $request, $corporate_id) - { - $request->validate([ - 'file' => 'required|file|mimes:xls,xlsx,csv,txt', - ]); - $corporate = Corporate::findOrFail($corporate_id)->load('currentPolicy'); - - $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); - $file = $request->file('file')->storeAs('temp', $file_name); - - $reader = ReaderEntityFactory::createReaderFromFile(Storage::path('temp/'.$file_name)); - $reader->open(Storage::path('temp/'.$file_name)); - - $writer = WriterEntityFactory::createXLSXWriter(); - $writer->openToFile(Storage::disk('public')->path('temp/result-'.$file_name)); - - $headers_map_to_table_fields = $this->memberEnrollmentService->doc_headers_to_field_map; - - // Write Header to File with certain Format from MemberEnrollmentService::$result_doc_headers - $result_headers = $this->memberEnrollmentService->result_doc_headers; - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($result_headers)); - $writer->addRow($singleRow); - - $imported_member_data = 0; - $failed_member_data = []; - foreach ($reader->getSheetIterator() as $sheet) { - $doc_headers_indexes = []; - foreach ($sheet->getRowIterator() as $index => $row) { - if ($index == 1) { // First Row Must be Header - foreach ($row->getCells() as $index => $cell) { - // Clear up the string and remove all spaces - $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; - } - } else { // Next Row Should be Data - // Collecting Values from table rows and map it to correct fields - $new_member_data = []; - foreach ($row->getCells() as $header_index => $cell) { - if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) { - $new_member_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue(); - } - } - - try { - // dd($new_member_data); - $rowResponse = $this->memberEnrollmentService->handleImportRow($corporate, $new_member_data); - - // Write Success Result to File - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($rowResponse)); - $writer->addRow($singleRow); - $imported_member_data++; - } catch (ImportRowException $e) { - // Write Data Validation Error to File - $new_member_data = array_merge($new_member_data, [ - 'ingestion_code' => $e->getCode(), - 'ingestion_status' => $e->getMessage(), - ]); - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); - $writer->addRow($singleRow); - $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; - } catch (\Exception $e) { - // Write Server Error to File - $new_member_data = array_merge($new_member_data, [ - 'ingestion_code' => $e->getCode(), - 'ingestion_status' => $e->getMessage(), - ]); - $singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRowWithResultFormat($new_member_data)); - $writer->addRow($singleRow); - $failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()]; - } - } - - } - - break; //only read first sheet - } - $reader->close(); - $writer->close(); - Storage::delete('temp/'.$file_name); - // throw(404); - - return [ - 'total_success_row' => $imported_member_data, - 'total_failed_row' => count($failed_member_data), - 'failed_row' => $failed_member_data, - 'result_file' => [ - 'url' => Storage::disk('public')->url('temp/result-'.$file_name), - 'name' => 'result-'.$file_name, - ] - ]; - } } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 6d3c1b46..57709300 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -1,11 +1,14 @@ group(function () { Route::get('corporates/{corporate_id}/divisions/{id}/edit', [DivisionController::class, 'edit']); Route::put('corporates/{corporate_id}/divisions/{id}', [DivisionController::class, 'update']); - Route::get('corporates/{corporate_id}/members', [MemberController::class, 'index']); - Route::post('corporates/{corporate_id}/members/import', [MemberController::class, 'import']); + Route::get('corporates/{corporate_id}/members', [CorporateMemberController::class, 'index']); + Route::post('corporates/{corporate_id}/members/import', [CorporateMemberController::class, 'import']); Route::get('corporates/{corporate_id}/diagnosis-exclusions', [DiagnosisExclusionController::class, 'index']); Route::post('corporates/{corporate_id}/diagnosis-exclusions/import', [DiagnosisExclusionController::class, 'import']); @@ -86,12 +89,17 @@ Route::prefix('internal')->group(function () { // Route::get('corporates/{corporate_id}/diagnosis-exclusions/import', [DiagnosisExclusionController::class, 'import']); Route::get('master/diagnosis', [DiagnosisController::class, 'index']); + Route::get('master/diagnosis/search', [DiagnosisController::class, 'search']); Route::get('master/drugs', [DrugController::class, 'index']); Route::get('master/formulariums', [FormulariumController::class, 'index']); Route::post('master/formulariums', [FormulariumController::class, 'store']); Route::post('master/formulariums/import', [FormulariumController::class, 'import']); - + Route::get('members', [MemberController::class, 'index']); + + Route::get('claims', [ClaimController::class, 'index']); + Route::post('check-limit', [ClaimController::class, 'checkLimit']); + }); // Route::get('something', [DiagnosisExclusionController::class, 'index']); diff --git a/app/Models/Claim.php b/app/Models/Claim.php index 35e0dd4a..39919309 100644 --- a/app/Models/Claim.php +++ b/app/Models/Claim.php @@ -23,4 +23,19 @@ class Claim extends Model { return $this->belongsTo(Member::class, 'member_id'); } + + public function diagnosis() + { + return $this->belongsTo(Icd::class, 'diagnosis_id'); + } + + public function plan() + { + return $this->belongsTo(Plan::class, 'plan_id'); + } + + public function benefit() + { + return $this->belongsTo(Benefit::class, 'benefit_id'); + } } diff --git a/app/Models/Corporate.php b/app/Models/Corporate.php index 561af4a6..d8147665 100755 --- a/app/Models/Corporate.php +++ b/app/Models/Corporate.php @@ -82,6 +82,11 @@ class Corporate extends Model return $this->morphMany(ImportLog::class, 'importable'); } + public function limitJournals() + { + return $this->morphMany(LimitJournal::class, 'journalable'); + } + public function services() { return $this->hasManyThrough(CorporateService::class, Service::class, 'corporate_id', 'service_code', 'id', 'service_code'); diff --git a/app/Models/LimitJournal.php b/app/Models/LimitJournal.php new file mode 100644 index 00000000..abba9b09 --- /dev/null +++ b/app/Models/LimitJournal.php @@ -0,0 +1,24 @@ +morphTo(); + } +} diff --git a/app/Models/Member.php b/app/Models/Member.php index 3ddf2b4c..759b47bd 100755 --- a/app/Models/Member.php +++ b/app/Models/Member.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Traits\Blameable; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -56,6 +57,8 @@ class Member extends Model "end_no_claim", ]; + protected $appends = ['full_name']; + public function claims() { return $this->hasMany(Claim::class, 'member_id', 'id'); @@ -66,6 +69,35 @@ class Member extends Model return $this->hasMany(CorporateEmployee::class, 'member_id'); } + public function corporates() + { + return $this + ->belongsToMany(Corporate::class, 'corporate_employees', 'corporate_id', 'member_id') + ->withPivot([ + 'branch_code', + 'divison_id', + 'nik', + 'status', + 'start', + 'end' + ]); + } + + public function currentCorporate() + { + return $this->belongsToMany(Corporate::class, 'corporate_employees', 'corporate_id', 'member_id') + // ->withPivot([ + // 'branch_code', + // 'divison_id', + // 'nik', + // 'status', + // 'start', + // 'end' + // ]) + ->where('start', '<', now()) + ->where('end', '>', now()); + } + public function memberPlans() { return $this->hasMany(MemberPlan::class, 'member_id'); @@ -91,6 +123,20 @@ class Member extends Model return $this->hasOne(MemberPolicy::class, 'member_id', 'member_id')->where('status', 'active')->latestOfMany(); } + public function getFullNameAttribute() + { + $arr = []; + if (!empty($this->name_prefix)) { + $arr[] = $this->name_prefix; + } + $arr[] = $this->name; + if (!empty($this->name_suffix)) { + $arr[] = $this->name_suffix; + } + + return implode(' ', $arr); + } + public function scopeFilter($query, array $filters) { $query->when($filters['search'] ?? false, function ($query, $search) { diff --git a/app/Services/ClaimService.php b/app/Services/ClaimService.php new file mode 100644 index 00000000..05f3618d --- /dev/null +++ b/app/Services/ClaimService.php @@ -0,0 +1,27 @@ + Str::random('16'), + 'member_id' => $member->id, + 'diagnosis_id' => $icd, + 'total_claim' => $totalClaim, + 'currency' => 'IDR', + 'plan_id' => $member->currentPlan->id, + 'benefit_id' => $benefit->id, + ]); + + $corporate = $member->asd; + + return $claim; + } +} \ No newline at end of file diff --git a/database/migrations/2022_11_23_140658_create_limit_journals_table.php b/database/migrations/2022_11_23_140658_create_limit_journals_table.php new file mode 100644 index 00000000..ff82510a --- /dev/null +++ b/database/migrations/2022_11_23_140658_create_limit_journals_table.php @@ -0,0 +1,41 @@ +id(); + $table->morphs('journalable'); + $table->string('mutation'); + $table->string('type'); + $table->string('balance'); + $table->text('description'); + + $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('limit_journals'); + } +}; diff --git a/frontend/dashboard/src/pages/Claims/LaravelTable.tsx b/frontend/dashboard/src/components/LaravelTable.tsx similarity index 73% rename from frontend/dashboard/src/pages/Claims/LaravelTable.tsx rename to frontend/dashboard/src/components/LaravelTable.tsx index 0ee480e1..18325952 100644 --- a/frontend/dashboard/src/pages/Claims/LaravelTable.tsx +++ b/frontend/dashboard/src/components/LaravelTable.tsx @@ -1,6 +1,6 @@ import { Card, Paper, TableContainer } from "@mui/material"; -import { LaravelPaginatedData } from "../../@types/paginated-data"; -import BasePagination from "../../components/BasePagination"; +import { LaravelPaginatedData } from "../@types/paginated-data"; +import BasePagination from "./BasePagination"; type LaravelTableProps = { isLoading: boolean; @@ -10,7 +10,7 @@ type LaravelTableProps = { TableContent: any; }; -export default function LaravelTable(props: LaravelTableProps) { +function LaravelTable(props: LaravelTableProps) { return ( @@ -23,4 +23,6 @@ export default function LaravelTable(props: LaravelTableProps) { } ) -} \ No newline at end of file +} + +export default LaravelTable \ No newline at end of file diff --git a/frontend/dashboard/src/components/MuiDialog.tsx b/frontend/dashboard/src/components/MuiDialog.tsx new file mode 100644 index 00000000..54ee1110 --- /dev/null +++ b/frontend/dashboard/src/components/MuiDialog.tsx @@ -0,0 +1,56 @@ +import { Dialog, DialogTitle, DialogContent, Stack, Typography, IconButton } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { ReactElement } from 'react'; +import Iconify from './Iconify'; + +// ---------------------------------------------------------------------- + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + maxWidth?: string; +}; + +// ---------------------------------------------------------------------- + +const MuiDialog = ({ title, openDialog, setOpenDialog, content, maxWidth }: MuiDialogProps) => { + const handleClose = () => { + setOpenDialog(false); + }; + + let maxWidthDialog = 'md'; + + if (maxWidth) { + maxWidthDialog = maxWidth; + } + + return ( + + + + {title?.icon ? ( + + + {title?.name} + + ) : ( + {title?.name ? title?.name : ''} + )} + + + + + + + {content ? content : 'Testing Content Dialog'} + + + ); +}; + +export default MuiDialog; diff --git a/frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx b/frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx new file mode 100644 index 00000000..3d336714 --- /dev/null +++ b/frontend/dashboard/src/components/dialogs/MemberSelectDialog.tsx @@ -0,0 +1,275 @@ +// @mui +import { styled } from '@mui/material/styles'; +import { + Typography, + LinearProgress, + linearProgressClasses, + Stack, + FormControlLabel, + TableRow, + TableCell, + Table, + TableBody, + TextField, + Button, +} from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import Checkbox from '@mui/material/Checkbox'; +// components +import { FormProvider, RHFTextField } from '../../components/hook-form'; +// React +import React, { ReactElement, useEffect, useState } from 'react'; +import { fCurrency } from '../../utils/formatNumber'; +// yup +import * as Yup from 'yup'; +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import MuiDialog from '../MuiDialog'; +import { Member } from '../../@types/member'; +import { LaravelPaginatedDataDefault } from '../../@types/paginated-data'; +import DataTable from '../LaravelTable'; +import axios from '../../utils/axios'; + +// ---------------------------------------------------------------------- + +type DataContent = { + info: string; + date: string; + time: string; +}; + +type MuiDialogProps = { + title?: { + name?: string; + icon?: string; + }; + openDialog: boolean; + setOpenDialog: Function; + content?: ReactElement; + onSelect?: Function; +}; + +type FormValuesProps = { + topup: string; +}; + +// ---------------------------------------------------------------------- + +const testData = { + companyName: 'PT. Aman Mineral', + policyNumber: 12345678, + totalMembers: 50, + totalCases: 100, + totalPersen: 75, + myLimit: '375.000.000', + totalLimit: 500000000, +}; + +const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ + height: 10, + borderRadius: 6, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[theme.palette.mode === 'light' ? 300 : 800], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 6, + background: 'linear-gradient(270deg, #19BBBB 38.42%, #FF9565 76.21%, #FE7253 104.02%)', + }, +})); + +// ---------------------------------------------------------------------- + +export default function MemberSelectDialog({ + openDialog, + setOpenDialog, + onSelect +}: MuiDialogProps) { + const [dataTableLoading, setDataTableLoading] = useState(false) + const [dataTableData, setDataTableData] = useState(LaravelPaginatedDataDefault) + const [searchFilter, setSearchFilter] = useState({ + search: '' + }) + + const handleSearchChange = (event: any) => { + setSearchFilter({...searchFilter, ...{search: event.target.value}}) + } + + const loadDataTableData = async (appliedFilter : any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : {}; + const response = await axios.get('/members', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + } + + const applyFilter = async (searchFilter: {search: string}) => { + await loadDataTableData(searchFilter); + } + + const handleFilterSubmit = (event: any) => { + event.preventDefault(); + applyFilter(searchFilter) + } + + useEffect(() => { + console.log('effect openDialog'); + if (openDialog === false) { + } + }, [openDialog]); + + const handlePageChange = () => { + console.log('handle change') + } + + // Called on every row to map the data to the columns + function createData(member: Member): Member { + return { + ...member, + }; + } + + const Row = (props: { row: ReturnType, onSelect }) => ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)} + > + {open ? : } + + */} + {/* { columns.map((column, index) => + { row[column.id] ?? '-' } + ) } */} + {/* TODO FIX DISPLAY DATA FROM RELATION */} + + {/* { id: 'member_id', label: 'MemberID', minWidth: 100, align: "left" }, + { id: 'principal_id', label: 'Mapping ID', minWidth: 100, align: "left" }, + { id: 'nik', label: 'NIK', minWidth: 100, align: "left" }, + { id: 'current_policy.policy_number', label: 'Policy Number', minWidth: 100, align: "left" }, + { id: 'effective_date', label: 'Effective Date', minWidth: 100, align: "left" }, + { id: 'name', label: 'Name', minWidth: 100, align: "left" }, + { id: 'nric', label: 'NRIC', minWidth: 100, align: "left" }, + { id: 'email', label: 'E-mail', minWidth: 100, align: "left" }, + { id: 'plan_id', label: 'PlanID', minWidth: 100, align: "left" }, + { id: 'termination_date', label: 'Termination Date', minWidth: 100, align: 'right' }, + { id: 'activation_date', label: 'Activation Date', minWidth: 100, align: "right" }, + */} + {props.row.member_id} + {props.row.principal_id} + {props.row.employeds?.[0]?.nik} + {props.row.current_policy?.policy_id} + {props.row.current_policy?.start} + {props.row.name} + {props.row.nric} + {props.row.email} + {props.row.current_plan?.code} + {props.row.current_policy?.start} + {props.row.current_policy?.end} + + + {/* COLLAPSIBLE ROW */} + {/* + + + + + No Extra Data + + + {false && } + + + */} + + ); + + const headStyle = { + fontWeight: 'bold', + }; + const TableContent = () => ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + + + Type + + + Code + + + Name + + + Version + + + Status + + + Action + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + + const getContent = () => ( + +
+ + + } + /> +
+ ); + + return ( + + ); +} diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index a7191de0..1072023d 100755 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -62,9 +62,10 @@ const navConfig = [ }, { title: 'CASE MANAGEMENT', - children: [ - { title: 'Request', path: '/case-request' }, - ], + path: '/claims', + // children: [ + // { title: 'Request', path: '/case-request' }, + // ], }, { title: 'CUSTOMER SERVICES', diff --git a/frontend/dashboard/src/pages/Claims/Create.tsx b/frontend/dashboard/src/pages/Claims/Create.tsx index 5e9fccdf..a40c882e 100755 --- a/frontend/dashboard/src/pages/Claims/Create.tsx +++ b/frontend/dashboard/src/pages/Claims/Create.tsx @@ -1,38 +1,40 @@ import * as Yup from 'yup'; -import { yupResolver } from "@hookform/resolvers/yup"; -import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material"; -import { 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 CorporateTabNavigations from "../CorporateTabNavigations"; -import DivisionsList from "./List"; -import { useMemo, useState } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Autocomplete, Button, Card, Collapse, Divider, Grid, Stack, 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'; +export default function Create() { + const [member, setMember] = useState(); + const selectedMemberDisplay = useRef(null); - -export default function Divisions() { - const { themeStretch } = useSettings(); - - const { corporate_id } = useParams(); - - const NewDivisionSchema = Yup.object().shape({ + const NewClaimSchema = Yup.object().shape({ name: Yup.string().required('Name is required'), - code: Yup.string().required('Corporate Code is required'), - active: Yup.boolean().required('Corporate Status is required'), + diagnosis_id: Yup.string().required('Diagnosis is required'), + total_claim: Yup.string().required('Total Claim is required'), }); const defaultValues = useMemo( () => ({ - code: '', + name: '', + diagnosis_id: null, + total_claim: 0, }), [] ); const methods = useForm({ - resolver: yupResolver(NewDivisionSchema), + resolver: yupResolver(NewClaimSchema), defaultValues, }); @@ -51,119 +53,79 @@ export default function Divisions() { console.log(data); }; - const [open, setOpen] = useState(false); + const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); - const benefits = [ - { - 'category' : 'General Practitioner', - 'childs' : [ - { - 'name' : 'External Doctor Online', - 'code' : 'gp-external-doctor-online' - }, - { - 'name' : 'External Doctor Offline', - 'code' : 'gp-external-doctor-offline' - }, - { - 'name' : 'Internal Doctor Online', - 'code' : 'gp-internal-doctor-online' - }, - { - 'name' : 'Internal Doctor Offline', - 'code' : 'gp-internal-doctor-offline' - }, - ] - }, - { - 'category' : 'Specialist', - 'childs' : [ - { - 'name' : 'External Doctor Online', - 'code' : 'sp-external-doctor-online' - }, - { - 'name' : 'External Doctor Offline', - 'code' : 'sp-external-doctor-offline' - }, - { - 'name' : 'Internal Doctor Online', - 'code' : 'sp-internal-doctor-online' - }, - { - 'name' : 'Internal Doctor Offline', - 'code' : 'sp-internal-doctor-offline' - }, - ] - }, - { - 'category' : 'Medicines', - 'childs' : [ - { - 'name' : 'Vitamins', - 'code' : 'medicines-vitamins' - }, - { - 'name' : 'Delivery Fee', - 'code' : 'medicines-delivery-fee' - }, - ] - }, - ]; + const memberSelected = (selectedMember: any) => { + setMember(selectedMember); + }; - const products = [ - { - 'name' : 'Inpatient', - 'code' : 'IP', - }, - { - 'name' : 'Outpatient', - 'code' : 'OP', - }, - { - 'name' : 'Dental', - 'code' : 'DT', - }, - { - 'name' : 'Dental', - 'code' : 'DTL', - }, - { - 'name' : 'Matternity', - 'code' : 'MT', - }, - { - 'name' : 'Special Benefit', - 'code' : 'SB', - }, - ]; + const [diagnosis, setDiagnosis] = useState([]); + + const searchDiagnosis = (search) => { + axios.get('master/diagnosis/search', {params: {search}}) + .then(function(res) { + setDiagnosis(res.data); + }) + } + + useEffect(() => { // Trigger First Search + axios.get('master/diagnosis/search') + .then(function(res) { + setDiagnosis(res.data); + }) + + }, []) + + const [isEligible, setIsEligible] = useState(false) + + useEffect(() => { + setIsEligible(false) + console.log('member change') + }, [member]) + + const [isCheckingLimit, setIsCheckingLimit] = useState(false) + const checkLimit = (event) => { + event.preventDefault(); + console.log(getValues('diagnosis_id')) + if (!member || !getValues('diagnosis_id')) { + enqueueSnackbar('Please Check the Data', { variant: 'error' }) + + return false; + } + + setIsCheckingLimit(true) + axios.post('check-limit', { + 'member_id' : member.id, + 'diagnosis' : getValues('diagnosis_id'), + 'total_claim' : getValues('total_claim') + }) + .then((res) => { + setIsEligible(true) + }) + .catch((err) => { + enqueueSnackbar('Failed Checking Limit : ' + err.message ?? '', { variant: 'error' }) + }) + .then(() => { + setIsCheckingLimit(false) + }) + } return ( - - + - @@ -171,71 +133,96 @@ export default function Divisions() { - Benefit Detail + Member - - - - - Benefit Configuration - - - }> - - - {benefits.map(row => ( - - {row.category} - - {row.childs.map(benefit => ( - - - - ))} - - - ))} - Admin Fee - - {benefits.map(row => ( - - - - ))} - - - - - - - {benefits.map(row => ( - - {row.category} - - {row.childs.map(benefit => ( - - - - ))} - - - ))} - Admin Fee - - {benefits.map(row => ( - - - - ))} - - + + + + + + + + + { member && ( + +
+ Plan : {JSON.stringify(member)} +
+
+ Benefit : +
+
+ Another : +
+
+ )} + + ( + + option ? `(${option.code}) ${option.name}` : '' + } + value={value || ''} + onChange={(event: any, newValue: any) => { + setValue('diagnosis_id', newValue?.id) + onChange(newValue); + }} + renderInput={(params) => ( + { + searchDiagnosis(event.target.value) + }} + /> + )} + /> + )} + /> + + + + { isEligible === true ? ( + + Create Claim + + ) : ( + + Check Limit + + )}
+ +
); } diff --git a/frontend/dashboard/src/pages/Claims/List.tsx b/frontend/dashboard/src/pages/Claims/List.tsx index 79772d12..db8a8930 100755 --- a/frontend/dashboard/src/pages/Claims/List.tsx +++ b/frontend/dashboard/src/pages/Claims/List.tsx @@ -1,5 +1,5 @@ // @mui -import { Box, Button, Card, Collapse, IconButton, MenuItem, Table, TableBody, TableCell, TableRow, TextField, Typography, Stack, Menu, ButtonGroup } from '@mui/material'; +import { Box, Button, Card, Collapse, IconButton, MenuItem, Table, TableBody, TableCell, TableRow, TextField, Typography, Stack, Menu, ButtonGroup, Link } from '@mui/material'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import AddIcon from '@mui/icons-material/Add'; @@ -11,7 +11,8 @@ import { useSearchParams } from 'react-router-dom'; // components import axios from '../../utils/axios'; import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data'; -import DataTable from './LaravelTable'; +import DataTable from '../../components/LaravelTable'; +import { fCurrency } from '../../utils/formatNumber'; export default function List() { const [searchParams, setSearchParams] = useSearchParams(); @@ -102,57 +103,17 @@ export default function List() { return (
- - {( !currentImportFileName && + - {/*

kjasndkjandskjasndkjansdkjansd

*/} - - - Import - Download Template - +
- )} - - {( currentImportFileName && - - - - - - - - )} - {( importResult && - - Last Import Result Report : {importResult.result_file?.name ?? "-"} - - )}
); } @@ -164,7 +125,7 @@ export default function List() { const loadDataTableData = async (appliedFilter : any | null = null) => { setDataTableLoading(true); const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); - const response = await axios.get('/master/drugs', { params: filter }); + const response = await axios.get('/claims', { params: filter }); // console.log(response.data); setDataTableLoading(false); @@ -191,7 +152,7 @@ export default function List() { }; // Called on every row to map the data to the columns - function createData( data: Icd ): Icd { + function createData( data: any ): any { return { ...data, } @@ -214,13 +175,14 @@ export default function List() { {open ? : } - {row.type} {row.code} - {row.name} - {row.version} + {row.member.full_name} + {row.plan.code} + {row.benefit.code} + ({row.diagnosis.code}) {row.diagnosis.name} + {fCurrency(row.total_claim)} - - + {/* */} {/* COLLAPSIBLE ROW */} @@ -246,12 +208,13 @@ export default function List() { - Type Code - Name - Version - Status - Action + Member Name + Plan + Benefit + Diagnosis + Total Claim + {/* Action */} {/* ------------------ END TABLE HEADER ------------------ */} diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 65e8f05b..a4c07e68 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -211,6 +211,10 @@ export default function Router() { path: 'claims', element: , }, + { + path: 'claims/create', + element: + } ] }, // { @@ -294,4 +298,5 @@ const CorporateServicesCreate = Loadable(lazy(() => import('../pages/Corporates/ const CorporateHospitals = Loadable(lazy(() => import('../pages/Corporates/Hospital/Index'))); const CorporateClaimHistories = Loadable(lazy(() => import('../pages/Corporates/ClaimHistory/Index'))); -const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); \ No newline at end of file +const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); +const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/Create'))); \ No newline at end of file