[WIP] Update Claim

This commit is contained in:
R
2023-03-06 02:10:08 +07:00
parent 91ba718a50
commit 35119ee8ec
18 changed files with 1066 additions and 18 deletions

View File

@@ -0,0 +1,49 @@
import axios from "@/utils/axios";
import { Autocomplete, TextField, CircularProgress } from "@mui/material";
import { useState } from "react";
export default function AutocompleteDiagnosis({ onChange, textLabel, currentValue = null })
{
const [options, setOptions] = useState([])
const [loading, setLoading] = useState(false)
return (<Autocomplete
defaultValue={currentValue ?? null}
onChange={(event, value) => {onChange(value)}}
isOptionEqualToValue={(option, value) => option.title === value.title}
getOptionLabel={(option) => option.title}
options={options}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label={textLabel != null ? textLabel : "Diagnosa ICD-X"}
onChange={(event) => {
setLoading(true);
axios.get('options?type=diagnosis&search='+event.target.value)
.then((res) => {
setOptions(res.data.map(function(icd) {
return {
title: icd.code + '-' + icd.name,
value: icd.id
}
}))
})
.then(() => {
setLoading(false);
})
}}
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</>
),
}}
/>
)}
/>
)
}

View File

@@ -0,0 +1,395 @@
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import {
Autocomplete,
Box,
Button,
Card,
Collapse,
Container,
Divider,
Grid,
InputAdornment,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableRow,
TextField,
Typography,
} from '@mui/material';
import { Controller, useForm } from 'react-hook-form';
import { useParams, useNavigate } from 'react-router-dom';
import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs';
import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form';
import Page from '../../components/Page';
import useSettings from '../../hooks/useSettings';
import { useEffect, useMemo, useRef, useState } from 'react';
import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog';
import { styled } from '@mui/system';
import axios from '../../utils/axios';
import { enqueueSnackbar } from 'notistack';
import { LoadingButton } from '@mui/lab';
import { fCurrency } from '../../utils/formatNumber';
import Iconify from '../../components/Iconify';
import Form from './Form';
import Documents from './components/Documents';
import DiagnosisHistory from './components/DiagnosisHistory';
import ClaimItems from './components/ClaimItems';
import DialogMemberBenefit from './components/DialogMemberBenefit';
import AutocompleteDiagnosis from '@/components/autocomplete/AutocompleteDiagnosis';
export default function ClaimsCreateUpdate() {
const { themeStretch } = useSettings();
const { id } = useParams();
const isEdit = id ? true : false;
const [currentClaim, setCurrentClaim] = useState();
const [documents, setDocuments] = useState([]);
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
// --------------------------------------------------------------
// Claim Item
const [claimItems, setClaimItems] = useState([]);
const [dialogAddClaimItemOpen, setDialogAddClaimItemOpen] = useState(false);
const [loadingClaimItems, setLoadingClaimItems] = useState(false);
const handleAddClaimItems = (items) => {
setClaimItems([...claimItems, ...items]);
};
const handleSaveClaimItems = () => {
console.log('Storing ', claimItems);
setLoadingClaimItems(true);
axios
.post(`claims/${id}/update-items`, {
benefit_items: claimItems.map((benefit) => {
return {
id: benefit.id,
biaya_diajukan: benefit.biaya_diajukan,
biaya_disetujui: benefit.biaya_disetujui,
};
}),
})
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
})
.catch((err) => {
setLoadingClaimItems(false);
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
setLoadingClaimItems(false);
});
};
// --------------------------------------------------------------
// Diagnosis
const [primaryDiagnosis, setPrimaryDiagnosis] = useState(null);
const [secondaryDiagnosis, setSecondaryDiagnosis] = useState(null);
const [loadingDiagnosis, setLoadingDiagnosis] = useState(false);
const handlePrimaryDiagnosisChange = ({ title, value }) => {
setPrimaryDiagnosis(value);
};
const handleSecondaryDiagnosisChange = ({ title, value }) => {
setSecondaryDiagnosis(value);
};
const handleSaveDiagnosis = () => {
setLoadingDiagnosis(true);
axios
.post(`claims/${id}/update-diagnosis`, {
primary: [primaryDiagnosis],
secondary: [secondaryDiagnosis],
})
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
})
.catch((err) => {
setLoadingDiagnosis(false);
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
setLoadingDiagnosis(false);
});
};
const handleDecline = () => {
axios
.post(`claims/${id}/decline`)
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
setCurrentClaim({ ...currentClaim, status: 'declined' });
})
.catch((err) => {
// setLoadingDiagnosis(false)
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
// setLoadingDiagnosis(false)
});
};
const handleApprove = () => {
axios
.post(`claims/${id}/approve`)
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
setCurrentClaim({ ...currentClaim, status: 'approved' });
})
.catch((err) => {
// setLoadingDiagnosis(false)
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
// setLoadingDiagnosis(false)
});
};
const handleReOpen = () => {
axios
.post(`claims/${id}/re-open`)
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
setCurrentClaim({ ...currentClaim, status: 'received' });
})
.catch((err) => {
// setLoadingDiagnosis(false)
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
// setLoadingDiagnosis(false)
});
};
useEffect(() => {
axios.get('/claims/' + id).then(({ data }) => {
const claim = data.data;
const allFiles = [...claim.claim_request.files, ...claim.files];
setCurrentClaim(claim);
setDocuments(allFiles);
setClaimItems(claim.benefit_items);
});
}, [id]);
return (
<Page title={`Claim : ${currentClaim?.code}`}>
<Container maxWidth={themeStretch ? false : 'xl'}>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<HeaderBreadcrumbs
heading={`Claim : ${currentClaim?.code}`}
links={[
{ name: 'Dashboard', href: '/dashboard' },
{
name: 'Claim',
href: '/claims',
},
{ name: currentClaim?.code ?? '' },
]}
/>
{/* Action Button */}
<Stack direction="row" spacing={2} sx={{ position: 'relative', bottom: '15px' }}>
{(currentClaim?.status == 'requested' || currentClaim?.status == 'received') && (
<>
<LoadingButton
loading={false}
variant="outlined"
color="error"
onClick={() => {
handleDecline();
}}
>
Decline
</LoadingButton>
<LoadingButton
loading={false}
variant="contained"
onClick={() => {
handleApprove();
}}
>
Approve
</LoadingButton>
</>
)}
{(currentClaim?.status == 'declined' || currentClaim?.status == 'approved') && (
<LoadingButton
loading={false}
variant="contained"
onClick={() => {
handleReOpen();
}}
>
Re-Open
</LoadingButton>
)}
</Stack>
</Stack>
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginY: 2 }}>
<Typography>Status : {currentClaim?.status}</Typography>
</Paper>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={5}>
{/* Dokumen Tambahan */}
<Documents files={documents}></Documents>
{/* Riwayat Diagnosa */}
<DiagnosisHistory diagnosis={[]}></DiagnosisHistory>
{/* Ringkasan Data Member */}
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="row" alignItems="center" spacing={1}>
<Iconify icon="eva:bell-fill" />
<Typography variant="body2" fontWeight={600}>
Ringkasan Data Nasabah
</Typography>
</Stack>
<Iconify icon="eva:eye-fill" />
</Stack>
<Paper sx={{ background: 'white', marginTop: 2, p: 2 }}>
<Stack>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Stack>
<Typography variant="body2" fontWeight={600}>
Nama Lengkap
</Typography>
<Typography variant="body2">
{currentClaim?.member?.full_name}
</Typography>
</Stack>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" fontWeight={600}>
Nomor Polis
</Typography>
<Typography variant="body2">{currentClaim?.member?.full_name}</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" fontWeight={600}>
Member ID
</Typography>
<Typography variant="body2">{currentClaim?.member?.member_id}</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" fontWeight={600}>
Tipe Claim
</Typography>
<Typography variant="body2">
{currentClaim?.claim_request.payment_type_name}
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" fontWeight={600}>
Tipe Nasabah
</Typography>
<Typography variant="body2">
{currentClaim?.member?.current_corporate?.name}
</Typography>
</Grid>
</Grid>
</Box>
</Stack>
</Paper>
</Paper>
</Grid>
<Grid item xs={7}>
{/* Diagnosis */}
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2 }}>
<Paper variant="outlined" sx={{ background: 'white', p: 2 }}>
<Stack spacing={2}>
<AutocompleteDiagnosis
onChange={handlePrimaryDiagnosisChange}
textLabel="Diagnosa Utama (ICD-X)"
currentValue={
currentClaim?.primary_diagnosis ? currentClaim?.primary_diagnosis : null
}
></AutocompleteDiagnosis>
<AutocompleteDiagnosis
onChange={handleSecondaryDiagnosisChange}
textLabel="Diagnosa Tambahan (ICD-X)"
currentValue={
currentClaim?.secondary_diagnosis ? currentClaim?.secondary_diagnosis : null
}
></AutocompleteDiagnosis>
</Stack>
</Paper>
<LoadingButton
variant="contained"
sx={{ marginTop: 2 }}
loading={loadingDiagnosis}
onClick={() => {
handleSaveDiagnosis();
}}
>
Simpan Claim Item
</LoadingButton>
</Paper>
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}>
<Stack direction="row" justifyContent="space-between">
<Typography sx={{ marginBottom: 1 }}>Client Benefit Configuration</Typography>
<Typography
onClick={() => {
setDialogAddClaimItemOpen(true);
}}
>
+ Add Benefit
</Typography>
</Stack>
<ClaimItems items={claimItems} setItems={setClaimItems}></ClaimItems>
<Stack alignItems={'flex-end'}>
<LoadingButton
variant="contained"
sx={{ marginTop: 2 }}
loading={loadingClaimItems}
onClick={() => {
handleSaveClaimItems();
}}
>
Simpan Claim Item
</LoadingButton>
</Stack>
<DialogMemberBenefit
openDialog={dialogAddClaimItemOpen}
setOpenDialog={setDialogAddClaimItemOpen}
member={currentClaim?.member ?? null}
onSubmit={handleAddClaimItems}
/>
</Paper>
</Grid>
</Grid>
</Box>
</Container>
</Page>
);
}

