[WIP] Claim Encounters

This commit is contained in:
R
2023-03-24 14:41:02 +07:00
parent 229908e492
commit 7b9a341ccd
23 changed files with 1099 additions and 37 deletions

View File

@@ -17,6 +17,7 @@ export default function AutocompleteDoctor({
currentValue,
textLabel,
currentOptions = [],
...other
}: autocompleteDoctorType) {
const [options, setOptions] = useState<IcdType>(currentOptions);
const [loading, setLoading] = useState(false);
@@ -56,7 +57,7 @@ export default function AutocompleteDoctor({
}}
loading={loading}
renderInput={(params) => (
<TextField {...params} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/>
<TextField {...params} {...other} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/>
)}
/>
);

View File

@@ -17,6 +17,7 @@ export default function AutocompleteHealthcare({
currentValue,
textLabel,
currentOptions = [],
...other
}: autocompleteHealthcareType) {
const [options, setOptions] = useState<IcdType>(currentOptions);
const [loading, setLoading] = useState(false);
@@ -56,7 +57,7 @@ export default function AutocompleteHealthcare({
}}
loading={loading}
renderInput={(params) => (
<TextField {...params} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/>
<TextField {...params} {...other} label={textLabel} variant="outlined" fullWidth onChange={(event) => {getOptions(event.target.value)}}/>
)}
/>
);

View File

@@ -49,13 +49,30 @@ export default function RHFDatepicker({ name, ...other }: IProps & TextFieldProp
/>
)}
/> */}
<DesktopDatePicker
{/* <DesktopDatePicker
value={field.value}
inputFormat="dd/MM/yyyy"
onChange={(value) => {
field.onChange(value)
}}
renderInput={(params) => <TextField {...params} fullWidth />}
/> */}
<DesktopDatePicker
value={field.value}
inputFormat="dd/MM/yyyy"
onChange={(value) => {
field.onChange(value);
}}
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!error}
helperText={error?.message}
{...other}
/>
)}
/>
</LocalizationProvider>
)}

View File

