diff --git a/Modules/Client/Http/Controllers/Api/ClaimReportController.php b/Modules/Client/Http/Controllers/Api/ClaimReportController.php
index 2ced9d97..e8b3967e 100644
--- a/Modules/Client/Http/Controllers/Api/ClaimReportController.php
+++ b/Modules/Client/Http/Controllers/Api/ClaimReportController.php
@@ -124,6 +124,13 @@ class ClaimReportController extends Controller
->where('claim_request_files.claim_request_id', '=', $claimRequestId)
->get();
$results['request_files'] = $request_files;
+ $documents = DB::table('files')
+ ->where('fileable_type', 'App\Models\ClaimRequest')
+ ->where('fileable_id', $claimRequestId)
+ ->select('original_name', \DB::raw("CONCAT('" . env('APP_URL') . "/storage/', path) as path"), 'type')
+ ->orderBy('id', 'desc')
+ ->get();
+ $results['documents'] = $documents;
return Helper::responseJson($results);
}
diff --git a/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php b/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php
index 10e4ada3..88711980 100644
--- a/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php
+++ b/Modules/Internal/Http/Controllers/Api/ClaimRequestController.php
@@ -15,9 +15,10 @@ use Modules\Internal\Transformers\ClaimRequestShowResource;
use Illuminate\Support\Facades\Storage;
use App\Services\ClaimRequestService;
use App\Exceptions\ImportRowException;
-
use App\Models\File;
use App\Models\FilesMcu;
+use Illuminate\Support\Facades\DB;
+use App\Models\Member;
class ClaimRequestController extends Controller
{
@@ -336,4 +337,111 @@ class ClaimRequestController extends Controller
]
];
}
+
+ public function claimRequestDetail($claimRequestId)
+ {
+ $status = DB::table('claim_requests')
+ ->leftJoin('claims', 'claim_requests.id', '=', 'claims.claim_request_id')
+ ->leftJoin('members', 'claim_requests.member_id', '=', 'members.id')
+ ->leftJoin('corporate_employees', 'members.id', '=', 'corporate_employees.member_id')
+ ->leftJoin('corporate_divisions', 'corporate_employees.division_id', '=', 'corporate_divisions.id')
+ ->where('claim_requests.id', '=', $claimRequestId)
+ ->select(
+ 'claim_requests.submission_date',
+ DB::raw('
+ CASE
+ WHEN claim_requests.status = "requested" THEN "requested"
+ WHEN claim_requests.status = "approved" AND claims.status = "approved" THEN "approved"
+ WHEN claim_requests.status = "approved" AND claims.status = "declined" THEN "declined"
+ WHEN claim_requests.status = "approved" AND claims.status = "disbrusmented" THEN "disbrusmented"
+ /*WHEN claim_requests.status = "approved" AND claims.status = "received" THEN "pending"*/
+ WHEN claim_requests.status = "approved" AND claims.status = "received" THEN "reviewed"
+ ELSE ""
+ END AS status
+ ')
+ )
+ ->first();
+ $results['status'] = $status;
+ $timeline = DB::table('claim_logs')
+ ->where('claim_logs.claim_request_id', '=', $claimRequestId)
+ ->select(
+ DB::raw('
+ CASE
+ WHEN claim_logs.status = "requested" THEN "Request"
+ WHEN claim_logs.status = "reviewed" THEN "Review"
+ WHEN claim_logs.status = "approved" THEN "Approval"
+ ELSE "-"
+ END AS txt_status
+ '),
+ DB::raw('
+ CASE
+ WHEN claim_logs.status = "requested" THEN "#159C9C"
+ WHEN claim_logs.status = "reviewed" THEN "#0C53B7"
+ WHEN claim_logs.status = "approved" THEN "#229A16"
+ ELSE "-"
+ END AS txt_status_color
+ '),
+ DB::raw('
+ CASE
+ WHEN claim_logs.status = "requested" THEN "#00AB5529"
+ WHEN claim_logs.status = "reviewed" THEN "#1890FF29"
+ WHEN claim_logs.status = "approved" THEN "#54D62C29"
+ ELSE "-"
+ END AS txt_status_backgroundColor
+ '),
+ 'claim_logs.date',
+ 'claim_logs.description',
+ 'claim_logs.status'
+ )
+ ->orderBy('claim_logs.id', 'desc')
+ ->get();
+ $results['timeline'] = $timeline;
+ $request_files = DB::table('claim_request_files')
+ ->where('claim_request_files.claim_request_id', '=', $claimRequestId)
+ ->get();
+ $results['request_files'] = $request_files;
+ $documents = DB::table('files')
+ ->where('fileable_type', 'App\Models\ClaimRequest')
+ ->where('fileable_id', $claimRequestId)
+ ->select('original_name', \DB::raw("CONCAT('" . env('APP_URL') . "/storage/', path) as path"), 'type')
+ ->orderBy('id', 'desc')
+ ->get();
+ $results['documents'] = $documents;
+ $dialog_submits = DB::table('claim_requests')
+ ->leftJoin('members', 'claim_requests.member_id','=', 'members.id')
+ ->where('claim_requests.id', $claimRequestId)
+ ->select('claim_requests.code', 'members.name', 'claim_requests.submission_date', 'claim_requests.service_code','claim_requests.status')
+ ->first();
+ $results['dialog_submits'] = $dialog_submits;
+
+ return Helper::responseJson($results);
+ }
+
+ public function invoiceFiles(Request $request, $claim_id)
+ {
+ if ($request->hasFile('invoice_files')) {
+ foreach ($request->invoice_files as $file) {
+ $pathFile = File::storeFile('claim-invoice', $claim_id, $file);
+ File::updateOrCreate([
+ 'fileable_type'=>'App\Models\ClaimRequest',
+ 'fileable_id' => $claim_id,
+ 'type' => 'claim-invoice',
+ 'name' => File::getFileName('claim-invoice', $claim_id, $file),
+ 'original_name' => $file->getClientOriginalName(),
+ 'extension' => $file->getClientOriginalExtension(),
+ 'path' => $pathFile,
+ 'created_by' => auth()->user()->id,
+ 'updated_by' => auth()->user()->id,
+ ]);
+ }
+ }
+ if($request->date)
+ {
+ DB::table('claim_requests')
+ ->where('id', $claim_id)
+ ->update(['invoice_date' => $request->date]);
+
+ }
+ return Helper::responseJson(data: $request->toArray(), message: 'Invoice Success Uploaded');
+ }
}
diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php
index 71dddf4d..6c664350 100644
--- a/Modules/Internal/Routes/api.php
+++ b/Modules/Internal/Routes/api.php
@@ -218,6 +218,8 @@ Route::prefix('internal')->group(function () {
Route::get('claim-requests/{id}', [ClaimRequestController::class, 'show'])->name('claim-requests.show');
Route::put('claim-requests/{id}', [ClaimRequestController::class, 'update'])->name('claim-requests.update');
Route::post('claim-requests/import', [ClaimRequestController::class, 'importClaim'])->name('claim-requests.importClaim');
+ Route::get('claim-requests/detail/{id}', [ClaimRequestController::class, 'claimRequestDetail']);
+ Route::post('claim-requests/{id}/invoice-files', [ClaimRequestController::class, 'invoiceFiles']);
});
Route::get('province', [ProvinceController::class, 'index']);
diff --git a/app/Models/File.php b/app/Models/File.php
index 6bf0952f..f172de82 100644
--- a/app/Models/File.php
+++ b/app/Models/File.php
@@ -43,6 +43,7 @@ class File extends Model
'claim-result' => 'claim/',
'claim-diagnosis' => 'claim/',
'claim-kondisi' => 'claim/',
+ 'claim-invoice' => 'claim/',
'docs' => 'docs/',
];
diff --git a/database/migrations/2023_10_26_085839_add_column_invoice_date_to_claim_requests.php b/database/migrations/2023_10_26_085839_add_column_invoice_date_to_claim_requests.php
new file mode 100644
index 00000000..aca5eb06
--- /dev/null
+++ b/database/migrations/2023_10_26_085839_add_column_invoice_date_to_claim_requests.php
@@ -0,0 +1,32 @@
+dateTime('invoice_date')->nullable()->after('claim_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('claim_requests', function (Blueprint $table) {
+ $table->dropColumn('invoice_date');
+ });
+ }
+};
diff --git a/frontend/client-portal/src/pages/ClaimReport/DetailTimeline.tsx b/frontend/client-portal/src/pages/ClaimReport/DetailTimeline.tsx
index 1e93f302..b058df06 100644
--- a/frontend/client-portal/src/pages/ClaimReport/DetailTimeline.tsx
+++ b/frontend/client-portal/src/pages/ClaimReport/DetailTimeline.tsx
@@ -13,6 +13,8 @@ import AddIcon from '@mui/icons-material/Add';
import Iconify from '../../components/Iconify';
import { useEffect, useState } from 'react';
import { format } from 'date-fns';
+import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
+import DescriptionIcon from '@mui/icons-material/Description';
const Item1 = styled(Paper)(({ theme }) => ({
...theme.typography.body2,
@@ -37,10 +39,12 @@ const Item2 = styled(Paper)(({ theme }) => ({
export default function NoOppositeContent({data}) {
const [timeline, setTimeline] = useState(null);
const [requestFile, setRequestFile] = useState(null);
+ const [document, setDocument] = useState(null);
useEffect(() => {
if (data && data.data) {
setTimeline(data.data.timeline);
setRequestFile(data.data.request_files);
+ setDocument(data.data.documents);
}
}, [data]);
@@ -65,7 +69,7 @@ export default function NoOppositeContent({data}) {
-
+
{dataTimeline.date ? format(new Date(dataTimeline.date), "HH : ii") : ''}
{dataTimeline.txt_status}
@@ -81,11 +85,13 @@ export default function NoOppositeContent({data}) {
}>
{dataRequestFile.type === 'claim-diagnosis' ?
- 'Dokumen Diagnosa'
+ 'Diagnosis'
: dataRequestFile.type === 'claim-kondisi' ?
- 'Dokumen Kondisi'
+ 'Condition'
: dataRequestFile.type === 'claim-result' ?
- 'Dokumen Hasil Penunjang'
+ 'Supporting Result'
+ : dataRequestFile.type === 'claim-invoice' ?
+ 'Invoice'
: ''}
@@ -95,6 +101,43 @@ export default function NoOppositeContent({data}) {
) : ''}
+ {dataTimeline.status === 'requested' ? (
+
+
+
+
+ Documents
+
+
+ {document?.map((dataDocument, index) => (
+
+
+ {dataDocument.type === 'claim-diagnosis' ?
+ 'Diagnosis'
+ : dataDocument.type === 'claim-kondisi' ?
+ 'Condition'
+ : dataDocument.type === 'claim-result' ?
+ 'Supporting Result'
+ : dataDocument.type === 'claim-invoice' ?
+ 'Invoice'
+ : ''}
+
+
+
+
+ {dataDocument.original_name ? dataDocument.original_name : '-'}
+
+
+
+ ))}
+
+
+
+ ) : ''}
diff --git a/frontend/dashboard/src/pages/ClaimRequests/Detail.tsx b/frontend/dashboard/src/pages/ClaimRequests/Detail.tsx
new file mode 100644
index 00000000..b4300ac6
--- /dev/null
+++ b/frontend/dashboard/src/pages/ClaimRequests/Detail.tsx
@@ -0,0 +1,300 @@
+// mui
+import { Container, Grid, Stack, Typography, Card, TextField, Divider, ButtonBase, Box, IconButton } from '@mui/material';
+// components
+import Page from '../../components/Page';
+// utils
+import useSettings from '../../hooks/useSettings';
+// react
+import { useNavigate, useParams, useLocation } from 'react-router-dom';
+import { useEffect, useState, useRef } from 'react';
+import axios from '../../utils/axios';
+// pages
+import DetailTimeline from '../../pages/ClaimRequests/DetailTimeline';
+import DetailStepper from '../../pages/ClaimRequests/DetailStepper';
+import { format } from 'date-fns';
+import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
+import Button from '@mui/material/Button';
+import AddIcon from '@mui/icons-material/Add';
+import RemoveIcon from '@mui/icons-material/Remove';
+import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers';
+import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
+import Iconify from '@/components/Iconify';
+import { fPostFormat } from '@/utils/formatTime';
+import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
+import DownloadIcon from '@mui/icons-material/Download';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+import { fDateTimesecond } from '@/utils/formatTime';
+import { makeFormData } from '@/utils/jsonToFormData';
+
+import { enqueueSnackbar } from 'notistack';
+
+// ----------------------------------------------------------------------
+
+export default function UserProfile() {
+ const location = useLocation();
+ const queryParams = new URLSearchParams(location.search);
+ const code = queryParams.get('code');
+
+ const navigate = useNavigate();
+ const { themeStretch } = useSettings();
+ const [data, setData] = useState();
+ const [dataDialog, setDataDialog] = useState();
+
+ const { id } = useParams();
+
+ useEffect(() => {
+ axios
+ .get('/claim-requests/detail/'+id)
+ .then((response) => {
+ setData(response.data);
+ setDataDialog(response.data.data.dialog_submits);
+
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+
+ }, []);
+
+ const [isInvoiceVisible, setInvoiceVisibility] = useState(false);
+
+ const handleInvoice = () => {
+ setInvoiceVisibility(!isInvoiceVisible);
+ }
+ const currentDate = new Date();
+ const formattedCurrentDate = format(currentDate, 'dd MMM yyyy');
+ const [dateInvoice, setDateInvoice] = useState(currentDate);
+
+ const fileInvoiceInput = useRef(null);
+ const [fileInvoices, setFileInvoices] = useState([]);
+
+ const handleInvoiceInputChange = (event) => {
+ if (event.target.files[0]) {
+ setFileInvoices([...fileInvoices, ...event.target.files]);
+ } else {
+ console.log('NO FILE');
+ }
+ };
+ const removeInvoiceFiles = (filesState, index) => {
+ setFileInvoices(
+ filesState.filter((file, fileIndex) => {
+ return fileIndex != index;
+ })
+ );
+ };
+ const date = dateInvoice ? fPostFormat(dateInvoice, 'yyyy-MM-dd') : null;
+
+ const [openDialogSubmit, setOpenDialogSubmit] = useState(false);
+ const handleCloseDialogSubmit = () => {
+ setOpenDialogSubmit(false);
+ }
+ const handleSubmitData = () => {
+ if(fileInvoices.length > 0)
+ {
+ //submit data
+ axios
+ .post('claim-requests/'+id+'/approve')
+ .then((response) => {
+ enqueueSnackbar('Success Submit Claim Request', { variant: 'success' });
+ setOpenDialogSubmit(false);
+ })
+ .catch(({ response }) => {
+ enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' });
+ });
+ //Upload file invoices
+ const formData = makeFormData({
+ date:date,
+ invoice_files: fileInvoices,
+ });
+ axios
+ .post('claim-requests/'+id+'/invoice-files', formData)
+ .then((response) => {
+ enqueueSnackbar(response.data.message ?? 'Success upload invoice', { variant: 'success' });
+ })
+ .catch(({ response }) => {
+ enqueueSnackbar(response.data.message ?? 'Something Went Wrong', { variant: 'error' });
+ });
+ }
+ else
+ {
+ enqueueSnackbar('Please upload file invoice, before submit', { variant: 'error' });
+ }
+
+ setTimeout(() =>
+ {
+ window.location.reload();
+ }, 5000);
+
+ };
+
+ return (
+
+
+
+ navigate(-1)} sx={{cursor:'pointer'}}/>
+ {code}
+ {data ? (
+
+ Submission Date
+ {(data && data.data) ? format(new Date(data.data.status.submission_date), "d MMM yyyy") : ''}
+
+ ) : ''}
+
+ {data ? (
+
+
+
+
+
+
+ Format Claim
+ } sx={{marginLeft: 'auto'}}>
+ Import
+
+
+
+
+
+ Request Claim
+ : < AddIcon/>} sx={{marginLeft: 'auto'}} onClick={() => handleInvoice()}>
+ Invoice
+
+
+
+
+
+
+
+ {
+ setDateInvoice(newValue);
+ }}
+ inputFormat="dd MMM yyyy"
+ renderInput={(params) => }
+ />
+
+ }
+ spacing={1}
+ sx={{ marginY: 2 }}
+ >
+ {fileInvoices &&
+ fileInvoices.map((file, index) => (
+
+
+
+ {file.name ? file.name : '-'}
+
+ {
+ removeInvoiceFiles(fileInvoices, index);
+ }}
+ sx={{cursor: 'pointer'}}
+ >
+
+ ))}
+
+ fileInvoiceInput.current?.click()}>
+
+
+
+ Upload Invoice
+
+
+
+
+
+
+
+
+
+
+
+
+ {dataDialog && dataDialog.status === 'requested' ? (
+ <>
+
+
+ >
+ ) : ''}
+ {/* Dialog Submits */}
+
+
+
+
+ ) : ''}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/dashboard/src/pages/ClaimRequests/DetailStepper.tsx b/frontend/dashboard/src/pages/ClaimRequests/DetailStepper.tsx
new file mode 100644
index 00000000..b788e29f
--- /dev/null
+++ b/frontend/dashboard/src/pages/ClaimRequests/DetailStepper.tsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stepper from '@mui/material/Stepper';
+import Step from '@mui/material/Step';
+import StepLabel from '@mui/material/StepLabel';
+import { useEffect, useState } from 'react';
+import ClearIcon from '@mui/icons-material/Clear';
+
+const steps = [
+ 'Request',
+ 'Review',
+ 'Approval',
+ 'Decline',
+ ];
+
+ export default function HorizontalLinearAlternativeLabelStepper({data}) {
+ const [active, setActive] = useState(0);
+ const [status, SetStatus] = useState(null);
+ let updatedSteps = [...steps];
+ useEffect(() => {
+ if (data && data.data) {
+ if (data.data.status.status === 'requested') {
+ setActive(1);
+ updatedSteps = updatedSteps.filter(step => step !== 'Decline');
+ }
+ else if (data.data.status.status === 'reviewed') {
+ setActive(2);
+ updatedSteps = updatedSteps.filter(step => step !== 'Decline');
+ }
+ else if (data.data.status.status === 'approved')
+ {
+ setActive(3);
+ updatedSteps = updatedSteps.filter(step => step !== 'Decline');
+ }
+ else if(data.data.status.status === 'declined')
+ {
+ setActive(4)
+ updatedSteps = updatedSteps.filter(step => step !== 'Approval');
+ }
+ }
+ SetStatus(updatedSteps);
+ }, [data]);
+
+
+
+
+ return (
+
+
+ {status?.map((label) => (
+
+ : ''}>{label}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/dashboard/src/pages/ClaimRequests/DetailTimeline.tsx b/frontend/dashboard/src/pages/ClaimRequests/DetailTimeline.tsx
new file mode 100644
index 00000000..b058df06
--- /dev/null
+++ b/frontend/dashboard/src/pages/ClaimRequests/DetailTimeline.tsx
@@ -0,0 +1,147 @@
+import * as React from 'react';
+import Timeline from '@mui/lab/Timeline';
+import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
+import TimelineSeparator from '@mui/lab/TimelineSeparator';
+import TimelineConnector from '@mui/lab/TimelineConnector';
+import TimelineContent from '@mui/lab/TimelineContent';
+import TimelineDot from '@mui/lab/TimelineDot';
+import {Typography, Card, Stack} from '@mui/material';
+import { styled } from '@mui/material/styles';
+import Paper from '@mui/material/Paper';
+import Button from '@mui/material/Button';
+import AddIcon from '@mui/icons-material/Add';
+import Iconify from '../../components/Iconify';
+import { useEffect, useState } from 'react';
+import { format } from 'date-fns';
+import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
+import DescriptionIcon from '@mui/icons-material/Description';
+
+const Item1 = styled(Paper)(({ theme }) => ({
+ ...theme.typography.body2,
+ padding: theme.spacing(1),
+ textAlign: 'center',
+ backgroundColor: '#919EAB29',
+ color: '#637381',
+ width: 'fit-content',
+ marginRight: 'auto',
+}));
+
+const Item2 = styled(Paper)(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ ...theme.typography.body2,
+ padding: theme.spacing(1),
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+ width: 'fit-content',
+ marginLeft: 'auto',
+}));
+
+export default function NoOppositeContent({data}) {
+ const [timeline, setTimeline] = useState(null);
+ const [requestFile, setRequestFile] = useState(null);
+ const [document, setDocument] = useState(null);
+ useEffect(() => {
+ if (data && data.data) {
+ setTimeline(data.data.timeline);
+ setRequestFile(data.data.request_files);
+ setDocument(data.data.documents);
+ }
+
+ }, [data]);
+ return (
+ <>
+ {timeline?.map((dataTimeline, index) => (
+
+ {dataTimeline.date ? format(new Date(dataTimeline.date), "d MMM yyyy") : ''}
+
+
+
+
+
+
+
+
+
+ {dataTimeline.date ? format(new Date(dataTimeline.date), "HH : ii") : ''}
+ {dataTimeline.txt_status}
+
+
+ Detail:
+ {dataTimeline.description}
+
+ {dataTimeline.status === 'reviewed' && requestFile ? (
+ <>
+ {requestFile?.map((dataRequestFile, index) => (
+
+ {dataRequestFile.description}
+ }>
+
+ {dataRequestFile.type === 'claim-diagnosis' ?
+ 'Diagnosis'
+ : dataRequestFile.type === 'claim-kondisi' ?
+ 'Condition'
+ : dataRequestFile.type === 'claim-result' ?
+ 'Supporting Result'
+ : dataRequestFile.type === 'claim-invoice' ?
+ 'Invoice'
+ : ''}
+
+
+
+ ))}
+ >
+ ) : ''}
+
+
+ {dataTimeline.status === 'requested' ? (
+
+
+
+
+ Documents
+
+
+ {document?.map((dataDocument, index) => (
+
+
+ {dataDocument.type === 'claim-diagnosis' ?
+ 'Diagnosis'
+ : dataDocument.type === 'claim-kondisi' ?
+ 'Condition'
+ : dataDocument.type === 'claim-result' ?
+ 'Supporting Result'
+ : dataDocument.type === 'claim-invoice' ?
+ 'Invoice'
+ : ''}
+
+
+
+
+ {dataDocument.original_name ? dataDocument.original_name : '-'}
+
+
+
+ ))}
+
+
+
+ ) : ''}
+
+
+
+ ))}
+ >
+ );
+}
diff --git a/frontend/dashboard/src/pages/ClaimRequests/List.tsx b/frontend/dashboard/src/pages/ClaimRequests/List.tsx
index 576ad6fa..852f3cb9 100644
--- a/frontend/dashboard/src/pages/ClaimRequests/List.tsx
+++ b/frontend/dashboard/src/pages/ClaimRequests/List.tsx
@@ -357,7 +357,7 @@ export default function List() {
Edit
-