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(() => { // Files Result Kondisi
console.log('defaultValues', defaultValues); const fileKondisiInput = useRef<HTMLInputElement>(null);
if (isEdit && currentClaim) { const [fileKondisis, setFileKondisis] = useState<Files>([]);
reset(defaultValues);
setMember(defaultValues.member)
}
if (!isEdit) {
reset(defaultValues);
setMember(defaultValues.member)
}
}, [isEdit, currentClaim]);
const fileSelected = (event, type) => { const handleKondisiInputChange = (event) => {
const files = event.target.files; if (event.target.files[0]) {
const currentFiles = getValues(`uploaded_files.${type}`) ?? []; setFileKondisis([...fileKondisis, ...event.target.files]);
setValue(`uploaded_files.${type}`, [...currentFiles, ...files]);
console.log('currentFiles', getValues('uploaded_files'));
};
const memberSelected = (member) => {
setMember(member)
};
const checkLimit = async () => {
console.log('CHECKING LIMIT');
};
const onSubmit = async (data: any) => {
try {
if (!isEdit) {
const response = await axios.post('/claims', data);
} else { } else {
const response = await axios.put('/claims/' + currentClaim?.id ?? '', data); console.log('NO FILE');
} }
reset(); };
enqueueSnackbar( const removeKondisiFiles = (filesState, index) => {
!isEdit ? 'Organizations Created Successfully!' : 'Organizations Udpated Successfully!', setFileKondisis(
{ variant: 'success' } filesState.filter((file, fileIndex) => {
return fileIndex != index;
})
); );
navigate('/claims'); };
// 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');
}
};
const removeDiagnosaFiles = (filesState, index) => {
setFileDiagnosas(
filesState.filter((file, fileIndex) => {
return fileIndex != index;
})
);
};
// Files Result Hasil Penunjang
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 onSubmit = async (data: FormValuesProps) => {
try {
const formData = new FormData();
formData.append('result_files', fileHasilPenunjangs);
formData.append('diagnosa_files', fileDiagnosaInput);
formData.append('kondisi_files', fileKondisiInput);
formData.append('provider_code', data.organization_id);
formData.append('_method', 'PUT');
const response = await axios.post(`/claim-requests/${data.id}`, formData);
reset();
enqueueSnackbar('Claim Request Updated Successfully!', { variant: 'success' });
navigate('/claim-requests');
} 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>
<Stack spacing={2} direction="row">
<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 item xs={2}>
<Button variant="outlined" fullWidth sx={{ p: 1.8 }} onClick={() => {
setIsMemberDialogOpen(true)
}}>
{member ? 'Change' : 'Search'}
</Button>
</Grid> */}
</Stack>
{member?.id && (
<Stack>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} md={6}> <Grid item xs={5}>
<Table border="light-700"> <Typography variant="subtitle1">Code*</Typography>
<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>
<Grid item xs={12} md={6}> <Grid item xs={7}>
<Table border="light-700"> <Typography variant="subtitle1">Name*</Typography>
<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>
<Grid item xs={5}>
<RHFTextField name="code" label="Code" disabled/>
</Grid> </Grid>
</Stack> <Grid item xs={7}>
)} <RHFTextField name="member_name" label="Name" disabled/>
</Grid>
{/* <input type="hidden" name="id"/> */}
<Controller
name="benefit"
control={control}
render={({ field: { onChange, value } }) => (
<Autocomplete
options={memberBenefits}
getOptionLabel={(option) =>
option ? `#${option.id} (${option.code}) ${option.description}` : ''
}
value={value || ''}
onChange={(event: any, newValue: any) => {
setValue('benefit_id', newValue?.id);
onChange(newValue);
}}
renderInput={(params) => (
<TextField
name="benefit"
{...params}
label="Benefit"
variant="outlined"
fullWidth
// onKeyPress={(event) => {
// if (event.key === 'Enter')
// searchDiagnosis(event.target.value)
// }}
/>
)}
/>
)}
/>
<Controller <Grid item xs={12}></Grid>
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 <Typography variant="subtitle1">Date of Submission*</Typography>
sx={{ </Grid>
backgroundColor: 'gray', <Grid item xs={3}>
paddingY: 1, <Typography variant="subtitle1">Claim Method*</Typography>
paddingX: 1.5, </Grid>
mb: 2, <Grid item xs={3}>
borderRadius: '3-xl', <Typography variant="subtitle1">Service Type*</Typography>
}} </Grid>
> <Grid item xs={3}>
{/* Checking */} <Typography variant="subtitle1">Code Provider*</Typography>
<Typography sx={{ typography: 'caption', display: 'flex', alignItems: 'center' }}> </Grid>
<Iconify
icon="bxs:info-circle"
width={12}
height={13}
sx={{ color: '#424242', marginRight: '6px' }}
/>
<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 && ( <Grid item xs={3}>
<RHFTextField InputProps={{endAdornment: (
<InputAdornment position="end">
<CalendarTodayIcon />
</InputAdornment>
), }}
name="date" label="Date of Submission" disabled/>
</Grid>
<Grid item xs={3}>
<RHFTextField name="claim_method" label="Claim Method" disabled/>
</Grid>
<Grid item xs={3}>
<RHFTextField name="service_type" label="Service Type*" disabled/>
</Grid>
<Grid item xs={3}>
<RHFTextField name="organization_id" label="Code Provider*"/>
</Grid>
{/* -------------------------------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>
<Avatar>
{/* <FileIcon /> */}
I
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))}
</List>
<Button
variant="outlined"
startIcon={<Add />}
component="label"
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }}
>
Invoice
<input
name="invoice"
hidden
accept="image/*,application/pdf"
multiple
type="file"
onChange={(event) => {
fileSelected(event, 'invoice');
}} }}
/> ></Iconify>
</Button>
<List>
{(getValues('uploaded_files.prescription') && getValues('uploaded_files.prescription').length
? getValues('uploaded_files.prescription')
: []
).map((file, index) => (
<ListItem
secondaryAction={
<IconButton edge="end" aria-label="delete">
<DeleteOutline />
</IconButton>
}
>
<ListItemAvatar>
<Avatar>
{/* <FileIcon /> */}
P
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))}
</List>
<Button
variant="outlined"
startIcon={<Add />}
component="label"
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }}
>
Prescription
<input
name="prescription"
hidden
accept="image/*,application/pdf"
multiple
type="file"
onChange={(event) => {
fileSelected(event, 'prescription');
}}
/>
</Button>
<List>
{(getValues('uploaded_files.diagnosis') && getValues('uploaded_files.diagnosis').length
? getValues('uploaded_files.diagnosis')
: []
).map((file, index) => (
<ListItem
secondaryAction={
<IconButton edge="end" aria-label="delete">
<DeleteOutline />
</IconButton>
}
>
<ListItemAvatar>
<Avatar>
{/* <FileIcon /> */}
DR
</Avatar>
</ListItemAvatar>
<ListItemText primary={file.name} secondary={file.type} />
</ListItem>
))}
</List>
<Button
variant="outlined"
startIcon={<Add />}
component="label"
sx={{ paddingY: 2, width: '100%', ':hover': { border: 'none' } }}
>
Doctor Result
<input
name="invoice"
hidden
accept="image/*,application/pdf"
multiple
type="file"
onChange={(event) => {
fileSelected(event, 'diagnosis');
}}
/>
</Button>
</React.Fragment>
)}
{isEligible === true ? (
<LoadingButton
onClick={handleSubmit(onSubmit)}
variant="contained"
color="success"
style={{ color: '#ffffff' }}
size="large"
fullWidth={true}
loading={isCheckingLimit}
>
Create Claim
</LoadingButton>
) : (
<LoadingButton
onClick={checkLimit}
variant="outlined"
size="large"
fullWidth={true}
loading={isCheckingLimit}
>
Check Limit
</LoadingButton>
)}
</Stack> </Stack>
))}
</Grid>
<Grid item xs={12}>
<ButtonBase sx={{ p: 4, border: '2px dashed #F9FAFB',
bgcolor: '#919EAB52',
borderRadius: '8px',
width: '100%', height: '60px'}} onClick={() => fileKondisiInput.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 File
</Typography>
</Box>
<input
type="file"
id="file"
ref={fileKondisiInput}
style={{ display: 'none' }}
multiple
onChange={handleKondisiInputChange}
accept="application/pdf"
/>
</ButtonBase>
</Grid>
</React.Fragment>
{/* -------------------------------Upload Dokumen Diagnosa------------------------------- */}
<React.Fragment>
<Grid item xs={12}>
<Typography variant='h6'> Diagnosis Document</Typography>
</Grid>
<Grid item xs={12}>
{fileDiagnosas &&
fileDiagnosas.map((file, index) => (
<Stack sx={{marginTop: 2}} direction="row" justifyContent={'space-between'} key={index}>
<Typography sx={{ color: "text.secondary" }}>{file.name}</Typography>
<Iconify
icon="eva:trash-2-outline"
color={'darkred'}
onClick={() => {
removeDiagnosaFiles(fileDiagnosas, index);
}}
></Iconify>
</Stack>
))}
</Grid>
<Grid item xs={12}>
<ButtonBase sx={{ p: 4, border: '2px dashed #F9FAFB',
bgcolor: '#919EAB52',
borderRadius: '8px',
width: '100%', height: '60px'}} onClick={() => fileDiagnosaInput.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={fileDiagnosaInput}
style={{ display: 'none' }}
multiple
onChange={handleDiagnosaInputChange}
accept="application/pdf"
/>
</ButtonBase>
</Grid>
</React.Fragment>
{/* -------------------------------Upload Result Hasil Penunjang------------------------------- */}
<React.Fragment>
<Grid item xs={12}>
<Typography variant='h6'> Supporting Result Document</Typography>
</Grid>
<Grid item xs={12}>
{fileHasilPenunjangs &&
fileHasilPenunjangs.map((file, index) => (
<Stack sx={{marginTop: 2}} direction="row" justifyContent={'space-between'} key={index}>
<Typography sx={{ color: "text.secondary" }}>{file.name}</Typography>
<Iconify
icon="eva:trash-2-outline"
color={'darkred'}
onClick={() => {
removeFiles(fileHasilPenunjangs, index);
}}
></Iconify>
</Stack>
))}
</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>
<MemberSelectDialog </Grid>
openDialog={isMemberDialogOpen} </Card>
setOpenDialog={setIsMemberDialogOpen} <Grid container marginTop={3}>
onSelect={memberSelected} <Grid item xs={12} md={12} >
></MemberSelectDialog> <Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button
sx={{
margin: 1
}}
type="submit"
variant="contained"
size="large"
color='inherit'
onClick={() => navigate(`/claim-requests`)}
>
Cancel
</Button>
<LoadingButton
type="submit"
variant="contained"
size="large"
loading={isSubmitting}
>
Update
</LoadingButton>
</Stack>
</Grid>
</Grid>
</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,17 +99,125 @@ 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>
<input
type="file"
id="file"
ref={importForm}
style={{ display: 'none' }}
onChange={handleImportChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
{!currentImportFileName && (
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}> <Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
<SearchInput onSearch={applyFilter} /> <SearchInput onSearch={applyFilter} />
{/* <Button <Button
variant="outlined" 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 />} startIcon={<AddIcon />}
sx={{ p: 1.8 }} sx={{ p: 1.8 }}
onClick={() => { onClick={() => {
@@ -105,8 +225,38 @@ export default function List() {
}} }}
> >
Create Create
</Button> */} </Button>
</Stack> </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,9 +535,12 @@ export default function List() {
function handleDownloadLog() {} function handleDownloadLog() {}
return ( return (
<Card> <Grid container>
<Grid item sm={12}>
<ImportForm /> <ImportForm />
</Grid>
<Grid item sm={12}>
<DataTable <DataTable
isLoading={dataTableIsLoading} isLoading={dataTableIsLoading}
lastRequest={0} lastRequest={0}
@@ -398,13 +548,15 @@ export default function List() {
handlePageChange={handlePageChange} handlePageChange={handlePageChange}
TableContent={<TableContent />} TableContent={<TableContent />}
/> />
</Grid>
<Grid item sm={12}>
<DialogDetailClaim <DialogDetailClaim
openDialog={openDialogDetailClaim} openDialog={openDialogDetailClaim}
setOpenDialog={setOpenDialogDetailClaim} setOpenDialog={setOpenDialogDetailClaim}
title={{ name: 'Claim Request Detail' }} title={{ name: 'Claim Request Detail' }}
data={{ claim: currentClaim, isLoading: loadingClaimDetail, handleDownloadLog }} data={{ claim: currentClaim, isLoading: loadingClaimDetail, handleDownloadLog }}
></DialogDetailClaim> ></DialogDetailClaim>
</Card> </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.