View File

@@ -0,0 +1,69 @@
import Iconify from '@/components/Iconify';
import { Divider, InputAdornment, Paper, Stack, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
export default function ClaimItems({ items, setItems }) {
const handleChangeBiayaDiajukan = (event, itemIndex: Number) => {
setItems(items.map((item, index) => {
if (index == itemIndex) {
return {...item, biaya_diajukan : event.target.value}
} else {
return item;
}
}))
}
const handleChangeBiayaDisetujui = (event, itemIndex: Number) => {
setItems(items.map((item, index) => {
if (index == itemIndex) {
return {...item, biaya_disetujui : event.target.value}
} else {
return item;
}
}))
}
const calculateBiayaDitolak = (biayaDiajukan: Number | null, biayaDisetujui: Number | null) => {
return (biayaDiajukan ? biayaDiajukan : 0) - (biayaDisetujui ? biayaDisetujui : 0)
}
const handleDeleteItem = (itemIndex: Number) => {
setItems(items.filter((item, index) => index != itemIndex))
}
return (
<Stack spacing={2}>
{items.length > 0 ? (
items.map((item, index) => (
<Paper variant="outlined" sx={{ background: 'white', p: 2 }} key={index}>
<Stack direction="row" justifyContent="space-between">
<Typography>#{index+1} ({item.code}) {item.description}</Typography>
<Iconify icon="eva:trash-fill" color="red" onClick={() => {handleDeleteItem(index)}}></Iconify>
</Stack>
<Stack
direction="row"
justifyContent="space-evenly"
divider={<Divider orientation="vertical" flexItem />}
>
<TextField label="Biaya Diajukan" variant="standard" fullWidth type="number" value={item.biaya_diajukan ?? 0} onChange={(event) => {handleChangeBiayaDiajukan(event, index)}}>
<InputAdornment position="start">IDR</InputAdornment>
{/* <InputMask mask="(0)999 999 99 99" maskChar=" " /> */}
</TextField>
<TextField label="Biaya Disetujui" variant="standard" fullWidth type="number" value={item.biaya_disetujui ?? 0} onChange={(event) => {handleChangeBiayaDisetujui(event, index)}}>
<InputAdornment position="start">IDR</InputAdornment>
{/* <InputMask mask="(0)999 999 99 99" maskChar=" " /> */}
</TextField>
<TextField label="Biaya Ditolak" variant="standard" fullWidth type="number" value={calculateBiayaDitolak(item.biaya_diajukan, item.biaya_disetujui)}>
<InputAdornment position="start">IDR</InputAdornment>
{/* <InputMask mask="(0)999 999 99 99" maskChar=" " /> */}
</TextField>
</Stack>
</Paper>
))
) : (
<Typography>No Benefit Item</Typography>
)}
</Stack>
);
}

View File

@@ -0,0 +1,54 @@
import Iconify from '@/components/Iconify';
import { Paper, Stack, Typography } from '@mui/material';
import { useState } from 'react';
export default function DiagnosisHistory({ diagnosis }) {
function DiagnosaItem({ item }) {
return (
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ p: 1 }}>
<Stack>
<Typography variant="body2" fontWeight="600">
Nama Penyakit
</Typography>
<Typography variant="body2">Claim Terakhir : 23 Januari 2023 08:00</Typography>
</Stack>
<Iconify icon="eva:arrow-ios-forward-fill"></Iconify>
</Stack>
);
}
return (
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="row" alignItems="center" spacing={1}>
<Iconify icon="eva:bell-fill" />
<Typography variant="body2" fontWeight={600}>
Riwayat Diagnosa
</Typography>
</Stack>
<Typography
variant="body2"
onClick={() => {
setOpenDialogRequestDocument(true);
}}
>
View All
</Typography>
</Stack>
<Paper sx={{ background: 'white', marginTop: 2 }}>
{ diagnosis.length > 0 ? (
<Stack sx={{ maxHeight: '250px', overflowY: 'scroll' }}>
{ diagnosis.map((diagnosa, index) => (
<DiagnosaItem item={diagnosa} key={index}></DiagnosaItem>
)) }
</Stack>
) : (
<Stack sx={{ p: 1 }}>
<Typography>Belum ada History Perawatan</Typography>
</Stack>
) }
</Paper>
</Paper>
);
}

