Merge remote-tracking branch 'origin/staging' into origin/production

This commit is contained in:
Linksehat Staging Server
2024-03-05 13:41:28 +07:00
41 changed files with 3431 additions and 2144 deletions

View File

@@ -1,35 +1,30 @@
import MuiDialog from "@/components/MuiDialog";
import { Autocomplete, Button, Card, Checkbox, DialogActions, Grid, TextField, Typography } from "@mui/material";
import { Button, Card, Checkbox, DialogActions, Grid, TextField, TextareaAutosize, Typography } from "@mui/material";
import { Paper } from "@mui/material";
import { Stack } from '@mui/material';
import React, { useEffect, useState } from 'react';
import { ClaimRequest, Files } from '@/@types/claims';
import { fDateOnly, fDateTimesecond, toTitleCase } from "@/utils/formatTime";
import { DetailClaimRequest } from "../Model/Types";
import { fDateTimesecond, toTitleCase } from "@/utils/formatTime";
import axios from "@/utils/axios";
import { enqueueSnackbar, useSnackbar } from "notistack";
import { enqueueSnackbar } from "notistack";
import { useNavigate } from "react-router";
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import { RHFTextField } from "@/components/hook-form";
type DialogConfirmationType = {
openDialog: boolean;
setOpenDialog: any;
onSubmit?: void;
approve: string;
claimRequest: ClaimRequest|undefined;
requestLog: DetailClaimRequest|undefined;
}
export default function DialogConfirmation({claimRequest, setOpenDialog, openDialog, approve, onSubmit} : DialogConfirmationType ) {
export default function DialogConfirmation({requestLog, setOpenDialog, openDialog, approve, onSubmit} : DialogConfirmationType ) {
const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar();
const [formData, setFormData] = useState({
date: claimRequest?.date,
id: claimRequest?.id,
reason: claimRequest?.reason
});
const handleChange = (field, value) => {
setFormData((prevData) => ({
...prevData,
@@ -38,24 +33,28 @@ export default function DialogConfirmation({claimRequest, setOpenDialog, openDia
};
const handleApprove = () => {
setFormData((prevData) => ({
...prevData,
status: approve,
}));
handleSubmit();
};
const handleSubmit = () => {
axios
.post(`customer-service/request/final-log`, formData)
.post(`claim-requests/${requestLog?.id}/submition`, formData)
.then((response) => {
enqueueSnackbar('Verification Request LOG Success', { variant: 'success' });
enqueueSnackbar('Submition Claim Request Success', { variant: 'success' });
setOpenDialog(false);
if (requestLog?.service_type == 'Inpatient'){
navigate('/case_management/inpatient_monitoring');
} else {
navigate('/custormer-service/final-log');
}
navigate('/claim-requests')
})
.catch(({ response }) => {
enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' });
});
}
const style1 = {
color: '#919EAB',
width: '30%'
@@ -66,34 +65,57 @@ export default function DialogConfirmation({claimRequest, setOpenDialog, openDia
const marginBottom1 = {
marginBottom: 1,
}
const marginBottom2 = {
marginBottom: 2,
}
const resetForm = () => {
setFormData({
status: approve,
no_identitas: requestLog?.no_identitas ?? '',
keterangan: '',
hak_kamar_pasien: '',
penempatan_kamar: '',
});
};
const handleCloseDialog = () => {
setOpenDialog(false);
resetForm();
}
const handleNumericInput = (input: any) => {
const numericInput = input.replace(/\D/g, '');
return numericInput;
};
const handleKeyPress = (e:any) => {
if (e.key === 'Enter' && !e.shiftKey) {
// Menghentikan default "Enter" (tidak membuat baris baru)
e.preventDefault();
// Menambahkan karakter baris baru
handleChange('keterangan', `${formData.keterangan}\n`);
}
};
console.log(approve, 'test')
const getContent = () => (
<Stack spacing={1} marginTop={2}>
<Typography variant="subtitle2">Are you sure to {approve == 'approved' ? 'approve' : 'deciline'} this final log ?</Typography>
<Typography variant="subtitle1">Are you sure to submit this claim ?</Typography>
<Grid item xs={12} md={12} marginTop={4}>
<Card sx={{padding:2, marginTop:2}} >
<Stack direction='row' spacing={2} sx={marginBottom1}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Member ID</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{requestLog?.member_id}</Typography>
</Stack>
<Stack direction='row' spacing={2} sx={marginBottom1}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Policy Number</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{requestLog?.policy_number}</Typography>
<Typography variant='subtitle2' sx={style1} gutterBottom>Code</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{requestLog?.code}</Typography>
</Stack>
<Stack direction='row' spacing={2} sx={marginBottom1}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Name</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{requestLog?.name}</Typography>
</Stack>
<Stack direction='row' spacing={2} sx={marginBottom1}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Submission Date</Typography>
<Typography variant='subtitle2' sx={style1} gutterBottom>Date Submission</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{requestLog?.submission_date ? fDateTimesecond(requestLog?.submission_date) : '-'}</Typography>
</Stack>
<Stack direction='row' spacing={2} sx={marginBottom1}>
@@ -105,58 +127,10 @@ export default function DialogConfirmation({claimRequest, setOpenDialog, openDia
<Typography variant='subtitle2' sx={style2} gutterBottom>{requestLog?.service_type}</Typography>
</Stack>
</Card>
<Card sx={{padding:2, marginTop:2}} >
<Stack direction='row' spacing={2} sx={marginBottom2}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Discharge Date</Typography>
<TextField
label="Discharge Date"
variant="outlined"
fullWidth
type="date"
value={formData.discharge_date ? fDateOnly(formData.discharge_date) : ''}
onChange={(e) => handleChange('discharge_date', e.target.value)}
/>
</Stack>
<Stack direction='row' spacing={2} sx={marginBottom2}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Catatan</Typography>
<TextField
label="Catatan"
variant="outlined"
fullWidth
value={formData.catatan}
onChange={(e) => handleChange('catatan', e.target.value)}
/>
</Stack>
<Stack direction='row' spacing={2} sx={marginBottom2}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Diagnosis ICD - X</Typography>
<Autocomplete
multiple
options={icdOptions}
getOptionLabel={(option) => option.label}
fullWidth
value={icdOptions.filter((icd) => formData.icdCodes.includes(icd.value))}
onChange={(e, newValues) => handleChange('icdCodes', newValues.map((value) => value.value))}
renderInput={(params) => (
<TextField
{...params}
label="Diagnosis ICD - X"
variant="outlined"
/>
)}
/>
</Stack>
</Card>
</Grid>
<DialogActions>
<Button variant="outlined" sx={{color: '#212B36', borderColor: '#919EAB52'}} onClick={handleCloseDialog}>Cancel</Button>
{approve == 'approved' ? (
<Button color="primary" variant="contained" onClick={() => handleApprove()}>Approve</Button>
) : (
<Button color="error" variant="contained" onClick={() => handleApprove()}>Decline</Button>
) }
<Button color="primary" variant="contained" onClick={() => handleApprove()}>Submit</Button>
</DialogActions>
</Stack>
);
@@ -168,7 +142,7 @@ export default function DialogConfirmation({claimRequest, setOpenDialog, openDia
openDialog={openDialog}
setOpenDialog={setOpenDialog}
content={getContent()}
maxWidth="xl"
maxWidth="md"
/>
);
}

View File

@@ -78,15 +78,13 @@ export default function FormEdit({ isEdit, currentClaim }: Props) {
);
const [date, setDate] = useState(currentClaim?.submission_date)
const [date, setDate] = useState(currentClaim?.submission_date ? fDateTimesecond(currentClaim?.submission_date) : null)
const id = currentClaim?.id
useEffect(() => {
setDate(currentClaim?.submission_date)
}, [currentClaim]);
console.log(date);
useEffect(() => {
if (isEdit && currentClaim) {
reset(defaultValues);
@@ -254,7 +252,6 @@ export default function FormEdit({ isEdit, currentClaim }: Props) {
</Stack>
)
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack direction="row" alignItems="center" sx={{ mb: 5 }}>
@@ -310,10 +307,14 @@ export default function FormEdit({ isEdit, currentClaim }: Props) {
<RHFDateTimePicker
{...field}
label="Date of Submission"
value={field.value || null}
onChange={() => setDate(field.value)}
value={field.value}
onChange={() =>
setDate(field.value)
}
dateFormat='dd MMM yyyy HH:mm:ss'
/>
)}
/>
</Grid>
<Grid item xs={3}>

View File

@@ -32,7 +32,7 @@ export default function ClaimsCreateUpdate() {
useEffect(() => {
if (isEdit) {
axios.get('/claim-requests/' + id).then((res) => {
axios.get('/claim-requests/' + id + '/show').then((res) => {
setCurrentClaim(res.data.data);
});

View File

@@ -38,6 +38,8 @@ import DialogDeleteFileLog from './Components/DialogDeleteFileLog';
import DialogBenefit from '../CustomerService/FinalLog/Components/DialogBenefit';
import DialogDeleteBenefit from '../CustomerService/FinalLog/Components/DialogDeleteBenefit';
import DialogEditBenefit from '../CustomerService/FinalLog/Components/DialogEditBenefit';
import DialogConfirmation from './Components/DialogConfirmation';
// ----------------------------------------------------------------------
@@ -49,6 +51,8 @@ export default function Detail() {
const navigate = useNavigate();
const { themeStretch } = useSettings();
const [claimRequests, setClaimRequest] = useState<DetailClaimRequest>();
const [openDialogSubmit, setOpenDialogSubmit] = useState(false);
const [isReversal, setIsReversal] = useState(false);
const { id } = useParams();
@@ -57,6 +61,7 @@ export default function Detail() {
.get('claim-requests/detail/'+id)
.then((response) => {
setClaimRequest(response.data.data)
setIsReversal(response.data.data.is_reversal)
})
.catch((error) => {
console.error(error);
@@ -223,13 +228,18 @@ export default function Detail() {
<Stack direction="column" spacing={2} sx={{marginBottom: 2}}>
<Typography variant='subtitle1' sx={{color: '#19BBBB'}} gutterBottom>Document </Typography>
</Stack>
<Stack direction="column" spacing={2} sx={{marginBottom: 2}}>
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogUploadFileLog(true)
}} >
<Typography variant="button" display="block">File</Typography>
</Button>
</Stack>
{
!isReversal ? (
<Stack direction="column" spacing={2} sx={{marginBottom: 2}}>
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogUploadFileLog(true)
}} >
<Typography variant="button" display="block">File</Typography>
</Button>
</Stack>
) : null
}
</Stack>
{claimRequests?.files?.map((documentType, index) => (
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{marginBottom: 2}} key={index}>
@@ -250,14 +260,19 @@ export default function Detail() {
<Typography variant="body1" gutterBottom>{documentType.original_name ? documentType.original_name : '-'}</Typography>
</a>
</Stack>
<Stack direction="column" spacing={2}>
<IconButton onClick={() => {
setDialogDeleteFileLog(true)
setPathFile(documentType.path)
}} aria-label="delete" size="small" sx={{ marginLeft: 'auto' }}>
<Delete color='error' fontSize="small" />
</IconButton>
</Stack>
{
!isReversal ? (
<Stack direction="column" spacing={2}>
<IconButton onClick={() => {
setDialogDeleteFileLog(true)
setPathFile(documentType.path)
}} aria-label="delete" size="small" sx={{ marginLeft: 'auto' }}>
<Delete color='error' fontSize="small" />
</IconButton>
</Stack>
) : null
}
</Stack>
))}
@@ -281,11 +296,15 @@ export default function Detail() {
<Card sx={{padding:2}} >
<Stack direction="row" alignItems="center" sx={{marginBottom: 4}}>
<Typography variant='subtitle1' sx={{color: '#19BBBB'}} gutterBottom>Benefit</Typography>
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogBenefit(true);
}} >
<Typography variant="button" display="block">Benefit</Typography>
</Button>
{
!isReversal ? (
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogBenefit(true);
}} >
<Typography variant="button" display="block">Benefit</Typography>
</Button>
) : null
}
</Stack>
<Box sx={{ border: '1px solid rgba(0,0,0,0.125)', px: '24px', py: '20px', marginBottom: '24px', borderRadius: '12px'}}>
@@ -298,29 +317,34 @@ export default function Detail() {
{item.benefit?.description}
</Typography>
</Grid>
<Grid item xs={6} sx={{ display: 'flex', placeContent: 'end' }}>
<MoreMenu actions={
<>
<MenuItem onClick={() => {
setDialogEditBenefit(true)
setIdBenefitData(item.id)
setBenefitConfigurationData(item)
}}
>
<EditOutlined />
Edit
</MenuItem>
<MenuItem onClick={() => {
setIdBenefitData(item.id)
setDialogDeleteBenefit(true)
}}
>
<Delete color='error'/>
Delete
</MenuItem>
</>
} />
</Grid>
{
!isReversal ? (
<Grid item xs={6} sx={{ display: 'flex', placeContent: 'end' }}>
<MoreMenu actions={
<>
<MenuItem onClick={() => {
setDialogEditBenefit(true)
setIdBenefitData(item.id)
setBenefitConfigurationData(item)
}}
>
<EditOutlined />
Edit
</MenuItem>
<MenuItem onClick={() => {
setIdBenefitData(item.id)
setDialogDeleteBenefit(true)
}}
>
<Delete color='error'/>
Delete
</MenuItem>
</>
} />
</Grid>
) : null
}
</Grid>
</Grid>
<Grid item xs={12} py={2}>
@@ -421,7 +445,7 @@ export default function Detail() {
<Grid item xs={12}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography variant="body2" sx={{ fontWeight: 'bold'}}>
<Typography variant="subtitle1" sx={{ fontWeight: 'bold'}}>
Total Benefit
</Typography>
</Grid>
@@ -436,12 +460,12 @@ export default function Detail() {
<Grid item xs={2}>
<Grid container sx={{ borderRight: `0.5px solid ${palette.light.grey[400]}` }}>
<Grid item xs={12}>
<Typography variant="caption">
<Typography variant="subtitle1">
Amount Incurred
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>
{totalAmountIncurred ? fNumber(totalAmountIncurred) : '0'}
</Typography>
</Grid>
@@ -452,12 +476,12 @@ export default function Detail() {
<Grid item xs={2}>
<Grid container sx={{ borderRight: `0.5px solid ${palette.light.grey[400]}` }}>
<Grid item xs={12}>
<Typography variant="caption">
<Typography variant="subtitle1">
Amount Approved
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>
{totalAmountApproved ? fNumber(totalAmountApproved) : '0'}
</Typography>
</Grid>
@@ -468,12 +492,12 @@ export default function Detail() {
<Grid item xs={3}>
<Grid container sx={{ borderRight: `0.5px solid ${palette.light.grey[400]}` }}>
<Grid item xs={12}>
<Typography variant="caption">
<Typography variant="subtitle1">
Amount Not Approved
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>
{totalAmountNotApproved ? fNumber(totalAmountNotApproved) : 0}
</Typography>
</Grid>
@@ -484,12 +508,12 @@ export default function Detail() {
<Grid item xs={2}>
<Grid container sx={{ borderRight: `0.5px solid ${palette.light.grey[400]}` }}>
<Grid item xs={12}>
<Typography variant="caption">
<Typography variant="subtitle1">
Excess Paid
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>
{totalExcessPaid ? fNumber(totalExcessPaid) : 0}
</Typography>
</Grid>
@@ -497,6 +521,23 @@ export default function Detail() {
</Grid>
</Grid>
</Box>
<Grid container spacing={1} marginY={2}>
<Grid item xs={6}>
<Typography variant="subtitle1">
Biaya disetujui
</Typography>
</Grid>
<Grid item xs={6} textAlign="right">
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>
{totalAmountApproved ? fNumber(totalAmountApproved) : '0'}
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle2">
Nominal diatas 1 juta akan menunggu persetujuan senior analyst
</Typography>
</Grid>
</Grid>
</Grid>
</Grid>
@@ -504,9 +545,25 @@ export default function Detail() {
: (
null
)}
</Box>
</Card>
{ claimRequests?.status === 'declined' ? (
<Card sx={{padding:2, marginTop:2}} >
<Stack direction="row" alignItems="center" sx={{marginBottom: 4}}>
<Typography variant='subtitle1' sx={{color: '#19BBBB'}} gutterBottom>Reason</Typography>
</Stack>
<Box sx={{ border: '1px solid rgba(0,0,0,0.125)', px: '24px', py: '20px', marginBottom: '24px', borderRadius: '12px'}}>
<Stack direction='row' spacing={2} sx={marginBottom1}>
<Typography variant='subtitle2' sx={style1} gutterBottom>Reason Decline</Typography>
<Typography variant='subtitle2' sx={style2} gutterBottom>{claimRequests?.reason_decline}</Typography>
</Stack>
</Box>
</Card>
) : null }
{/* PR Buat pindahin ke componen */}
{/* <CardBenefit
requestLog={requestLog}
@@ -542,6 +599,42 @@ export default function Detail() {
requestLog={requestLog}
openDialog={openDialogEditDetail}
/> */}
{(claimRequests?.status === 'requested') || (claimRequests?.status === 'declined') ? (
<Grid item xs={12} md={12}>
<Stack direction="row" padding={4} sx={{ justifyContent: 'flex-end' }}>
<div style={{ marginRight: '10px' }}> {/* Perubahan sintaksis disini */}
<Button
variant="outlined"
color='inherit'
onClick={() => {
navigate('/claim-requests')
}}
>
Cancel
</Button>
</div>
<div>
<Button
variant="contained"
onClick={() => {
setOpenDialogSubmit(true);
setApprove('approved');
}}
>
Submit
</Button>
</div>
<DialogConfirmation
setOpenDialog={setOpenDialogSubmit}
requestLog={claimRequests}
openDialog={openDialogSubmit}
approve={approve}
>
</DialogConfirmation>
</Stack>
</Grid>
) : null}
</Grid>
</Grid>

View File

@@ -123,6 +123,11 @@ export default function List() {
variant="outlined"
fullWidth
onChange={handleSearchChange}
onKeyDown={(event) => {
if (event.key === 'Enter') {
handleSearchSubmit(event);
}
}}
value={searchText}
placeholder='Search Code or Name...'
/>
@@ -152,7 +157,7 @@ export default function List() {
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDatePicker
label="End Date"
inputFormat="MM/dd/yyyy"
inputFormat="dd/MM/yyyy"
value={searchParams.get('end_date')}
onChange={(value) => {
try {
@@ -274,7 +279,7 @@ export default function List() {
}
const handleGetData = (type :string) => {
axios.get(`corporates/${corporate_id}/data-plan-benefit`)
axios.get(`claim-requests/export`)
.then((response) => {
const link = document.createElement('a');
link.href = response.data.data.file_url;
@@ -361,14 +366,32 @@ export default function List() {
</Stack>
)}
{importResult && (
// <Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
// <Box sx={{ color: 'text.secondary' }}>
// Last Import Result Report :{' '}
// <a href={importResult.result_file?.url ?? '#'}>
// {importResult.result_file?.name ?? '-'}
// </a>
// </Box>
// </Stack>
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: 'text.secondary' }}>
Last Import Result Report :{' '}
Last Import Result :{' '}
<Box sx={{ color: 'success.main', display: 'inline' }}>
{importResult.result_file?.total_success_row ?? 0}
</Box>{' '}
Row Processed,{' '}
<Box sx={{ color: 'error.main', display: 'inline' }}>
{importResult.result_file?.total_failed_row}
</Box>{' '}
Failed, Report :{' '}
<a href={importResult.result_file?.url ?? '#'}>
{importResult.result_file?.name ?? '-'}
</a>
</Box>
</Stack>
)}
</div>
);
@@ -393,7 +416,7 @@ export default function List() {
setDataTableLoading(true);
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/claim-requests', { params: filter });
console.log(response.data);
setDataTableLoading(false);
@@ -468,8 +491,12 @@ export default function List() {
<TableCell align="left">{row.service_name}</TableCell>
<TableCell align="left">{row.payment_type_name}</TableCell>
<TableCell align="left">
{ row.status == "requested" ?
{ row.status == "requested" ?
(<Label variant='ghost' color='primary'>{capitalizeFirstLetter(row.status)}</Label>) :
row.status == "submission" ?
(<Label color='info'> {capitalizeFirstLetter(row.status)}</Label>) :
row.status == "declined" ?
(<Label color='error'> {capitalizeFirstLetter(row.status)}</Label>) :
(<Label color='success'> {capitalizeFirstLetter(row.status)}</Label>)
}
</TableCell>

View File

@@ -51,6 +51,7 @@ export type DetailClaimRequest = {
benefit_data : BenefitData[],
diagnosis : Diagnosis[],
request_log : RequestLogType | undefined,
reason_decline : string,
}
export type RequestLogType = {

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ import List from "./List";
export default function Claims() {
const pageTitle = 'Claim';
const pageTitle = 'Claim Management';
return (
<Page title={ pageTitle } sx={{ mx: 2}}>
@@ -16,7 +16,7 @@ export default function Claims() {
links={[
{ name: 'Dashboard', href: '/dashboard' },
{
name: 'Claim',
name: 'Claim Management',
href: '/claims',
},
]}

View File

@@ -1,6 +1,7 @@
// @mui
import {
Box,
Grid,
Button,
Card,
Collapse,
@@ -17,12 +18,24 @@ import {
ButtonGroup,
Tooltip,
TableHead,
Checkbox,
InputAdornment,
TableSortLabel,
FormControl
} from '@mui/material';
import { visuallyHidden } from '@mui/utils';
import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { fDateOnly } from '@/utils/formatTime';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined';
import AssessmentIcon from '@mui/icons-material/Assessment';
// hooks
import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Link, Navigate, useNavigate, useSearchParams } from 'react-router-dom';
import { LoadingButton } from '@mui/lab';
// components
import axios from '../../utils/axios';
import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../@types/paginated-data';
@@ -32,23 +45,146 @@ import EditRoundedIcon from '@mui/icons-material/EditRounded';
import { Chip } from '@mui/material';
import Iconify from '@/components/Iconify';
import { enqueueSnackbar } from 'notistack';
import { fDate } from '../../utils/formatTime';
import { fDate, fDateTime } from '../../utils/formatTime';
import { Claims } from '@/@types/claims';
import Label from '@/components/Label';
import { capitalizeFirstLetter } from '@/utils/formatString';
import TableMoreMenu from '@/components/table/TableMoreMenu';
import Edit from '@mui/icons-material/Edit';
import { Download } from '@mui/icons-material';
import { Add, Search } from '@mui/icons-material';
import Autocomplete from '@mui/material/Autocomplete';
import DownloadIcon from '@mui/icons-material/Download';
import UploadIcon from '@mui/icons-material/Upload';
import CancelIcon from '@mui/icons-material/Cancel';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
export default function List() {
const [selectAll, setSelectAll] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const [providers, setProviders] = useState(null);
// const [searchText, setSearchText] = useState('');
const [order, setOrder] = useState<Order>('desc');
const [orderBy, setOrderBy] = useState('created_at');
const [perPage, setPerPage] = useState<number>(0);
const handleChange = (event, newValue) => {
// Jika newValue tidak undefined, atur nilai dataProvider
if (newValue !== undefined) {
setDataProvider(newValue.service_code);
} else {
// Jika tidak ada yang dipilih, set dataProvider menjadi string kosong
setDataProvider(null);
}
};
// Dummy data
const dummyServices = [
{ service_code: '1', name: 'Service 1' },
{ service_code: '2', name: 'Service 2' },
{ service_code: '3', name: 'Service 3' },
// tambahkan data lain sesuai kebutuhan
];
const handleSelectAll = () => {
setSelectAll(!selectAll);
if (!selectAll) {
const requestedIds = dataTableData.data
.filter(row => row.status === 'received') // Memfilter baris dengan status 'requested'
.map(row => row.id); // Mengambil hanya ID dari baris-baris yang memenuhi kondisi
setSelectedRows(requestedIds);
} else {
setSelectedRows([]);
}
};
const handleRowSelect = (id) => {
if (selectedRows.includes(id)) {
setSelectedRows(selectedRows.filter(rowId => rowId !== id));
} else {
setSelectedRows([...selectedRows, id]);
}
};
const [searchParams, setSearchParams] = useSearchParams();
const [importResult, setImportResult] = useState(null);
const [startDate, setStartDate] = useState(null);
const [searchText, setSearchText] = useState('');
const [endDate, setEndDate] = useState(null);
const navigate = useNavigate();
const [dataProvider, setDataProvider] = useState(null);
useEffect(() => {
if (startDate !== null || endDate !== null || dataProvider !== null
|| order !== null || orderBy !== null || perPage !== 0) {
loadDataTableData();
getProvider();
}
}, [startDate, endDate, dataProvider, order, orderBy, perPage]);
const [isLoading, setIsLoading] = useState(false);
const [isLoadingImport, setIsLoadingImport] = useState(false);
const handleExportReport = async () => {
const year = startDate?.getFullYear();
const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit
const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit
const formattedDate = year && month && day ? `${year}-${month}-${day}` : '';
const year1 = endDate?.getFullYear();
const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit
const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit
const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : '';
var filter = Object.fromEntries([...searchParams.entries()]);
setIsLoading(true)
await axios
.get('/claims/export-claim-management',{
params: {
search: searchText,
start_date: formattedDate ? formattedDate : null,
end_date:formattedDate1,
provider: dataProvider,
order: order,
orderBy: orderBy,
page: perPage,
}
})
.then((res) => {
enqueueSnackbar('Data berhasil di Export', {
variant: 'success',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
});
setIsLoading(false)
document.location.href = res.data.data.file_url;
})
.catch((err) =>
enqueueSnackbar('Data Gagal di Export', {
variant: 'error',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
})
);
};
function SearchInput(props: any) {
// SEARCH
const searchInput = useRef<HTMLInputElement>(null);
const [searchText, setSearchText] = useState('');
const handleSearchChange = (event: any) => {
const newSearchText = event.target.value ?? '';
@@ -73,7 +209,7 @@ export default function List() {
useEffect(() => {
// Trigger First Search
setSearchText(searchParams.get('search') ?? '');
// setSearchText(searchParams.get('search') ?? '');
}, []);
return (
@@ -126,41 +262,331 @@ export default function List() {
);
}
const searchInput = useRef<HTMLInputElement>(null);
//handle search
const handleSearchChange = (event: any) => {
const newSearchText = event.target.value ?? '';
setSearchText(newSearchText);
};
const handleSearchSubmit = (event: any) => {
event.preventDefault();
loadDataTableData();
};
useEffect(() => {
// Trigger First Search
//setSearchText(searchText);
}, []);
const item = [
{
id: '',
value: '',
name: 'Semua',
},
];
// const handleClick = () => {
// }
// Dummy Default Data
const [dataTableIsLoading, setDataTableLoading] = useState(true);
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>(
LaravelPaginatedDataDefault
);
const loadDataTableData = async (appliedFilter: any | null = null) => {
setDataTableLoading(true);
const year = startDate?.getFullYear();
const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit
const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit
const formattedDate = year && month && day ? `${year}-${month}-${day}` : '';
const year1 = endDate?.getFullYear();
const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit
const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit
const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : '';
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/claims', { params: filter });
// console.log(response.data);
const response = await axios.get('/claims', {
params: {
search: searchText,
start_date: formattedDate ? formattedDate : null,
end_date:formattedDate1,
provider: dataProvider,
order: order,
orderBy: orderBy,
page: perPage,
}
});
setDataTableLoading(false);
setDataTableData(response.data);
};
const getProvider = async () => {
const response = await axios.get('/claims/get-provider');
setProviders(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);
setPerPage(value);
};
useEffect(() => {
loadDataTableData();
}, []);
const [openDialogSubmit, setOpenDialogSubmit] = useState(false);
const handleCloseDialogSubmit = () => {
setOpenDialogSubmit(false);
}
function toTitleCase(str: string | null) {
return str.replace(/\w\S*/g, function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}
const [approve, setApprove] = useState('');
const [reasonDecline, setReasonDecline] = useState('');
const handleReasonDeclineChange = (event) => {
setReasonDecline(event.target.value);
// Tambahkan logika yang diperlukan di sini
};
const handleSubmitData = () => {
//approve or decline
if (!reasonDecline && approve == 'decline') {
enqueueSnackbar('Mohon isi alasan', { variant: 'warning' });
return false;
}
Promise.all(selectedRows.map(send_bulk))
.then(() => {
enqueueSnackbar('All requests processed successfully', { variant: 'success' });
setOpenDialogSubmit(false);
setTimeout(() => {
window.location.reload();
}, 5000); // Reload the page after 5 seconds
})
.catch((error) => {
enqueueSnackbar(error.response?.data?.message ?? 'Something went wrong!', { variant: 'error' });
});
};
function send_bulk(id) {
return axios.post(`claims/${id}/${approve}`, { reasonDecline: reasonDecline });
}
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const createMenu = Boolean(anchorEl);
const importHospital = useRef<HTMLInputElement>(null);
const [currentImportFileName, setCurrentImportFileName] = useState(null);
const [importLoading, setImportLoading] = useState(false);
const [importResult, setImportResult] = useState(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleImportButton = () => {
if (importHospital?.current) {
handleClose();
importHospital.current ? importHospital.current.click() : console.log('No File selected');
} else {
alert('No file selected');
}
};
const handleCancelImportButton = () => {
if(importHospital.current)
{
importHospital.current.value = '';
importHospital.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(importHospital.current && importHospital.current.files)
{
if (importHospital.current?.files.length) {
const formData = new FormData();
formData.append('file', importHospital.current?.files[0]);
setImportLoading(true);
axios
.post('claims/import', formData)
.then((response) => {
handleCancelImportButton();
loadDataTableData();
setImportResult(response.data);
setImportLoading(false);
enqueueSnackbar('Success Import Hospitals', { variant: 'success' });
})
.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 = () => {
axios.get('claims/download-template').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 handleExportReportFiled = async () => {
await axios
.post('claims/exportFiled', { params: importResult?.data.failed_rows })
.then((res) => {
enqueueSnackbar('Data berhasil di Export', {
variant: 'success',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
});
setIsLoading(false)
document.location.href = res.data.data.file_url;
})
.catch((err) =>
enqueueSnackbar('Data Gagal di Export', {
variant: 'error',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
})
);
};
// useEffect(() => {
// loadDataTableData();
// getProvider();
// }, []);
const headStyle = {
fontWeight: 'bold',
};
const headCells = [
{
id: 'code',
align: 'left',
label: 'Code',
isSort: true,
},
{
id: 'name',
align: 'left',
label: 'Name',
isSort: false,
},
{
id: 'member_id',
align: 'left',
label: 'Member ID',
isSort: false,
},
{
id: 'created_at',
align: 'left',
label: 'Date Submission',
isSort: true,
},
{
id: 'plan_code',
align: 'left',
label: 'Plan ID',
isSort: true,
},
{
id: 'service_code',
align: 'left',
label: 'Service',
isSort: false,
},
{
id: 'corporate_policies',
align: 'left',
label: 'Policy Number',
isSort: true,
},
{
id: 'provider',
align: 'left',
label: 'Provider',
isSort: false,
},
{
id: 'tot_bill',
align: 'left',
label: 'Total Billing',
isSort: false,
},
{
id: 'status',
align: 'left',
label: 'Status',
isSort: false,
},
{
id: 'action',
align: 'left',
label: '',
isSort: false,
},
];
const orders = {
order: order,
setOrder: setOrder,
orderBy: orderBy,
setOrderBy: setOrderBy,
};
const createSortHandler = (property: string) => (event: React.MouseEvent<unknown>) => {
handleRequestSort(event, property);
};
const handleRequestSort = async (event: React.MouseEvent<unknown>, property: string) => {
const isAsc = orders?.orderBy === property && orders?.order === 'asc';
orders?.setOrder(isAsc ? 'desc' : 'asc');
orders?.setOrderBy(property);
};
// Called on every row to map the data to the columns
function createData(data: Claims): Claims {
return {
@@ -171,10 +597,18 @@ export default function List() {
{
/* ------------------ TABLE ROW ------------------ */
}
function Row(props: { row: ReturnType<typeof createData> }) {
const { row } = props;
function Row(props: { row: ReturnType<typeof createData>, isSelected: boolean, onSelect: (id: string) => void }) {
const { row, isSelected, onSelect } = props;
// Memperbaiki destrukturisasi props
const handleRowCheckboxChange = () => {
onSelect(row.id); // Panggil fungsi onSelect dari komponen induk dengan id baris saat checkbox di baris diklik
};
const [open, setOpen] = React.useState(false);
const test = 1000;
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
@@ -183,16 +617,22 @@ export default function List() {
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
</IconButton>
</TableCell> */}
<TableCell align="left">{row.claim_request?.code}</TableCell>
<TableCell align="left">
{row?.status == 'received' ? (
<Checkbox checked={isSelected} onChange={handleRowCheckboxChange} />
):''}
</TableCell>
<TableCell align="left">{row?.code}</TableCell>
{/* <TableCell align="left">{row.code}</TableCell> */}
<TableCell align="left">{row.member?.current_plan?.code}</TableCell>
<TableCell align="left">{row.member?.current_corporate?.payor_id}</TableCell>
<TableCell align="left">{row.member?.current_corporate?.code}</TableCell>
<TableCell align="left">{row.member?.current_corporate?.current_policy?.code}</TableCell>
<TableCell align="left">{row.member?.member_id}</TableCell>
<TableCell align="left">{row.benefit_desc}</TableCell>
<TableCell align="center">
<TableCell align="left">{row?.name}</TableCell>
<TableCell align="left">{row?.member_id}</TableCell>
<TableCell align="left">{row?.created_at ? fDateTime(row?.created_at) : ''}</TableCell>
<TableCell align="left">{row?.plan_code}</TableCell>
<TableCell align="left">{row?.service_code}</TableCell>
<TableCell align="left">{row?.corporate_policies}</TableCell>
<TableCell align="left">{row?.provider}</TableCell>
<TableCell align="left">Rp. {row?.tot_bill?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")}</TableCell>
<TableCell align="left">
{row.status == 'draft' && (<Label color='secondary' variant='ghost'>{capitalizeFirstLetter(row.status)}</Label>)}
{row.status == 'requested' && (<Label color='primary' variant='ghost'>{capitalizeFirstLetter(row.status)}</Label>)}
{row.status == 'received' && (<Label color='secondary' variant='ghost'>{capitalizeFirstLetter(row.status)}</Label>)}
@@ -202,18 +642,17 @@ export default function List() {
{row.status == 'declined' && (<Label color='error' variant='ghost'>{capitalizeFirstLetter(row.status)}</Label>)}
</TableCell>
<TableMoreMenu actions={
<>
<MenuItem onClick={() =>navigate(`/claims/edit/${row.id}`) }>
<Edit />
Edit
</MenuItem>
<MenuItem onClick={() => navigate('/claims/detail/'+row.id+'') }>
<FindInPageOutlinedIcon />
Detail
</MenuItem>
</>
} />
<TableCell align="left">
<TableMoreMenu actions={
<>
<MenuItem onClick={() => navigate('/claims/detail/'+row.id_log+'/'+row.id+'') }>
<FindInPageOutlinedIcon />
Detail
</MenuItem>
</>
} />
</TableCell>
</TableRow>
@@ -236,40 +675,77 @@ export default function List() {
/* ------------------ END TABLE ROW ------------------ */
}
function TableContent() {
return (
<Table aria-label="collapsible table">
{/* ------------------ TABLE HEADER ------------------ */}
<TableHead>
<TableRow>
{/* <TableCell style={headStyle} align="left" /> */}
<TableCell style={headStyle} align="left">
Code
</TableCell>
<TableCell style={headStyle} align="left">
Plan ID
</TableCell>
<TableCell style={headStyle} align="left">
Payor ID
</TableCell>
<TableCell style={headStyle} align="left">
Corporate ID
</TableCell>
<TableCell style={headStyle} align="left">
Policy Number
</TableCell>
<TableCell style={headStyle} align="left">
Member ID
</TableCell>
<TableCell style={headStyle} align="left">
Benefit Desc
</TableCell>
<TableCell style={headStyle} align="left">
Status
</TableCell>
<TableCell style={headStyle} align="left">
</TableCell>
{selectedRows.length > 0 ? (
<>
<TableCell style={{ backgroundColor: '#D1F1F1' }} align="left" colSpan={2}>
<Stack direction="row">
<Checkbox checked={selectAll} onChange={handleSelectAll} />
{selectedRows.length > 0 ? selectedRows.length : '0'} &nbsp;<Typography variant='subtitle2'>Selected</Typography>
</Stack>
</TableCell>
<TableCell style={{ backgroundColor: '#D1F1F1' }} align="left" colSpan={6}>
</TableCell>
<TableCell style={{ backgroundColor: '#D1F1F1' }} align="right" colSpan={2}>
<Button variant="text" color="error" startIcon={<CancelIcon />} onClick={() => {setOpenDialogSubmit(true);
setApprove('decline');}}>
<Typography variant='subtitle2'>Decline</Typography>
</Button>
</TableCell>
<TableCell style={{ backgroundColor: '#D1F1F1' }} align="left" colSpan={2}>
<Button variant="text" color="primary" startIcon={<CheckCircleIcon />} onClick={() => {setOpenDialogSubmit(true);
setApprove('approve');}}>
<Typography variant='subtitle2'>Approve</Typography>
</Button>
</TableCell>
</>
) : (
<>
<TableCell style={headStyle} align="left">
<Checkbox checked={selectAll} onChange={handleSelectAll} />
</TableCell>
{headCells &&
headCells.map((headCell, index) => (
<TableCell
key={index}
sortDirection={orders?.orderBy === headCell.id ? orders.order : false}
// @ts-ignore
align={headCell.align}
sx={{ padding: 2 }}
width={headCell.width ? headCell.width : 'auto'}
>
{headCell.isSort ? (
<TableSortLabel
active={orders?.orderBy === headCell.id}
direction={orders?.orderBy === headCell.id ? orders.order : 'asc'}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orders?.orderBy === headCell.id ? (
<Box component="span" sx={visuallyHidden}>
{orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</Box>
) : null}
</TableSortLabel>
) : (
headCell.label
)}
</TableCell>
))}
</>
)}
</TableRow>
</TableHead>
{/* ------------------ END TABLE HEADER ------------------ */}
@@ -278,7 +754,7 @@ export default function List() {
{dataTableIsLoading ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
<TableCell colSpan={11} align="center">
Loading
</TableCell>
</TableRow>
@@ -286,7 +762,7 @@ export default function List() {
) : dataTableData.data.length === 0 ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
<TableCell colSpan={11} align="center">
No Data
</TableCell>
</TableRow>
@@ -294,7 +770,7 @@ export default function List() {
) : (
<TableBody>
{dataTableData.data.map((row) => (
<Row key={row.id} row={row} />
<Row key={row.id} row={row} isSelected={selectedRows.includes(row.id)} onSelect={handleRowSelect} />
))}
</TableBody>
)}
@@ -305,7 +781,212 @@ export default function List() {
return (
<Card>
<ImportForm />
<Grid
container
spacing={2}
sx={{ p: 2, justifyContent: 'space-between', alignItems: 'center' }}
>
<Grid item xs={12} md={12} lg={12}>
<form style={{ width: '100%' }}>
<Grid container spacing={1} sx={{ justifyContent: 'space-between', alignItems: 'center' }}>
<input
type="file"
id="file"
ref={importHospital}
style={{ display: 'none' }}
onChange={handleImportChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
{!currentImportFileName && (
<>
<Grid item xs={12} md={3}>
<TextField
id="search-input"
ref={searchInput}
variant="outlined"
value={searchText}
fullWidth
onChange={handleSearchChange}
onKeyDown={(event) => {
if (event.key === 'Enter') {
handleSearchSubmit(event);
}
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
placeholder: 'Search Code or Name',
}}
/>
</Grid>
<Grid item xs={12} md={4} display="flex" sx={{ gap: '16px' }}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDatePicker
value={startDate}
inputFormat="dd/MM/yyyy"
onChange={(value) => {
// loadDataTableData();
setStartDate(value);
}}
renderInput={(params) => <TextField {...params} fullWidth label="Start" />}
/>
</LocalizationProvider>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDatePicker
value={endDate}
inputFormat="dd/MM/yyyy"
onChange={(value) => {
setEndDate(value);
}}
renderInput={(params) => (
<TextField
{...params}
fullWidth
label="End"
// error={!!error}
// helperText={error?.message}
// {...other}
/>
)}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={12} md={2}>
{
providers && (
<Autocomplete
id="provider"
options={providers}
getOptionLabel={(option) => option.name || ''}
value={providers.find((item) => item.id === dataProvider) || null}
onChange={(event, value) => {
if (value) {
setDataProvider(value.id);
} else {
setDataProvider(null);
}
}}
renderInput={(params) => (
<TextField
{...params}
label="Provider"
fullWidth
/>
)}
/>
)
}
</Grid>
<Grid item xs={12} md={3} display="flex" sx={{ gap: '16px' }}>
<FormControl >
<LoadingButton
id="upload-button"
variant="outlined"
startIcon={<UploadIcon />}
sx={{ p: 1.8 }}
loading={isLoadingImport}
onClick={handleClick}
>
<Typography variant="inherit" sx={{ marginLeft: 1 }}>
Import
</Typography>
</LoadingButton>
<Menu
id="import-button"
anchorEl={anchorEl}
open={createMenu}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleImportButton}>
<Typography variant='body2'>Import</Typography>
</MenuItem>
<MenuItem
onClick={() => {
handleGetTemplate();
}}
>
<Typography variant='body2'> Download Template</Typography>
</MenuItem>
</Menu>
</FormControl>
<FormControl >
<LoadingButton
id="upload-button"
variant="contained"
startIcon={<Download />}
sx={{ p: 1.8 }}
onClick={handleExportReport}
loading={isLoading}
>
<Typography variant="inherit" sx={{ marginLeft: 1 }}>
Export
</Typography>
</LoadingButton>
</FormControl>
</Grid>
</>
)}
{currentImportFileName && (
<Grid item xs={12} md={12}>
<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>
</Grid>
)}
{importResult && (
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: 'text.secondary' }}>
Last Import Result :{' '}
<Box sx={{ color: 'success.main', display: 'inline' }}>
{importResult.data.total_success_row ?? 0}
</Box>{' '}
Row Processed,{' '}
<Box sx={{ color: 'error.main', display: 'inline' }}>
{importResult.data.total_failed_row}
</Box>{' '}
Failed
{/* {importResult.data.failed_rows.map((row, index) => (
<Typography variant='body' key={index} color="error"> [Code={row.code ? row.code : 'Required'}]</Typography>
))} */}
&nbsp;<u onClick={handleExportReportFiled} style={{cursor:'pointer'}}>Download Data Filed</u>
</Box>
</Stack>
)}
</Grid>
</form>
</Grid>
</Grid>
<DataTable
isLoading={dataTableIsLoading}
@@ -314,6 +995,43 @@ export default function List() {
handlePageChange={handlePageChange}
TableContent={<TableContent />}
/>
<Dialog open={openDialogSubmit} onClose={handleCloseDialogSubmit} fullWidth={true}>
<DialogTitle sx={{ backgroundColor: '#19BBBB', color: '#FFF', padding: 2 }}>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Stack direction="row" alignItems='center' spacing={1}>
<Typography variant="h6">Confirmation</Typography>
</Stack>
<IconButton sx={{ color: '#FFF' }} onClick={handleCloseDialogSubmit}>
<CloseIcon />
</IconButton>
</Stack>
</DialogTitle>
<DialogContent>
<Stack spacing={2} padding={2}>
<Typography variant='body1'>Are you sure to {toTitleCase(approve)} this claim ?</Typography>
{approve == "decline" ? (
<Stack direction='row' spacing={2} marginTop={2}>
<TextField
id="outlined-multiline-static"
label="Reason decline"
multiline
rows={4} // Tentukan jumlah baris yang diinginkan
defaultValue=""
onChange={handleReasonDeclineChange}
variant="outlined"
sx={{width:'100%'}}
// fullWidth // Gunakan ini jika Anda ingin input memenuhi lebar Stack
/>
</Stack>
): ''}
</Stack>
</DialogContent>
<DialogActions>
<Button variant="outlined" sx={{color: '#212B36', borderColor: '#919EAB52'}} onClick={handleCloseDialogSubmit}>Cancel</Button>
<Button sx={{backgroundColor: (approve === 'decline' ? '' : '#19BBBB'), color: (approve === 'decline' ? '#FF4842' : ''), borderColor: '#FF4842'}} onClick={handleSubmitData} variant={(approve === 'decline' ? 'outlined' : 'contained')}>{(approve === "decline" ? 'Decline' : 'Approve')}</Button>
</DialogActions>
</Dialog>
</Card>
);
}

View File

@@ -145,51 +145,62 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
const methods = useForm<any>({
resolver: yupResolver(validationSchema),
defaultValues
defaultValues,
reValidateMode: "onChange"
});
let width = claimInput ? 2 : 2.36;
const {fields, append, remove} = useFieldArray({name: 'benefit_data',control: methods.control});
const { handleSubmit, reset, watch, setValue, formState: { isDirty, isSubmitting, errors } } = methods
// Buat total data
let totalAmountIncurred = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.amount_incurred || 0);
}, 0);
let totalAmountApproved = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.amount_approved || 0);
}, 0);
let totalAmountNotApproved = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.amount_not_approved || 0);
}, 0);
let totalExcessPaid = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.excess_paid || 0);
}, 0);
const {fields, append, remove} = useFieldArray({name: 'benefit_data',control: methods.control,});
const { handleSubmit, reset, watch, setValue, setError, clearErrors, formState: { isDirty, isSubmitting, errors,isValid } } = methods
const errorsExist = errors ? Object.keys(errors).length > 0 : false;
// Calculate
const benefitData = watch('benefit_data');
const [isDisableSave, setDisableSave] = useState(false)
benefitData?.map((item, index) => {
totalAmountIncurred += parseFloat(item.amount_incurred);
totalAmountApproved += parseFloat(item.amount_approved);
totalAmountNotApproved += parseFloat(item.amount_not_approved);
totalExcessPaid += parseFloat(item.excess_paid);
const totalAll = () => {
let totalAmountIncurred = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.amount_incurred || 0);
}, 0);
let totalAmountApproved = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.amount_approved || 0);
}, 0);
let totalAmountNotApproved = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.amount_not_approved || 0);
}, 0);
let totalExcessPaid = (requestLog?.benefit_data || []).reduce((accumulator, item) => {
return accumulator + (item.excess_paid || 0);
}, 0);
if (totalAmountApproved != 0 && totalAmountIncurred != 0) {
if (totalAmountApproved > totalAmountIncurred){
setValue(`benefit_data.${index}.amount_approved`, 0)
alert('Total Amount Approved tidak boleh lebih dari Total Incurred')
}
}
benefitData?.map((item, index) => {
totalAmountIncurred += parseFloat(item.amount_incurred);
totalAmountApproved += parseFloat(item.amount_approved);
totalAmountNotApproved += parseFloat(item.amount_not_approved);
totalExcessPaid += parseFloat(item.excess_paid);
});
return {
totalAmountIncurred,
totalAmountApproved,
totalAmountNotApproved,
totalExcessPaid
}
}
const handleOnChangeNominal = (key) => {
if (totalAll().totalAmountApproved > totalAll().totalAmountIncurred){
// setValue(`benefit_data.${key}.amount_approved`, 0);
setError(`benefit_data.${key}.amount_approved`, {message: 'Amount Approve tidak boleh lebih dari Amount Incurred'});
} else {
clearErrors(`benefit_data.${key}.amount_approved`);
}
}
});
// Submit Form
// =====================================
const submitHandler = async (data: BenefitConfigurationListType) => {
const mapData = data.benefit_data.map((item) => ({
...item,
reason: item.reason.value
reason: item.reason ? item.reason.value : null
}));
const newData = {
@@ -206,8 +217,6 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
}
}
// Calculate
const getContent = () => !addBenefit ? (
<Stack spacing={2} sx={{marginTop: 2, padding: 2}} direction="column">
<Stack direction="row" spacing={2}>
@@ -281,6 +290,10 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
name={`benefit_data.${index}.amount_incurred`}
placeholder='Amount Incurred'
required
onChange={(event) => {
setValue(`benefit_data.${index}.amount_incurred`, event.target.value)
handleOnChangeNominal(index)}
}
/>
</Grid>
</Grid>
@@ -301,6 +314,10 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
name={`benefit_data.${index}.amount_approved`}
placeholder='Amount Approved'
required
onChange={(event) => {
setValue(`benefit_data.${index}.amount_approved`, event.target.value)
handleOnChangeNominal(index)}
}
/>
</Grid>
</Grid>
@@ -434,7 +451,7 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalAmountIncurred)}
{fNumber(totalAll().totalAmountIncurred)}
</Typography>
</Grid>
</Grid>
@@ -450,7 +467,7 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalAmountApproved)}
{fNumber(totalAll().totalAmountApproved)}
</Typography>
</Grid>
</Grid>
@@ -466,7 +483,7 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalAmountNotApproved)}
{fNumber(totalAll().totalAmountNotApproved)}
</Typography>
</Grid>
</Grid>
@@ -482,7 +499,7 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalExcessPaid)}
{fNumber(totalAll().totalExcessPaid)}
</Typography>
</Grid>
</Grid>
@@ -499,7 +516,7 @@ export default function DialogBenefit({requestLog, setOpenDialog, openDialog, cl
<DialogActions>
<Stack direction="row" sx={{marginTop:3}} alignItems="center" justifyContent="space-between" spacing={2}>
<Button variant="outlined" onClick={handleCloseDialogBenefit}><Typography>Cancel</Typography></Button>
<LoadingButton disabled={isDisableSave} type="submit" variant="contained" loading={isSubmitting}>
<LoadingButton disabled={errorsExist} type="submit" variant="contained" loading={isSubmitting}>
Save
</LoadingButton>
</Stack>

View File

@@ -62,23 +62,36 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
defaultValues
});
const { handleSubmit, reset, watch, setValue, formState: { isDirty, isSubmitting, errors } } = methods;
const { handleSubmit, reset, watch, setValue, setError, clearErrors, formState: { isDirty, isSubmitting, errors } } = methods;
const errorsExist = errors ? Object.keys(errors).length > 0 : false;
const totalAll = () => {
// Ambil nilai dari form menggunakan watch
const amountIncurred = parseFloat(watch('amount_incurred'));
const amountApproved = parseFloat(watch('amount_approved'));
const amountNotApproved = parseFloat(watch('amount_not_approved'));
const excessPaid = parseFloat(watch('excess_paid'));
// Hitung total baru
const totalAmountIncurred = total.totalAmountIncurred - data?.amount_incurred + amountIncurred;
const totalAmountApproved = total.totalAmountApproved - data?.amount_approved + amountApproved;
const totalAmountNotApproved = total.totalAmountNotApproved - data?.amount_not_approved + amountNotApproved;
const totalExcessPaid = total.totalExcessPaid - data?.excess_paid + excessPaid;
// Ambil nilai dari form menggunakan watch
const amountIncurred = parseFloat(watch('amount_incurred'));
const amountApproved = parseFloat(watch('amount_approved'));
const amountNotApproved = parseFloat(watch('amount_not_approved'));
const excessPaid = parseFloat(watch('excess_paid'));
return {
totalAmountIncurred,
totalAmountApproved,
totalAmountNotApproved,
totalExcessPaid
}
}
// Hitung total baru
const totalAmountIncurred = total.totalAmountIncurred - data?.amount_incurred + amountIncurred;
const totalAmountApproved = total.totalAmountApproved - data?.amount_approved + amountApproved;
const totalAmountNotApproved = total.totalAmountNotApproved - data?.amount_not_approved + amountNotApproved;
const totalExcessPaid = total.totalExcessPaid - data?.excess_paid + excessPaid;
if (totalAmountApproved > totalAmountIncurred) {
alert('Total Approve tidak boleh melebihi Total Incurred')
setValue('amount_approved', data?.amount_approved)
const handleOnChangeNominal = (key) => {
if (totalAll().totalAmountApproved > totalAll().totalAmountIncurred){
// setValue(`benefit_data.${key}.amount_approved`, 0);
setError(`amount_approved`, {message: 'Amount Approve tidak boleh lebih dari Amount Incurred'});
} else {
clearErrors(`amount_approved`);
}
}
// if (totalAmountIncurred !== (totalAmountApproved+totalAmountNotApproved)){
@@ -142,6 +155,10 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
name={`amount_incurred`}
placeholder='Amount Incurred'
required
onChange={(event) => {
setValue(`amount_incurred`, event.target.value)
handleOnChangeNominal(id)}
}
/>
</Grid>
</Grid>
@@ -162,6 +179,10 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
name={`amount_approved`}
placeholder='Amount Approved'
required
onChange={(event) => {
setValue(`amount_approved`, event.target.value)
handleOnChangeNominal(id)}
}
/>
</Grid>
</Grid>
@@ -275,7 +296,7 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{totalAmountIncurred ? fNumber(totalAmountIncurred) : 0}
{totalAll().totalAmountIncurred ? fNumber(totalAll().totalAmountIncurred) : 0}
</Typography>
</Grid>
</Grid>
@@ -291,7 +312,7 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalAmountApproved)}
{totalAll().totalAmountApproved ? fNumber(totalAll().totalAmountApproved) : 0}
</Typography>
</Grid>
</Grid>
@@ -307,7 +328,7 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalAmountNotApproved)}
{totalAll().totalAmountNotApproved ? fNumber(totalAll().totalAmountNotApproved) : 0}
</Typography>
</Grid>
</Grid>
@@ -323,7 +344,7 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ fontWeight: 'bold', textAlign: 'right' }}>
{fNumber(totalExcessPaid)}
{totalAll().totalExcessPaid ? fNumber(totalAll().totalExcessPaid) : 0}
</Typography>
</Grid>
</Grid>
@@ -337,7 +358,7 @@ export default function DialogEditBenefit({id, data, setOpenDialog, openDialog,
<DialogActions>
<Stack direction="row" sx={{marginTop:3}} alignItems="center" justifyContent="space-between" spacing={2}>
<Button variant="outlined" onClick={handleCloseDialog}><Typography>Cancel</Typography></Button>
<LoadingButton type="submit" variant="contained" loading={isSubmitting}>
<LoadingButton disabled={errorsExist} type="submit" variant="contained" loading={isSubmitting}>
Save
</LoadingButton>
</Stack>

View File

@@ -68,6 +68,7 @@ export default function Detail() {
const navigate = useNavigate();
const { themeStretch } = useSettings();
const [requestLog, setRequestLog] = useState<DetailFinalLogType>();
const [isReversal, setIsReversal] = useState(false);
const { id } = useParams();
@@ -77,6 +78,7 @@ export default function Detail() {
.get('customer-service/request/'+id)
.then((response) => {
setRequestLog(response.data.data)
setIsReversal(response.data.data.is_reversal)
})
.catch((error) => {
console.error(error);
@@ -304,11 +306,15 @@ export default function Detail() {
<Card sx={{padding:2}} >
<Stack direction="row" alignItems="center" sx={{marginBottom: 4}}>
<Typography variant='subtitle1' sx={{color: '#19BBBB'}} gutterBottom>Benefit</Typography>
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogBenefit(true);
}} >
<Typography variant="button" display="block">Benefit</Typography>
</Button>
{
!isReversal ? (
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogBenefit(true);
}} >
<Typography variant="button" display="block">Benefit</Typography>
</Button>
) : null
}
</Stack>
{requestLog?.benefit_data?.map((item, index) => (
@@ -321,29 +327,33 @@ export default function Detail() {
{item.benefit?.description}
</Typography>
</Grid>
<Grid item xs={6} sx={{ display: 'flex', placeContent: 'end' }}>
<MoreMenu actions={
<>
<MenuItem onClick={() => {
setDialogEditBenefit(true)
setIdBenefitData(item.id)
setBenefitConfigurationData(item)
}}
>
<EditOutlined />
Edit
</MenuItem>
<MenuItem onClick={() => {
setIdBenefitData(item.id)
setDialogDeleteBenefit(true)
}}
>
<Delete color='error'/>
Delete
</MenuItem>
</>
} />
</Grid>
{
!isReversal ? (
<Grid item xs={6} sx={{ display: 'flex', placeContent: 'end' }}>
<MoreMenu actions={
<>
<MenuItem onClick={() => {
setDialogEditBenefit(true)
setIdBenefitData(item.id)
setBenefitConfigurationData(item)
}}
>
<EditOutlined />
Edit
</MenuItem>
<MenuItem onClick={() => {
setIdBenefitData(item.id)
setDialogDeleteBenefit(true)
}}
>
<Delete color='error'/>
Delete
</MenuItem>
</>
} />
</Grid>
) : null
}
</Grid>
</Grid>
<Grid item xs={12}>
@@ -619,13 +629,16 @@ export default function Detail() {
<Stack direction="column" spacing={2} sx={{marginBottom: 2}}>
<Typography variant='subtitle1' sx={{color: '#19BBBB'}} gutterBottom>Files </Typography>
</Stack>
<Stack direction="column" spacing={2} sx={{marginBottom: 2}}>
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogUploadFileLog(true)
}} >
<Typography variant="button" display="block">Files</Typography>
</Button>
</Stack>
{ !isReversal ? (
<Stack direction="column" spacing={2} sx={{marginBottom: 2}}>
<Button variant="outlined" startIcon={<AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => {
setDialogUploadFileLog(true)
}} >
<Typography variant="button" display="block">Files</Typography>
</Button>
</Stack>
) : null }
</Stack>
{requestLog?.files?.map((documentType, index) => (
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{marginBottom: 2}} key={index}>
@@ -638,14 +651,17 @@ export default function Detail() {
<Typography variant="body2" gutterBottom>{documentType.original_name ? documentType.original_name : '-'}</Typography>
</a>
</Stack>
<Stack direction="column" spacing={2}>
<IconButton onClick={() => {
setDialogDeleteFileLog(true)
setPathFile(documentType.path)
}} aria-label="delete" size="small" sx={{ marginLeft: 'auto' }}>
<Delete color='error' fontSize="small" />
</IconButton>
</Stack>
{ !isReversal ? (
<Stack direction="column" spacing={2}>
<IconButton onClick={() => {
setDialogDeleteFileLog(true)
setPathFile(documentType.path)
}} aria-label="delete" size="small" sx={{ marginLeft: 'auto' }}>
<Delete color='error' fontSize="small" />
</IconButton>
</Stack>
) : null }
</Stack>
))}

View File

@@ -456,7 +456,7 @@ export default function Router() {
element: <ClaimsCreate />,
},
{
path: 'claims/detail/:id',
path: 'claims/detail/:id/:id_claim',
element: <ClaimsDetail />,
},
{