list dan detail approval pov apporoval

This commit is contained in:
2025-09-12 09:29:31 +07:00
parent 6a885c84ac
commit c65a95ce09
7 changed files with 746 additions and 15 deletions

View File

@@ -137,6 +137,11 @@ class NavigationSeeder extends Seeder
'path' => '/case_management/inpatient_monitoring',
'permission' => 'final-log-list'
],
[
'title' => 'Approval Inpatient',
'path' => '/case_management/approval_inpatient_monitoring',
'permission' => 'approval-log-list'
],
],
'permission' => null
],

View File

@@ -76,6 +76,7 @@ class PermissionTableSeeder extends Seeder
'user-access-list',
'report-katalog-dokter',
'invoice-payment-list',
'approval-inpatient-monitoring',
]
],
####################### CLIENT PORTAL #########################

View File

@@ -85,6 +85,7 @@ const navConfig = [
{ title: 'Daily Monitoring', path: '/case_management/daily_monitoring' },
// { title: 'Laboratorium Result', path: '/case_management/laboratorium_result' },
{ title: 'Inpatient Monitoring', path: '/case_management/inpatient_monitoring' },
{ title: 'Approval Monitoring', path: '/case_management/inpatient_monitoring' },
],
},
{

View File

@@ -0,0 +1,30 @@
import { Card, Stack } from "@mui/material";
import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs";
import Page from "../../../components/Page";
import List from "./List";
export default function Claims() {
const pageTitle = 'Approval Monitoring';
return (
<Page title={ pageTitle } sx={{ mx: 2}}>
<HeaderBreadcrumbs
heading={ pageTitle }
links={[
{ name: 'Dashboard', href: '/dashboard' },
{
name: 'Approval Monitoring',
href: '/approval_inpatient_monitoring',
},
]}
/>
{/* <Stack> */}
<List />
{/* </Stack> */}
</Page>
);
}

View File

@@ -0,0 +1,636 @@
// @mui
import {
Box,
Button,
Card,
Collapse,
IconButton,
MenuItem,
Table,
TableBody,
TableCell,
TableRow,
TextField,
Typography,
Stack,
Menu,
ButtonGroup,
FormControl,
Select,
Link,
Chip,
TableHead,
InputLabel,
Grid,
} 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';
import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
// hooks
import React, { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Navigate, useNavigate, useSearchParams } from 'react-router-dom';
import useSettings from '@/hooks/useSettings';
// 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 { LoadingButton } from '@mui/lab';
import { enqueueSnackbar } from 'notistack';
import { Divider } from '@mui/material';
import Iconify from '@/components/Iconify';
import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim';
import { fDateTimesecond } from '@/utils/formatTime';
import { capitalizeFirstLetter } from '@/utils/formatString';
import Label from '@/components/Label';
import TableMoreMenu from '@/components/table/TableMoreMenu';
import { Import } from '@/@types/claims';
import { FinalLogType } from '../../CustomerService/FinalLog/Model/Types';
import DialogDeleteFinalLOG from '@/pages/CustomerService/FinalLog/Components/DialogDeleteFinalLOG';
import { Delete } from '@mui/icons-material';
import useAuth from '@/hooks/useAuth';
// import LoadingButton from '@/theme/overrides/LoadingButton';
export default function List() {
const { themeColorPresets } = useSettings();
const [searchParams, setSearchParams] = useSearchParams();
const [importResult, setImportResult] = useState<Import>(null);
const { user } = useAuth();
const navigate = useNavigate()
const fileOptions = {
kondisi: 'Dokumen Billing',
diagnosa: 'Dokumen Diagnosa',
result: 'Dokumen Penduk Medis',
none: 'Belum ada Dokumen'
};
function SearchInput(props: any) {
// SEARCH
const searchInput = useRef<HTMLInputElement>(null);
const [searchText, setSearchText] = useState('');
const handleSearchChange = (event: any) => {
const newSearchText = event.target.value ?? '';
setSearchText(newSearchText);
};
const handleSearchSubmit = (event: any) => {
event.preventDefault();
props.onSearch({ search: searchText }); // Trigger to Parent
};
useEffect(() => {
// Trigger First Search
setSearchText(searchParams.get('search') ?? '');
}, []);
return (
<form onSubmit={handleSearchSubmit} style={{ width: '100%' }}>
<TextField
id="search-input"
ref={searchInput}
label="Search"
variant="outlined"
fullWidth
onChange={handleSearchChange}
value={searchText}
placeholder='Search Code or Name...'
/>
</form>
);
}
function ImportForm(props: any) {
// IMPORT
// Create Button Menu
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const createMenu = Boolean(anchorEl);
const importForm = useRef<HTMLInputElement>(null);
const [currentImportFileName, setCurrentImportFileName] = useState(null);
const [importLoading, setImportLoading] = useState(false);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleImportButton = () => {
if (importForm?.current) {
handleClose();
importForm.current ? importForm.current.click() : console.log('No File selected');
} else {
alert('No file selected');
}
};
const handleCancelImportButton = () => {
importForm.current.value = '';
importForm.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 (importForm.current?.files.length) {
const formData = new FormData();
formData.append('file', importForm.current?.files[0]);
setImportLoading(true);
axios
.post(`claim-requests/import`, formData)
.then((response) => {
handleCancelImportButton();
loadDataTableData();
setImportResult(response.data);
// alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows');
setImportLoading(false);
})
.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 = (type :string) => {
axios.get('corporates/import-document-example/' + type)
.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 handleGetData = (type :string) => {
axios.get(`corporates/${corporate_id}/data-plan-benefit`)
.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();
})
}
return (
<div>
<input
type="file"
id="file"
ref={importForm}
style={{ display: 'none' }}
onChange={handleImportChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
{!currentImportFileName && (
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
<Grid item md={9}>
<SearchInput onSearch={applyFilter} />
</Grid>
<Grid item md={2}>
<FormControl fullWidth>
<InputLabel>File</InputLabel>
<Select
value={searchParams.get('file') ?? 'semua'} // Pastikan menggunakan kunci 'file'
label="File"
onChange={(el) => {
const selectedValue = el.target.value;
const filter = Object.fromEntries(searchParams.entries());
if (selectedValue === 'semua') {
delete filter.file; // Menghapus filter 'file' jika memilih 'semua'
} else {
filter.file = selectedValue; // Menambahkan atau memperbarui filter 'file'
}
setSearchParams(filter); // Update state searchParams
loadDataTableData(filter); // Memuat data sesuai filter
}}
>
<MenuItem value={'semua'}>Semua</MenuItem>
{Object.entries(fileOptions).map((option, index) => (
<MenuItem value={option[0]} key={index}>
{option[1]}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item md={1}>
<Button
variant="outlined"
startIcon={<UploadIcon />}
sx={{ p: 1.8 }}
onClick={handleClick}
>
Import
</Button>
</Grid>
<Menu
id="import-button"
anchorEl={anchorEl}
open={createMenu}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleImportButton}>Import</MenuItem>
<MenuItem onClick={() => {handleGetTemplate('claim-request')}}>Download Template</MenuItem>
<MenuItem onClick={() => {handleGetData('data-plan-benefit')}}>Download Claim Request</MenuItem>
</Menu>
{/* <Button
variant="contained"
startIcon={<AddIcon />}
sx={{ p: 1.8 }}
onClick={() => {
navigate('/claim-requests/create');
}}
>
Create
</Button> */}
</Stack>
)}
{currentImportFileName && (
<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>
)}
{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>
)}
</div>
);
}
// Dummy Default Data
const [dataTableIsLoading, setDataTableLoading] = useState(true);
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>(
LaravelPaginatedDataDefault
);
const loadDataTableData = async (appliedFilter: any | null = null) => {
setDataTableLoading(true);
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/customer-service/request?final_log=1&service_code=IP', { 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);
};
// Handel Delete Final LOG
const [idFinalLog, setidFinalLog] = useState<number>();
const [openDialogDeleteFinalLog, setDialogDeleteFinalLog] = useState(false)
useEffect(() => {
loadDataTableData();
}, []);
const headStyle = {
fontWeight: 'bold',
};
// Called on every row to map the data to the columns
function createData(data: FinalLogType) {
return {
...data,
};
}
{
/* ------------------ TABLE ROW ------------------ */
}
function Row(props: { row: ReturnType<typeof createData> }) {
const { row } = props;
const [open, setOpen] = React.useState(false);
const [loadingApprove, setLoadingApprove] = React.useState(false);
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
{/* <TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
</IconButton>
</TableCell> */ }
{/* <TableCell align="left">
<Typography
// onClick={() => {
// handleShowClaim(row);
// }}
>
{row.id}
</Typography>
</TableCell> */}
<TableCell align="left">{row.code}</TableCell>
<TableCell align="left">{row.provider}</TableCell>
<TableCell align="left">{row.member_name}</TableCell>
<TableCell align="left"><Label>{fDateTimesecond(row.admission_date)}</Label></TableCell>
<TableCell align="left">{row.service_name}</TableCell>
<TableCell align="left">{row.payment_type_name}</TableCell>
<TableCell align="left">
{row.files_by_type?.final_log_diagnosis?.length > 0 && (
<>
<Label variant='ghost' color='primary'>
{row.files_by_type.final_log_diagnosis.length} File Diagnosa
</Label>
<br />
</>
)}
{row.files_by_type?.final_log_kondisi?.length > 0 && (
<>
<Label variant='ghost' color='success'>
{row.files_by_type.final_log_kondisi.length} File Billing
</Label>
<br />
</>
)}
{row.files_by_type?.final_log_result?.length > 0 && (
<Label variant='ghost' color='warning'>
{row.files_by_type.final_log_result.length} File Pendukung Medis
</Label>
)}
</TableCell>
<TableCell align="left">
{ row.status_final_log == "requested" ?
(<Label variant='ghost' color='primary'>{capitalizeFirstLetter(row.status_final_log)}</Label>) :
row.status_final_log == "declined" ?
(<Label color='error'> {capitalizeFirstLetter(row.status_final_log)}</Label>)
:
(<Label color='success'> {capitalizeFirstLetter(row.status_final_log)}</Label>)
}
</TableCell>
<TableCell align="right">
<TableMoreMenu actions={
<>
{/* <MenuItem onClick={() => navigate(`/claim-requests/edit/${row.id}`)}>
<EditOutlinedIcon />
Edit
</MenuItem> */}
<MenuItem onClick={() => navigate ('/custormer-service/final-log/detail/'+row.id+'/'+user.id)}>
<FindInPageOutlinedIcon />
Detail
</MenuItem>
<MenuItem onClick={() => {
setidFinalLog(row.id)
setDialogDeleteFinalLog(true)
}}
>
<Delete color='error'/>
Delete
</MenuItem>
</>
} />
</TableCell>
{/* <TableCell>
<IconButton
onClick={() => {
handleShowClaim(row);
}}
>
<Iconify icon="eva:eye-fill" />
</IconButton>
</TableCell> */}
</TableRow>
{/* COLLAPSIBLE ROW */}
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ borderBottom: 1 }}>
<Stack
divider={<Divider orientation="horizontal" flexItem />}
spacing={1}
sx={{ marginY: 2 }}
>
<Box>
<Typography fontWeight={600}>Berkas Hasil Penunjang</Typography>
{/* {row.files_by_type?.claim_kondisi &&
row.files_by_type?.claim_kondisi.map((file, index) => (
<Stack direction="row" key={index}>
<Typography sx={{ marginRight: 2 }}>-</Typography>{' '}
<a href={file.url} target="_blank">
{file.name}
</a>
</Stack>
))} */}
{row.files_by_type?.claim_kondisi && (
<>
<Typography fontWeight={600} sx={{ marginRight: 4 }}> - Kondisi</Typography>
{row.files_by_type?.claim_kondisi.map((file, index) => (
<Stack direction="row" key={index}>
<a href={file.url} target="_blank">
{file.name}
</a>
</Stack>
))}
</>
)}
{row.files_by_type?.claim_diagnosis && (
<>
<Typography fontWeight={600} sx={{ marginRight: 4 }}> - Diagnosa</Typography>
{row.files_by_type?.claim_diagnosis.map((file, index) => (
<Stack direction="row" key={index}>
<a href={file.url} target="_blank">
{file.name}
</a>
</Stack>
))}
</>
)}
{row.files_by_type?.claim_result && (
<>
<Typography fontWeight={600} sx={{ marginRight: 4 }}> - Hasil</Typography>
{row.files_by_type?.claim_result.map((file, index) => (
<Stack direction="row" key={index}>
<a href={file.url} target="_blank">
{file.name}
</a>
</Stack>
))}
</>
)}
{(!row.files_by_type?.claim_result && !row.files_by_type?.claim_diagnosis && !row.files_by_type?.claim_kondisi)&& <Typography>Tidak ada berkas</Typography>}
</Box>
</Stack>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
{
/* ------------------ 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">
ID Request LOG
</TableCell> */}
<TableCell style={headStyle} align="left">
Code
</TableCell>
<TableCell style={headStyle} align="left">
Provider
</TableCell>
<TableCell style={headStyle} align="left">
Name
</TableCell>
<TableCell style={headStyle} align="left">
Date of Admission
</TableCell>
<TableCell style={headStyle} align="left">
Service Type
</TableCell>
<TableCell style={headStyle} align="left">
Claim Method
</TableCell>
<TableCell style={headStyle} align="left">
File Upload
</TableCell>
<TableCell style={headStyle} align="left">
Status
</TableCell>
<TableCell style={headStyle} align="right"></TableCell>
</TableRow>
</TableHead>
{/* ------------------ END TABLE HEADER ------------------ */}
{/* ------------------ TABLE ROW ------------------ */}
{dataTableIsLoading ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
Loading
</TableCell>
</TableRow>
</TableBody>
) : dataTableData.data.length === 0 ? (
<TableBody>
<TableRow>
<TableCell colSpan={8} align="center">
No Data
</TableCell>
</TableRow>
</TableBody>
) : (
<TableBody>
{dataTableData.data.map((row) => (
<Row key={row.id} row={row} />
))}
</TableBody>
)}
{/* ------------------ END TABLE ROW ------------------ */}
</Table>
);
}
return (
<Grid container>
<Grid item sm={12}>
<ImportForm />
</Grid>
<Grid item sm={12}>
<DataTable
isLoading={dataTableIsLoading}
lastRequest={0}
data={dataTableData}
handlePageChange={handlePageChange}
TableContent={<TableContent />}
/>
</Grid>
<Grid item sm={12}>
{/* Dialog Delete */}
<DialogDeleteFinalLOG
id={idFinalLog}
openDialog={openDialogDeleteFinalLog}
setOpenDialog={setDialogDeleteFinalLog}
/>
</Grid>
</Grid>
);
}