@@ -19,30 +19,25 @@ import {
TextField,
Typography,
} from '@mui/material';
import { Controller, useForm } from 'react-hook-form';
import { useParams, useNavigate } from 'react-router-dom';
import { useParams } 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 React, { useEffect, useMemo, useRef, useState } from 'react';
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 { fDate } from '@/utils/formatTime';
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';
import AutocompleteDiagnosisControlled from '@/components/autocomplete/AutocompleteDiagnosisControlled';
import { Icd, IcdType } from '@/@types/diagnosis';
import { OrganizationType, PractitionerType } from '@/@types/doctor';
import ClaimDetail from './components/ClaimDetail';
import DialogHistoryPerawatan from './components/DialogHistoryPerawatan';
import { IconButton } from '@mui/material';
import { Tooltip } from '@mui/material';
export default function ClaimsCreateUpdate() {
const { themeStretch } = useSettings();
@@ -198,8 +193,9 @@ export default function ClaimsCreateUpdate() {
});
};
// ----------------------------------------------------------------
// History Perawatan
const [openDialogHistoryPerawatan, setOpenDialogHistoryPerawatan] = useState(false);
useEffect(() => {
axios.get('/claims/' + id).then(({ data }) => {
@@ -212,6 +208,33 @@ export default function ClaimsCreateUpdate() {
});
}, [id]);
const handleSetFinalEncounter = (encounter) => {
const tempLastFinalEncounterId = currentClaim.final_encounter_id
axios.post(`/claims/${id}/set-final-encounter`, {
encounter_id: encounter.id
})
.then((res) => {
setCurrentClaim({...currentClaim, ...{final_encounter_id: encounter.id}})
enqueueSnackbar(res.data.message, {variant: 'success'})
})
.catch((err) => {
setCurrentClaim({...currentClaim, ...{final_encounter_id: tempLastFinalEncounterId}})
enqueueSnackbar(err.message, {variant: 'error'})
})
setCurrentClaim({...currentClaim, ...{final_encounter_id: encounter.id}})
}
// ------------------------------------------------------------------
// Edit History Perawatan
const [openDialogEditHistoryPerawatan, setOpenDialogEditHistoryPerawatan] = useState(false)
const [editedEncounter, setEditedEncounter] = useState(null)
const showEditEncounter = (encounter) => {
setEditedEncounter(encounter)
setOpenDialogEditHistoryPerawatan(true)
}
return (
<Page title={`Claim : ${currentClaim?.code}`}>
<Container maxWidth={themeStretch ? false : 'xl'}>
@@ -359,9 +382,86 @@ export default function ClaimsCreateUpdate() {
</Grid>
<Grid item xs={7}>
{/* Diagnosis */}
{/* Claim Detail */}
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2 }}>
<ClaimDetail claim={currentClaim} />
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{ marginBottom: 2 }}
>
<Stack direction="row" alignItems="center" spacing={1}>
<Iconify icon="eva:bell-fill" />
<Typography variant="body2" fontWeight={600}>
History Perawatan
</Typography>
</Stack>
<Typography
onClick={() => {
setOpenDialogHistoryPerawatan(true);
}}
>
+ Tambah History Perawatan
</Typography>
</Stack>
{/* For Creation History Perawatan / Encounter */}
<DialogHistoryPerawatan
openDialog={openDialogHistoryPerawatan}
setOpenDialog={setOpenDialogHistoryPerawatan}
onChange={() => {}}
claim={currentClaim}
></DialogHistoryPerawatan>
<Paper sx={{ background: 'white', marginY: 2, p: 2 }}>
<Stack sx={{ maxHeight: '250px', overflowY: 'scroll' }}>
{currentClaim?.encounters && currentClaim?.encounters.map((encounter, index) => (
<React.Fragment key={index}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
sx={{ marginY: 1 }}
>
<Stack>
<Typography fontWeight={'bold'} onClick={() => {showEditEncounter(encounter)}}><a href="#">{ encounter.class_name }</a></Typography>
<Typography sx={{ color: '#777', fontSize: '10px' }}>
({ fDate(encounter.start) } - { fDate(encounter.end) })
</Typography>
<Typography variant="body2">Diagnosis: { encounter.primary_diagnosis ? (`(${encounter.primary_diagnosis.diagnosis?.code}) ${encounter.primary_diagnosis.diagnosis?.name}`) : '-'}</Typography>
<Typography variant="body2">Dokter: { encounter.primary_doctor ? (`${encounter.primary_doctor.name}`) : '-'}</Typography>
</Stack>
<Tooltip title="Diagnosa Final">
<IconButton onClick={() => {handleSetFinalEncounter(encounter)}}>
<Iconify
icon="eva:checkmark-circle-2-fill"
color={currentClaim.final_encounter_id == encounter.id ? "green" : "gray"}
sx={{ margin: 2 }}
></Iconify>
</IconButton>
</Tooltip>
</Stack>
<Divider />
</React.Fragment>
))}
{(!currentClaim?.encounters || currentClaim?.encounters.length == 0) && (
<Typography>Belum ada History Perawatan</Typography>
)}
</Stack>
</Paper>
{/* For Editing History Perawatan / Encounter */}
<DialogHistoryPerawatan
openDialog={openDialogEditHistoryPerawatan}
setOpenDialog={setOpenDialogEditHistoryPerawatan}
onChange={() => {}}
claim={currentClaim}
encounter={editedEncounter}
></DialogHistoryPerawatan>
{/* <ClaimDetail claim={currentClaim} /> */}
</Paper>
<Paper variant="outlined" sx={{ background: '#f4f6f8', p: 2, marginTop: 2 }}>

View File

