diff --git a/Modules/Client/Http/Controllers/Api/ClaimReportController.php b/Modules/Client/Http/Controllers/Api/ClaimReportController.php index b83234ad..8e2ee1f0 100644 --- a/Modules/Client/Http/Controllers/Api/ClaimReportController.php +++ b/Modules/Client/Http/Controllers/Api/ClaimReportController.php @@ -6,6 +6,8 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\ClaimRequest; use Modules\Client\Transformers\ClaimReport\ShowResources; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\DB; class ClaimReportController extends Controller { @@ -57,6 +59,130 @@ class ClaimReportController extends Controller ]); } + public function claimDetail($corporate_id, $claimRequestId) + { + $claimRequestId = Crypt::decrypt($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('corporate_employees.corporate_id', '=', $corporate_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; + + return Helper::responseJson($results); + } + + public function claimDetailHistory($corporate_id, $claimRequestId) + { + $claimRequestId = Crypt::decrypt($claimRequestId); + + $member = 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('corporate_employees.corporate_id', '=', $corporate_id) + ->where('claim_requests.id', '=', $claimRequestId) + ->select( + 'claim_requests.code','members.member_id', 'members.name' + ) + ->first(); + $results['member'] = $member; + $claim_item = DB::table('claim_items') + ->leftJoin('claims','claim_items.claim_id', '=', 'claims.id') + ->leftJoin('benefits', 'claim_items.claim_itemable_id', '=', 'benefits.id') + ->leftJoin('claim_requests', 'claims.claim_request_id', '=', 'claim_requests.id') + ->leftJoin('members', 'claim_requests.member_id', '=', 'members.id') + ->leftJoin('corporate_employees', 'members.id', '=', 'corporate_employees.member_id') + ->where('corporate_employees.corporate_id', '=', $corporate_id) + ->where('claim_requests.id', '=', $claimRequestId) + ->select( + DB::raw('ROW_NUMBER() OVER (ORDER BY claim_items.id DESC) as claim_item_number'), + 'claim_items.nominal_ditagihkan', + 'claim_items.nominal_dicover', + 'benefits.description', + 'claim_requests.submission_date' + ) + ->orderBy('claim_items.id', 'desc') + ->get(); + $results['claim_item'] = $claim_item; + $tot_claim_item = DB::table('claim_items') + ->leftJoin('claims','claim_items.claim_id', '=', 'claims.id') + ->leftJoin('benefits', 'claim_items.claim_itemable_id', '=', 'benefits.id') + ->leftJoin('claim_requests', 'claims.claim_request_id', '=', 'claim_requests.id') + ->leftJoin('members', 'claim_requests.member_id', '=', 'members.id') + ->leftJoin('corporate_employees', 'members.id', '=', 'corporate_employees.member_id') + ->where('corporate_employees.corporate_id', '=', $corporate_id) + ->where('claim_requests.id', '=', $claimRequestId) + ->select( + DB::raw('SUM(claim_items.nominal_ditagihkan) AS nominal_ditagihkan'), + DB::raw('SUM(claim_items.nominal_dicover) AS nominal_dicover'), + DB::raw('(SUM(claim_items.nominal_ditagihkan) - SUM(claim_items.nominal_dicover)) AS difference'), + ) + ->orderBy('claim_items.id', 'desc') + ->first(); + $results['tot_claim_item'] = $tot_claim_item; + + + return Helper::responseJson($results); + } + public function show($corporateId, $claimRequestId) { $data = ClaimRequest::query() diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index cd3acf81..02e0be16 100644 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -55,6 +55,8 @@ Route::prefix('client')->group(function () { // Route::get('topup', [TopUpController::class, 'get']); Route::post('topup', [TopUpController::class, 'store']); Route::get('claim-report/claim-status', [ClaimReportController::class, 'claimStatus']); + Route::get('claim-report/detail/{id}', [ClaimReportController::class, 'claimDetail']); + Route::get('claim-report/detail-history/{id}', [ClaimReportController::class, 'claimDetailHistory']); Route::get('corporate', [CorporateCurrentController::class, 'index']); Route::put('corporate-update', [CorporateCurrentController::class, 'update']); diff --git a/Modules/Client/Transformers/ClaimReport/MemberResources.php b/Modules/Client/Transformers/ClaimReport/MemberResources.php index 18f5e077..aae5a707 100644 --- a/Modules/Client/Transformers/ClaimReport/MemberResources.php +++ b/Modules/Client/Transformers/ClaimReport/MemberResources.php @@ -3,6 +3,7 @@ namespace Modules\Client\Transformers\ClaimReport; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Support\Facades\Crypt; class MemberResources extends JsonResource { @@ -21,7 +22,7 @@ class MemberResources extends JsonResource 'full_name' => $this->full_name, 'division_name' => $this->division_name ?? '', 'status' => $this->status, - 'claimRequestId' => $this->claim_request_id, + 'claimRequestId' => Crypt::encrypt($this->claim_request_id), 'submission_date' => $this->submission_date, ]; } diff --git a/app/Services/CorporateMemberService.php b/app/Services/CorporateMemberService.php index 3a6192df..d4380631 100644 --- a/app/Services/CorporateMemberService.php +++ b/app/Services/CorporateMemberService.php @@ -77,8 +77,8 @@ class CorporateMemberService 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 "review" + /*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 '), diff --git a/database/migrations/2023_10_11_131156_create_claim_logs_table.php b/database/migrations/2023_10_11_131156_create_claim_logs_table.php new file mode 100644 index 00000000..998c4e2d --- /dev/null +++ b/database/migrations/2023_10_11_131156_create_claim_logs_table.php @@ -0,0 +1,37 @@ +id(); + $table->bigInteger('claim_request_id'); + $table->string('status', 255); + $table->dateTime('date'); + $table->text('description')->nullable(); + $table->text('device')->nullable(); + $table->bigInteger('created_by'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('claim_logs'); + } +}; diff --git a/database/migrations/2023_10_11_145555_create_claim_request_files_table.php b/database/migrations/2023_10_11_145555_create_claim_request_files_table.php new file mode 100644 index 00000000..73e07d98 --- /dev/null +++ b/database/migrations/2023_10_11_145555_create_claim_request_files_table.php @@ -0,0 +1,36 @@ +id(); + $table->bigInteger('claim_request_id'); + $table->dateTime('date'); + $table->string('type', 255); + $table->text('description')->nullable(); + $table->bigInteger('created_by'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('claim_request_files'); + } +}; diff --git a/frontend/client-portal/public/icons/ic_gmail.svg b/frontend/client-portal/public/icons/ic_gmail.svg new file mode 100644 index 00000000..c71a7032 --- /dev/null +++ b/frontend/client-portal/public/icons/ic_gmail.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/client-portal/src/pages/ClaimReport/Detail.tsx b/frontend/client-portal/src/pages/ClaimReport/Detail.tsx new file mode 100644 index 00000000..8fd44714 --- /dev/null +++ b/frontend/client-portal/src/pages/ClaimReport/Detail.tsx @@ -0,0 +1,69 @@ +// mui +import { Container, Grid, Stack, Typography } from '@mui/material'; +// components +import Page from '../../components/Page'; +// utils +import useSettings from '../../hooks/useSettings'; +// section +import CardFamilyInformation from '../../sections/alarm-center/user-profile/CardFamilyInformation'; +// react +import { useNavigate, useParams } from 'react-router-dom'; +import ButtonBack from '../../components/ButtonBack'; +import { useEffect, useState, useContext } from 'react'; +import axios from '../../utils/axios'; +import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate'; +// pages +import DetailTimeline from '../../pages/ClaimReport/DetailTimeline'; +import DetailStepper from '../../pages/ClaimReport/DetailStepper'; +import { format } from 'date-fns'; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; + +// ---------------------------------------------------------------------- + +export default function UserProfile() { + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const [data, setData] = useState(); + + const { corporateValue } = useContext(UserCurrentCorporateContext); + const { id } = useParams(); + + useEffect(() => { + axios + .get(corporateValue + '/claim-report/detail/' + id) + .then((response) => { + setData(response.data); + }) + .catch((error) => { + console.error(error); + }); + + }, []); + + return ( + + + + navigate(-1)} sx={{cursor:'pointer'}}/> + Detail + {data ? ( + + Submission Date + {(data && data.data) ? format(new Date(data.data.status.submission_date), "d MMM yyyy") : ''} + + ) : ''} + + {data ? ( + + + + + + + + + ) : ''} + + + ); +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/ClaimReport/DetailHistory.tsx b/frontend/client-portal/src/pages/ClaimReport/DetailHistory.tsx new file mode 100644 index 00000000..c9e45f76 --- /dev/null +++ b/frontend/client-portal/src/pages/ClaimReport/DetailHistory.tsx @@ -0,0 +1,291 @@ +// mui +import { + Container, + Grid, + Stack, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Button, + Box, + TableSortLabel, + Avatar } from '@mui/material'; +// components +import Page from '../../components/Page'; +// utils +import useSettings from '../../hooks/useSettings'; +// section +import CardFamilyInformation from '../../sections/alarm-center/user-profile/CardFamilyInformation'; +// react +import { useNavigate, useParams } from 'react-router-dom'; +import ButtonBack from '../../components/ButtonBack'; +import { useEffect, useState, useContext } from 'react'; +import axios from '../../utils/axios'; +import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate'; +import { format } from 'date-fns'; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; +import { fCurrency } from '../../utils/formatNumber'; + +// ---------------------------------------------------------------------- + +export default function DetailHistory() { + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const [data, setData] = useState(); + + const { corporateValue } = useContext(UserCurrentCorporateContext); + const { id } = useParams(); + + useEffect(() => { + axios + .get(corporateValue + '/claim-report/detail-history/' + id) + .then((response) => { + setData(response.data); + }) + .catch((error) => { + console.error(error); + }); + + }, []); + + console.log(data?.data?.claim_item.length); + + return ( + + + + navigate(-1)} sx={{cursor:'pointer'}}/> + History + + {data ? ( + + + + Name + {data.data.member.name} + + + Member ID + {data.data.member.member_id} + + + Claim Code + {data.data.member.code} + + + + + + {/* Table Header */} + + + + No + + + Date + + + Requirment + + + Request Claim + + + Approval Claim + + + + {/* End Table Header */} + {/* Table Body */} + + {data?.data?.claim_item?.length === 0 ? ( + + + No data available + + + ) : ( + data.data.claim_item?.map((dataItem, index) => ( + + + {dataItem.claim_item_number} + + + + {format(new Date(dataItem.submission_date), "d MMM yyyy")} + + + + {dataItem.description} + + + {fCurrency(dataItem.nominal_ditagihkan)} + + + {fCurrency(dataItem.nominal_dicover)} + + + )) + )} + + + {/* End Table Body */} +
+
+
+ + + + {/* Table Body */} + + + + + + + + + + + + + Request Claim + + + {data.data.tot_claim_item.nominal_ditagihkan ? fCurrency(data.data.tot_claim_item.nominal_ditagihkan) : '-'} + + + + + + + + + + + + + + Approval Claim + + + {data.data.tot_claim_item.nominal_dicover ? fCurrency(data.data.tot_claim_item.nominal_dicover) : '-'} + + + + + + + + + + + + + + Difference + + + {data.data.tot_claim_item.difference ? fCurrency(data.data.tot_claim_item.difference) : '-'} + + + + {/* End Table Body */} +
+
+ + Note : Apabila terdapat perbedaan nominal silahkan hubungi kami + Gmail Icon + +
+
+ ) : ''} +
+
+ ); +} \ No newline at end of file diff --git a/frontend/client-portal/src/pages/ClaimReport/DetailStepper.tsx b/frontend/client-portal/src/pages/ClaimReport/DetailStepper.tsx new file mode 100644 index 00000000..18dc959a --- /dev/null +++ b/frontend/client-portal/src/pages/ClaimReport/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/client-portal/src/pages/ClaimReport/DetailTimeline.tsx b/frontend/client-portal/src/pages/ClaimReport/DetailTimeline.tsx new file mode 100644 index 00000000..18399f23 --- /dev/null +++ b/frontend/client-portal/src/pages/ClaimReport/DetailTimeline.tsx @@ -0,0 +1,108 @@ +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'; + +const Item1 = styled(Paper)(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, + 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); + useEffect(() => { + if (data && data.data) { + setTimeline(data.data.timeline); + setRequestFile(data.data.request_files); + } + + }, [data]); + console.log(timeline); + console.log(requestFile); + 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} + + + ))} + + ) : ''} + + + + + + ))} + + ); +} diff --git a/frontend/client-portal/src/pages/ClaimReport/List.tsx b/frontend/client-portal/src/pages/ClaimReport/List.tsx index 03d3db7d..4c3417f6 100644 --- a/frontend/client-portal/src/pages/ClaimReport/List.tsx +++ b/frontend/client-portal/src/pages/ClaimReport/List.tsx @@ -53,7 +53,7 @@ export default function List() { /* ------------------------------ handle order ------------------------------ */ const [order, setOrder] = useState('desc'); - const [orderBy, setOrderBy] = useState('code'); + const [orderBy, setOrderBy] = useState('member_id'); const orders = { order: order, @@ -174,8 +174,6 @@ export default function List() { params: { ...parameters, type: 'claim-report' }, }); - console.log(response.data.data); - setData( response.data.data.map((obj: any) => ({ ...obj, @@ -244,7 +242,7 @@ export default function List() { > Pending - ) : obj.status === 'review' ? ( + ) : obj.status === 'reviewed' ? ( - navigate ('/employee-data/user-profile/'+obj.personId)}> + navigate ('/claim-report/detail/'+obj.claimRequestId)}> Detail - navigate ('/employee-data/user-profile/'+obj.personId)}> + navigate ('/claim-report/detail-history/'+obj.claimRequestId)}> History diff --git a/frontend/client-portal/src/pages/EmployeeData/UserProfile.tsx b/frontend/client-portal/src/pages/EmployeeData/UserProfile.tsx index e5afd27e..1bd40e40 100644 --- a/frontend/client-portal/src/pages/EmployeeData/UserProfile.tsx +++ b/frontend/client-portal/src/pages/EmployeeData/UserProfile.tsx @@ -17,12 +17,13 @@ import ButtonBack from '../../components/ButtonBack'; import { useEffect, useState, useContext } from 'react'; import axios from '../../utils/axios'; import { UserCurrentCorporateContext } from '../../contexts/UserCurrentCorporate'; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; // ---------------------------------------------------------------------- export default function UserProfile() { const { themeStretch } = useSettings(); - // const navigate = useNavigate(); + const navigate = useNavigate(); const [data, setData] = useState(); const { corporateValue } = useContext(UserCurrentCorporateContext); @@ -44,13 +45,14 @@ export default function UserProfile() { return ( - + {/* navigate()}> */} - - Profile + navigate(-1)}/> + Profile + {data ? ( {/* Row 1 */} @@ -60,6 +62,7 @@ export default function UserProfile() { + ) : ''} ); diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index 4db6bd16..228f1d38 100644 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -168,9 +168,13 @@ export default function Router() { index: true, }, { - path: 'dialog-detail/:id', - element: , + path: '/claim-report/detail/:id', + element: , }, + { + path: '/claim-report/detail-history/:id', + element: , + } ], }, { @@ -252,6 +256,8 @@ const ClaimReport = Loadable(lazy(() => import('../pages/ClaimReport/Index'))); const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); const ClaimShow = Loadable(lazy(() => import('../pages/Claims/Show'))); const DialogDetailClaim = Loadable(lazy(()=> import('../pages/ClaimReport/DialogDetailClaim'))); +const DetailClaimReport = Loadable(lazy(()=> import('../pages/ClaimReport/Detail'))); +const DetailHitoryClaimReport = Loadable(lazy(()=> import('../pages/ClaimReport/DetailHistory'))); // Claim submit const ClaimSubmit = Loadable(lazy(() => import('../pages/ClaimSubmit/Index')));