tuning import. listing, edit

This commit is contained in:
2023-10-25 08:46:42 +07:00
parent f53945e4a0
commit 5265aecd05
20 changed files with 1023 additions and 566 deletions

View File

@@ -66,5 +66,6 @@ Route::prefix('client')->group(function () {
Route::get('claims/{id}', [ClaimController::class, 'show']); Route::get('claims/{id}', [ClaimController::class, 'show']);
Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store'); Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store');
Route::post('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show');
}); });
}); });

View File

@@ -36,6 +36,7 @@ class ClaimController extends Controller
'claimRequest', 'claimRequest',
'claimRequest.service' 'claimRequest.service'
]) ])
->where('status', '!=', 'requested') // penjagaan agar approve baru masuk ke claim management
->latest() ->latest()
->paginate(10); ->paginate(10);

View File

@@ -4,12 +4,18 @@ namespace Modules\Internal\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Models\ClaimRequest; use App\Models\ClaimRequest;
use App\Models\Organization;
use App\Services\ClaimService; use App\Services\ClaimService;
use App\Services\ImportService;
use Illuminate\Contracts\Support\Renderable; use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Controller; use Illuminate\Routing\Controller;
use Modules\Internal\Transformers\ClaimRequestResource; use Modules\Internal\Transformers\ClaimRequestResource;
use Modules\Internal\Transformers\ClaimRequestShowResource; use Modules\Internal\Transformers\ClaimRequestShowResource;
use Illuminate\Support\Facades\Storage;
use App\Services\ClaimRequestService;
use App\Exceptions\ImportRowException;
use App\Models\File; use App\Models\File;
use App\Models\FilesMcu; use App\Models\FilesMcu;
@@ -24,6 +30,9 @@ class ClaimRequestController extends Controller
$claimRequests = ClaimRequest::query() $claimRequests = ClaimRequest::query()
->when($request->search, function ($q, $search) { ->when($request->search, function ($q, $search) {
$q->where('code', 'LIKE', "%".$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) { ->when($request->orderBy, function ($q, $orderBy) use ($request) {
if (in_array($orderBy, ['submission_date', 'code'])) { if (in_array($orderBy, ['submission_date', 'code'])) {
@@ -73,7 +82,10 @@ class ClaimRequestController extends Controller
'histories' => function ($history) { 'histories' => function ($history) {
$history->latest(); $history->latest();
}, },
'files' 'files',
'member',
'claim',
'claim.organization',
]); ]);
return Helper::responseJson(data: ClaimRequestShowResource::make($claimRequest)); return Helper::responseJson(data: ClaimRequestShowResource::make($claimRequest));
@@ -97,7 +109,68 @@ class ClaimRequestController extends Controller
*/ */
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
// $claim_id = ClaimRequest::find($id)->claim_id;
$organization_id = Organization::where('code', $request->provider_code)->first();
if (!$organization_id) {
return response()->json(['error' => true, 'message' => 'Data tidak ditemukan'], 404);
}
$newClaimRequest = ClaimRequestService::updateClaimRequest(code: $code, member: $member, paymentType: 'reimbursement', serviceCode: $request->service_code);
ClaimRequested::dispatch($newClaimRequest);
// Log History
$newClaimRequest->histories()->create([
'title' => 'Update Claim Requested',
'description' => "Update Claim Requested for Member : {$member->member_id} - ({$member->full_name})",
'type' => 'info',
'system_origin' => 'hospital-portal'
]);
if ($request->hasFile('result_files')) {
foreach ($request->result_files as $file) {
$pathFile = File::storeFile('claim-result', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'claim-result',
'name' => File::getFileName('claim-result', $newClaimRequest->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('claim-diagnosis', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'claim-diagnosis',
'name' => File::getFileName('claim-diagnosis', $newClaimRequest->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('claim-kondisi', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'claim-kondisi',
'name' => File::getFileName('claim-kondisi', $newClaimRequest->id, $file),
'original_name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => $pathFile,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id,
]);
}
}
} }
/** /**
@@ -167,4 +240,100 @@ class ClaimRequestController extends Controller
} }
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 = ClaimRequest::$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 ClaimRequestService();
$claimRequestService->handleClaimRequestRow($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);
$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,
]
];
}
} }

View File

@@ -546,6 +546,12 @@ class CorporateController extends Controller
"file_url" => url('files/Template - Formularium - Corporate.xlsx') "file_url" => url('files/Template - Formularium - Corporate.xlsx')
]); ]);
break; break;
case 'claim-request':
return Helper::responseJson([
'file_name' => "Template Format Claim.xlsx",
"file_url" => url('files/Template Format Claim.xlsx')
]);
break;
default: default:
return Helper::responseJson([], 'error', 404); return Helper::responseJson([], 'error', 404);
break; break;

View File

@@ -213,6 +213,8 @@ Route::prefix('internal')->group(function () {
Route::get('claim-requests', [ClaimRequestController::class, 'index'])->name('claim-requests.index'); Route::get('claim-requests', [ClaimRequestController::class, 'index'])->name('claim-requests.index');
Route::post('claim-requests/{id}/approve', [ClaimRequestController::class, 'approve'])->name('claim-requests.approve'); Route::post('claim-requests/{id}/approve', [ClaimRequestController::class, 'approve'])->name('claim-requests.approve');
Route::get('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show'); Route::get('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show');
Route::put('claim-requests/{id}', [ClaimRequestController::class, 'update'])->name('claim-requests.update');
Route::post('claim-requests/import', [ClaimRequestController::class, 'importClaim'])->name('claim-requests.importClaim');
}); });
Route::get('province', [ProvinceController::class, 'index']); Route::get('province', [ProvinceController::class, 'index']);

View File

@@ -216,5 +216,10 @@ class Helper
return $sPaymentMethod[$id]; return $sPaymentMethod[$id];
} }
public static function formatDateDB($date){
$convertedDate = Carbon::createFromFormat('d-m-Y', $date)->format('Y-m-d H:i:s');
return $convertedDate;
}
} }

View File

@@ -29,6 +29,7 @@ class Claim extends Model
'currency', 'currency',
'plan_id', 'plan_id',
'benefit_id', 'benefit_id',
'organization_id',
'status', 'status',
'service_code' 'service_code'
]; ];
@@ -196,6 +197,11 @@ class Claim extends Model
return $this->belongsTo(Member::class, 'member_id'); return $this->belongsTo(Member::class, 'member_id');
} }
public function organization()
{
return $this->belongsTo(Organization::class, 'organization_id');
}
public function encounters() public function encounters()
{ {
return $this->belongsToMany(Encounter::class, 'claim_encounter'); return $this->belongsToMany(Encounter::class, 'claim_encounter');

View File

@@ -38,6 +38,71 @@ class ClaimRequest extends Model
'deleted_by', 'deleted_by',
]; ];
public static $doc_headers_to_field_map = [
"PAYOR ID" => "payor_id",
"CORPORATE ID" => "corporate_id",
"POLICY NUMBER" => "policy_number",
"MEMBER ID" => "member_id",
"MEMBER NAME" => "member_name",
"RECORD TYPE (P/D)" => "record_type",
"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_code",
"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 = [ public static $status = [
'draft' => 'Draft', 'draft' => 'Draft',
'requested' => 'Requested', 'requested' => 'Requested',

View File

@@ -98,4 +98,9 @@ class Organization extends Model
{ {
return $this->hasMany(PractitionerRole::class, 'organization_id'); return $this->hasMany(PractitionerRole::class, 'organization_id');
} }
public function claims()
{
return $this->hasMany(Claim::class, 'organization_id', 'id');
}
} }

View File

@@ -6,9 +6,16 @@ use App\Events\ClaimApproved;
use App\Events\ClaimRequested; use App\Events\ClaimRequested;
use App\Models\Claim; use App\Models\Claim;
use App\Models\ClaimRequest; use App\Models\ClaimRequest;
use App\Models\Organization;
use App\Helpers\Helper;
use App\Models\Icd; use App\Models\Icd;
use App\Models\Member; use App\Models\Member;
use Carbon\Carbon; use Carbon\Carbon;
use App\Exceptions\ImportRowException;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use DB; use DB;
use Str; use Str;
@@ -32,6 +39,7 @@ class ClaimRequestService{
$claimRequest = ClaimRequest::create($claimRequestData); $claimRequest = ClaimRequest::create($claimRequestData);
DB::commit(); DB::commit();
return $claimRequest; return $claimRequest;
} catch (\Exception $error) { } catch (\Exception $error) {
DB::rollBack(); DB::rollBack();
@@ -40,4 +48,105 @@ class ClaimRequestService{
} }
} }
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'],
'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(){
try {
} 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'];
$submissionDate = Helper::formatDateDB($row['admission_date']);
$paymentType = $row['claim_type'];
$status = $row['status'];
$serviceCode = $row['coverage_type'];
$newClaimRequest = $this->storeClaimRequest(code: $code, member: $member, paymentType: $paymentType, serviceCode: $serviceCode, submissionDate: $submissionDate, status: $status);
$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;
// dd($claim_request_data['admission_date']);
$this->validatePlanRow($claim_request_data);
return $newClaimRequest;
} catch (\Exception $e) {
throw $e;
}
}
} }

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('claims', function (Blueprint $table) {
$table->integer('organization_id')->after('benefit_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('claims', function (Blueprint $table) {
$table->dropColumn('organization_id');
});
}
};

View File

@@ -0,0 +1,49 @@
import { Member } from "./member";
export type ClaimRequest = {
id: number;
code: string;
name: string;
submission_date: string;
payment_type: string;
service_code: string;
claim_method: string;
service_type: string;
code_provider: string;
file_condition: Files;
member: Member;
claim: {
organization: Organizations
}
};
export type Files = {
name: string;
url: string;
path: string;
}
export type Organizations = {
id: number;
code: string;
name: string;
address: string;
type: string;
lat: string;
lng: string;
phone: string;
timezone: string;
active: boolean | number;
province_id: number;
city_id: number;
district_id: number;
village_id: number;
postal_code: string;
description: string;
technology: string;
support_services: string;
merchant_code: string;
merchant_key: string;
image_url: string;
region_groups: string;
};

View File

@@ -15,44 +15,42 @@ import { enqueueSnackbar } from 'notistack';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { fCurrency } from '../../utils/formatNumber'; import { fCurrency } from '../../utils/formatNumber';
import Iconify from '../../components/Iconify'; import Iconify from '../../components/Iconify';
import { ClaimRequest } from '@/@types/claims';
import Form from './Form'; import Form from './Form';
export default function ClaimsCreateUpdate() { export default function ClaimsCreateUpdate() {
const { themeStretch } = useSettings(); const { themeStretch } = useSettings();
const { id } = useParams(); const { id } = useParams();
const isEdit = id ? true : false; const isEdit = id ? true : false;
const [currentClaim, setCurrentClaim] = useState(); const [currentClaim, setCurrentClaim] = useState<ClaimRequest>();
useEffect(() => { useEffect(() => {
if (isEdit) { if (isEdit) {
axios.get('/claims/' + id).then((res) => { axios.get('/claim-requests/' + id).then((res) => {
// console.log('Yeet', res.data); console.log('Yeet', res.data);
setCurrentClaim(res.data); setCurrentClaim(res.data.data);
}); });
console.log(currentClaim)
} }
}, [id]); }, [id]);
return ( return (
<Page title={isEdit ? `Edit Claim : ${currentClaim?.id}` : "Create New Claim"}> <Page title={isEdit ? `Edit Claim Request` : "Create New Claim"}>
<Container maxWidth={themeStretch ? false : 'xl'}> <Container maxWidth={themeStretch ? false : 'xl'}>
<Stack direction="row" alignItems="center"> <Stack direction="row" alignItems="center">
<HeaderBreadcrumbs <HeaderBreadcrumbs
heading={ heading={'Edit Claim Request'}
!isEdit
? 'Create New Claim'
: `Edit Claim : ${currentClaim?.code}`
}
links={[ links={[
{ name: 'Dashboard', href: '/dashboard' }, { name: 'Dashboard', href: '/dashboard' },
{ {
name: 'Claim', name: 'Claim Request',
href: '/claims',
}, },
{ name: !isEdit ? 'Create' : currentClaim?.id ?? '' },
]} ]}
/> />
</Stack> </Stack>

View File

@@ -3,7 +3,7 @@ import { useSnackbar } from 'notistack';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useRef, useEffect, useMemo, useState } from 'react';
import axios from '../../utils/axios'; import axios from '../../utils/axios';
import { FormProvider, RHFTextField } from '../../components/hook-form'; import { FormProvider, RHFTextField } from '../../components/hook-form';
import { import {
@@ -24,16 +24,29 @@ import {
ListItemAvatar, ListItemAvatar,
Avatar, Avatar,
ListItemText, ListItemText,
Card,
InputAdornment,
Divider,
ButtonBase,
Box,
} from '@mui/material'; } from '@mui/material';
import Iconify from '../../components/Iconify'; import Iconify from '../../components/Iconify';
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { fCurrency } from '../../utils/formatNumber'; import { fCurrency } from '../../utils/formatNumber';
import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog'; import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog';
import { Add, DeleteOutline } from '@mui/icons-material'; import { Add, DeleteOutline } from '@mui/icons-material';
import { ClaimRequest, Files } from '@/@types/claims';
import { fDateTimesecond } from '@/utils/formatTime';
interface FormValuesProps extends Partial<ClaimRequest> {
taxes: boolean;
inStock: boolean;
}
type Props = { type Props = {
isEdit: boolean; isEdit: boolean;
currentClaim?: any; currentClaim?: ClaimRequest;
}; };
export default function ClaimForm({ isEdit, currentClaim }: Props) { export default function ClaimForm({ isEdit, currentClaim }: Props) {
@@ -41,28 +54,39 @@ export default function ClaimForm({ isEdit, currentClaim }: Props) {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const NewCorporateSchema = Yup.object().shape({ const EditClaimSchema = Yup.object().shape({
name: Yup.string().required('Name is required'), organization_id: Yup.string().required('Name is required'),
code: Yup.string().required('Corporate Code is required'),
active: Yup.boolean().required('Corporate Status is required'),
// file: Yup.boolean().required('Corporate Status is required'),
}); });
const defaultValues = useMemo( const defaultValues = useMemo(
() => ({ () => ({
member: currentClaim?.member || {}, id: currentClaim?.id || '-',
member_id: currentClaim?.member_id || null, code: currentClaim?.code || '-',
diagnosis_id: currentClaim?.diagnosis_id || null, member_name: currentClaim?.member?.name || '-',
total_claim: currentClaim?.total_claim || 0, date: currentClaim?.submission_date ? fDateTimesecond(currentClaim?.submission_date) : '-',
claim_method: currentClaim?.payment_type || '-',
service_type: currentClaim?.service_code || '-',
organization_id: currentClaim?.claim?.organization?.code || '-',
}), }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentClaim] [currentClaim]
); );
const methods = useForm<any>({ useEffect(() => {
resolver: yupResolver(NewCorporateSchema), console.log(currentClaim, 'er')
if (isEdit && currentClaim) {
reset(defaultValues);
}
if (!isEdit) {
reset(defaultValues);
}
}, [isEdit, currentClaim]);
const methods = useForm<FormValuesProps>({
resolver: yupResolver(EditClaimSchema),
defaultValues, defaultValues,
}); });
const { const {
reset, reset,
watch, watch,
@@ -83,514 +107,326 @@ export default function ClaimForm({ isEdit, currentClaim }: Props) {
const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false); const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false);
const [member, setMember] = useState({}) const [member, setMember] = useState({})
// ----------------------------------------------------------------------
useEffect(() => {
console.log('defaultValues', defaultValues); // Files Result Kondisi
if (isEdit && currentClaim) { const fileKondisiInput = useRef<HTMLInputElement>(null);
reset(defaultValues); const [fileKondisis, setFileKondisis] = useState<Files>([]);
setMember(defaultValues.member)
const handleKondisiInputChange = (event) => {
if (event.target.files[0]) {
setFileKondisis([...fileKondisis, ...event.target.files]);
} else {
console.log('NO FILE');
} }
if (!isEdit) { };
reset(defaultValues); const removeKondisiFiles = (filesState, index) => {
setMember(defaultValues.member) setFileKondisis(
filesState.filter((file, fileIndex) => {
return fileIndex != index;
})
);
};
// Files Result Diagnosa
const fileDiagnosaInput = useRef<HTMLInputElement>(null);
const [fileDiagnosas, setFileDiagnosas] = useState([]);
const handleDiagnosaInputChange = (event) => {
if (event.target.files[0]) {
setFileDiagnosas([...fileDiagnosas, ...event.target.files]);
} else {
console.log('NO FILE');
} }
}, [isEdit, currentClaim]); };
const removeDiagnosaFiles = (filesState, index) => {
const fileSelected = (event, type) => { setFileDiagnosas(
const files = event.target.files; filesState.filter((file, fileIndex) => {
const currentFiles = getValues(`uploaded_files.${type}`) ?? []; return fileIndex != index;
})
setValue(`uploaded_files.${type}`, [...currentFiles, ...files]); );
console.log('currentFiles', getValues('uploaded_files'));
}; };
const memberSelected = (member) => { // Files Result Hasil Penunjang
setMember(member) const fileHasilPenunjangInput = useRef<HTMLInputElement>(null);
const [fileHasilPenunjangs, setFileHasilPenunjangs] = 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 checkLimit = async () => {
console.log('CHECKING LIMIT');
};
const onSubmit = async (data: any) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
if (!isEdit) { const formData = new FormData();
const response = await axios.post('/claims', data); formData.append('result_files', fileHasilPenunjangs);
} else { formData.append('diagnosa_files', fileDiagnosaInput);
const response = await axios.put('/claims/' + currentClaim?.id ?? '', data); formData.append('kondisi_files', fileKondisiInput);
} formData.append('provider_code', data.organization_id);
formData.append('_method', 'PUT');
const response = await axios.post(`/claim-requests/${data.id}`, formData);
reset(); reset();
enqueueSnackbar( enqueueSnackbar('Claim Request Updated Successfully!', { variant: 'success' });
!isEdit ? 'Organizations Created Successfully!' : 'Organizations Udpated Successfully!', navigate('/claim-requests');
{ variant: 'success' }
);
navigate('/claims');
} catch (error: any) { } catch (error: any) {
if (error && error.response.status === 422) { if (error && error.response.status === 422) {
for (const [key, value] of Object.entries(error.response.data.errors)) { for (const [key, value] of Object.entries(error.response.data.errors)) {
setError(key, { message: value[0] }); // setError(key, { message: value[0] });
enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); enqueueSnackbar('Failed Processing Request', { variant: 'error' });
} }
} else { } else {
enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' });
} }
} }
const ascent = document?.querySelector('ascent');
if (ascent != null) {
ascent.innerHTML = '';
}
}; };
function generate(files, element: React.ReactElement) {
return files.map((value) =>
React.cloneElement(element, {
key: value,
})
);
}
const headStyle = {};
return ( return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}> <FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}> <Card sx={{paddingX:2, paddingY:2}}>
<Typography variant="h6">Member</Typography> <Grid container spacing={2}>
<Grid item xs={5}>
<Stack spacing={2} direction="row"> <Typography variant="subtitle1">Code*</Typography>
<Grid item xs={12}>
<RHFTextField
name="member_id"
label="Member"
variant="outlined"
fullWidth
value={member?.name || ''}
InputProps={{
readOnly: true,
}}
onClick={() => {
if (!isEdit) setIsMemberDialogOpen(true);
if (isEdit) enqueueSnackbar('Cannot Change Member', { variant: 'error' });
}}
/>
</Grid> </Grid>
{/* <Grid item xs={2}> <Grid item xs={7}>
<Button variant="outlined" fullWidth sx={{ p: 1.8 }} onClick={() => { <Typography variant="subtitle1">Name*</Typography>
setIsMemberDialogOpen(true) </Grid>
}}> <Grid item xs={5}>
{member ? 'Change' : 'Search'} <RHFTextField name="code" label="Code" disabled/>
</Button> </Grid>
</Grid> */} <Grid item xs={7}>
</Stack> <RHFTextField name="member_name" label="Name" disabled/>
</Grid>
{/* <input type="hidden" name="id"/> */}
{member?.id && (
<Stack> <Grid item xs={12}></Grid>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Table border="light-700">
<TableBody>
<TableRow>
<TableCell style={headStyle} align="left">
Name
</TableCell>
<TableCell align="left">{member?.full_name}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
DOB
</TableCell>
<TableCell align="left">
{member?.birth_date} ({member?.age + ' years'})
</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Marital Status
</TableCell>
<TableCell align="left">{member?.marital_status}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Record Type
</TableCell>
<TableCell align="left">{member?.record_type}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Principal ID
</TableCell>
<TableCell align="left">
{member?.principal_id} (
{member?.relation_with_principal})
</TableCell>
</TableRow>
</TableBody>
</Table>
</Grid>
<Grid item xs={12} md={6}>
<Table border="light-700">
<TableBody>
<TableRow>
<TableCell style={headStyle} align="left">
Plan
</TableCell>
<TableCell align="left">{member?.current_plan?.code}</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Active
</TableCell>
<TableCell align="left">
{member?.current_plan?.start} -{' '}
{member?.current_plan?.end} (Active)
</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Corporate Limit
</TableCell>
<TableCell align="left">
{fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)}
</TableCell>
</TableRow>
<TableRow>
<TableCell style={headStyle} align="left">
Plan Usage
</TableCell>
<TableCell align="left">
{fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</Grid>
</Grid>
</Stack>
)}
<Controller <Grid item xs={3}>
name="benefit" <Typography variant="subtitle1">Date of Submission*</Typography>
control={control} </Grid>
render={({ field: { onChange, value } }) => ( <Grid item xs={3}>
<Autocomplete <Typography variant="subtitle1">Claim Method*</Typography>
options={memberBenefits} </Grid>
getOptionLabel={(option) => <Grid item xs={3}>
option ? `#${option.id} (${option.code}) ${option.description}` : '' <Typography variant="subtitle1">Service Type*</Typography>
} </Grid>
value={value || ''} <Grid item xs={3}>
onChange={(event: any, newValue: any) => { <Typography variant="subtitle1">Code Provider*</Typography>
setValue('benefit_id', newValue?.id); </Grid>
onChange(newValue);
}}
renderInput={(params) => (
<TextField
name="benefit"
{...params}
label="Benefit"
variant="outlined"
fullWidth
// onKeyPress={(event) => {
// if (event.key === 'Enter')
// searchDiagnosis(event.target.value)
// }}
/>
)}
/>
)}
/>
<Controller
name="diagnosis"
control={control}
render={({ field: { onChange, value } }) => (
<Autocomplete
options={diagnosisOption}
getOptionLabel={(option) => (option ? `(${option.code}) ${option.name}` : '')}
value={value || ''}
onChange={(event: any, newValue: any) => {
setValue('diagnosis_id', newValue?.id);
// setValue('diagnosis', newValue)
onChange(newValue);
}}
renderInput={(params) => (
<TextField
name="diagnosis"
{...params}
label="Diagnosis"
variant="outlined"
fullWidth
onKeyPress={(event) => {
if (event.key === 'Enter') searchDiagnosis(event.target.value);
}}
/>
)}
/>
)}
/>
{isCheckingLimit && ( <Grid item xs={3}>
<Stack <RHFTextField InputProps={{endAdornment: (
sx={{ <InputAdornment position="end">
backgroundColor: 'gray', <CalendarTodayIcon />
paddingY: 1, </InputAdornment>
paddingX: 1.5, ), }}
mb: 2, name="date" label="Date of Submission" disabled/>
borderRadius: '3-xl', </Grid>
}} <Grid item xs={3}>
> <RHFTextField name="claim_method" label="Claim Method" disabled/>
{/* Checking */} </Grid>
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}> <Grid item xs={3}>
<Iconify <RHFTextField name="service_type" label="Service Type*" disabled/>
icon="bxs:info-circle" </Grid>
width={12} <Grid item xs={3}>
height={13} <RHFTextField name="organization_id" label="Code Provider*"/>
sx={{ color: '#424242', marginRight: '6px' }} </Grid>
/>
<Typography variant="caption" component="span">
Please Wait, Checking Eligibilty
</Typography>
</Typography>
</Stack>
)}
{false && isCheckingLimit == false && isEligible == null && (
<Stack
sx={{
backgroundColor: 'gray',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* No Data Selected */}
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:info-circle"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Please Select Diagnosis !
</Typography>
</Typography>
</Stack>
)}
{!isCheckingLimit && isEligible !== null && isEligible && (
<Stack
sx={{
backgroundColor: '#B2E8E8',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* Eligible */}
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:lock-alt"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Diagnosis is Eligible
</Typography>
</Typography>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
125.000.000 / 125.000.000
</Typography>
</Stack>
)}
{!isCheckingLimit && isEligible !== null && !isEligible && (
<Stack
sx={{
backgroundColor: '#B2E8E8',
paddingY: 1,
paddingX: 1.5,
mb: 2,
borderRadius: '3-xl',
}}
>
{/* Not Eligible */}
{/* <Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}>
<Iconify
icon="bxs:lock-alt"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<Typography variant="caption" component="span">
Not Eligible
</Typography>
</Typography>
<Typography sx={{ typography: 'caption', color: '#637381' }}>
125.000.000 / 125.000.000
</Typography> */}
</Stack>
)}
<RHFTextField type="number" name="total_claim" label="Total Claim" />
{isEdit && ( {/* -------------------------------Upload Dokumen Kondisi------------------------------- */}
<React.Fragment> <React.Fragment>
<Typography variant="h6">Documents</Typography> <Grid item xs={12}>
<Typography variant='h6'> Condition Document</Typography>
<List> </Grid>
{(getValues('uploaded_files.invoice') && getValues('uploaded_files.invoice').length <Grid item xs={12}>
? getValues('uploaded_files.invoice') {fileKondisis &&
: [] fileKondisis.map((file, index) => (
).map((file, index) => ( <Stack sx={{marginTop: 2}} direction="row" justifyContent={'space-between'} key={index}>
<ListItem <Typography sx={{ color: "text.secondary" }}>{file.name}</Typography>
secondaryAction={ <Iconify
<IconButton edge="end" aria-label="delete"> icon="eva:trash-2-outline"
<DeleteOutline /> color={'darkred'}
</IconButton> onClick={() => {
} removeKondisiFiles(fileKondisis, index);
> }}
<ListItemAvatar> ></Iconify>
<Avatar> </Stack>
{/* <FileIcon /> */}
I
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))} ))}
</List> </Grid>
<Button <Grid item xs={12}>
variant="outlined" <ButtonBase sx={{ p: 4, border: '2px dashed #F9FAFB',
startIcon={<Add />} bgcolor: '#919EAB52',
component="label" borderRadius: '8px',
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }} width: '100%', height: '60px'}} onClick={() => fileKondisiInput.current?.click()}>
> <Box
Invoice sx={{
<input display: 'flex',
name="invoice" placeItems: 'center',
hidden gap: 1,
accept="image/*,application/pdf" placeContent: 'center',
multiple }}
type="file" >
onChange={(event) => { <Iconify icon="icon-park-outline:upload-one" fontSize="3em" />
fileSelected(event, 'invoice'); <Typography variant="body1" fontWeight="bold">
}} Add File
/> </Typography>
</Button> </Box>
<List> <input
{(getValues('uploaded_files.prescription') && getValues('uploaded_files.prescription').length type="file"
? getValues('uploaded_files.prescription') id="file"
: [] ref={fileKondisiInput}
).map((file, index) => ( style={{ display: 'none' }}
<ListItem multiple
secondaryAction={ onChange={handleKondisiInputChange}
<IconButton edge="end" aria-label="delete"> accept="application/pdf"
<DeleteOutline /> />
</IconButton> </ButtonBase>
} </Grid>
> </React.Fragment>
<ListItemAvatar> {/* -------------------------------Upload Dokumen Diagnosa------------------------------- */}
<Avatar> <React.Fragment>
{/* <FileIcon /> */} <Grid item xs={12}>
P <Typography variant='h6'> Diagnosis Document</Typography>
</Avatar> </Grid>
</ListItemAvatar> <Grid item xs={12}>
<ListItemText primary={file.name} secondary={file.type} /> {fileDiagnosas &&
</ListItem> fileDiagnosas.map((file, index) => (
))} <Stack sx={{marginTop: 2}} direction="row" justifyContent={'space-between'} key={index}>
</List> <Typography sx={{ color: "text.secondary" }}>{file.name}</Typography>
<Button <Iconify
variant="outlined" icon="eva:trash-2-outline"
startIcon={<Add />} color={'darkred'}
component="label" onClick={() => {
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }} removeDiagnosaFiles(fileDiagnosas, index);
> }}
Prescription ></Iconify>
<input </Stack>
name="prescription" ))}
hidden </Grid>
accept="image/*,application/pdf" <Grid item xs={12}>
multiple <ButtonBase sx={{ p: 4, border: '2px dashed #F9FAFB',
type="file" bgcolor: '#919EAB52',
onChange={(event) => { borderRadius: '8px',
fileSelected(event, 'prescription'); width: '100%', height: '60px'}} onClick={() => fileDiagnosaInput.current?.click()}>
}} <Box
/> sx={{
</Button> display: 'flex',
placeItems: 'center',
<List> gap: 1,
{(getValues('uploaded_files.diagnosis') && getValues('uploaded_files.diagnosis').length placeContent: 'center',
? getValues('uploaded_files.diagnosis') }}
: [] >
).map((file, index) => ( <Iconify icon="icon-park-outline:upload-one" fontSize="3em" />
<ListItem <Typography variant="body1" fontWeight="bold">
secondaryAction={ Add Result
<IconButton edge="end" aria-label="delete"> </Typography>
<DeleteOutline /> </Box>
</IconButton> <input
} type="file"
> id="file"
<ListItemAvatar> ref={fileDiagnosaInput}
<Avatar> style={{ display: 'none' }}
{/* <FileIcon /> */} multiple
DR onChange={handleDiagnosaInputChange}
</Avatar> accept="application/pdf"
</ListItemAvatar> />
<ListItemText primary={file.name} secondary={file.type} /> </ButtonBase>
</ListItem> </Grid>
))} </React.Fragment>
</List> {/* -------------------------------Upload Result Hasil Penunjang------------------------------- */}
<Button <React.Fragment>
variant="outlined" <Grid item xs={12}>
startIcon={<Add />} <Typography variant='h6'> Supporting Result Document</Typography>
component="label" </Grid>
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }} <Grid item xs={12}>
> {fileHasilPenunjangs &&
Doctor Result fileHasilPenunjangs.map((file, index) => (
<input <Stack sx={{marginTop: 2}} direction="row" justifyContent={'space-between'} key={index}>
name="invoice" <Typography sx={{ color: "text.secondary" }}>{file.name}</Typography>
hidden <Iconify
accept="image/*,application/pdf" icon="eva:trash-2-outline"
multiple color={'darkred'}
type="file" onClick={() => {
onChange={(event) => { removeFiles(fileHasilPenunjangs, index);
fileSelected(event, 'diagnosis'); }}
}} ></Iconify>
/> </Stack>
</Button> ))}
</Grid>
<Grid item xs={12}>
<ButtonBase sx={{ p: 4, border: '2px dashed #F9FAFB',
bgcolor: '#919EAB52',
borderRadius: '8px',
width: '100%', height: '60px'}} onClick={() => fileHasilPenunjangInput.current?.click()}>
<Box
sx={{
display: 'flex',
placeItems: 'center',
gap: 1,
placeContent: 'center',
}}
>
<Iconify icon="icon-park-outline:upload-one" fontSize="3em" />
<Typography variant="body1" fontWeight="bold">
Add Result
</Typography>
</Box>
<input
type="file"
id="file"
ref={fileHasilPenunjangInput}
style={{ display: 'none' }}
multiple
onChange={handleResultInputChange}
accept="application/pdf"
/>
</ButtonBase>
</Grid>
</React.Fragment> </React.Fragment>
)}
{isEligible === true ? ( </Grid>
<LoadingButton </Card>
onClick={handleSubmit(onSubmit)} <Grid container marginTop={3}>
variant="contained" <Grid item xs={12} md={12} >
color="success" <Stack direction="row" alignItems="center" justifyContent="flex-end">
style={{ color: '#ffffff' }} <Button
size="large" sx={{
fullWidth={true} margin: 1
loading={isCheckingLimit} }}
> type="submit"
Create Claim variant="contained"
</LoadingButton> size="large"
) : ( color='inherit'
<LoadingButton onClick={() => navigate(`/claim-requests`)}
onClick={checkLimit} >
variant="outlined" Cancel
size="large" </Button>
fullWidth={true} <LoadingButton
loading={isCheckingLimit} type="submit"
> variant="contained"
Check Limit size="large"
</LoadingButton> loading={isSubmitting}
)} >
</Stack> Update
</LoadingButton>
<MemberSelectDialog </Stack>
openDialog={isMemberDialogOpen} </Grid>
setOpenDialog={setIsMemberDialogOpen} </Grid>
onSelect={memberSelected}
></MemberSelectDialog>
</FormProvider> </FormProvider>
); );
} }