@@ -21,19 +21,19 @@ export default function DiagnosisHistory({ diagnosis }) {
<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" />
<Iconify icon="eva:clock-outline" />
<Typography variant="body2" fontWeight={600}>
Riwayat Diagnosa
</Typography>
</Stack>
<Typography
{/* <Typography
variant="body2"
onClick={() => {
setOpenDialogRequestDocument(true);
}}
>
View All
</Typography>
</Typography> */}
</Stack>
<Paper sx={{ background: 'white', marginTop: 2 }}>

View File

@@ -0,0 +1,73 @@
import MuiDialog from '@/components/MuiDialog';
import axios from '@/utils/axios';
import { Button, Checkbox, Typography } from '@mui/material';
import { Paper } from '@mui/material';
import { Stack } from '@mui/material';
import { enqueueSnackbar } from 'notistack';
import { useState } from 'react';
import FormHistoryPerawatan from './FormHistoryPerawatan';
type DialogHistoryPerawatanType = {
openDialog: boolean;
setOpenDialog: void;
onSubmit?: void;
claim: any; // TODO create ClaimType
encounter?: any;
}
export default function DialogHistoryPerawatan({ openDialog, setOpenDialog, onSubmit, claim, encounter } : DialogHistoryPerawatanType) {
const isEdit = encounter?.id != null
// 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 = (data) => {
if (!isEdit) {
axios.post(`claims/${claim.id}/encounters`, data)
.then((res) => {
enqueueSnackbar(res.data.message, {variant: 'success'})
setOpenDialog(false);
})
.catch((err) => {
enqueueSnackbar(err.message, {variant: 'error'})
})
} else {
axios.post(`claims/${claim.id}/encounters/${data.id}/update`, data)
.then((res) => {
enqueueSnackbar(res.data.message, {variant: 'success'})
setOpenDialog(false);
})
.catch((err) => {
enqueueSnackbar(err.message, {variant: 'error'})
})
}
};
const getContent = () => (
<Stack spacing={1} marginTop={2}>
<FormHistoryPerawatan claim={claim} onSubmit={handleSubmit} encounter={encounter}></FormHistoryPerawatan>
</Stack>
);
return (
<MuiDialog
title={{ name: !isEdit ? 'Tambah History Perawatan' : 'Edit History Perawatan' }}
openDialog={openDialog}
setOpenDialog={setOpenDialog}
content={getContent()}
maxWidth="xl"
/>
);
}

View File

