From 53b35e86c210289f8c11e9a07a46a5860ec002c8 Mon Sep 17 00:00:00 2001 From: R Date: Wed, 8 Mar 2023 03:13:13 +0700 Subject: [PATCH] [WIP] Copy Claims Pages from Dashboard --- .../src/@types/paginated-data.ts | 15 + .../src/components/LaravelTable.tsx | 28 + .../src/pages/Claims/CreateUpdate.tsx | 64 ++ .../client-portal/src/pages/Claims/Form.tsx | 596 ++++++++++++++++++ .../client-portal/src/pages/Claims/Index.tsx | 30 + .../client-portal/src/pages/Claims/List.tsx | 286 +++++++++ .../client-portal/src/pages/Claims/Show.tsx | 458 ++++++++++++++ .../pages/Claims/components/ClaimItems.tsx | 69 ++ .../Claims/components/DiagnosisHistory.tsx | 54 ++ .../Claims/components/DialogMemberBenefit.tsx | 59 ++ .../src/pages/Claims/components/Documents.tsx | 68 ++ frontend/client-portal/src/routes/index.tsx | 17 + frontend/client-portal/vite.config.ts | 4 + 13 files changed, 1748 insertions(+) create mode 100644 frontend/client-portal/src/components/LaravelTable.tsx create mode 100755 frontend/client-portal/src/pages/Claims/CreateUpdate.tsx create mode 100644 frontend/client-portal/src/pages/Claims/Form.tsx create mode 100755 frontend/client-portal/src/pages/Claims/Index.tsx create mode 100755 frontend/client-portal/src/pages/Claims/List.tsx create mode 100644 frontend/client-portal/src/pages/Claims/Show.tsx create mode 100644 frontend/client-portal/src/pages/Claims/components/ClaimItems.tsx create mode 100644 frontend/client-portal/src/pages/Claims/components/DiagnosisHistory.tsx create mode 100644 frontend/client-portal/src/pages/Claims/components/DialogMemberBenefit.tsx create mode 100644 frontend/client-portal/src/pages/Claims/components/Documents.tsx diff --git a/frontend/client-portal/src/@types/paginated-data.ts b/frontend/client-portal/src/@types/paginated-data.ts index 738f8785..eca6d930 100755 --- a/frontend/client-portal/src/@types/paginated-data.ts +++ b/frontend/client-portal/src/@types/paginated-data.ts @@ -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 +} \ No newline at end of file diff --git a/frontend/client-portal/src/components/LaravelTable.tsx b/frontend/client-portal/src/components/LaravelTable.tsx new file mode 100644 index 00000000..18325952 --- /dev/null +++ b/frontend/client-portal/src/components/LaravelTable.tsx @@ -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 ( + + + { props.TableContent } + + + { !props.isLoading ? + () : + (
) + } +
+ ) +} + +export default LaravelTable \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Claims/CreateUpdate.tsx b/frontend/client-portal/src/pages/Claims/CreateUpdate.tsx new file mode 100755 index 00000000..58bb43d6 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/CreateUpdate.tsx @@ -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 ( + + + + + + +
+ + + ); +} diff --git a/frontend/client-portal/src/pages/Claims/Form.tsx b/frontend/client-portal/src/pages/Claims/Form.tsx new file mode 100644 index 00000000..a4898695 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/Form.tsx @@ -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({ + 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 ( + + + Member + + + + { + if (!isEdit) setIsMemberDialogOpen(true); + if (isEdit) enqueueSnackbar('Cannot Change Member', { variant: 'error' }); + }} + /> + + {/* + + */} + + + {member?.id && ( + + + + + + + + Name + + {member?.full_name} + + + + DOB + + + {member?.birth_date} ({member?.age + ' years'}) + + + + + Marital Status + + {member?.marital_status} + + + + Record Type + + {member?.record_type} + + + + Principal ID + + + {member?.principal_id} ( + {member?.relation_with_principal}) + + + +
+
+ + + + + + Plan + + {member?.current_plan?.code} + + + + Active + + + {member?.current_plan?.start} -{' '} + {member?.current_plan?.end} (Active) + + + + + Corporate Limit + + + {fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)} + + + + + Plan Usage + + + {fCurrency(0)} / {fCurrency(member?.current_plan?.limit_rules)} + + + +
+
+
+
+ )} + + ( + + option ? `#${option.id} (${option.code}) ${option.description}` : '' + } + value={value || ''} + onChange={(event: any, newValue: any) => { + setValue('benefit_id', newValue?.id); + onChange(newValue); + }} + renderInput={(params) => ( + { + // if (event.key === 'Enter') + // searchDiagnosis(event.target.value) + // }} + /> + )} + /> + )} + /> + + ( + (option ? `(${option.code}) ${option.name}` : '')} + value={value || ''} + onChange={(event: any, newValue: any) => { + setValue('diagnosis_id', newValue?.id); + // setValue('diagnosis', newValue) + onChange(newValue); + }} + renderInput={(params) => ( + { + if (event.key === 'Enter') searchDiagnosis(event.target.value); + }} + /> + )} + /> + )} + /> + + {isCheckingLimit && ( + + {/* Checking */} + + + + Please Wait, Checking Eligibilty + + + + )} + {false && isCheckingLimit == false && isEligible == null && ( + + {/* No Data Selected */} + + + + Please Select Diagnosis ! + + + + )} + {!isCheckingLimit && isEligible !== null && isEligible && ( + + {/* Eligible */} + + + + Diagnosis is Eligible + + + + 125.000.000 / 125.000.000 + + + )} + {!isCheckingLimit && isEligible !== null && !isEligible && ( + + {/* Not Eligible */} + {/* + + + Not Eligible + + + + 125.000.000 / 125.000.000 + */} + + )} + + + + {isEdit && ( + + Documents + + + {(getValues('uploaded_files.invoice') && getValues('uploaded_files.invoice').length + ? getValues('uploaded_files.invoice') + : [] + ).map((file, index) => ( + + + + } + > + + + {/* */} + I + + + + + ))} + + + + {(getValues('uploaded_files.prescription') && getValues('uploaded_files.prescription').length + ? getValues('uploaded_files.prescription') + : [] + ).map((file, index) => ( + + + + } + > + + + {/* */} + P + + + + + ))} + + + + + {(getValues('uploaded_files.diagnosis') && getValues('uploaded_files.diagnosis').length + ? getValues('uploaded_files.diagnosis') + : [] + ).map((file, index) => ( + + + + } + > + + + {/* */} + DR + + + + + ))} + + + + )} + + {isEligible === true ? ( + + Create Claim + + ) : ( + + Check Limit + + )} +
+ + +
+ ); +} diff --git a/frontend/client-portal/src/pages/Claims/Index.tsx b/frontend/client-portal/src/pages/Claims/Index.tsx new file mode 100755 index 00000000..5fc6d983 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/Index.tsx @@ -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 ( + + + + + {/* */} + + {/* */} + + ); +} diff --git a/frontend/client-portal/src/pages/Claims/List.tsx b/frontend/client-portal/src/pages/Claims/List.tsx new file mode 100755 index 00000000..d575fcc3 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/List.tsx @@ -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(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 ( + + + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + + return ( +
+ + + {/* */} + +
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + 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 }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + + setOpen(!open)}> + {open ? : } + + + {row.code} + {row.member?.full_name} + {row.plan?.code} + {row.claim_request?.service?.name} + + ({row.diagnoses[0]?.icd?.code}) {row.diagnoses[0]?.icd?.name} + + {fCurrency(row.total_claim)} + + {row.status == 'draft' && ()} + {row.status == 'requested' && ()} + {row.status == 'received' && ()} + {row.status == 'approved' && ()} + {row.status == 'postpone' && ()} + {row.status == 'paid' && ()} + {row.status == 'declined' && ()} + + + + {['approved', 'paid'].includes(row.status) && ( + { + navigate('/claims/' + row.id); + }}> + )} + {!['approved', 'paid'].includes(row.status) && ( + { + navigate('/claims/' + row.id); + }}> + )} + + + {/* COLLAPSIBLE ROW */} + + + + {/* + + Description : {row.description} + + */} + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + + + Code + + + Member Name + + + Plan + + + Benefit + + + Diagnosis + + + Total Claim + + + Status + + + Action + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + return ( + + + + } + /> + + ); +} diff --git a/frontend/client-portal/src/pages/Claims/Show.tsx b/frontend/client-portal/src/pages/Claims/Show.tsx new file mode 100644 index 00000000..de19c504 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/Show.tsx @@ -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 ( + + + + + + {/* Action Button */} + + {(currentClaim?.status == 'requested' || currentClaim?.status == 'received') && ( + <> + { + handleDecline(); + }} + > + Decline + + { + handleApprove(); + }} + > + Approve + + + )} + {(currentClaim?.status == 'declined' || currentClaim?.status == 'approved') && ( + { + handleReOpen(); + }} + > + Re-Open + + )} + + + + + + + + Status : {currentClaim?.status} + { currentClaim?.status == 'approved' && ( + { + handleDownloadFinalLog(currentClaim.id); + }} + > + Download Final LOG + + )} + + + + + + + {/* Dokumen Tambahan */} + + + {/* Riwayat Diagnosa */} + + + {/* Ringkasan Data Member */} + + + + + + Ringkasan Data Nasabah + + + + + + + + + + + + + Nama Lengkap + + + {currentClaim?.member?.full_name} + + + + + + + Nomor Polis + + {currentClaim?.member?.full_name} + + + + + Member ID + + {currentClaim?.member?.member_id} + + + + + Tipe Claim + + + {currentClaim?.claim_request?.payment_type_name} + + + + + + Tipe Nasabah + + + {currentClaim?.member?.current_corporate?.name} + + + + + + + + + + + {/* Diagnosis */} + + + + + + + + + {(currentClaim?.status == 'requested' || currentClaim?.status == 'received') && ( + + { + handleSaveDiagnosis(); +}} +> +Simpan Claim Item + + )} + + + + + Client Benefit Configuration + { + setDialogAddClaimItemOpen(true); + }} + > + + Add Benefit + + + + + + {(currentClaim?.status == 'requested' || currentClaim?.status == 'received') && ( + + { + handleSaveClaimItems(); + }} + > + Simpan Claim Item + + )} + + + + + + + + + + ); +} diff --git a/frontend/client-portal/src/pages/Claims/components/ClaimItems.tsx b/frontend/client-portal/src/pages/Claims/components/ClaimItems.tsx new file mode 100644 index 00000000..9a803a9c --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/components/ClaimItems.tsx @@ -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 ( + + {items.length > 0 ? ( + items.map((item, index) => ( + + + #{index+1} ({item.code}) {item.description} + {handleDeleteItem(index)}}> + + } + > + {handleChangeBiayaDiajukan(event, index)}}> + IDR + {/* */} + + {handleChangeBiayaDisetujui(event, index)}}> + IDR + {/* */} + + + IDR + {/* */} + + + + )) + ) : ( + No Benefit Item + )} + + ); +} diff --git a/frontend/client-portal/src/pages/Claims/components/DiagnosisHistory.tsx b/frontend/client-portal/src/pages/Claims/components/DiagnosisHistory.tsx new file mode 100644 index 00000000..db44687f --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/components/DiagnosisHistory.tsx @@ -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 ( + + + + Nama Penyakit + + Claim Terakhir : 23 Januari 2023 08:00 + + + + ); + } + + return ( + + + + + + Riwayat Diagnosa + + + { + setOpenDialogRequestDocument(true); + }} + > + View All + + + + + { diagnosis.length > 0 ? ( + + { diagnosis.map((diagnosa, index) => ( + + )) } + + ) : ( + + Belum ada History Perawatan + + ) } + + + ); +} diff --git a/frontend/client-portal/src/pages/Claims/components/DialogMemberBenefit.tsx b/frontend/client-portal/src/pages/Claims/components/DialogMemberBenefit.tsx new file mode 100644 index 00000000..757b3448 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/components/DialogMemberBenefit.tsx @@ -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 = () => ( + + { benefits.map((benefit, index) => ( + + + + + {benefit.description} + {benefit.code} + + { toggleBenefit(benefit) }}> + + + ))} + + + + ); + + + return ( + + ); +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/Claims/components/Documents.tsx b/frontend/client-portal/src/pages/Claims/components/Documents.tsx new file mode 100644 index 00000000..fefe2a94 --- /dev/null +++ b/frontend/client-portal/src/pages/Claims/components/Documents.tsx @@ -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 ( + + + + { fileCategory(item.type) } + + { item.name } + + + + ); + } + + return ( + + + + Dokumen Tambahan + + { + setOpenDialogRequestDocument(true); + }} + > + + Request Document + + + + + { files.length > 0 ? ( + + { files.map((file, index) => ( + + )) } + + ) : ( + + Belum ada History Perawatan + + )} + + + ); +} diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index 089595a9..704cdf95 100755 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -108,6 +108,22 @@ export default function Router() { }, ], }, + { + path: '/claims', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + ], + }, { path: '*', element: , @@ -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'))); diff --git a/frontend/client-portal/vite.config.ts b/frontend/client-portal/vite.config.ts index a56ef19d..557fde33 100755 --- a/frontend/client-portal/vite.config.ts +++ b/frontend/client-portal/vite.config.ts @@ -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') }], + } })