commit client portal

This commit is contained in:
Muhammad Fajar
2023-09-27 15:39:20 +07:00
parent 3f54e07fb5
commit 2f7a450d28
11 changed files with 1830 additions and 1650 deletions

View File

@@ -0,0 +1,147 @@
<?php
namespace Modules\Client\Http\Controllers\Api;
use App\Events\ClaimRequested;
use App\Helpers\Helper;
use App\Models\File;
use App\Models\Member;
use App\Services\ClaimRequestService;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class ClaimRequestController extends Controller
{
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index()
{
return view('client::index');
}
/**
* Show the form for creating a new resource.
* @return Renderable
*/
public function create()
{
return view('client::create');
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Renderable
*/
public function store(Request $request)
{
$request->validate([
'member_id' => 'required',
'service_code' => 'required|in:OP,IP'
]);
$member = Member::find($request->member_id);
$newClaimRequest = ClaimRequestService::storeClaimRequest(member: $member, paymentType: 'reimbursement', serviceCode: $request->service_code);
ClaimRequested::dispatch($newClaimRequest);
// Log History
$newClaimRequest->histories()->create([
'title' => 'New Claim Requested',
'description' => "Claim Requested for Member : {$member->member_id} - ({$member->full_name})",
'type' => 'info',
'system_origin' => 'hospital-portal'
]);
if ($request->hasFile('result_files')) {
foreach ($request->result_files as $file) {
$pathFile = File::storeFile('claim-result', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'claim-result',
'name' => File::getFileName('claim-result', $newClaimRequest->id, $file),
'original_name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => $pathFile,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id,
]);
}
}
if ($request->hasFile('diagnosa_files')) {
foreach ($request->diagnosa_files as $file) {
$pathFile = File::storeFile('claim-diagnosis', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'claim-diagnosis',
'name' => File::getFileName('claim-diagnosis', $newClaimRequest->id, $file),
'original_name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => $pathFile,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id,
]);
}
}
if ($request->hasFile('kondisi_files')) {
foreach ($request->kondisi_files as $file) {
$pathFile = File::storeFile('claim-kondisi', $newClaimRequest->id, $file);
$newClaimRequest->files()->updateOrCreate([
'type' => 'claim-kondisi',
'name' => File::getFileName('claim-kondisi', $newClaimRequest->id, $file),
'original_name' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'path' => $pathFile,
'created_by' => auth()->user()->id,
'updated_by' => auth()->user()->id,
]);
}
}
return Helper::responseJson(data: $request->toArray(), message: 'Claim Request berhasil ajukan!');
}
/**
* Show the specified resource.
* @param int $id
* @return Renderable
*/
public function show($id)
{
return view('client::show');
}
/**
* Show the form for editing the specified resource.
* @param int $id
* @return Renderable
*/
public function edit($id)
{
return view('client::edit');
}
/**
* Update the specified resource in storage.
* @param Request $request
* @param int $id
* @return Renderable
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
}

View File

@@ -9,6 +9,7 @@ use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Client\Transformers\ClaimReport\MemberResources as ClaimReportMemberResources;
use Modules\Client\Transformers\Dashboard\MemberResources as ClaimSubmitMemberResources;
use Modules\Client\Transformers\Dashboard\MemberResources as DashboardMemberResources;
use Modules\Client\Transformers\Dashboard\MemberAlarmCenterResources as DashboardMemberAlarmResources;
use Modules\Client\Transformers\DataMemberResource;
@@ -31,7 +32,7 @@ class CorporateMemberController extends Controller
return response()->json(Helper::paginateResources(ClaimReportMemberResources::collection($members)));
case 'claim-submit':
$members = $this->corporateMemberService->getAllMemberClaimReports($corporate_id, $request);
return response()->json(Helper::paginateResources(ClaimReportMemberResources::collection($members)));
return response()->json(Helper::paginateResources(ClaimSubmitMemberResources::collection($members)));
case 'alarm-center':
$members = $this->corporateMemberService->getAllMemberAlarmCenter($corporate_id, $request);
return response()->json(Helper::paginateResources(DashboardMemberAlarmResources::collection($members)));

View File

@@ -11,6 +11,7 @@ use Modules\Client\Http\Controllers\Api\ClaimController;
use Modules\Client\Http\Controllers\Api\TopUpController;
use Modules\Internal\Http\Controllers\ClaimEncounterController;
use App\Models\Encounter;
use Modules\Client\Http\Controllers\Api\ClaimRequestController;
use Modules\Client\Http\Controllers\Api\DataController;
use Modules\Internal\Transformers\EncounterResource;
@@ -54,5 +55,7 @@ Route::prefix('client')->group(function () {
Route::post('topup', [TopUpController::class, 'store']);
});
Route::get('claims/{id}', [ClaimController::class, 'show']);
Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store');
});
});

View File

@@ -34,7 +34,7 @@ class CorporateMemberService
'division' => 'corporate_divisions.name',
default => ''
};
if ($request->order){
if ($request->order) {
$query->getQuery()->orderBy($orderBy, $request->order);
}
})
@@ -48,7 +48,6 @@ class CorporateMemberService
$limit = $request->has('perPage') ? $request->input('perPage') : 10;
return Member::query()
->joinClaimRequests('right')
->joinCorporateEmployees('left')
->joinCorporateDivisions('left')
->with('currentPlan')
@@ -62,11 +61,6 @@ class CorporateMemberService
->orWhere('members.name', 'like', "%" . $search . "%");
});
})
->when($request->input('division'), function (Builder $division, $value) {
$division->whereHas('division', function (Builder $corporateEmployee) use ($value) {
$corporateEmployee->where('division_id', $value);
});
})
->when($request->has('orderBy'), function (Builder $query) use ($request) {
$orderBy = match ($request->orderBy) {
'memberId' => 'member_id',
@@ -80,7 +74,7 @@ class CorporateMemberService
$query->getQuery()->orderBy('corporate_divisions.name', $request->order);
}
})
->select(['members.id', 'members.person_id', 'members.member_id', 'members.name', 'corporate_divisions.name AS division_name', 'members.active', 'claim_requests.id', 'claim_requests.member_id', 'claim_requests.submission_date'])
->select(['members.id', 'members.person_id', 'members.member_id', 'members.name', 'corporate_divisions.name AS division_name', 'members.active'])
->paginate($limit);
}
@@ -122,8 +116,8 @@ class CorporateMemberService
->paginate($limit);
}
public function getAllEncounter(int $corporateId){
public function getAllEncounter(int $corporateId)
{
return Encounter::query()->select(['id'])->paginate(10);
}
}

View File

@@ -64,7 +64,7 @@ type CardPolicyProps = {
members: {
memberId: string;
memberFullName: string;
}
};
};
};
@@ -194,7 +194,6 @@ export default function CardPolicy(props: CardPolicyProps) {
openDialog={openDialog}
setOpenDialog={setOpenDialog}
title={{ name: dialogTitle }}
data={members}
/>
)}

View File

@@ -11,14 +11,14 @@ import {
IconButton,
} from '@mui/material';
import { Search as SearchIcon } from '@mui/icons-material';
import axios from '../../utils/axios';
// components
import MuiDialog from '../../components/MuiDialog';
import Iconify from '../../components/Iconify';
import { useSnackbar } from 'notistack';
// React
import { ReactElement, useState } from 'react';
import DialogClaimSubmitMemberSubmission from './DialogClaimSubmitMemberSubmission';
import { ReactElement, useContext, useEffect, useState } from 'react';
import DialogRequestLog from './DialogRequestLog';
import axios from '../../utils/axios';
import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate';
// ----------------------------------------------------------------------
@@ -45,7 +45,7 @@ type MuiDialogProps = {
openDialog: boolean;
setOpenDialog: Function;
content?: ReactElement;
data?: DataContentType[];
// data?: DataContent[];
};
// ----------------------------------------------------------------------
@@ -68,9 +68,11 @@ export default function DialogClaimSubmitMember({
title,
openDialog,
setOpenDialog,
data,
}: MuiDialogProps) {
const { corporateValue } = useContext(UserCurrentCorporateContext);
/* ---------------------------------- data ---------------------------------- */
const [data, setData] = useState([]);
const [dataMemberClaim, setDataMemberClaim] = useState<DataContentType>({
id: 0,
fullName: '',
@@ -85,31 +87,16 @@ export default function DialogClaimSubmitMember({
/* --------------------------------- Search --------------------------------- */
const [searchText, setSearchText] = useState('');
/* --------------------------------- Benefit--------------------------------- */
const {enqueueSnackbar} = useSnackbar();
const [openDialogBenefit, setOpenDialogBenefit] = useState(false);
const [currentMember, setCurrentMember] = useState(null);
const [nameMember, setNameMember] = useState('');
const [loadingBenefit, setLoadingBenefit] = useState(false);
const [appliedParams, setAppliedParams] = useState({});
const handleSearchSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// await new Promise((resolve) => setTimeout(resolve, 500));
// setLoadingBenefit(true)
// axios.post('/search-member', {
// search: searchText,
// })
// .then((response) => {
// setOpenDialogBenefit(true)
// setCurrentMember(response.data.data)
// setNameMember(response.data.data.name);
// })
// .catch(({response}) => {
// enqueueSnackbar(response.data.errors ? response.data.errors[0] : (response.data ? response.data.meta.message : 'Opps, Something went Wrong!'), {variant : "error"})
// })
// .then(() => {
// setLoadingBenefit(false)
// });
event.preventDefault();
if (searchText === '') {
setAppliedParams({});
} else {
setAppliedParams({ search: searchText });
}
await new Promise((resolve) => setTimeout(resolve, 500));
};
/* -------------------------------------------------------------------------- */
@@ -119,7 +106,7 @@ export default function DialogClaimSubmitMember({
/* -------------------------------------------------------------------------- */
/* ------------------------------ Icon On Click ----------------------------- */
const [openDialogClaimMember, setOpenDialogMemberClaim] = useState(false);
const [openDialogRequestLog, setOpenDialogRequestLog] = useState(false);
const clickHandler = ({ id, fullName, memberId, limit, avatar }: DataContentType) => {
setDataMemberClaim({
@@ -136,10 +123,22 @@ export default function DialogClaimSubmitMember({
title: avatar && avatar.title,
},
});
setOpenDialogMemberClaim(true);
setOpenDialogRequestLog(true);
};
/* -------------------------------------------------------------------------- */
useEffect(() => {
(async () => {
if (openDialog === true) {
const response = await axios.get(`${corporateValue}/members`, {
params: { ...appliedParams, type: 'claim-submit' },
});
setData(response.data.data);
}
})();
}, [corporateValue, openDialog, appliedParams]);
const getContent = () => (
<Stack>
<Stack direction="row" justifyContent="space-between" alignItems="center" paddingY={1}>
@@ -168,57 +167,58 @@ export default function DialogClaimSubmitMember({
/>
</form>
<Stack marginTop={2} spacing={1}>
{data &&
data.map((row: DataContentType, key) => (
<Card key={key} sx={{ paddingY: 1, paddingX: 2 }}>
<Stack direction="row" alignItems="center" spacing={2}>
<img
width={40}
height={40}
src={row.avatar ? row.avatar.url : '/images/member.png'}
alt={row.avatar ? row.avatar.url : 'user-profile'}
style={{ borderRadius: '50%' }}
/>
<Stack sx={{ flex: '45%' }}>
<Typography variant="subtitle1">{row.fullName}</Typography>
<Typography color="#637381" variant="body2" sx={{ fontWeight: 500 }}>
Member ID : {row.memberId}
</Typography>
</Stack>
<Stack spacing={1} paddingY={1}>
<Typography color="#0A0A0A" variant="caption">
Total Limit
</Typography>
<BorderLinearProgress
variant="determinate"
value={row.limit && row.limit.percentage}
/>
<Typography variant="subtitle2" sx={{ fontWeight: 500 }}>
{row.limit && row.limit.current} /{' '}
<Typography variant="body2" color="#757575" component="span">
{row.limit && row.limit.total}
</Typography>
</Typography>
</Stack>
<IconButton
onClick={() =>
clickHandler({
id: row.id,
fullName: row.fullName,
memberId: row.memberId,
limit: {
current: row.limit.current,
total: row.limit.total,
percentage: row.limit.percentage,
},
})
}
>
<Iconify icon="ic:round-chevron-right" />
</IconButton>
{data.map((row: DataContentType, key) => (
<Card
key={key}
sx={{ paddingY: 1, paddingX: 2 }}
onClick={() =>
clickHandler({
id: row.id,
fullName: row.fullName,
memberId: row.memberId,
limit: {
current: row.limit.current,
total: row.limit.total,
percentage: row.limit.percentage,
},
})
}
>
<Stack direction="row" alignItems="center" spacing={2}>
<img
width={40}
height={40}
src={row.avatar ? row.avatar.url : '/images/member.png'}
alt={row.avatar ? row.avatar.url : 'user-profile'}
style={{ borderRadius: '50%' }}
/>
<Stack sx={{ flex: '45%' }}>
<Typography variant="subtitle1">{row.fullName}</Typography>
<Typography color="#637381" variant="body2" sx={{ fontWeight: 500 }}>
Member ID : {row.memberId}
</Typography>
</Stack>
</Card>
))}
<Stack spacing={1} paddingY={1}>
<Typography color="#0A0A0A" variant="caption">
Total Limit
</Typography>
<BorderLinearProgress
variant="determinate"
value={row.limit && row.limit.percentage}
/>
<Typography variant="subtitle2" sx={{ fontWeight: 500 }}>
{row.limit && row.limit.current} /{' '}
<Typography variant="body2" color="#757575" component="span">
{row.limit && row.limit.total}
</Typography>
</Typography>
</Stack>
<IconButton>
<Iconify icon="ic:round-chevron-right" />
</IconButton>
</Stack>
</Card>
))}
</Stack>
</Stack>
);
@@ -233,10 +233,9 @@ export default function DialogClaimSubmitMember({
maxWidth="sm"
/>
<DialogClaimSubmitMemberSubmission
title={title}
openDialog={openDialogClaimMember}
setOpenDialog={setOpenDialogMemberClaim}
<DialogRequestLog
openDialog={openDialogRequestLog}
setOpenDialog={setOpenDialogRequestLog}
data={dataMemberClaim}
/>
</>

View File

@@ -1,371 +0,0 @@
// @mui
import { styled } from '@mui/material/styles';
import {
Typography,
LinearProgress,
linearProgressClasses,
Stack,
Card,
Button,
Link,
Switch,
SwitchProps,
ButtonGroup,
} from '@mui/material';
import { Add as AddIcon, Cancel as CancelIcon } from '@mui/icons-material';
// components
import MuiDialog from '../../components/MuiDialog';
import Iconify from '../../components/Iconify';
import { FormProvider } from '../../components/hook-form';
// React
import { ReactElement, useEffect, useState } from 'react';
// yup
import * as Yup from 'yup';
// form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { LoadingButton } from '@mui/lab';
import { fSplit } from '../../utils/formatNumber';
/* ---------------------------------- types --------------------------------- */
type DataContentType = {
id: number;
fullName: string;
memberId: string;
limit: {
current: number;
total: number;
percentage: number;
};
avatar?: {
url?: string;
title?: string;
};
};
type MuiDialogProps = {
title?: {
name?: string;
icon?: string;
};
openDialog: boolean;
setOpenDialog: Function;
content?: ReactElement;
data: DataContentType;
};
type BorderLinearProgressProps = {
percentage: number;
};
type FormValuesProps = {
invoice: '';
};
/* -------------------------------------------------------------------------- */
/* --------------------------------- styles --------------------------------- */
const BorderLinearProgress = styled(LinearProgress)<BorderLinearProgressProps>(
({ theme, percentage }) => ({
height: 10,
borderRadius: 6,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: theme.palette.grey[theme.palette.mode === 'light' ? 300 : 800],
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 6,
background: 'linear-gradient(270deg, #19BBBB 38.42%, #FF9565 76.21%, #FE7253 104.02%)',
'&::before': {
content: '""',
position: 'absolute',
right: 0,
top: 0,
width: `${100 - percentage}%`,
zIndex: 1,
bottom: 0,
background: '#DFE3E8',
},
},
})
);
/* -------------------------------------------------------------------------- */
const DialogClaimSubmitMemberSubmission = ({
title,
openDialog,
setOpenDialog,
data,
}: MuiDialogProps) => {
/* ---------------------------- Get Current Date ---------------------------- */
const current = new Date();
const date = `${current.getDate()} / ${current.getMonth() + 1} / ${current.getFullYear()}`;
/* -------------------------------------------------------------------------- */
/* ------------------------------- file input ------------------------------- */
// const [multipleImages, setMultipleImages] = useState([]);
// Functions to preview multiple images
// const changeMultipleFiles = (e) => {
// if (e.target.files) {
// const imageArray = Array.from(e.target.files).map((file) => URL.createObjectURL(file));
// setMultipleImages((prevImages) => prevImages.concat(imageArray));
// }
// };
// const render = (data) => {
// data.map((image) => {
// <Typography key={image}>{image}</Typography>;
// });
// };
// const FileForm = (props: any) => (
// <input
// type="file"
// multiple
// {...register('invoice', { required: true })}
// onChange={changeMultipleFiles}
// />
// );
/* -------------------------------------------------------------------------- */
/* ------------------------------- Form Submit ------------------------------ */
const ClaimSubmitSchema = Yup.object().shape({
invoice: Yup.mixed()
.required('You need to provide a file')
// @ts-ignore
.test('fileSize', 'The file is too large', (value) => {
for (let index = 0; index < value.length; index++) {
return value ? value[index].size <= 2000000 : false;
}
}),
});
const methods = useForm<FormValuesProps>({
resolver: yupResolver(ClaimSubmitSchema),
});
const {
register,
reset,
handleSubmit,
formState: { isSubmitting, errors },
} = methods;
// const {
// register,
// reset,
// handleSubmit,
// formState: { isSubmitting },
// } = useForm({ resolver: yupResolver(ClaimSubmitSchema) });
const onSubmit = ({ invoice }: FormValuesProps) => {
// console.log(invoice);
};
/* -------------------------------------------------------------------------- */
/* ---------------------------- Ios Switch Style ---------------------------- */
const IosSwitch = styled((props: SwitchProps) => (
<Switch focusVisibleClassName=".Mui-focusVisible" disableRipple {...props} />
))(({ theme }) => ({
width: 28,
height: 16,
padding: 0,
marginRight: '10px',
'& .MuiSwitch-switchBase': {
padding: 0,
margin: 2,
transitionDuration: '300ms',
'&.Mui-checked': {
transform: 'translateX(12px)',
color: '#fff',
'& + .MuiSwitch-track': {
opacity: 1,
border: 0,
},
'&.Mui-disabled + .MuiSwitch-track': {
opacity: 0.5,
},
},
'&.Mui-focusVisible .MuiSwitch-thumb': {
color: '#33cf4d',
border: '6px solid #fff',
},
'&.Mui-disabled .MuiSwitch-thumb': {
color: theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[600],
},
'&.Mui-disabled + .MuiSwitch-track': {
opacity: theme.palette.mode === 'light' ? 0.7 : 0.3,
},
},
'& .MuiSwitch-thumb': {
boxSizing: 'border-box',
width: 12,
height: 12,
},
'& .MuiSwitch-track': {
borderRadius: 26 / 2,
opacity: 1,
transition: theme.transitions.create(['background-color'], {
duration: 500,
}),
},
}));
/* -------------------------------------------------------------------------- */
useEffect(() => {
if (openDialog === false) {
reset();
}
}, [openDialog, reset]);
const getContent = () => (
<Stack>
<Stack direction="row" justifyContent="space-between" alignItems="center" paddingY={1}>
<Typography variant="subtitle1">Claim Submission</Typography>
<Stack sx={{ color: '#757575' }}>
<Typography variant="caption">Submission date</Typography>
<Typography variant="caption">{date}</Typography>
</Stack>
</Stack>
<Card sx={{ paddingY: 1, paddingX: 2, marginTop: 2, backgroundColor: '#F4F6F8' }}>
<Stack direction="row" alignItems="center" spacing={2}>
<img
width={40}
height={40}
src="/images/member.png"
alt="user-profile"
style={{ borderRadius: '50%' }}
/>
<Stack sx={{ flex: '45%' }}>
<Typography variant="subtitle1">{data && data.fullName}</Typography>
<Typography color="#637381" variant="body2" sx={{ fontWeight: 500 }}>
Member ID : {data && data.memberId}
</Typography>
</Stack>
</Stack>
</Card>
<Card sx={{ paddingY: 1, paddingX: 2, marginTop: 2 }}>
<Stack spacing={1} paddingY={1}>
<Stack direction="row" justifyContent="space-between">
<Typography color="#0A0A0A" variant="caption">
Total Limit
</Typography>
<Link variant="caption" textAlign="center" href="#">
Details Benefits <Iconify icon="ic:round-chevron-right" />
</Link>
</Stack>
<BorderLinearProgress
variant="determinate"
value={100}
percentage={data && data.limit ? data.limit.percentage : 100}
/>
<Typography variant="subtitle2" sx={{ fontWeight: 500 }}>
{fSplit(data && data.limit ? data.limit.current : 0)} /{' '}
<Typography variant="body2" color="#757575" component="span">
{fSplit(data && data.limit ? data.limit.total : 0)}
</Typography>
</Typography>
</Stack>
</Card>
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
{/* Invoice */}
<Stack marginTop={2} spacing={1}>
<Stack>
<Typography variant="subtitle2">Real Invoice</Typography>
<Typography color="#9E9E9E" variant="caption">
Real invoice required
</Typography>
</Stack>
<input {...register('invoice')} type="file" />
{errors.invoice && errors.invoice.message ? <p>{errors.invoice.message}</p> : ''}
</Stack>
{/* Prescription */}
{/* <Stack marginTop={2} spacing={1}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack>
<Typography variant="subtitle2">
Doctor's Prescription and Another Documents
</Typography>
<Typography color="#9E9E9E" variant="caption">
Doctor's Prescription required
</Typography>
</Stack>
<Stack
direction="row"
padding={1}
alignItems="center"
sx={{
backgroundColor: 'white',
border: '1px solid #E0E0E0',
borderRadius: '6px',
height: 32,
}}
>
<IosSwitch defaultChecked />
<Typography color="#404040" variant="body2">
Yes
</Typography>
</Stack>
</Stack>
<ImportForm label="Add Prescription" />
</Stack> */}
{/* Laboratory */}
{/* <Stack marginTop={2} spacing={1}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack>
<Typography variant="subtitle2">
Doctor's Prescription and Another Documents
</Typography>
<Typography color="#9E9E9E" variant="caption">
Doctor's Prescription required
</Typography>
</Stack>
<Stack
direction="row"
padding={1}
alignItems="center"
sx={{
backgroundColor: 'white',
border: '1px solid #E0E0E0',
borderRadius: '6px',
height: 32,
}}
>
<IosSwitch defaultChecked />
<Typography color="#404040" variant="body2">
Yes
</Typography>
</Stack>
</Stack>
<ImportForm label="Add Result" />
</Stack> */}
{/* Submit */}
<Stack marginTop={1}>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
sx={{ marginTop: 2 }}
>
Ajukan Permintaan
</LoadingButton>
</Stack>
</FormProvider>
</Stack>
);
return (
<>
<MuiDialog
title={title}
openDialog={openDialog}
setOpenDialog={setOpenDialog}
content={getContent()}
maxWidth="sm"
/>
</>
);
};
export default DialogClaimSubmitMemberSubmission;

View File

@@ -0,0 +1,353 @@
// @mui
import { styled } from '@mui/material/styles';
import {
Typography,
LinearProgress,
linearProgressClasses,
Stack,
Card,
Button,
Divider,
Avatar,
} from '@mui/material';
// components
import MuiDialog from '../../components/MuiDialog';
import Iconify from '../../components/Iconify';
// React
import { ReactElement, useRef, useState } from 'react';
// form
import { LoadingButton } from '@mui/lab';
import axios from '../../utils/axios';
import { enqueueSnackbar } from 'notistack';
import { fPostFormat } from '../../utils/formatTime';
import { fCurrency } from '../../utils/formatNumber';
/* ---------------------------------- types --------------------------------- */
type DataContentType = {
id: number;
fullName: string;
memberId: string;
limit: {
current: number;
total: number;
percentage: number;
};
avatar?: {
url?: string;
title?: string;
};
};
type MuiDialogProps = {
title?: {
name?: string;
icon?: string;
};
openDialog: boolean;
setOpenDialog: Function;
content?: ReactElement;
data: DataContentType;
};
/* -------------------------------------------------------------------------- */
/* --------------------------------- styles --------------------------------- */
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 10,
borderRadius: 6,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: theme.palette.grey[theme.palette.mode === 'light' ? 300 : 800],
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 6,
background: 'linear-gradient(270deg, #19BBBB 38.42%, #FF9565 76.21%, #FE7253 104.02%)',
},
}));
/* -------------------------------------------------------------------------- */
const DialogRequestLog = ({ openDialog, setOpenDialog, data }: MuiDialogProps) => {
const [serviceCode, setServiceCode] = useState('IP');
const fileDiagnosaInput = useRef<HTMLInputElement>(null);
const [fileDiagnosas, setFileDiagnosas] = useState([]);
const handleDiagnosaInputChange = (event) => {
if (event.target.files[0]) {
setFileDiagnosas([...fileDiagnosas, ...event.target.files]);
} else {
console.log('NO FILE');
}
};
const removeDiagnosaFiles = (filesState, index) => {
setFileDiagnosas(filesState.filter((file, fileIndex) => fileIndex != index));
};
const fileKondisiInput = useRef<HTMLInputElement>(null);
const [fileKondisis, setFileKondisis] = useState([]);
const handleKondisiInputChange = (event) => {
if (event.target.files[0]) {
setFileKondisis([...fileKondisis, ...event.target.files]);
} else {
console.log('NO FILE');
}
};
const removeKondisiFiles = (filesState, index) => {
setFileKondisis(filesState.filter((file, fileIndex) => fileIndex != index));
};
const fileHasilPenunjangInput = useRef<HTMLInputElement>(null);
const [fileHasilPenunjangs, setFileHasilPenunjangs] = useState([]);
const handleResultInputChange = (event) => {
if (event.target.files[0]) {
setFileHasilPenunjangs([...fileHasilPenunjangs, ...event.target.files]);
} else {
console.log('NO FILE');
}
};
const removeFiles = (filesState, index) => {
setFileHasilPenunjangs(filesState.filter((file, fileIndex) => fileIndex != index));
};
const [submitLoading, setSubmitLoading] = useState(false);
function submitRequest() {
setSubmitLoading(true);
const formData = new FormData();
formData.append('member_id', data.id);
formData.append('result_files', fileHasilPenunjangs);
formData.append('diagnosa_files', fileDiagnosas);
formData.append('kondisi_files', fileKondisis);
formData.append('service_code', serviceCode);
axios
.post('/claim-requests', formData)
.then((response) => {
enqueueSnackbar(response.data.message ?? 'Berhasil membuat data', { variant: 'success' });
})
.catch(({ response }) => {
enqueueSnackbar(response.data.message ?? 'Something Went Wrong', { variant: 'error' });
})
.then(() => {
setSubmitLoading(false);
});
}
const getContent = () => (
<Stack paddingY={1}>
<Stack direction="row" justifyContent={'end'} sx={{ marginBottom: 2 }}>
<Typography textAlign={'right'} variant="body2">
Submission Date : <br /> {fPostFormat(new Date(), 'dd/MM/yyyy')}
</Typography>
</Stack>
<Card sx={{ p: 1, background: '#f4f6f8', marginBottom: 2 }}>
<Stack direction="row" spacing={2}>
<Button
sx={{ padding: 2, width: '100%' }}
variant={serviceCode == 'IP' ? 'contained' : ''}
onClick={() => {
setServiceCode('IP');
}}
>
Rawat Inap
</Button>
<Button
sx={{ padding: 2, width: '100%' }}
variant={serviceCode == 'OP' ? 'contained' : ''}
onClick={() => {
setServiceCode('OP');
}}
>
Rawat Jalan
</Button>
</Stack>
</Card>
<Card sx={{ p: 1, background: '#f4f6f8', marginBottom: 2 }}>
<Stack direction="row">
<Avatar
src="https://minimal-assets-api.vercel.app/assets/images/avatars/avatar_5.jpg"
alt={data.fullName}
sx={{ marginTop: 1, width: 48, height: 48 }}
/>
<Stack sx={{ p: 1 }}>
<Typography>{data.fullName}</Typography>
<Typography>{data.memberId}</Typography>
</Stack>
</Stack>
</Card>
<Card sx={{ paddingY: 1, paddingX: 2 }}>
<Typography variant="body1" sx={{ marginBottom: 1, fontWeight: 600 }}>
Total Limit
</Typography>
<BorderLinearProgress variant="determinate" value={data.limit.percentage} />
<Typography sx={{ textAlign: 'right', marginTop: 1 }}>
{fCurrency(data.limit.current)} / {fCurrency(data.limit.total)}
</Typography>
</Card>
<Stack
divider={<Divider orientation="horizontal" flexItem />}
spacing={4}
sx={{ marginY: 2 }}
>
<Stack sx={{ marginTop: 2 }}>
<Typography variant="body1" fontWeight={600}>
<Iconify icon="eva:file-text-fill" /> Dokumen Kondisi
</Typography>
<Stack
divider={<Divider orientation="horizontal" flexItem />}
spacing={1}
sx={{ marginY: 2 }}
>
{fileKondisis &&
fileKondisis.map((file, index) => (
<Stack direction="row" justifyContent={'space-between'} key={index}>
<Typography sx={{ color: 'text.secondary' }}>{file.name}</Typography>
<Iconify
icon="eva:trash-2-outline"
color={'darkred'}
onClick={() => {
removeKondisiFiles(fileKondisis, index);
}}
/>
</Stack>
))}
</Stack>
<input
type="file"
id="file"
ref={fileKondisiInput}
style={{ display: 'none' }}
multiple
onChange={handleKondisiInputChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
<LoadingButton
variant="outlined"
onClick={() => {
fileKondisiInput?.current?.click();
}}
>
<Iconify icon="eva:plus-fill" />
Add Result
</LoadingButton>
</Stack>
<Stack sx={{ marginTop: 2 }}>
<Typography variant="body1" fontWeight={600}>
<Iconify icon="eva:file-text-fill" /> Dokumen Diagnosa
</Typography>
{/* <Typography variant="body2">Hasil Lab, </Typography> */}
<Stack
divider={<Divider orientation="horizontal" flexItem />}
spacing={1}
sx={{ marginY: 2 }}
>
{fileDiagnosas &&
fileDiagnosas.map((file, index) => (
<Stack direction="row" justifyContent={'space-between'} key={index}>
<Typography sx={{ color: 'text.secondary' }}>{file.name}</Typography>
<Iconify
icon="eva:trash-2-outline"
color={'darkred'}
onClick={() => {
removeDiagnosaFiles(fileDiagnosas, index);
}}
/>
</Stack>
))}
</Stack>
<input
type="file"
id="file"
ref={fileDiagnosaInput}
style={{ display: 'none' }}
multiple
onChange={handleDiagnosaInputChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
<LoadingButton
variant="outlined"
onClick={() => {
fileDiagnosaInput?.current?.click();
}}
>
<Iconify icon="eva:plus-fill" />
Add Result
</LoadingButton>
</Stack>
<Stack sx={{ marginTop: 2 }}>
<Typography variant="body1" fontWeight={600}>
<Iconify icon="eva:file-text-fill" /> Dokumen Hasil Penunjang
</Typography>
<Stack
divider={<Divider orientation="horizontal" flexItem />}
spacing={1}
sx={{ marginY: 2 }}
>
{fileHasilPenunjangs &&
fileHasilPenunjangs.map((file, index) => (
<Stack direction="row" justifyContent={'space-between'} key={index}>
<Typography sx={{ color: 'text.secondary' }}>{file.name}</Typography>
<Iconify
icon="eva:trash-2-outline"
color={'darkred'}
onClick={() => {
removeFiles(fileHasilPenunjangs, index);
}}
/>
</Stack>
))}
</Stack>
<input
type="file"
id="file"
ref={fileHasilPenunjangInput}
style={{ display: 'none' }}
multiple
onChange={handleResultInputChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
<LoadingButton
variant="outlined"
onClick={() => {
fileHasilPenunjangInput?.current?.click();
}}
>
<Iconify icon="eva:plus-fill" />
Add File
</LoadingButton>
</Stack>
</Stack>
<LoadingButton
variant="contained"
sx={{ marginTop: 2, p: 2 }}
onClick={() => {
submitRequest();
}}
loading={submitLoading}
>
LOG Request
</LoadingButton>
</Stack>
);
return (
<>
<MuiDialog
title={{ name: data.fullName }}
openDialog={openDialog}
setOpenDialog={setOpenDialog}
content={getContent()}
maxWidth="sm"
/>
</>
);
};
export default DialogRequestLog;

View File

@@ -4,21 +4,21 @@ import numeral from 'numeral';
// load a locale
numeral.register('locale', 'id', {
delimiters: {
thousands: '.',
decimal: ','
thousands: '.',
decimal: ',',
},
abbreviations: {
thousand: 'k',
million: 'm',
billion: 'b',
trillion: 't'
thousand: 'k',
million: 'm',
billion: 'b',
trillion: 't',
},
ordinal : function (number: number) {
return number === 1 ? 'er' : 'ème';
ordinal: function (number: number) {
return number === 1 ? 'er' : 'ème';
},
currency: {
symbol: 'Rp '
}
symbol: 'Rp ',
},
});
// switch between locales

View File

@@ -1,4 +1,4 @@
import { format,parseISO, getTime, setHours, setMinutes , formatDistanceToNow } from 'date-fns';
import { format, parseISO, getTime, setHours, setMinutes, formatDistanceToNow } from 'date-fns';
// ----------------------------------------------------------------------
@@ -21,10 +21,14 @@ export function fDateTimeSuffix(date: Date | string | number) {
export function fToNow(date: Date | string | number) {
return formatDistanceToNow(new Date(date), {
addSuffix: true
addSuffix: true,
});
}
export function fPostFormat(date: Date | string | number, dateFormat = 'yyyy-MM-dd HH:mm:ss') {
return format(new Date(date), dateFormat);
}
// export function fDateString(date) {
// const dateObj = parseISO(date);
// const formattedDate = format(dateObj, 'dd MMMM yyyy');
@@ -36,4 +40,4 @@ export function fToNow(date: Date | string | number) {
// const datePart = date.split(' ')[0]; // Memisahkan bagian tanggal
// const formattedDate = fDateString(datePart); // Menggunakan fungsi sebelumnya untuk memformat tanggal
// return formattedDate;
// }
// }

File diff suppressed because it is too large Load Diff