From d92e2aba2ee47cf67790a96d184fb21a3e28ffc6 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 14:26:14 +0700 Subject: [PATCH 01/13] Report Request LOG dan Final LOG --- .../Controllers/Api/RequestLogController.php | 2 + .../Controllers/Api/ReportLogController.php | 192 ++++++ .../Controllers/Api/RequestLogController.php | 4 +- Modules/Internal/Routes/api.php | 9 + .../Transformers/ReportLogResource.php | 58 ++ app/Helpers/Helper.php | 25 + app/Models/RequestLog.php | 2 + ...1_30_102516_add_coloum_to_request_logs.php | 34 + .../layouts/dashboard/navbar/NavConfig.tsx | 1 + .../pages/CustomerService/Request/List.tsx | 162 ++++- .../dashboard/src/pages/Report/Log/Index.tsx | 35 + .../dashboard/src/pages/Report/Log/List.tsx | 605 ++++++++++++++++++ frontend/dashboard/src/routes/index.tsx | 17 + 13 files changed, 1135 insertions(+), 11 deletions(-) create mode 100644 Modules/Internal/Http/Controllers/Api/ReportLogController.php create mode 100644 Modules/Internal/Transformers/ReportLogResource.php create mode 100644 database/migrations/2024_01_30_102516_add_coloum_to_request_logs.php create mode 100644 frontend/dashboard/src/pages/Report/Log/Index.tsx create mode 100644 frontend/dashboard/src/pages/Report/Log/List.tsx diff --git a/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php b/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php index 3f6328c9..7bc347e7 100644 --- a/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php +++ b/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php @@ -339,6 +339,8 @@ class RequestLogController extends Controller 'status_final_log' => 'requested', 'final_log' => 1, 'discharge_date' => $request->discharge_date, + 'created_final_by'=> date('Y-m-d H:i:s'), + 'created_final_at'=> auth()->user()->id, ]); if ($request->hasFile('result_files')) { foreach ($request->result_files as $file) { diff --git a/Modules/Internal/Http/Controllers/Api/ReportLogController.php b/Modules/Internal/Http/Controllers/Api/ReportLogController.php new file mode 100644 index 00000000..4407cdc0 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/ReportLogController.php @@ -0,0 +1,192 @@ +where('deleted_at', null) + ->when($request->final_log, function($q, $final_log) { + $q->where('final_log', $final_log); + }) + ->when($request->search, function ($q, $search) { + $q->where('code', 'LIKE', "%".$search."%"); + $q->orWhereHas('member', function ($subQuery) use ($search) { + $subQuery->where('name', 'LIKE', "%".$search."%"); + }); + }) + ->when($request->orderBy, function ($q, $orderBy) use ($request) { + if (in_array($orderBy, ['submission_date', 'code', 'service_code', 'status'])) { + $q->orderBy($orderBy, $request->order); + } + }) + ->when(empty($request->orderBy), function ($q) { + $q->orderBy('submission_date', 'desc'); + }) + ->when($request->service_code, function($q, $service_code) { + if ($service_code == 'IP'){ // Penjagaan sementara agar ini hanya muncul di inpatient monitoring + $q->where('service_code', $service_code); + } else { + $q->where('service_code', '!=', 'IP'); // Dan selain IP muncul di final LOG + } + }) + // ->where('status', $request->status) + ->with(['member', 'files', 'service', 'member.currentPolicy']) + ->paginate(); + + return Helper::paginateResources(ReportLogResource::collection($requestLog)); + } + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + $claimRequest = RequestLog::findOrFail($id); + $claimRequest->load([ + 'histories' => function ($history) { + $history->latest(); + }, + 'files', + 'member', + 'member.currentPlan' => function($memberPlan) { + $memberPlan->join('request_logs', 'request_logs.service_code', '=', 'plans.service_code'); + }, + // 'member.current_policy', + 'claim', + 'organization', + + ]); + + return Helper::responseJson(data: RequestLogShowResource::make($claimRequest)); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('internal::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(Request $request, $id) + { + + } + + + /** + * Generate Export Excel Request LOG + */ + + public function generateDataRequestLogExcel(){ + $file_name = 'Data Request LOG'; + // Membuat penulis entitas Spout + $writer = WriterEntityFactory::createXLSXWriter(); + // Membuka penulis untuk menulis ke file + $writer->openToFile(public_path('files/Data Request LOG.xlsx')); + + // Sheet 1 + $writer->getCurrentSheet()->setName('Data'); + $headers_map_to_table_fields = RequestLog::$listing_data_doc_headers; + $headerRow = WriterEntityFactory::createRowFromArray($headers_map_to_table_fields); + $writer->addRow($headerRow); + + $dataRequestLog = RequestLog::query() + // ->whereHas('corporatePlan', function ($corporatePlan) use ($corporate_id) { + // $corporatePlan->where('corporate_id', $corporate_id); + // }) + ->with('member') + ->orderBy('id', 'desc') + ->get()->toArray(); + + // dd($dataRequestLog); + foreach ($dataRequestLog as $index => $row){ + $serviceType = $this->getServiceName($row['service_code']); + + $rowData = [ + $row['id'], // id + $row['code'], // code + $row['member']['name'], // name + $row['submission_date'], // submission date + $serviceType, // service type + $row['payment_type_name'], // service type + $row['status'], // service type + ]; + $row = WriterEntityFactory::createRowFromArray($rowData); + $writer->addRow($row); + } + $writer->close(); + + return Helper::responseJson([ + 'file_name' => "Data Request Log " . date('Y-m-d h:i:s'), + "file_url" => url('files/Data Request LOG.xlsx') + ]); + } + +} diff --git a/Modules/Internal/Http/Controllers/Api/RequestLogController.php b/Modules/Internal/Http/Controllers/Api/RequestLogController.php index 1a7875f4..a4479d40 100644 --- a/Modules/Internal/Http/Controllers/Api/RequestLogController.php +++ b/Modules/Internal/Http/Controllers/Api/RequestLogController.php @@ -63,8 +63,8 @@ class RequestLogController extends Controller }); }) ->when($request->orderBy, function ($q, $orderBy) use ($request) { - if (in_array($orderBy, ['submission_date', 'code'])) { - $q->orderBy($orderBy, $request->orderBy); + if (in_array($orderBy, ['submission_date', 'code', 'service_code', 'status'])) { + $q->orderBy($orderBy, $request->order); } }) ->when(empty($request->orderBy), function ($q) { diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index b7800eb2..5126ef8c 100644 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -45,6 +45,9 @@ use Modules\Internal\Http\Controllers\Api\LaboratoriumResultController; use Modules\Internal\Http\Controllers\Api\CorporateManageController; use Modules\Internal\Http\Controllers\ClaimEncounterController; +// Report +use Modules\Internal\Http\Controllers\Api\ReportLogController; + /* |-------------------------------------------------------------------------- @@ -315,6 +318,12 @@ Route::prefix('internal')->group(function () { Route::get('claim-requests/service/{id}', [ClaimRequestController::class, 'getServiceMember']); + // Report API + Route::prefix('report')->group(function () { + Route::prefix('/logs')->group(function () { + Route::get('/', [ReportLogController::class, 'index']); + }); + }); }); diff --git a/Modules/Internal/Transformers/ReportLogResource.php b/Modules/Internal/Transformers/ReportLogResource.php new file mode 100644 index 00000000..5db086dd --- /dev/null +++ b/Modules/Internal/Transformers/ReportLogResource.php @@ -0,0 +1,58 @@ +files->mapToGroups(function($file) { + return [Str::slug($file->type, '_') => $file]; + }); + $provider = Organization::where('id', $this->organization_id)->first(); + $documentQty = File::where(['fileable_type' => 'App\Models\RequestLog', 'fileable_id' => $this->id])->get()->toArray(); + $parsedDateTime = Carbon::parse($this->created_at); + $formattedDateTime = $parsedDateTime->format('Y-m-d H:i:s'); + + $durationGl = Helper::differenceTime($formattedDateTime, $this->submission_date); + $durationFinalGl = Helper::differenceTime($this->created_final_at, $this->approved_by); + + $data = [ + 'id' => $this->id, + 'code' => $this->code, + 'created_at' => $formattedDateTime, + 'created_final_at' => $this->created_final_at, + 'submission_date' => $this->submission_date, + 'approved_by' => Helper::userName($this->approved_by), + 'approved_final_log_at' => $this->approved_final_log_at, + 'approved_final_log_by' => Helper::userName($this->approved_final_log_by), + 'service_name' => $this->service ? $this->service->name : '', + 'provider' => $provider ? $provider->name : '-', + 'document_qty' => count($documentQty), + 'status' => $this->status ?? '-', + 'status_final_log' => $this->status_final_log ?? '-', + 'member_name' => $this->member->name, + 'payment_type' => $this->payment_type, + 'payment_type_name' => $this->payment_type_name, + 'duration_gl' => $durationGl, + 'duration_final_gl' => $this->final_log == 1 ? $durationFinalGl : '-', + 'files_by_type' => $filesGroupByType + ]; + + return $data; + } +} diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index e41efe7a..b9b1a09b 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -9,6 +9,7 @@ use Symfony\Component\HttpFoundation\Response; use PHPMailer\PHPMailer\PHPMailer; use Illuminate\Support\Facades\DB; use App\Models\Member; +use App\Models\User; use App\Models\Service; class Helper @@ -89,6 +90,22 @@ class Helper return $principalName->name; } + public static function userName($id) + { + $user = User::find($id); + if ($user) { + $person = $user->person; + if ($person) { + return $person->name; + } else { + return 'Person not found for this user.'; + } + } else { + return 'User not found.'; + } + + } + public static function serviceName($code) { $serviceName = Service::where('code', $code)->get()->first(); @@ -138,6 +155,14 @@ class Helper return $datesAvailabilities; } + public static function differenceTime($startDate, $endDate){ + $startTime = Carbon::parse($startDate); + $endTime = Carbon::parse($endDate); + + $interval = $endTime->diff($startTime); + return $interval->format('%d days, %h hours and %i minutes'); + } + public static function dailyAvailabilities($availabilities) { $hours = [ diff --git a/app/Models/RequestLog.php b/app/Models/RequestLog.php index ce7fb29b..8f7f9a20 100644 --- a/app/Models/RequestLog.php +++ b/app/Models/RequestLog.php @@ -47,6 +47,8 @@ class RequestLog extends Model 'approved_at', 'approved_final_log_by', 'approved_final_log_at', + 'created_final_at', + 'created_final_by', ]; protected $hidden = [ diff --git a/database/migrations/2024_01_30_102516_add_coloum_to_request_logs.php b/database/migrations/2024_01_30_102516_add_coloum_to_request_logs.php new file mode 100644 index 00000000..65f73135 --- /dev/null +++ b/database/migrations/2024_01_30_102516_add_coloum_to_request_logs.php @@ -0,0 +1,34 @@ +dateTime('created_final_at'); + $table->bigInteger('created_final_by'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('request_logs', function (Blueprint $table) { + $table->dropColumn('created_final_at'); + $table->dropColumn('created_final_by'); + }); + } +}; diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index 687e7a3a..be279ee1 100644 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -91,6 +91,7 @@ const navConfig = [ { title: 'REPORT', children: [ + { title: 'Letter of Guarantee', path: '/report/logs' }, { title: 'Appointment', path: '/report/appointments' }, { title: 'Live Chat', path: '/report/live-chat' }, { title: 'Linksehat Payment', path: '/report/linksehat-payments' }, diff --git a/frontend/dashboard/src/pages/CustomerService/Request/List.tsx b/frontend/dashboard/src/pages/CustomerService/Request/List.tsx index 44cef222..ecf25789 100644 --- a/frontend/dashboard/src/pages/CustomerService/Request/List.tsx +++ b/frontend/dashboard/src/pages/CustomerService/Request/List.tsx @@ -9,6 +9,7 @@ import { Table, TableBody, TableCell, + TableSortLabel, TableRow, TextField, Typography, @@ -26,6 +27,7 @@ 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 { visuallyHidden } from '@mui/utils'; import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; import ApprovalIcon from '../../../../build/icons/ic_approval.svg'; @@ -57,6 +59,7 @@ import { RequestLogType } from '../Request/Model/Types'; import SvgIconStyle from '../../../components/SvgIconStyle'; import { Delete } from '@mui/icons-material'; import DialogDeleteRequestLOG from '../Request/Components/DialogDeleteRequestLOG'; +import { HeadCell, Order } from '@/@types/table'; // import LoadingButton from '@/theme/overrides/LoadingButton'; export default function List() { @@ -298,8 +301,15 @@ export default function List() { 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', { params: filter }); + const parameters = + Object.keys(appliedParams).length !== 0 + ? appliedParams + : Object.fromEntries([...searchParams.entries(), ['order', order], ['orderBy', orderBy]]); + + const response = await axios.get('/customer-service/request', { + params: { ...parameters }, + }); + setDataTableLoading(false); setDataTableData(response.data); }; @@ -319,10 +329,6 @@ export default function List() { const [idRequestLog, setidRequestLog] = useState(); const [openDialogDeleteRequestLog, setDialogDeleteRequestLog] = useState(false) - useEffect(() => { - loadDataTableData(); - }, []); - const headStyle = { fontWeight: 'bold', }; @@ -484,6 +490,104 @@ export default function List() { /* ------------------ END TABLE ROW ------------------ */ } + /* -------------------------------- headCell -------------------------------- */ + const headCells: HeadCell[] = [ + { + id: 'code', + align: 'left', + label: 'Code', + isSort: true, + }, + { + id: 'provider', + align: 'left', + label: 'Provider', + isSort: false, + }, + + { + id: 'name', + align: 'left', + label: 'Name', + isSort: false, + }, + { + id: 'submission_date', + align: 'left', + label: 'Submision Date', + isSort: true, + }, + { + id: 'service_code', + align: 'left', + label: 'Service Type', + isSort: true, + }, + { + id: 'claim_method', + align: 'left', + label: 'Claim Method', + isSort: false, + }, + { + id: 'status', + align: 'left', + label: 'Status', + isSort: true, + }, + { + id: '', + align: 'left', + label: 'Action', + isSort: false, + }, + ]; + /* -------------------------------------------------------------------------- */ + + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + + /* ------------------------------ handle params ----------------------------- */ + const [appliedParams, setAppliedParams] = useState({}); + + const params = { + searchParams: searchParams, + setSearchParams: setSearchParams, + appliedParams: appliedParams, + setAppliedParams: setAppliedParams, + }; + + /* ------------------------------ handle order ------------------------------ */ + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('submission_date'); + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + + /* ------------------------------- handle sort ------------------------------ */ + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['order', isAsc ? 'desc' : 'asc'], + ['orderBy', property], + ]); + params?.setAppliedParams(parameters); + }; + + useEffect(() => { + loadDataTableData(); + }, [appliedParams, searchParams, order, orderBy, setSearchParams]); + + function TableContent() { return ( @@ -494,7 +598,7 @@ export default function List() { {/* ID Request LOG */} - + {/* Code @@ -504,7 +608,17 @@ export default function List() { Name - Date of Submission + {}} + > + Submision Date + + + sorted ascending + + Service Type @@ -515,7 +629,37 @@ export default function List() { Status - + */} + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + {/* ------------------ END TABLE HEADER ------------------ */} diff --git a/frontend/dashboard/src/pages/Report/Log/Index.tsx b/frontend/dashboard/src/pages/Report/Log/Index.tsx new file mode 100644 index 00000000..9f5b1cbe --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Log/Index.tsx @@ -0,0 +1,35 @@ +import { Card, Grid, Container } from '@mui/material'; +import { useParams } from 'react-router-dom'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import Page from '../../../components/Page'; +import useSettings from '../../../hooks/useSettings'; +import List from './List'; + +export default function Doctors() { + const { themeStretch } = useSettings(); + + const { id } = useParams(); + + const pageTitle = 'Letter of Guarantee'; + return ( + + + + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Log/List.tsx b/frontend/dashboard/src/pages/Report/Log/List.tsx new file mode 100644 index 00000000..6ea09c9d --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Log/List.tsx @@ -0,0 +1,605 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableSortLabel, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Link, + Chip, + TableHead, + Grid, + SvgIcon, +} from '@mui/material'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; +import { visuallyHidden } from '@mui/utils'; + +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + + +// 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 { LoadingButton } from '@mui/lab'; +import { enqueueSnackbar } from 'notistack'; +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 DialogDeleteRequestLOG from '../Request/Components/DialogDeleteRequestLOG'; +import { HeadCell, Order } from '@/@types/table'; +// import LoadingButton from '@/theme/overrides/LoadingButton'; + +export default function List() { + const { themeColorPresets } = useSettings(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + + const navigate = useNavigate() + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(''); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({ search: searchText }); // Trigger to Parent + }; + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + + const handleClick = (event: React.MouseEvent) => { + 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(`customer-service/request/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(`customer-service/request/data`) + .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 ( +
+ + {!currentImportFileName && ( + + + + + Import + {handleGetTemplate('template-request-log')}}>Download Template + {handleGetData('data-request-log')}}>Download Request LOG + + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )} + + {importResult && ( + + + Last Import Result :{' '} + + {importResult.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.total_failed_row} + {' '} + Failed, Report :{' '} + + {importResult.result_file?.name ?? '-'} + + + + )} +
+ ); + } + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const parameters = + Object.keys(appliedParams).length !== 0 + ? appliedParams + : Object.fromEntries([...searchParams.entries(), ['order', order], ['orderBy', orderBy]]); + + const response = await axios.get('/report/logs', { + params: { ...parameters }, + }); + + 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); + }; + + // Called on every row to map the data to the columns + function createData(data: any): any { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType }) { + const { row } = props; + const [open, setOpen] = React.useState(false); + const [loadingApprove, setLoadingApprove] = React.useState(false); + + return ( + + *': { borderBottom: 'unset' } }}> + {row.code} + {row.member_name} + + + {row.approved_by} + + + {row.approved_final_log_by} + {row.service_name} + {row.provider} + {row.document_qty} + {row.duration_gl} + {row.duration_final_gl} + + { row.status == "requested" ? + () : + row.status == "declined" ? + () + : + row.status == "canceled" ? + () + : + () + } + + + { row.status_final_log == "requested" ? + () : + row.status_final_log == "declined" ? + () + : + row.status_final_log == "canceled" ? + () + : + row.status_final_log == "unknown" ? + () + : + () + } + + {/* + + navigate ('/custormer-service/request/detail/'+row.id+'')}> + + Detail + + + } /> + */} + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + /* -------------------------------- headCell -------------------------------- */ + const headCells: HeadCell[] = [ + { + id: 'code', + align: 'left', + label: 'Code', + isSort: true, + }, + { + id: 'name', + align: 'left', + label: 'Member', + isSort: false, + }, + { + id: 'created_at', + align: 'left', + label: 'GL Create Time', + isSort: true, + }, + { + id: 'submission_date', + align: 'left', + label: 'GL Submit Time', + isSort: true, + }, + { + id: 'approved_by', + align: 'left', + label: 'GL Created by', + isSort: false, + }, + { + id: 'name', + align: 'left', + label: 'FGL Create Time', + isSort: false, + }, + { + id: 'submission_date', + align: 'left', + label: 'FGL Submit Time', + isSort: true, + }, + { + id: 'service_code', + align: 'left', + label: 'FGL Created by', + isSort: true, + }, + { + id: 'claim_method', + align: 'left', + label: 'Service', + isSort: false, + }, + { + id: 'status', + align: 'left', + label: 'Provider', + isSort: true, + }, + { + id: '', + align: 'left', + label: 'Document Qty ', + isSort: false, + }, + { + id: '', + align: 'left', + label: 'Duration GL ', + isSort: false, + }, + { + id: '', + align: 'left', + label: 'DurationĀ FGL ', + isSort: false, + }, + { + id: '', + align: 'left', + label: 'Status GL ', + isSort: false, + }, + { + id: '', + align: 'left', + label: 'Status Final GL ', + isSort: false, + }, + ]; + /* -------------------------------------------------------------------------- */ + + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + + /* ------------------------------ handle params ----------------------------- */ + const [appliedParams, setAppliedParams] = useState({}); + + const params = { + searchParams: searchParams, + setSearchParams: setSearchParams, + appliedParams: appliedParams, + setAppliedParams: setAppliedParams, + }; + + /* ------------------------------ handle order ------------------------------ */ + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('submission_date'); + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + + /* ------------------------------- handle sort ------------------------------ */ + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['order', isAsc ? 'desc' : 'asc'], + ['orderBy', property], + ]); + params?.setAppliedParams(parameters); + }; + + useEffect(() => { + loadDataTableData(); + }, [appliedParams, searchParams, order, orderBy, setSearchParams]); + + + function TableContent() { + return ( +
+ {/* ------------------ TABLE HEADER ------------------ */} + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + // --------------------------------------------------------- + return ( + + + + + + + } + /> + + + ); +} diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index 3d0a249c..92c0f5ad 100644 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -385,6 +385,19 @@ export default function Router() { element: }, + { + path: 'report/logs', + element: , + }, + { + path: 'report/logs/:id', + element: , + }, + { + path: 'report/logs/:id/show', + element: , + }, + { path: 'report/appointments', element: , @@ -637,6 +650,10 @@ const MasterDoctorsCreate = Loadable(lazy(() => import('../pages/Master/Doctors/ const MasterHospitals = Loadable(lazy(() => import('../pages/Master/Hospitals/Index'))); const MasterHospitalsCreate = Loadable(lazy(() => import('../pages/Master/Hospitals/Create'))); +const Log = Loadable(lazy(() => import('../pages/Report/Log/Index'))); +const LogCreate = Loadable(lazy(() => import('../pages/Report/Log/Create'))); +const LogShow = Loadable(lazy(() => import('../pages/Report/Log/Show'))); + const Appointment = Loadable(lazy(() => import('../pages/Report/Appointments/Index'))); const AppointmentCreate = Loadable(lazy(() => import('../pages/Report/Appointments/Create'))); const AppointmentShow = Loadable(lazy(() => import('../pages/Report/Appointments/Show'))); From a80bbf4a430006f91878a52d2687aec10b65a099 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 14:44:49 +0700 Subject: [PATCH 02/13] update --- Modules/Internal/Transformers/ReportLogResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Internal/Transformers/ReportLogResource.php b/Modules/Internal/Transformers/ReportLogResource.php index 5db086dd..8e4fbad1 100644 --- a/Modules/Internal/Transformers/ReportLogResource.php +++ b/Modules/Internal/Transformers/ReportLogResource.php @@ -29,7 +29,7 @@ class ReportLogResource extends JsonResource $formattedDateTime = $parsedDateTime->format('Y-m-d H:i:s'); $durationGl = Helper::differenceTime($formattedDateTime, $this->submission_date); - $durationFinalGl = Helper::differenceTime($this->created_final_at, $this->approved_by); + $durationFinalGl = Helper::differenceTime($this->created_final_at, $this->approved_final_log_by); $data = [ 'id' => $this->id, From db9f07932ebbe967ea9abf6a15ab7e4cb2087376 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 14:48:38 +0700 Subject: [PATCH 03/13] update --- .../dashboard/src/pages/Report/Log/Show.tsx | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 frontend/dashboard/src/pages/Report/Log/Show.tsx diff --git a/frontend/dashboard/src/pages/Report/Log/Show.tsx b/frontend/dashboard/src/pages/Report/Log/Show.tsx new file mode 100644 index 00000000..be9d1c46 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Log/Show.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import View from './View'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Appointment } from '../../../@types/doctor'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentAppointment, setCurrentAppointment] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/appointments/' + id).then((res) => { + setCurrentAppointment(res.data); + }); + } + }, [id]); + + return ( + + + + + + + + + + ); +} From 88aa3459cb09f8b61258b537e9b8ca6097cfc9c3 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 14:50:08 +0700 Subject: [PATCH 04/13] update file report --- .../dashboard/src/pages/Report/Log/Create.tsx | 93 ++++++ .../dashboard/src/pages/Report/Log/Form.tsx | 260 +++++++++++++++++ .../dashboard/src/pages/Report/Log/View.tsx | 275 ++++++++++++++++++ 3 files changed, 628 insertions(+) create mode 100644 frontend/dashboard/src/pages/Report/Log/Create.tsx create mode 100644 frontend/dashboard/src/pages/Report/Log/Form.tsx create mode 100644 frontend/dashboard/src/pages/Report/Log/View.tsx diff --git a/frontend/dashboard/src/pages/Report/Log/Create.tsx b/frontend/dashboard/src/pages/Report/Log/Create.tsx new file mode 100644 index 00000000..efb7a395 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Log/Create.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState } from 'react'; +import { paramCase } from 'change-case'; +import { useParams, useLocation } from 'react-router-dom'; +// @mui +import { Container, Stack } from '@mui/material'; +import useSettings from '../../../hooks/useSettings'; +import Page from '../../../components/Page'; +import Form from './Form'; +import HeaderBreadcrumbs from '../../../components/HeaderBreadcrumbs'; +import axios from '../../../utils/axios'; +import { Practitioner } from '../../../@types/doctor'; +import ButtonBack from '../../../components/ButtonBack'; + +export default function Create() { + const { themeStretch } = useSettings(); + const { id } = useParams(); + + const isEdit = id ? true : false; + + const [currentPractitioner, setCurrentPractitioner] = useState(); + + useEffect(() => { + if (isEdit) { + axios.get('/doctors/' + id).then((res) => { + setCurrentPractitioner(res.data); + }); + } + }, [id]); + + return ( + + + + {/* */} + + + +
+ + + ); +} +// const pageTitle = 'Create Data Dokter'; +// return ( +// +// +// + +// +// +// +// +// +// +// +// +// +// ); +// } diff --git a/frontend/dashboard/src/pages/Report/Log/Form.tsx b/frontend/dashboard/src/pages/Report/Log/Form.tsx new file mode 100644 index 00000000..39885db8 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Log/Form.tsx @@ -0,0 +1,260 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Practitioner } from '../../../@types/doctor'; + +import { Label, Rowing } from '@mui/icons-material'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentPractitioner?: Practitioner; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function PractitionerForm({ isEdit, currentPractitioner }: Props) { + const navigate = useNavigate(); + const [practitioner_group, setPractitionerGroups] = useState([]); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentPractitioner?.id, + name: currentPractitioner?.name || '', + address: currentPractitioner?.address || '', + birth_date: currentPractitioner?.birth_date || '', + gender: currentPractitioner?.gender || '', + description: currentPractitioner?.description || '', + birth_place: currentPractitioner?.birth_place || '', + active: currentPractitioner?.active === 1 ? true : false, + avatar_url: currentPractitioner?.avatar_url || '', + doctor_id: currentPractitioner?.doctor_id || '', + organizations: currentPractitioner?.organizations || [], + specialities: currentPractitioner?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentPractitioner] + ); + + console.log('defaultValues', defaultValues); + + function StatusLabel({ value }: { value: boolean }) { + return ( + + ); + } + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentPractitioner) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentPractitioner]); + + const handleActivate = (event: React.ChangeEvent) => { + setValue('active', event.target.checked); + + console.log('event.target.checked', event.target.checked); + + const formData = new FormData(); + formData.append('active', event.target.checked ? '1' : '0'); + formData.append('_method', 'PUT'); + axios.post('/doctors/' + currentPractitioner?.id ?? '', formData); + + enqueueSnackbar('active Updated Successfully!', { variant: 'success' }); + }; + + return ( + + + + {/* */} + + + + Data Dokter + + + {/* Status Rumah Sakit */} + + + + + Informasi Umum + + + + + Nama Dokter + {currentPractitioner?.name ? currentPractitioner?.name : '-'} + No Telp + {currentPractitioner?.phone ? currentPractitioner?.phone : '-'} + Tempat Lahir + + {currentPractitioner?.birth_place ? currentPractitioner?.birth_place : '-'} + + Alamat + {currentPractitioner?.address ? currentPractitioner?.address : '-'} + + + Jenis Kelamin + {currentPractitioner?.gender ? currentPractitioner?.gender : '-'} + Email + {currentPractitioner?.email ? currentPractitioner?.email : '-'} + Tanggal Lahir + + {currentPractitioner?.birth_date ? currentPractitioner?.birth_date : '-'} + + + + + + Tempat Praktik + {currentPractitioner?.organizations?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + Spesialisasi + {currentPractitioner?.specialities?.map((item, index) => ( + + + + {item.name} + + + + ))} + + + + + ); +} diff --git a/frontend/dashboard/src/pages/Report/Log/View.tsx b/frontend/dashboard/src/pages/Report/Log/View.tsx new file mode 100644 index 00000000..8105b8b1 --- /dev/null +++ b/frontend/dashboard/src/pages/Report/Log/View.tsx @@ -0,0 +1,275 @@ +import * as Yup from 'yup'; +import { useSnackbar } from 'notistack'; +import { useNavigate } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import MenuItem from '@mui/material/MenuItem'; + +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import * as React from 'react'; + +// form +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +// @mui +import { styled } from '@mui/material/styles'; +import { LoadingButton } from '@mui/lab'; +import { + Box, + Avatar, + Button, + ButtonGroup, + Card, + FormHelperText, + Grid, + Stack, + Typography, + TextField, + Chip, + Badge, + Divider, +} from '@mui/material'; + +import CancelIcon from '@mui/icons-material/Cancel'; + +// components +import { + FormProvider, + RHFTextField, + RHFRadioGroup, + RHFUploadAvatar, + RHFSwitch, + RHFEditor, + RHFDatepicker, + RHFMultiCheckbox, + RHFCheckbox, + RHFCustomMultiCheckbox, +} from '../../../components/hook-form'; +import axios from '../../../utils/axios'; +import { fCurrency } from '../../../utils/formatNumber'; +import { Appointment } from '../../../@types/doctor'; + +import { Label, Rowing, Spa } from '@mui/icons-material'; +import { border } from '@mui/system'; + +const LabelStyle = styled(Typography)(({ theme }) => ({ + ...theme.typography.subtitle2, + color: theme.palette.text.secondary, + marginBottom: theme.spacing(1), +})); + +const HeaderStyle = styled('header')(({ theme }) => ({ + paddingBottom: theme.spacing(5), + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + +const Title = styled(Typography)(({ theme }) => ({ + ...theme.typography.h4, + boxShadow: 'none', + // paddingBottom: theme.spacing(3), + fontWeight: 700, + color: '#005B7F', +})); + +interface FormValuesProps extends Partial { + taxes: boolean; + inStock: boolean; +} + +type Props = { + isEdit: boolean; + currentAppointment?: Appointment; +}; + +const Span = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(1), +})); + +const Text = styled(Typography)(({ theme }) => ({ + boxShadow: 'none', + paddingBottom: theme.spacing(3), +})); + +export default function AppointmentForm({ isEdit, currentAppointment }: Props) { + const navigate = useNavigate(); + + // const [ errors, setErrors ] = useState<{ [key: string]: string }>({}); + + const { enqueueSnackbar } = useSnackbar(); + + const NewCorporateSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + // file: Yup.boolean().required('Corporate Status is required'), + }); + + const defaultValues = useMemo( + () => ({ + id: currentAppointment?.id, + name: currentAppointment?.name || '', + address: currentAppointment?.address || '', + birth_date: currentAppointment?.birth_date || '', + gender: currentAppointment?.gender || '', + description: currentAppointment?.description || '', + birth_place: currentAppointment?.birth_place || '', + active: currentAppointment?.active === 1 ? true : false, + avatar_url: currentAppointment?.avatar_url || '', + doctor_id: currentAppointment?.doctor_id || '', + organizations: currentAppointment?.organizations || [], + specialities: currentAppointment?.specialities || [], + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentAppointment] + ); + + const methods = useForm({ + resolver: yupResolver(NewCorporateSchema), + defaultValues, + }); + + const { + reset, + watch, + control, + setValue, + getValues, + setError, + handleSubmit, + formState: { isSubmitting }, + } = methods; + + const values = watch(); + + useEffect(() => { + if (isEdit && currentAppointment) { + reset(defaultValues); + } + if (!isEdit) { + reset(defaultValues); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEdit, currentAppointment]); + + return ( + + + + {/* */} + + + + } + spacing={2} + > + Data Appointment + + + + + + + + + + + Tanggal Booking : + + {currentAppointment?.date_created ? currentAppointment?.date_created : '-'} + + + + + + Tanggal Appointment : + + {currentAppointment?.date_appointment + ? currentAppointment?.date_appointment + : '-'} + + + + + + + Nama Dokter + + {currentAppointment?.doctor_name ? currentAppointment?.doctor_name : '-'} + + Faskes + + {currentAppointment?.health_care ? currentAppointment?.health_care : '-'} + + + + Spesialis + {currentAppointment?.speciality ? currentAppointment?.speciality : '-'} + Appointment Via Web/App + + {currentAppointment?.appointment_media + ? currentAppointment?.appointment_media + : '-'} + + + + + + + + Data Pembayaran + + + + {currentAppointment?.payment_detail !== null ? ( + + + Metode Pembayaran + + {currentAppointment?.payment_method ? currentAppointment?.payment_method : '-'} + + Harga + + {currentAppointment?.payment_detail?.gross_amount + ? currentAppointment?.payment_detail?.gross_amount + : '-'} + + Mata Uang + + {currentAppointment?.payment_detail?.currency + ? currentAppointment?.payment_detail?.currency + : '-'} + + + + Tipe Pembayaran + + {currentAppointment?.payment_detail?.payment_type + ? currentAppointment?.payment_detail?.payment_type + : '-'} + + Waktu Transaksi + + {currentAppointment?.payment_detail?.transaction_time + ? currentAppointment?.payment_detail?.transaction_time + : '-'} + + Status + + {currentAppointment?.payment_detail?.status_message + ? currentAppointment?.payment_detail?.status_message + : '-'} + + + + ) : ( + Belum ada pembayaran + )} + + + + + ); +} From 1cd65a9077edfb189d8b5acf9e453f64054c5f5c Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 14:52:15 +0700 Subject: [PATCH 05/13] update --- frontend/dashboard/src/@types/table.ts | 131 ++++++ .../src/components/BaseTablePagination.tsx | 25 ++ frontend/dashboard/src/components/Table.tsx | 395 ++++++++++++++++++ 3 files changed, 551 insertions(+) create mode 100644 frontend/dashboard/src/@types/table.ts create mode 100644 frontend/dashboard/src/components/BaseTablePagination.tsx create mode 100644 frontend/dashboard/src/components/Table.tsx diff --git a/frontend/dashboard/src/@types/table.ts b/frontend/dashboard/src/@types/table.ts new file mode 100644 index 00000000..f01ed4ea --- /dev/null +++ b/frontend/dashboard/src/@types/table.ts @@ -0,0 +1,131 @@ +import { SelectChangeEvent } from '@mui/material'; +import { Dispatch, FormEvent, SetStateAction } from 'react'; + +/* ------------------------------- pagination ------------------------------- */ +export type PaginationTableProps = { + current_page: number; + from: number; + last_page: number; + links: []; + path: string; + per_page: number; + to: number; + total: number; +}; +/* -------------------------------------------------------------------------- */ + +/* ---------------------------------- order --------------------------------- */ +export type Order = 'asc' | 'desc'; +/* -------------------------------------------------------------------------- */ + +/* --------------------------------- filter --------------------------------- */ +export type DivisionDataProps = { + id: number; + name: string; +}; + +export type StatusDataProps = { + id: number; + name: string; +}; +/* -------------------------------------------------------------------------- */ + +/* -------------------------------- headcell -------------------------------- */ +export type HeadCell = { + id: Extract; + align: string; + label: string; + isSort: boolean; + width?: number; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------- division filter ---------------------------- */ +export type DivisionData = { + id: number; + name: string; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------- status filter ---------------------------- */ +export type Status = { + id: number; + name: string; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------------- row ---------------------------------- */ +export type TableListProps = { + headCells?: HeadCell[]; + rows?: Array; + paginations?: { + page: number; + setPage: Dispatch>; + rowsPerPage: number; + setRowsPerPage: Dispatch>; + paginationTable: PaginationTableProps; + setPaginationTable: Dispatch>; + }; + orders?: { + order: Order; + setOrder: Dispatch>; + orderBy: string; + setOrderBy: Dispatch>; + }; + loadings: { + isLoading: boolean; + setIsLoading: Dispatch>; + }; + params?: { + searchParams: URLSearchParams; + setSearchParams: any; + appliedParams: {}; + setAppliedParams: Dispatch>; + }; + searchs?: { + useSearchs: boolean; + fullWidth?: boolean; + searchText: string; + setSearchText: Dispatch>; + handleSearchSubmit: (event: FormEvent) => void; + }; + filters?: { + useFilter: boolean; + config: { + label: string; + divisionValue: string; + divisionData: DivisionData[]; + handleDivisionChange: (event: SelectChangeEvent) => void; + }; + }; + filterStatus?: { + useFilter: boolean; + config: { + label: string; + statusValue: string; + statusData: Status[]; + handleStatusChange: (event: SelectChangeEvent) => void; + }; + }; + filterStartDate?: { + useFilter: boolean; + startDate: string; + setStartDate: Dispatch>; + handleStartDateChange: (event: FormEvent) => void; + }; + filterEndDate?: { + useFilter: boolean; + endDate: string; + setEndDate: Dispatch>; + handleEndDateChange: (event: FormEvent) => void; + }; + exportReport?: { + useExport: boolean; + startDate: string; + endDate: string; + status: string; + handleExportReport: (event: FormEvent) => void; + }; + exportLoading?: boolean; +}; +/* -------------------------------------------------------------------------- */ diff --git a/frontend/dashboard/src/components/BaseTablePagination.tsx b/frontend/dashboard/src/components/BaseTablePagination.tsx new file mode 100644 index 00000000..3af750e6 --- /dev/null +++ b/frontend/dashboard/src/components/BaseTablePagination.tsx @@ -0,0 +1,25 @@ +/* ---------------------------------- @mui ---------------------------------- */ +import { TablePagination, TablePaginationProps } from '@mui/material'; +import { Box } from '@mui/system'; + +export default function BaseTablePagination({ + count, + onPageChange, + page, + rowsPerPage, + onRowsPerPageChange, +}: TablePaginationProps) { + return ( + + + + ); +} diff --git a/frontend/dashboard/src/components/Table.tsx b/frontend/dashboard/src/components/Table.tsx new file mode 100644 index 00000000..ebe4498d --- /dev/null +++ b/frontend/dashboard/src/components/Table.tsx @@ -0,0 +1,395 @@ +/* ---------------------------------- @mui ---------------------------------- */ +import { + Paper, + Table as TableContent, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Button, + TableSortLabel, + Box, + Grid, + FormControl, + InputLabel, + Select, + MenuItem, + InputAdornment, + Typography, +} from '@mui/material'; +import { visuallyHidden } from '@mui/utils'; +/* ---------------------------------- react --------------------------------- */ +import { Fragment } from 'react'; +/* -------------------------------- component ------------------------------- */ +import BaseTablePagination from './BaseTablePagination'; +/* ---------------------------------- utils --------------------------------- */ +import { Download, Search as SearchIcon } from '@mui/icons-material'; +/* ---------------------------------- types --------------------------------- */ +import { DivisionDataProps, StatusDataProps, TableListProps } from '../@types/table'; +import { LoadingButton } from '@mui/lab'; + +export default function Table({ + headCells, + rows, + paginations, + orders, + loadings, + params, + filters, + filterStatus, + filterStartDate, + filterEndDate, + searchs, + exportReport, + exportLoading, +}: TableListProps) { + /* ------------------------------- handle sort ------------------------------ */ + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['order', isAsc ? 'desc' : 'asc'], + ['orderBy', property], + ]); + params?.setAppliedParams(parameters); + }; + /* -------------------------------------------------------------------------- */ + + /* -------------------------- enchanced table head -------------------------- */ + const EnhancedTableHead = () => { + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + + return ( + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + ); + }; + /* -------------------------------------------------------------------------- */ + + /* ------------------------ button change pagination ------------------------ */ + const onPageChangeHandle = async ( + event: React.MouseEvent | null, + newPage: number + ) => { + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['page', newPage + 1], + ['per_page', paginations?.rowsPerPage], + ]); + paginations?.setPage(newPage); + await new Promise((resolve) => setTimeout(resolve, 500)); + params?.setAppliedParams(parameters); + }; + /* -------------------------------------------------------------------------- */ + + /* --------------------------- row page per limit --------------------------- */ + const onRowsPerPageChangeHandle = async (event: React.ChangeEvent) => { + params?.searchParams.delete('page'); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['per_page', parseInt(event.target.value, 10)], + ]); + + paginations?.setPage(0); + paginations?.setRowsPerPage(parseInt(event.target.value, 10)); + await new Promise((resolve) => setTimeout(resolve, 500)); + params?.setAppliedParams(parameters); + }; + /* -------------------------------------------------------------------------- */ + + return ( + // + + {/* Field 1 */} + + + {filters && filters.useFilter ? ( + + + + Division + + + + + + searchs?.setSearchText(event.target.value)} + value={searchs?.searchText} + fullWidth + /> + + + + ) : null} + + {searchs && searchs.useSearchs ? ( + + {filterStatus && filterStatus.useFilter ? ( + +
+ searchs.setSearchText(event.target.value)} + value={searchs.searchText} + fullWidth + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search Name or Member ID... " + /> + +
+ ) : exportReport && exportReport.useExport && filterStatus === undefined ? ( + +
+ searchs.setSearchText(event.target.value)} + value={searchs.searchText} + fullWidth + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search Name or Member ID... " + /> + +
+ ) : ( + +
+ searchs.setSearchText(event.target.value)} + value={searchs.searchText} + fullWidth + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search Name or Member ID... " + /> + +
+ )} +
+ ) : null} + + {/* Start date */} + {filterStartDate && filterStartDate.useFilter ? ( + +
+ filterStartDate.setStartDate(event.target.value)} + fullWidth + label="Start Date" + InputLabelProps={{ + shrink: true, + }} + /> + +
+ ) : null} + + {/* End Date */} + + {filterEndDate && filterEndDate.useFilter ? ( + +
+ filterEndDate.setEndDate(event.target.value)} + fullWidth + label="End Date" + InputLabelProps={{ + shrink: true, + }} + /> + +
+ ) : null} + + {/* Filter status */} + {filterStatus && filterStatus.useFilter ? ( + + + Status + + + + ) : null} + + {/* Export Report */} + {exportReport && exportReport.useExport ? ( + + + } + sx={{ p: 1.8 }} + onClick={() => exportReport.handleExportReport()} + loading={exportLoading} + > + + Export + + + + + ) : null} +
+
+ {/* End Field 1 */} + {/* Field 2 */} + + {/* Table */} + + + {/* Table Header */} + + {/* End Table Header */} + {/* Table Body */} + + {loadings.isLoading && rows && rows.length >= 1 ? ( + + + Loading . . . + + + ) : rows && rows.length >= 1 ? ( + rows.map((row, rowIndex) => ( + + {headCells && + //@ts-ignore + headCells.map((head, headIndex) => ( + //@ts-ignore + + {row[head.id]} + + ))} + + )) + ) : loadings.isLoading === false && rows && rows.length === 0 ? ( + + + No Data Found + + + ) : ( + + + Loading . . . + + + )} + + {/* End Table Body */} + + + {/* End Table */} + + {/* Pagination */} + {paginations && ( + + )} + {/* End Pagination */} + + {/* End Field 2 */} +
+ //
+ ); +} From 529cb5c483f688d0a62a819ca2c182e63f97bd4d Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 16:54:54 +0700 Subject: [PATCH 06/13] update start date menghitung final log --- Modules/Internal/Transformers/ReportLogResource.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/Internal/Transformers/ReportLogResource.php b/Modules/Internal/Transformers/ReportLogResource.php index 8e4fbad1..4fcf7dcf 100644 --- a/Modules/Internal/Transformers/ReportLogResource.php +++ b/Modules/Internal/Transformers/ReportLogResource.php @@ -8,6 +8,7 @@ use Illuminate\Support\Str; use App\Models\Organization; use App\Models\File; use App\Models\User; +use App\Models\RequestLogBenefit; use Carbon\Carbon; class ReportLogResource extends JsonResource @@ -28,8 +29,16 @@ class ReportLogResource extends JsonResource $parsedDateTime = Carbon::parse($this->created_at); $formattedDateTime = $parsedDateTime->format('Y-m-d H:i:s'); + $timeInsertBenefit = RequestLogBenefit::where('request_log_id', $this->id)->first(); + + if ($timeInsertBenefit){ + $durationFinalGl = Helper::differenceTime($this->created_final_at,$timeInsertBenefit->created_at); + } else { + $durationFinalGl = 0; + } + $durationGl = Helper::differenceTime($formattedDateTime, $this->submission_date); - $durationFinalGl = Helper::differenceTime($this->created_final_at, $this->approved_final_log_by); + $data = [ 'id' => $this->id, From c734a8ce6c2d5af21ff365ddd487b703f06ce58f Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 08:50:50 +0700 Subject: [PATCH 07/13] bugs fix create at final log --- .../Http/Controllers/Api/RequestLogController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php b/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php index 191262fa..1869899d 100644 --- a/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php +++ b/Modules/HospitalPortal/Http/Controllers/Api/RequestLogController.php @@ -341,8 +341,8 @@ class RequestLogController extends Controller 'status_final_log' => 'requested', 'final_log' => 1, 'discharge_date' => $request->discharge_date, - 'created_final_by'=> date('Y-m-d H:i:s'), - 'created_final_at'=> auth()->user()->id, + 'created_final_by'=> auth()->user()->id, + 'created_final_at'=> date('Y-m-d H:i:s'), ]); if ($request->hasFile('result_files')) { foreach ($request->result_files as $file) { From f23d6ab476774267ed1fa23dedfe195a36526a7c Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 09:20:00 +0700 Subject: [PATCH 08/13] update fgl create --- frontend/dashboard/src/pages/Report/Log/List.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/dashboard/src/pages/Report/Log/List.tsx b/frontend/dashboard/src/pages/Report/Log/List.tsx index 6ea09c9d..9043750a 100644 --- a/frontend/dashboard/src/pages/Report/Log/List.tsx +++ b/frontend/dashboard/src/pages/Report/Log/List.tsx @@ -324,8 +324,8 @@ export default function List() { {row.approved_by} - - + + {row.approved_final_log_by} {row.service_name} {row.provider} From aaac2f0a877fc6e4aa7e6732341861f990527049 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 09:26:59 +0700 Subject: [PATCH 09/13] update submit fgl --- frontend/dashboard/src/pages/Report/Log/List.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/dashboard/src/pages/Report/Log/List.tsx b/frontend/dashboard/src/pages/Report/Log/List.tsx index 9043750a..037742c2 100644 --- a/frontend/dashboard/src/pages/Report/Log/List.tsx +++ b/frontend/dashboard/src/pages/Report/Log/List.tsx @@ -325,7 +325,7 @@ export default function List() { {row.approved_by} - + {row.approved_final_log_by} {row.service_name} {row.provider} From a794379973b84821df0eaf14291102a4b8082e31 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 09:47:01 +0700 Subject: [PATCH 10/13] update list --- Modules/Internal/Transformers/ReportLogResource.php | 9 ++++++--- frontend/dashboard/src/pages/Report/Log/List.tsx | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Modules/Internal/Transformers/ReportLogResource.php b/Modules/Internal/Transformers/ReportLogResource.php index 4fcf7dcf..ce7e7dd2 100644 --- a/Modules/Internal/Transformers/ReportLogResource.php +++ b/Modules/Internal/Transformers/ReportLogResource.php @@ -32,9 +32,11 @@ class ReportLogResource extends JsonResource $timeInsertBenefit = RequestLogBenefit::where('request_log_id', $this->id)->first(); if ($timeInsertBenefit){ - $durationFinalGl = Helper::differenceTime($this->created_final_at,$timeInsertBenefit->created_at); + $created_final_at = $timeInsertBenefit->created_at; + $durationFinalGl = Helper::differenceTime($timeInsertBenefit->created_at, $this->approved_final_log_at); } else { $durationFinalGl = 0; + $created_final_at = NULL; } $durationGl = Helper::differenceTime($formattedDateTime, $this->submission_date); @@ -44,7 +46,7 @@ class ReportLogResource extends JsonResource 'id' => $this->id, 'code' => $this->code, 'created_at' => $formattedDateTime, - 'created_final_at' => $this->created_final_at, + 'created_final_at' => $created_final_at, 'submission_date' => $this->submission_date, 'approved_by' => Helper::userName($this->approved_by), 'approved_final_log_at' => $this->approved_final_log_at, @@ -59,7 +61,8 @@ class ReportLogResource extends JsonResource 'payment_type_name' => $this->payment_type_name, 'duration_gl' => $durationGl, 'duration_final_gl' => $this->final_log == 1 ? $durationFinalGl : '-', - 'files_by_type' => $filesGroupByType + 'files_by_type' => $filesGroupByType, + 'time_insert_benefit' => $filesGroupByType, ]; return $data; diff --git a/frontend/dashboard/src/pages/Report/Log/List.tsx b/frontend/dashboard/src/pages/Report/Log/List.tsx index 037742c2..d4de6dd5 100644 --- a/frontend/dashboard/src/pages/Report/Log/List.tsx +++ b/frontend/dashboard/src/pages/Report/Log/List.tsx @@ -416,10 +416,10 @@ export default function List() { isSort: false, }, { - id: 'submission_date', + id: 'fgl_submission_date', align: 'left', label: 'FGL Submit Time', - isSort: true, + isSort: false, }, { id: 'service_code', From 26519cb895a80046b363bb0ef1043d89bfaaa4e1 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 10:07:33 +0700 Subject: [PATCH 11/13] update --- Modules/Internal/Transformers/ReportLogResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Internal/Transformers/ReportLogResource.php b/Modules/Internal/Transformers/ReportLogResource.php index ce7e7dd2..454da946 100644 --- a/Modules/Internal/Transformers/ReportLogResource.php +++ b/Modules/Internal/Transformers/ReportLogResource.php @@ -36,7 +36,7 @@ class ReportLogResource extends JsonResource $durationFinalGl = Helper::differenceTime($timeInsertBenefit->created_at, $this->approved_final_log_at); } else { $durationFinalGl = 0; - $created_final_at = NULL; + $created_final_at = false; } $durationGl = Helper::differenceTime($formattedDateTime, $this->submission_date); From 914e907e0c007e6ae139f72eb7f943e6473976cf Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 10:24:37 +0700 Subject: [PATCH 12/13] update list --- frontend/dashboard/src/pages/Report/Log/List.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/dashboard/src/pages/Report/Log/List.tsx b/frontend/dashboard/src/pages/Report/Log/List.tsx index d4de6dd5..1aeb3e27 100644 --- a/frontend/dashboard/src/pages/Report/Log/List.tsx +++ b/frontend/dashboard/src/pages/Report/Log/List.tsx @@ -324,7 +324,7 @@ export default function List() { {row.approved_by} - + {row.approved_final_log_by} {row.service_name} From 3ccb9c0a4cbb8b13cc87bdd2a9307eca1ae028d5 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Wed, 31 Jan 2024 10:32:08 +0700 Subject: [PATCH 13/13] update --- Modules/Internal/Transformers/ReportLogResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Internal/Transformers/ReportLogResource.php b/Modules/Internal/Transformers/ReportLogResource.php index 454da946..056e9c4c 100644 --- a/Modules/Internal/Transformers/ReportLogResource.php +++ b/Modules/Internal/Transformers/ReportLogResource.php @@ -36,7 +36,7 @@ class ReportLogResource extends JsonResource $durationFinalGl = Helper::differenceTime($timeInsertBenefit->created_at, $this->approved_final_log_at); } else { $durationFinalGl = 0; - $created_final_at = false; + $created_final_at = null; } $durationGl = Helper::differenceTime($formattedDateTime, $this->submission_date);