[WIP] Copy Claims Pages from Dashboard
This commit is contained in:
@@ -12,3 +12,18 @@ export type LaravelPaginatedData = {
|
||||
to?: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const LaravelPaginatedDataDefault = {
|
||||
current_page: 1,
|
||||
data: [],
|
||||
path: "",
|
||||
first_page_url: "",
|
||||
last_page: 1,
|
||||
last_page_url: "",
|
||||
next_page_url: "",
|
||||
prev_page_url: "",
|
||||
per_page: 10,
|
||||
from: 0,
|
||||
to: 0,
|
||||
total: 0
|
||||
}
|
||||
28
frontend/client-portal/src/components/LaravelTable.tsx
Normal file
28
frontend/client-portal/src/components/LaravelTable.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Card, Paper, TableContainer } from "@mui/material";
|
||||
import { LaravelPaginatedData } from "../@types/paginated-data";
|
||||
import BasePagination from "./BasePagination";
|
||||
|
||||
type LaravelTableProps = {
|
||||
isLoading: boolean;
|
||||
lastRequest: number;
|
||||
data: LaravelPaginatedData;
|
||||
handlePageChange: void;
|
||||
TableContent: any;
|
||||
};
|
||||
|
||||
function LaravelTable(props: LaravelTableProps) {
|
||||
return (
|
||||
<Card>
|
||||
<TableContainer component={Paper}>
|
||||
{ props.TableContent }
|
||||
</TableContainer>
|
||||
|
||||
{ !props.isLoading ?
|
||||
(<BasePagination paginationData={props.data} onPageChange={props.handlePageChange}/>) :
|
||||
(<div></div>)
|
||||
}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default LaravelTable
|
||||
64
frontend/client-portal/src/pages/Claims/CreateUpdate.tsx
Executable file
64
frontend/client-portal/src/pages/Claims/CreateUpdate.tsx
Executable file
@@ -0,0 +1,64 @@
|
||||
import * as Yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Autocomplete, Button, Card, Collapse, Container, Divider, Grid, Stack, Table, TableBody, TableCell, TableRow, TextField, Typography } from '@mui/material';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useParams, 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';
|
||||
|
||||
export default function ClaimsCreateUpdate() {
|
||||
|
||||
const { themeStretch } = useSettings();
|
||||
const { id } = useParams();
|
||||
|
||||
const isEdit = id ? true : false;
|
||||
|
||||
const [currentClaim, setCurrentClaim] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit) {
|
||||
axios.get('/claims/' + id).then((res) => {
|
||||
// console.log('Yeet', res.data);
|
||||
setCurrentClaim(res.data);
|
||||
});
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
|
||||
return (
|
||||
<Page title={isEdit ? `Edit Claim : ${currentClaim?.id}` : "Create New Claim"}>
|
||||
<Container maxWidth={themeStretch ? false : 'xl'}>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<HeaderBreadcrumbs
|
||||
heading={
|
||||
!isEdit
|
||||
? 'Create New Claim'
|
||||
: `Edit Claim : ${currentClaim?.code}`
|
||||
}
|
||||
links={[
|
||||
{ name: 'Dashboard', href: '/dashboard' },
|
||||
{
|
||||
name: 'Claim',
|
||||
href: '/claims',
|
||||
},
|
||||
{ name: !isEdit ? 'Create' : currentClaim?.id ?? '' },
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Form isEdit={isEdit} currentClaim={currentClaim} />
|
||||
</Container>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
596
frontend/client-portal/src/pages/Claims/Form.tsx
Normal file
596
frontend/client-portal/src/pages/Claims/Form.tsx
Normal file
@@ -0,0 +1,596 @@
|
||||
import * as Yup from 'yup';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import axios from '../../utils/axios';
|
||||
import { FormProvider, RHFTextField } from '../../components/hook-form';
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
Grid,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme,
|
||||
List,
|
||||
ListItem,
|
||||
IconButton,
|
||||
ListItemAvatar,
|
||||
Avatar,
|
||||
ListItemText,
|
||||
} from '@mui/material';
|
||||
import Iconify from '../../components/Iconify';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { fCurrency } from '../../utils/formatNumber';
|
||||
import MemberSelectDialog from '../../components/dialogs/MemberSelectDialog';
|
||||
import { Add, DeleteOutline } from '@mui/icons-material';
|
||||
|
||||
type Props = {
|
||||
isEdit: boolean;
|
||||
currentClaim?: any;
|
||||
};
|
||||
|
||||
export default function ClaimForm({ isEdit, currentClaim }: Props) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const NewCorporateSchema = Yup.object().shape({
|
||||
name: Yup.string().required('Name is required'),
|
||||
code: Yup.string().required('Corporate Code is required'),
|
||||
active: Yup.boolean().required('Corporate Status is required'),
|
||||
// file: Yup.boolean().required('Corporate Status is required'),
|
||||
});
|
||||
|
||||
const defaultValues = useMemo(
|
||||
() => ({
|
||||
member: currentClaim?.member || {},
|
||||
member_id: currentClaim?.member_id || null,
|
||||
diagnosis_id: currentClaim?.diagnosis_id || null,
|
||||
total_claim: currentClaim?.total_claim || 0,
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[currentClaim]
|
||||
);
|
||||
|
||||
const methods = useForm<any>({
|
||||
resolver: yupResolver(NewCorporateSchema),
|
||||
defaultValues,
|
||||
});
|
||||
const {
|
||||
reset,
|
||||
watch,
|
||||
control,
|
||||
setValue,
|
||||
getValues,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = methods;
|
||||
|
||||
const values = watch();
|
||||
|
||||
const [isCheckingLimit, setIsCheckingLimit] = useState(false);
|
||||
const [isEligible, setIsEligible] = useState(false);
|
||||
const [memberBenefits, setMemberBenefits] = useState([]);
|
||||
const [diagnosisOption, setDiagnosisOption] = useState([]);
|
||||
const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false);
|
||||
const [member, setMember] = useState({})
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log('defaultValues', defaultValues);
|
||||
if (isEdit && currentClaim) {
|
||||
reset(defaultValues);
|
||||
setMember(defaultValues.member)
|
||||
}
|
||||
if (!isEdit) {
|
||||
reset(defaultValues);
|
||||
setMember(defaultValues.member)
|
||||
}
|
||||
}, [isEdit, currentClaim]);
|
||||
|
||||
const fileSelected = (event, type) => {
|
||||
const files = event.target.files;
|
||||
const currentFiles = getValues(`uploaded_files.${type}`) ?? [];
|
||||
|
||||
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 {
|
||||
const response = await axios.put('/claims/' + currentClaim?.id ?? '', data);
|
||||
}
|
||||
reset();
|
||||
enqueueSnackbar(
|
||||
!isEdit ? 'Organizations Created Successfully!' : 'Organizations Udpated Successfully!',
|
||||
{ variant: 'success' }
|
||||
);
|
||||
navigate('/claims');
|
||||
} catch (error: any) {
|
||||
if (error && error.response.status === 422) {
|
||||
for (const [key, value] of Object.entries(error.response.data.errors)) {
|
||||
setError(key, { message: value[0] });
|
||||
enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' });
|
||||
}
|
||||
} else {
|
||||
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 (
|
||||
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack spacing={3}>
|
||||
<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 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
|
||||
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
|
||||
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 && (
|
||||
<Stack
|
||||
sx={{
|
||||
backgroundColor: 'gray',
|
||||
paddingY: 1,
|
||||
paddingX: 1.5,
|
||||
mb: 2,
|
||||
borderRadius: '3-xl',
|
||||
}}
|
||||
>
|
||||
{/* Checking */}
|
||||
<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 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 && (
|
||||
<React.Fragment>
|
||||
<Typography variant="h6">Documents</Typography>
|
||||
|
||||
<List>
|
||||
{(getValues('uploaded_files.invoice') && getValues('uploaded_files.invoice').length
|
||||
? getValues('uploaded_files.invoice')
|
||||
: []
|
||||
).map((file, index) => (
|
||||
<ListItem
|
||||
secondaryAction={
|
||||
<IconButton edge="end" aria-label="delete">
|
||||
<DeleteOutline />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<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');
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
|
||||
<MemberSelectDialog
|
||||
openDialog={isMemberDialogOpen}
|
||||
setOpenDialog={setIsMemberDialogOpen}
|
||||
onSelect={memberSelected}
|
||||
></MemberSelectDialog>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
30
frontend/client-portal/src/pages/Claims/Index.tsx
Executable file
30
frontend/client-portal/src/pages/Claims/Index.tsx
Executable file
@@ -0,0 +1,30 @@
|
||||
import { Card, Stack } from "@mui/material";
|
||||
import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs";
|
||||
import Page from "../../components/Page";
|
||||
import List from "./List";
|
||||
|
||||
|
||||
|
||||
export default function Claims() {
|
||||
|
||||
const pageTitle = 'Claim';
|
||||
return (
|
||||
<Page title={ pageTitle } sx={{ mx: 2}}>
|
||||
|
||||
<HeaderBreadcrumbs
|
||||
heading={ pageTitle }
|
||||
links={[
|
||||
{ name: 'Dashboard', href: '/dashboard' },
|
||||
{
|
||||
name: 'Claim',
|
||||
href: '/claims',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* <Stack> */}
|
||||
<List />
|
||||
{/* </Stack> */}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
286
frontend/client-portal/src/pages/Claims/List.tsx
Executable file
286
frontend/client-portal/src/pages/Claims/List.tsx
Executable file
@@ -0,0 +1,286 @@
|
||||
// @mui
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Collapse,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
Stack,
|
||||
Menu,
|
||||
ButtonGroup,
|
||||
Link,
|
||||
} from '@mui/material';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import UploadIcon from '@mui/icons-material/Upload';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
// hooks
|
||||
import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
// components
|
||||
import axios from '../../utils/axios';
|
||||
import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data';
|
||||
import DataTable from '../../components/LaravelTable';
|
||||
import { fCurrency } from '../../utils/formatNumber';
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import { Chip } from '@mui/material';
|
||||
import Iconify from '@/components/Iconify';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
export default function List() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [importResult, setImportResult] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
function SearchInput(props: any) {
|
||||
// SEARCH
|
||||
const searchInput = useRef<HTMLInputElement>(null);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const handleSearchChange = (event: any) => {
|
||||
const newSearchText = event.target.value ?? '';
|
||||
setSearchText(newSearchText);
|
||||
};
|
||||
|
||||
const handleSearchSubmit = (event: any) => {
|
||||
event.preventDefault();
|
||||
props.onSearch({ search: searchText }); // Trigger to Parent
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Trigger First Search
|
||||
setSearchText(searchParams.get('search') ?? '');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSearchSubmit} style={{ width: '100%' }}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
ref={searchInput}
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onChange={handleSearchChange}
|
||||
value={searchText}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportForm(props: any) {
|
||||
// IMPORT
|
||||
// Create Button Menu
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
|
||||
<SearchInput onSearch={applyFilter} />
|
||||
{/* <Button
|
||||
variant="outlined"
|
||||
startIcon={<AddIcon />}
|
||||
sx={{ p: 1.8 }}
|
||||
onClick={() => {
|
||||
navigate('/claims/create');
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button> */}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Dummy Default Data
|
||||
const [dataTableIsLoading, setDataTableLoading] = useState(true);
|
||||
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>(
|
||||
LaravelPaginatedDataDefault
|
||||
);
|
||||
|
||||
const loadDataTableData = async (appliedFilter: any | null = null) => {
|
||||
setDataTableLoading(true);
|
||||
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
|
||||
const response = await axios.get('/claims', { params: filter });
|
||||
// console.log(response.data);
|
||||
setDataTableLoading(false);
|
||||
|
||||
setDataTableData(response.data);
|
||||
};
|
||||
|
||||
const applyFilter = async (searchFilter: { search: string }) => {
|
||||
await loadDataTableData(searchFilter);
|
||||
setSearchParams(searchFilter);
|
||||
};
|
||||
|
||||
const handlePageChange = (event: ChangeEvent, value: number): void => {
|
||||
const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]);
|
||||
loadDataTableData(filter);
|
||||
setSearchParams(filter);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadDataTableData();
|
||||
}, []);
|
||||
|
||||
const headStyle = {
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
|
||||
// Called on every row to map the data to the columns
|
||||
function createData(data: any): any {
|
||||
return {
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
/* ------------------ TABLE ROW ------------------ */
|
||||
}
|
||||
function Row(props: { row: ReturnType<typeof createData> }) {
|
||||
const { row } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
||||
<TableCell>
|
||||
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
|
||||
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell align="left">{row.code}</TableCell>
|
||||
<TableCell align="left">{row.member?.full_name}</TableCell>
|
||||
<TableCell align="left">{row.plan?.code}</TableCell>
|
||||
<TableCell align="left">{row.claim_request?.service?.name}</TableCell>
|
||||
<TableCell align="left">
|
||||
({row.diagnoses[0]?.icd?.code}) {row.diagnoses[0]?.icd?.name}
|
||||
</TableCell>
|
||||
<TableCell align="left">{fCurrency(row.total_claim)}</TableCell>
|
||||
<TableCell align="center">
|
||||
{row.status == 'draft' && (<Chip label='Draft' color="default" variant="outlined" />)}
|
||||
{row.status == 'requested' && (<Chip label='Requested' color="primary" />)}
|
||||
{row.status == 'received' && (<Chip label='Received' color="success" variant='outlined' />)}
|
||||
{row.status == 'approved' && (<Chip label='Approved' color="success" />)}
|
||||
{row.status == 'postpone' && (<Chip label='Postpone' color="primary" variant="outlined" />)}
|
||||
{row.status == 'paid' && (<Chip label='Paid' color="warning" />)}
|
||||
{row.status == 'declined' && (<Chip label='Declined' color="error" />)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="right">
|
||||
{['approved', 'paid'].includes(row.status) && (
|
||||
<Iconify icon="eva:eye-fill" onClick={(e) => {
|
||||
navigate('/claims/' + row.id);
|
||||
}}></Iconify>
|
||||
)}
|
||||
{!['approved', 'paid'].includes(row.status) && (
|
||||
<Iconify icon="eva:edit-outline" onClick={(e) => {
|
||||
navigate('/claims/' + row.id);
|
||||
}}></Iconify>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/* COLLAPSIBLE ROW */}
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
{/* <Box sx={{ borderBottom: 1 }}>
|
||||
<Typography variant="body2" gutterBottom component="div">
|
||||
Description : {row.description}
|
||||
</Typography>
|
||||
</Box> */}
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
{
|
||||
/* ------------------ END TABLE ROW ------------------ */
|
||||
}
|
||||
|
||||
function TableContent() {
|
||||
return (
|
||||
<Table aria-label="collapsible table">
|
||||
{/* ------------------ TABLE HEADER ------------------ */}
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell style={headStyle} align="left" />
|
||||
<TableCell style={headStyle} align="left">
|
||||
Code
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Member Name
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Plan
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Benefit
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Diagnosis
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Total Claim
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Status
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="right">
|
||||
Action
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
{/* ------------------ END TABLE HEADER ------------------ */}
|
||||
|
||||
{/* ------------------ TABLE ROW ------------------ */}
|
||||
{dataTableIsLoading ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
Loading
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : dataTableData.data.length === 0 ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
No Data
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : (
|
||||
<TableBody>
|
||||
{dataTableData.data.map((row) => (
|
||||
<Row key={row.id} row={row} />
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
{/* ------------------ END TABLE ROW ------------------ */}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<ImportForm />
|
||||
|
||||
<DataTable
|
||||
isLoading={dataTableIsLoading}
|
||||
lastRequest={0}
|
||||
data={dataTableData}
|
||||
handlePageChange={handlePageChange}
|
||||
TableContent={<TableContent />}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
458
frontend/client-portal/src/pages/Claims/Show.tsx
Normal file
458
frontend/client-portal/src/pages/Claims/Show.tsx
Normal file
@@ -0,0 +1,458 @@
|
||||
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)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Initial LOG
|
||||
const [loadingLog, setLoadingLog] = useState(false)
|
||||
|
||||
const handleDownloadLog = (claim_id) => {
|
||||
setLoadingLog(true);
|
||||
axios
|
||||
.post(`generate-log/${claim_id}`, {
|
||||
responseType: 'blob',
|
||||
})
|
||||
.then((response) => {
|
||||
window.open(URL.createObjectURL(response.data));
|
||||
setLoadingLog(false);
|
||||
setOpenDialog(false);
|
||||
})
|
||||
.catch((response) => {
|
||||
enqueueSnackbar(response.message, { variant: 'error' });
|
||||
setLoadingLog(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------
|
||||
// Final LOG
|
||||
const [loadingFinalLog, setLoadingFinalLog] = useState(false)
|
||||
const handleDownloadFinalLog = (claim_id) => {
|
||||
setLoadingFinalLog(true);
|
||||
axios
|
||||
.get(`final-log/${claim_id}`, {
|
||||
responseType: 'blob',
|
||||
})
|
||||
.then((response) => {
|
||||
window.open(URL.createObjectURL(response.data));
|
||||
setLoadingFinalLog(false);
|
||||
})
|
||||
.catch((response) => {
|
||||
enqueueSnackbar(response.message, { variant: 'error' });
|
||||
setLoadingFinalLog(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
axios.get('/claims/' + id).then(({ data }) => {
|
||||
const claim = data.data;
|
||||
const allFiles = [...(claim.claim_request ? 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 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography>Status : {currentClaim?.status}</Typography>
|
||||
{ currentClaim?.status == 'approved' && (
|
||||
<LoadingButton
|
||||
loading={loadingFinalLog}
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
handleDownloadFinalLog(currentClaim.id);
|
||||
}}
|
||||
>
|
||||
Download Final LOG
|
||||
</LoadingButton>
|
||||
)}
|
||||
</Stack>
|
||||
</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>
|
||||
|
||||
{(currentClaim?.status == 'requested' || currentClaim?.status == 'received') && (
|
||||
|
||||
<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'}>
|
||||
|
||||
{(currentClaim?.status == 'requested' || currentClaim?.status == 'received') && (
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -108,6 +108,22 @@ export default function Router() {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/claims',
|
||||
element: (
|
||||
<AuthProvider>
|
||||
<AuthGuard>
|
||||
<DashboardLayout />
|
||||
</AuthGuard>
|
||||
</AuthProvider>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
element: <Claims />,
|
||||
index: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <LogoOnlyLayout />,
|
||||
@@ -136,3 +152,4 @@ const AlarmCenterUserProfile = Loadable(lazy(() => import('../pages/AlarmCenter/
|
||||
|
||||
// Claim Report
|
||||
const ClaimReport = Loadable(lazy(() => import('../pages/ClaimReport/Index')));
|
||||
const Claims = Loadable(lazy(() => import('../pages/Claims/Index')));
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import svgrPlugin from 'vite-plugin-svgr'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import path from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -20,4 +21,7 @@ export default defineConfig({
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user