@@ -0,0 +1,352 @@
import * as Yup from 'yup';
import { IcdType } from '@/@types/diagnosis';
import { OrganizationType, PractitionerType } from '@/@types/doctor';
import AutocompleteDiagnosisControlled from '@/components/autocomplete/AutocompleteDiagnosisControlled';
import { LoadingButton } from '@mui/lab';
import { Paper, Stack, TextField } from '@mui/material';
import { Controller, useForm } from 'react-hook-form';
import React, { useEffect, useMemo, useState } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import { enqueueSnackbar } from 'notistack';
import axios from '@/utils/axios';
import { FormProvider, RHFDatepicker, RHFTextField, RHFSelect } from '@/components/hook-form';
import AutocompleteDoctor from '@/components/autocomplete/AutocompleteDoctor';
import AutocompleteHealthcare from '@/components/autocomplete/AutocompleteHealthcare';
import { Typography } from '@mui/material';
import Iconify from '@/components/Iconify';
import { Divider } from '@mui/material';
type FormHistoryPerawatanProps = {
claim: any;
onSubmit: void;
encounter?: any; // TODO EncounterType
};
export default function FormHistoryPerawatan({
claim,
onSubmit,
encounter,
}: FormHistoryPerawatanProps) {
const isEdit = encounter?.id != null;
// --------------------------------------------------------------
// Diagnosis
const [primaryDiagnosis, setPrimaryDiagnosis] = useState(null);
const [secondaryDiagnosis, setSecondaryDiagnosis] = useState(null);
const [loadingDiagnosis, setLoadingDiagnosis] = useState(false);
const [primaryDiagnosisOptions, setPrimaryDiagnosisOptions] = useState([]);
const [secondaryDiagnosisOptions, setSecondaryDiagnosisOptions] = useState([]);
const [doctorOptions, setDoctorOptions] = useState([]);
const [healthcareOptions, setHealthcareOptions] = useState([]);
const ClaimDetailSchema = Yup.object().shape({
primary_diagnosis: Yup.object().required('Diagnosis Utama Wajib dipilih'),
// secondary_diagnosis: Yup.object().required('Diagnosis Utama Wajib dipilih'),
doctor: Yup.object().required('Dokter Harus dipilih'),
healthcare: Yup.object().required('Healthcare Harus dipilih'),
});
interface FormValuesProps extends Partial<any> {
service_code: String;
primary_diagnosis: IcdType;
secondary_diagnosis: IcdType;
doctor: PractitionerType;
healthcare: OrganizationType;
secondary_diagnoses: IcdType[];
}
const defaultValues = useMemo(
() => ({
service_code: 'OP',
tanggal_masuk: null,
tanggal_keluar: null,
primary_diagnosis: null,
doctor: null,
healthcare: null,
no_medrec: '',
bed: '',
duration: '',
secondary_diagnoses: [],
}),
[claim]
);
const methods = useForm<FormValuesProps>({
resolver: yupResolver(ClaimDetailSchema),
defaultValues,
});
const {
reset,
watch,
control,
setValue,
getValues,
setError,
handleSubmit,
formState: { isSubmitting },
} = methods;
const values = watch();
const handleSaveDiagnosis = () => {
setLoadingDiagnosis(true);
axios
.post(`claims/${claim.id}/update-diagnosis`, {
primary: [getValues('primary_diagnosis')?.id],
secondary: [getValues('secondary_diagnosis')?.id],
})
.then((res) => {
enqueueSnackbar(res.data.message, { variant: 'success' });
})
.catch((err) => {
setLoadingDiagnosis(false);
enqueueSnackbar(err.response?.data?.message ?? err?.message, { variant: 'error' });
})
.then(() => {
setLoadingDiagnosis(false);
});
};
useEffect(() => {
if (claim) {
// SET Default Primary Diagnosis
// if (claim.primary_diagnosis.length && claim.primary_diagnosis[0].icd) {
// setPrimaryDiagnosisOptions(
// claim.primary_diagnosis.map((diagnosis) => {
// if (diagnosis.icd) {
// return diagnosis.icd;
// }
// })
// );
// setValue('primary_diagnosis', claim.primary_diagnosis[0].icd);
// }
// SET Default Secondary Diagnosis
// if (claim.secondary_diagnosis.length && claim.secondary_diagnosis[0].icd) {
// setSecondaryDiagnosisOptions(
// claim.secondary_diagnosis.map((diagnosis) => {
// if (diagnosis.icd) {
// return diagnosis.icd;
// }
// })
// );
// setValue('secondary_diagnosis', claim.secondary_diagnosis[0].icd);
// }
}
}, [claim]);
useEffect(() => {
if (encounter?.id) {
// Primary Diagnosis
if (encounter.primary_diagnosis && encounter.primary_diagnosis?.diagnosis) {
setPrimaryDiagnosisOptions([encounter.primary_diagnosis.diagnosis]);
setValue('primary_diagnosis', encounter.primary_diagnosis.diagnosis);
}
// Sondary Diagnoses
if (encounter.secondary_diagnoses && encounter.secondary_diagnoses.length) {
encounter.secondary_diagnoses.map();
setPrimaryDiagnosisOptions([encounter.primary_diagnosis.diagnosis]);
setValue('primary_diagnosis', encounter.primary_diagnosis.diagnosis);
}
if (encounter.primary_doctor) {
// TODO Clear Up Data to used by autocomplete
setDoctorOptions([encounter.primary_doctor]);
setValue('doctor', encounter.primary_doctor);
}
if (encounter.healthcare) {
// TODO Clear Up Data to used by autocomplete
setDoctorOptions([encounter.healthcare]);
setValue('healthcare', encounter.healthcare);
}
setValue('service_code', encounter.class);
setValue('tanggal_masuk', encounter.start);
setValue('tanggal_keluar', encounter.end);
setValue('medical_record_number', encounter.medical_record_number);
setValue('number_of_bed', encounter.number_of_bed);
setValue('duration_day', encounter.duration_day);
setValue('secondary_diagnoses', encounter.secondary_diagnosis);
setValue('id', encounter.id);
}
}, [encounter]);
function handleAddSecondaryDiagnosis() {
setValue('secondary_diagnoses', [...getValues('secondary_diagnoses'), null]);
}
function handleRemoveSecondaryDiagnosis(diagnosis) {
// TODO Fix Bug
const newDiagnoses = getValues('secondary_diagnoses').filter((val) => {
return diagnosis !== val;
});
setValue('secondary_diagnoses', newDiagnoses);
}
const services = [
{
code: 'OP',
name: 'Rawat Jalan',
},
{
code: 'IP',
name: 'Rawat Inap',
},
];
const submitting = () => {
onSubmit(getValues());
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(submitting)}>
<Stack spacing={2}>
<Stack spacing={2} sx={{ background: 'white', p: 2 }}>
<RHFSelect name="service_code" label="Layanan" placeholder="Layanan">
{/* <option value="" /> */}
{services.map((option, index) => (
<option key={index} value={option.code}>
{option.name}
</option>
))}
</RHFSelect>
<RHFDatepicker
name="tanggal_masuk"
placeholder="DD/MM/YYYY"
label="Tanggal Masuk"
></RHFDatepicker>
<RHFDatepicker
name="tanggal_keluar"
placeholder="DD/MM/YYYY"
label="Tanggal Keluar"
></RHFDatepicker>
<Controller
name="healthcare"
control={control}
render={({ field: { onChange, value } }) => (
<AutocompleteHealthcare
onChange={onChange}
currentOptions={healthcareOptions}
currentValue={value}
textLabel="Tempat Dirawat"
placeholder="Ketik nama fasilitas kesehatan"
/>
)}
/>
<Controller
name="doctor"
control={control}
render={({ field: { onChange, value } }) => (
<AutocompleteDoctor
onChange={onChange}
currentOptions={doctorOptions}
currentValue={value}
textLabel="Dokter yang merawat"
placeholder="Ketik nama dokter"
/>
)}
/>
<RHFTextField
name="medical_record_number"
placeholder='"No. Rekam Medis" di fasilitas kesehatan'
label="No. Rekam Medis"
></RHFTextField>
<RHFTextField name="number_of_bed" label="Jumlah Tempat Tidur"></RHFTextField>
<RHFTextField name="duration_day" label="Lama Perawatan (hari)"></RHFTextField>
</Stack>
<Paper variant="outlined" sx={{ background: 'white', p: 2 }}>
<Stack spacing={2}>
{/* {JSON.stringify(getValues('primary_diagnosis'))} */}
<Controller
name="primary_diagnosis"
control={control}
render={({ field: { onChange, value } }) => (
<AutocompleteDiagnosisControlled
onChange={onChange}
currentOptions={primaryDiagnosisOptions}
currentValue={value}
textLabel="Diagnosis Utama (ICD-X)"
/>
)}
/>
{/* SECONDARY DIAGNOSES */}
<Stack direction="row" justifyContent="end">
<Typography
onClick={() => {
handleAddSecondaryDiagnosis();
}}
>
+ Secondary Diagnosis
</Typography>
</Stack>
{getValues('secondary_diagnoses') &&
getValues('secondary_diagnoses').map(function (diagnosis, index) {
return (
<React.Fragment key={index}>
{/* <Stack direction="row" sx={{ width: '100%'}}> */}
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
divider={<Divider orientation="horizontal" flexItem />}
>
<Typography>
<strong>#{index + 1}</strong>
</Typography>
<Iconify
icon="eva-trash-outline"
color="red"
onClick={() => {
handleRemoveSecondaryDiagnosis(diagnosis);
}}
></Iconify>
</Stack>
<Controller
name={`secondary_diagnoses[${index}]`}
control={control}
sx={{ width: '100%' }}
render={({ field: { onChange, value } }) => (
<AutocompleteDiagnosisControlled
onChange={onChange}
currentOptions={[diagnosis]}
currentValue={value}
textLabel="Diagnosis Tambahan (ICD-X)"
/>
)}
/>
{/* </Stack> */}
</React.Fragment>
);
})}
</Stack>
</Paper>
{(claim?.status == 'requested' || claim?.status == 'received') && (
<LoadingButton
variant="contained"
sx={{ marginTop: 2 }}
loading={loadingDiagnosis}
onClick={() => {
submitting();
}}
>
Simpan Detail
</LoadingButton>
)}
</Stack>
</FormProvider>
);
}