View File

@@ -0,0 +1,59 @@
import MuiDialog from "@/components/MuiDialog";
import { Button, Checkbox, Typography } from "@mui/material";
import { Paper } from "@mui/material";
import { Stack } from '@mui/material';
import { useState } from "react";
export default function DialogMemberBenefit({member, setOpenDialog, openDialog, onSubmit}) {
const benefits = member?.current_plan?.benefits ?? [];
const [selectedBenefits, setSelectedBenefits] = useState([]);
const toggleBenefit = (benefit) => {
if (selectedBenefits.includes(benefit)) {
console.log('removing', benefit)
setSelectedBenefits(selectedBenefits.filter((throughBenefit) => benefit.id != throughBenefit.id))
} else {
console.log('adding', benefit)
setSelectedBenefits([...selectedBenefits, benefit])
}
}
const handleSubmit = () => {
onSubmit(selectedBenefits);
console.log ('submitting')
setOpenDialog(false);
setSelectedBenefits([]);
}
const getContent = () => (
<Stack spacing={1} marginTop={2}>
{ benefits.map((benefit, index) => (
<Paper sx={{ background: 'white', marginTop: 2, p: 2 }} key={index}>
<Stack direction="row" justifyContent="space-between" alignItems='center'>
<Stack>
<Typography variant="body1" fontWeight={600}>{benefit.description}</Typography>
<Typography variant="body2">{benefit.code}</Typography>
</Stack>
<Checkbox checked={selectedBenefits.includes(benefit)} onClick={() => { toggleBenefit(benefit) }}></Checkbox>
</Stack>
</Paper>
))}
<Button variant="contained" onClick={() => {handleSubmit()}}>Tambah</Button>
</Stack>
);
return (
<MuiDialog
title={{name: "Add Member Benefit"}}
openDialog={openDialog}
setOpenDialog={setOpenDialog}
content={getContent()}
maxWidth="xl"
/>
);
}