View File

@@ -117,7 +117,7 @@ export default function Detail() {
});
}
const { id } = useParams();
const { id, approval } = useParams();
useEffect(() => {
axios
@@ -474,14 +474,46 @@ export default function Detail() {
>
Simpan
</LoadingButton> */}
<Stack direction="row" spacing={2} sx={{ mt: 6 }}>
<Stack direction="row" spacing={2} sx={{ mt: 6 }}>
{approval ? (
<>
<Box sx={{ flexGrow: 1 }} />
{/* GRUP TOMBOL DI KANAN */}
<Stack direction="row" spacing={1.5} mt={2}>
<Button
color='error'
variant="outlined"
size="small"
onClick={() => {
setDialogSendWa(true);
setShareLink(false);
}}
>
Decline
</Button>
<Button
variant="contained"
size="small"
color='primary'
onClick={() => {
setDialogSendWa(true);
setShareLink(true);
}}
>
Approve
</Button>
</Stack>
</>
) : (
<>
{/* TOMBOL SIMPAN DI KIRI */}
<LoadingButton
type="submit"
variant="contained"
sx={{ p: 2, backgroundColor: "#19BBBB" }}
loading={false}
size='small'
size="small"
>
Simpan
</LoadingButton>
@@ -489,19 +521,36 @@ export default function Detail() {
{/* Ini adalah spacer untuk mendorong tombol berikutnya ke kanan */}
<Box sx={{ flexGrow: 1 }} />
{/* GRUP TOMBOL DI KANAN */}
<Stack direction="row" spacing={1.5} mt={2}>
<Button variant="contained" size="small" sx={{ p: 2, backgroundColor: "#19BBBB" }}
onClick={() => {setDialogSendWa(true); setShareLink(false); }}>
Kirim (WA Chatbot)
</Button>
<Button variant="contained" size="small" sx={{ p: 2, backgroundColor: "#19BBBB" }}
onClick={() => {setDialogSendWa(true); setShareLink(true); }}>
Share Link
</Button>
</Stack>
{/* GRUP TOMBOL DI KANAN */}
<Stack direction="row" spacing={1.5} mt={2}>
<Button
variant="contained"
size="small"
sx={{ p: 2, backgroundColor: "#19BBBB" }}
onClick={() => {
setDialogSendWa(true);
setShareLink(false);
}}
>
Kirim (WA Chatbot)
</Button>
<Button
variant="contained"
size="small"
sx={{ p: 2, backgroundColor: "#19BBBB" }}
onClick={() => {
setDialogSendWa(true);
setShareLink(true);
}}
>
Share Link
</Button>
</Stack>
</>
)}
</Stack>
</Stack>
</FormProvider>

View File

@@ -269,6 +269,10 @@ export default function Router() {
path: 'inpatient_monitoring', // Inpatient Monitoring
element: <InpatientMonitoring />
},
{
path: 'approval_inpatient_monitoring', // Approval Monitoring
element: <ApprovalMonitoring />
},
]
},
{
@@ -555,6 +559,10 @@ export default function Router() {
{
path: 'custormer-service/final-log/detail/:id',
element: <FinalLogDetail />,
},
{
path: 'custormer-service/final-log/detail/:id/:approval',
element: <FinalLogDetail />,
},
{
path: 'e-prescription/live-chat',
@@ -714,6 +722,7 @@ const DetailLabResultForm = Loadable(lazy(() => import('../pages/CaseManage
const DetailLabResultList = Loadable(lazy(() => import('../pages/CaseManagement/LaboratoriumResult/Components/DetailLabResultList')))
// Inpatient Monitoring
const InpatientMonitoring = Loadable(lazy(() => import('../pages/CaseManagement/InpatientMonitoring/Index')))
const ApprovalMonitoring = Loadable(lazy(() => import('../pages/CaseManagement/ApprovalMonitoring/Index')))
/**