View File

@@ -17,12 +17,17 @@ import {
ButtonGroup, ButtonGroup,
Link, Link,
Chip, Chip,
TableHead,
Grid,
} from '@mui/material'; } from '@mui/material';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import UploadIcon from '@mui/icons-material/Upload'; import UploadIcon from '@mui/icons-material/Upload';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
// hooks // hooks
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
@@ -38,6 +43,10 @@ import { enqueueSnackbar } from 'notistack';
import { Divider } from '@mui/material'; import { Divider } from '@mui/material';
import Iconify from '@/components/Iconify'; import Iconify from '@/components/Iconify';
import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim'; 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 LoadingButton from '@/theme/overrides/LoadingButton'; // import LoadingButton from '@/theme/overrides/LoadingButton';
export default function List() { export default function List() {
@@ -45,6 +54,8 @@ export default function List() {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const [importResult, setImportResult] = useState(null); const [importResult, setImportResult] = useState(null);
const navigate = useNavigate()
function SearchInput(props: any) { function SearchInput(props: any) {
// SEARCH // SEARCH
const searchInput = useRef<HTMLInputElement>(null); const searchInput = useRef<HTMLInputElement>(null);
@@ -75,6 +86,7 @@ export default function List() {
fullWidth fullWidth
onChange={handleSearchChange} onChange={handleSearchChange}
value={searchText} value={searchText}
placeholder='Search Code or Name...'
/> />
</form> </form>
); );
@@ -87,26 +99,164 @@ export default function List() {
const createMenu = Boolean(anchorEl); const createMenu = Boolean(anchorEl);
const importForm = useRef<HTMLInputElement>(null); const importForm = useRef<HTMLInputElement>(null);
const [currentImportFileName, setCurrentImportFileName] = useState(null); const [currentImportFileName, setCurrentImportFileName] = useState(null);
const [importLoading, setImportLoading] = useState(false);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); 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 ( return (
<div> <div>
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}> <input
<SearchInput onSearch={applyFilter} /> type="file"
{/* <Button id="file"
variant="outlined" ref={importForm}
startIcon={<AddIcon />} style={{ display: 'none' }}
sx={{ p: 1.8 }} onChange={handleImportChange}
onClick={() => { accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
navigate('/claims/create'); />
}} {!currentImportFileName && (
> <Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
Create <SearchInput onSearch={applyFilter} />
</Button> */} <Button
</Stack> variant="outlined"
startIcon={<UploadIcon />}
sx={{ p: 1.8 }}
onClick={handleClick}
>
Import
</Button>
<Menu
id="import-button"
anchorEl={anchorEl}
open={createMenu}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleImportButton}>Import</MenuItem>
<MenuItem onClick={() => {handleGetTemplate('claim-request')}}>Download Template test</MenuItem>
<MenuItem onClick={() => {handleGetData('data-plan-benefit')}}>Download Claim Request</MenuItem>
</Menu>
<Button
variant="contained"
startIcon={<AddIcon />}
sx={{ p: 1.8 }}
onClick={() => {
navigate('/claims/create');
}}
>
Create
</Button>
</Stack>
)}
{currentImportFileName && (
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
<ButtonGroup variant="outlined" aria-label="outlined button group" fullWidth>
<Button onClick={handleImportButton} fullWidth>
{currentImportFileName ?? 'No File Selected'}
</Button>
<Button
onClick={handleCancelImportButton}
size="small"
fullWidth={false}
sx={{ p: 1.8 }}
>
<CancelIcon color="error" />
</Button>
</ButtonGroup>
<LoadingButton
id="upload-button"
variant="outlined"
startIcon={<UploadIcon />}
sx={{ p: 1.8 }}
onClick={handleUpload}
loading={importLoading}
>
Upload
</LoadingButton>
</Stack>
)}
</div> </div>
); );
} }
@@ -176,43 +326,43 @@ export default function List() {
return ( return (
<React.Fragment> <React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}> <TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell> {/* <TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}> <IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />} {open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
</IconButton> </IconButton>
</TableCell> </TableCell> */ }
<TableCell align="left"> <TableCell align="left">
<Typography <Typography
onClick={() => { // onClick={() => {
handleShowClaim(row); // handleShowClaim(row);
}} // }}
color={themeColorPresets}
> >
{row.code} {row.code}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell align="left">{row.member?.full_name}</TableCell> <TableCell align="left">{row.member?.full_name}</TableCell>
<TableCell align="left">{row.member?.current_policy?.code}</TableCell> <TableCell align="left"><Label>{fDateTimesecond(row.submission_date)}</Label></TableCell>
<TableCell align="left">{row.submission_date}</TableCell>
<TableCell align="left">{row.service_name}</TableCell> <TableCell align="left">{row.service_name}</TableCell>
{/* <TableCell align="left">{row.payment_type_name}</TableCell> */} <TableCell align="left">{row.payment_type_name}</TableCell>
<TableCell align="left">{'-'}</TableCell> <TableCell align="left">
<TableCell align="right"> { row.status == "requested" ?
<Chip label={row.status} /> (<Label variant='ghost' color='primary'>{capitalizeFirstLetter(row.status)}</Label>) :
(<Label color='success'> {capitalizeFirstLetter(row.status)}</Label>)
}
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
{row.status == 'requested' && ( <TableMoreMenu actions={
<LoadingButton <>
loading={loadingApprove} <MenuItem onClick={() => navigate(`/claim-requests/edit/${row.id}`)}>
variant="outlined" <EditOutlinedIcon />
size="small" Edit
onClick={() => { </MenuItem>
handleApprove(row); <MenuItem onClick={() => ''}>
}} <FindInPageOutlinedIcon />
> Detail
Ajukan Claim <Iconify icon="eva:arrow-forward-outline"></Iconify> </MenuItem>
</LoadingButton> </>
)} } />
</TableCell> </TableCell>
{/* <TableCell> {/* <TableCell>
@@ -306,9 +456,9 @@ export default function List() {
return ( return (
<Table aria-label="collapsible table"> <Table aria-label="collapsible table">
{/* ------------------ TABLE HEADER ------------------ */} {/* ------------------ TABLE HEADER ------------------ */}
<TableBody> <TableHead>
<TableRow> <TableRow>
<TableCell style={headStyle} align="left" /> {/* <TableCell style={headStyle} align="left" /> */}
<TableCell style={headStyle} align="left"> <TableCell style={headStyle} align="left">
Code Code
</TableCell> </TableCell>
@@ -316,23 +466,20 @@ export default function List() {
Name Name
</TableCell> </TableCell>
<TableCell style={headStyle} align="left"> <TableCell style={headStyle} align="left">
Nomor Polis Date of Submission
</TableCell>
<TableCell style={headStyle} align="left">
Tanggal Pengajuan
</TableCell> </TableCell>
<TableCell style={headStyle} align="left"> <TableCell style={headStyle} align="left">
Service Type Service Type
</TableCell> </TableCell>
<TableCell style={headStyle} align="left"> <TableCell style={headStyle} align="left">
Claim Type Claim Method
</TableCell> </TableCell>
<TableCell style={headStyle} align="left"> <TableCell style={headStyle} align="left">
Status Status
</TableCell> </TableCell>
<TableCell style={headStyle} align="right"></TableCell> <TableCell style={headStyle} align="right"></TableCell>
</TableRow> </TableRow>
</TableBody> </TableHead>
{/* ------------------ END TABLE HEADER ------------------ */} {/* ------------------ END TABLE HEADER ------------------ */}
{/* ------------------ TABLE ROW ------------------ */} {/* ------------------ TABLE ROW ------------------ */}
@@ -388,23 +535,28 @@ export default function List() {
function handleDownloadLog() {} function handleDownloadLog() {}
return ( return (
<Card> <Grid container>
<ImportForm /> <Grid item sm={12}>
<ImportForm />
</Grid>
<DataTable <Grid item sm={12}>
isLoading={dataTableIsLoading} <DataTable
lastRequest={0} isLoading={dataTableIsLoading}
data={dataTableData} lastRequest={0}
handlePageChange={handlePageChange} data={dataTableData}
TableContent={<TableContent />} handlePageChange={handlePageChange}
/> TableContent={<TableContent />}
/>
<DialogDetailClaim </Grid>
openDialog={openDialogDetailClaim} <Grid item sm={12}>
setOpenDialog={setOpenDialogDetailClaim} <DialogDetailClaim
title={{ name: 'Claim Request Detail' }} openDialog={openDialogDetailClaim}
data={{ claim: currentClaim, isLoading: loadingClaimDetail, handleDownloadLog }} setOpenDialog={setOpenDialogDetailClaim}
></DialogDetailClaim> title={{ name: 'Claim Request Detail' }}
</Card> data={{ claim: currentClaim, isLoading: loadingClaimDetail, handleDownloadLog }}
></DialogDetailClaim>
</Grid>
</Grid>
); );
} }

View File

@@ -369,10 +369,18 @@ export default function Router() {
path: 'claim-requests', path: 'claim-requests',
element: <ClaimRequests />, element: <ClaimRequests />,
}, },
{
path: 'claim-requests/edit/:id',
element: <ClaimRequestsCreate />,
},
{ {
path: 'claims/create', path: 'claims/create',
element: <ClaimsCreate />, element: <ClaimsCreate />,
}, },
{
path: 'claims/edit/:id',
element: <ClaimsCreate />,
},
{ {
path: 'claims/:id', path: 'claims/:id',
element: <ClaimShow />, element: <ClaimShow />,
@@ -540,6 +548,7 @@ const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate'))
const ClaimShow = Loadable(lazy(() => import('../pages/Claims/Show'))); const ClaimShow = Loadable(lazy(() => import('../pages/Claims/Show')));
const ClaimRequests = Loadable(lazy(() => import('../pages/ClaimRequests/Index'))); const ClaimRequests = Loadable(lazy(() => import('../pages/ClaimRequests/Index')));
const ClaimRequestsCreate = Loadable(lazy(() => import('../pages/ClaimRequests/CreateUpdate')));
const Membership = Loadable(lazy(() => import('../pages/Service/Membership/index'))); const Membership = Loadable(lazy(() => import('../pages/Service/Membership/index')));

View File

@@ -1,3 +1,5 @@
import { string } from "yup";
export function clearTag(htmlString: string | undefined) { export function clearTag(htmlString: string | undefined) {
return htmlString?.replace(/(<([^>]+)>)/gi, ""); return htmlString?.replace(/(<([^>]+)>)/gi, "");
} }
@@ -9,3 +11,9 @@ export function limitString(anyString: string | undefined, limit: number = 50) {
export function makeExcerpt(htmlString: string | undefined, limit: number = 50) { export function makeExcerpt(htmlString: string | undefined, limit: number = 50) {
return limitString(clearTag(htmlString ?? ''), limit); return limitString(clearTag(htmlString ?? ''), limit);
} }
export function capitalizeFirstLetter(text: string) {
return text.charAt(0).toUpperCase() + text.slice(1);
}

View File

@@ -10,6 +10,10 @@ export function fDateTime(date: Date | string | number) {
return format(new Date(date), 'dd MMM yyyy p'); return format(new Date(date), 'dd MMM yyyy p');
} }
export function fDateTimesecond(date: Date | string | number) {
return format(new Date(date), 'dd MMM yyyy HH:mm:ss');
}
export function fTimestamp(date: Date | string | number) { export function fTimestamp(date: Date | string | number) {
return getTime(new Date(date)); return getTime(new Date(date));
} }

Binary file not shown.