View File

@@ -0,0 +1,68 @@
import Iconify from '@/components/Iconify';
import { Paper, Stack, Typography } from '@mui/material';
import { useState } from 'react';
export default function Documents({ files }) {
// --------------------------------------------------------------
// Dialog Request Document
const [openDialogRequestDocument, setOpenDialogRequestDocument] = useState(false);
function FileItem({item}) {
function fileCategory(type: string) {
switch(type) {
case 'claim-result':
return 'Claim Result';
case 'claim-diagnosis':
return 'Claim Diagnosis';
case 'claim-condition':
return 'Claim Condition';
default:
return 'Other File';
}
}
return (
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ p: 1 }}>
<Stack>
<Typography variant="body2" fontWeight="600">
{ fileCategory(item.type) }
</Typography>
<Typography variant="body2"><a href={item.url} target="_blank">{ item.name }</a></Typography>
</Stack>
<Iconify icon="eva:arrow-ios-forward-fill"></Iconify>
</Stack>
);
}
return (
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2 }}>
<Stack direction="row" justifyContent="space-between">
<Typography variant="body2" fontWeight={600}>
Dokumen Tambahan
</Typography>
<Typography
variant="body2"
onClick={() => {
setOpenDialogRequestDocument(true);
}}
>
+ Request Document
</Typography>
</Stack>
<Paper sx={{ background: 'white', marginTop: 2 }}>
{ files.length > 0 ? (
<Stack sx={{ maxHeight: '250px', overflowY: 'scroll' }}>
{ files.map((file, index) => (
<FileItem item={file} key={index}></FileItem>
)) }
</Stack>
) : (
<Stack sx={{ p: 1 }}>
<Typography>Belum ada History Perawatan</Typography>
</Stack>
)}
</Paper>
</Paper>
);
}

View File

@@ -280,7 +280,7 @@ export default function Router() {
},
{
path: 'claims/:id',
element: <ClaimsCreate />,
element: <ClaimShow />,
},
{
path: 'profile',
@@ -402,5 +402,6 @@ const Profile = Loadable(lazy(() => import('../pages/Profile/Index')));
const Claims = Loadable(lazy(() => import('../pages/Claims/Index')));
const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate')));
const ClaimShow = Loadable(lazy(() => import('../pages/Claims/Show')));
const ClaimRequests = Loadable(lazy(() => import('../pages/ClaimRequests/Index')));