diff --git a/Modules/Internal/Http/Controllers/Api/RequestLogController.php b/Modules/Internal/Http/Controllers/Api/RequestLogController.php index ba9ab91f..d66b3255 100755 --- a/Modules/Internal/Http/Controllers/Api/RequestLogController.php +++ b/Modules/Internal/Http/Controllers/Api/RequestLogController.php @@ -346,6 +346,11 @@ class RequestLogController extends Controller $requestLog->approved_at = Carbon::now(); } + if ($request->status_approval){ + $requestLog->status_approval = $request->status_approval; + $requestLog->approval_nominal_by = auth()->user()->id; + } + $requestLog->save(); // update nirc member @@ -1224,6 +1229,34 @@ class RequestLogController extends Controller ]); } } + return Helper::responseJson(data: $request->toArray(), message: 'File Success Uploaded'); + } + + public function approvalFiles(Request $request, $id) + { + Helper::setCustomPHPIniSettings(); + $requestLog = RequestLog::findOrFail($id); + $nominal = $request->nominal; + if($nominal){ + $requestLog->nominal = $nominal; + $requestLog->save(); + } + if ($request->hasFile('approval_files')) { + foreach ($request->approval_files as $file) { + $fileData = File::storeFile('approval', $id, $file); + $requestLog->files()->updateOrCreate([ + 'type' => 'approval', + 'name' => $fileData['name'], + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'source' => env('FILESYSTEM_DISK'), + 'path' => $fileData['path'], + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + 'reason' => $request->reason, + ]); + } + } return Helper::responseJson(data: $request->toArray(), message: 'File Success Uploaded'); } diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index fc319d4a..daf58971 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -323,6 +323,7 @@ Route::prefix('internal')->group(function () { Route::post('customer-service/request/exportFiledInvoice', [RequestLogController::class, 'exportFiledInvoice']); Route::get('customer-service/request/data', [RequestLogController::class, 'generateDataRequestLogExcel']); Route::post('customer-service/request/{id}/add_file', [RequestLogController::class, 'requestFiles']); + Route::post('customer-service/request/{id}/approval_files', [RequestLogController::class, 'approvalFiles']); Route::post('customer-service/request/{id}/delete_file', [RequestLogController::class, 'deleteFiles']); Route::post('customer-service/request/final-log', [RequestLogController::class, 'updateFinalLog']); diff --git a/Modules/Internal/Transformers/RequestLogResource.php b/Modules/Internal/Transformers/RequestLogResource.php index e2e018e8..4f45edd9 100755 --- a/Modules/Internal/Transformers/RequestLogResource.php +++ b/Modules/Internal/Transformers/RequestLogResource.php @@ -31,6 +31,8 @@ class RequestLogResource extends JsonResource 'status' => $this->status ?? 'unknown', 'provider' => $provider ? $provider->name : '-', 'status_final_log' => $this->status_final_log ?? 'unknown', + 'nominal' => $this->nominal ?? 'unknown', + 'status_approval' => $this->status_approval ?? 'requested', 'service_name' => $this->service ? $this->service->name : '', 'payment_type' => $this->payment_type, 'payment_type_name' => $this->payment_type_name, diff --git a/Modules/Internal/Transformers/RequestLogShowResource.php b/Modules/Internal/Transformers/RequestLogShowResource.php index 9f2a1143..54a67090 100755 --- a/Modules/Internal/Transformers/RequestLogShowResource.php +++ b/Modules/Internal/Transformers/RequestLogShowResource.php @@ -33,7 +33,7 @@ class RequestLogShowResource extends JsonResource $corporateId = $requestLog['member']['current_plan']['corporate_id'] ?? 0; $member_id = $requestLog['member_id']; $planMember = MemberPlan::where('member_id', $member_id)->get('plan_id'); - + $planId = Plan::whereIn('id', $planMember)->where('service_code', $requestLog['service_code'])->first(); $benefit = CorporateBenefit::with(['benefit', 'plan'])->where('plan_id', $planId->id)->get()->toArray(); $benefitDetailLog = RequestLogBenefit::with('benefit')->where('request_log_id', $requestLog['id'])->get()->toArray(); @@ -174,9 +174,12 @@ class RequestLogShowResource extends JsonResource 'keterangan' => $requestLog['keterangan'], 'hak_kamar_pasien' => $requestLog['hak_kamar_pasien'], 'penempatan_kamar' => $requestLog['penempatan_kamar'], + 'nominal' => $requestLog['nominal'], + 'status_approval' => $requestLog['status_approval'], 'catatan' => $requestLog['catatan'], 'reason' => $requestLog['reason'], 'diagnosis' => $icd, + 'url_approval' => env('LMS_WEB_URL') . '/custormer-service/final-log/detail/'.$requestLog['id'] . '/' . auth()->user()->id, 'is_reversal' => $isReversal, // untuk penjagaan, jika true tidak bisa di edit/hapus lagi diff --git a/app/Console/Commands/UpdateNoSjp.php b/app/Console/Commands/UpdateNoSjp.php new file mode 100644 index 00000000..366ddb0c --- /dev/null +++ b/app/Console/Commands/UpdateNoSjp.php @@ -0,0 +1,62 @@ +info('Checking Data ...'); + $client = new Client([ 'base_uri' => 'https://api.linksehat.dev', 'headers' => ['Content-Type' => 'application/json'], 'timeout' => 10, ]); + + $liveChats = Livechat::query() + ->join('tx_livechat_summary', 'tx_livechat_summary.nIDLiveChat', '=', 'tx_livechat.nID') + ->whereNull('tx_livechat.sNoSpj') + ->whereDate('tx_livechat.dCreateOn', Carbon::today()) + ->select('tx_livechat.nID', 'tx_livechat_summary.nID as summary_id') + ->get(); + + foreach ($liveChats as $row) { + try { + $prescriptionData = [ + 'nIDLivechats' => [$row->nID] + ]; + + $response = $client->post('/api/rujukan-dan-sjp', [ + 'json' => $prescriptionData, + ]); + + $body = json_decode($response->getBody()->getContents(), true); + + LivechatUpdateLog::create([ + 'nIDLivechat' => $row->nID, + 'nIDSummary' => $row->summary_id, + 'status' => 'success', + 'response' => json_encode($body) + ]); + + $this->info("Nomor SJP berhasil dibuat untuk Livechat {$row->nID}"); + + } catch (\Exception $e) { + LivechatUpdateLog::create([ + 'nIDLivechat' => $row->nID, + 'nIDSummary' => $row->summary_id, + 'status' => 'fail', + 'response' => $e->getMessage() + ]); + + $this->error("Gagal generate Nomor SJP Livechat {$row->nID}: " . $e->getMessage()); + } + } + + $this->info('Proses selesai.'); + + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 5489a453..46ff531f 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -17,6 +17,7 @@ class Kernel extends ConsoleKernel { // $schedule->command('inspire')->hourly(); $schedule->command('log:auto-update-status')->dailyAt('01:00'); + $schedule->command('update:no-sjp')->everyThirtyMinutes(); } /** diff --git a/app/Models/File.php b/app/Models/File.php index a6adacf9..793dd4d3 100755 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -111,28 +111,62 @@ class File extends Model // return $path; // } + // public static function storeFile($type, $id, $file) + // { + // // Pastikan directory tidak punya trailing slash + // $directory = rtrim(self::getDirectory($type), '/'); + + // // Buat nama file yang unik dan aman + // $originalName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); + // $extension = $file->getClientOriginalExtension(); + // $safeName = Str::slug($originalName); + // $uniqueName = $safeName . '-' . uniqid() . '.' . $extension; + + // // Upload file ke disk 's3' dengan visibility 'public' + // $path = Storage::disk('s3')->putFileAs( + // $directory, + // $file, + // $uniqueName, + // 'public' + // ); + + // // Kembalikan path dan nama unik agar bisa digunakan di controller + // return [ + // 'path' => $directory . '/' . $uniqueName, // hasil konsisten + // 'name' => $uniqueName, + // ]; + // } + public static function storeFile($type, $id, $file) { - // Pastikan directory tidak punya trailing slash + // 1. Ambil nama disk dari konfigurasi default + // Nilainya akan 'public', 'local', atau 's3' tergantung .env Anda + $disk = config('filesystems.default'); + $directory = rtrim(self::getDirectory($type), '/'); - // Buat nama file yang unik dan aman + // Buat nama file yang unik dan aman (kode Anda sudah bagus) $originalName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $extension = $file->getClientOriginalExtension(); $safeName = Str::slug($originalName); $uniqueName = $safeName . '-' . uniqid() . '.' . $extension; - // Upload file ke disk 's3' dengan visibility 'public' - $path = Storage::disk('s3')->putFileAs( - $directory, - $file, - $uniqueName, - 'public' + // 2. Gunakan disk yang sudah dinamis + $path = Storage::disk($disk)->putFileAs( + $directory, + $file, + $uniqueName ); - // Kembalikan path dan nama unik agar bisa digunakan di controller + // 3. (Sangat Direkomendasikan) Tambahkan penanganan error + if ($path === false) { + Log::error("Gagal menyimpan file ke disk '{$disk}' pada path: {$directory}/{$uniqueName}"); + return false; // Kembalikan false jika upload gagal + } + + // 4. Kembalikan path asli dari hasil upload untuk konsistensi return [ - 'path' => $directory . '/' . $uniqueName, // hasil konsisten + 'path' => $path, // Gunakan $path yang dikembalikan oleh Storage 'name' => $uniqueName, ]; } diff --git a/app/Models/LivechatUpdateLog.php b/app/Models/LivechatUpdateLog.php new file mode 100644 index 00000000..fb34d6fd --- /dev/null +++ b/app/Models/LivechatUpdateLog.php @@ -0,0 +1,18 @@ +integer('nominal')->default(0)->after('total_cob'); + $table->string('status_approval')->nullable()->after('status_final_log'); + $table->integer('approval_nominal_by')->nullable()->after('status_approval'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('request_logs', function (Blueprint $table) { + $table->dropColumn('nominal'); + $table->dropColumn('status_approval'); + $table->dropColumn('approval_nominal_by'); + }); + } +}; diff --git a/database/migrations/2025_09_22_091254_create_livechat_update_logs_table.php b/database/migrations/2025_09_22_091254_create_livechat_update_logs_table.php new file mode 100644 index 00000000..9aaabd7e --- /dev/null +++ b/database/migrations/2025_09_22_091254_create_livechat_update_logs_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('nIDLivechat'); + $table->unsignedBigInteger('nIDSummary')->nullable(); + $table->string('status'); + $table->text('response')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('livechat_update_logs'); + } +}; diff --git a/database/seeders/LivechatUpdateLogSeeder.php b/database/seeders/LivechatUpdateLogSeeder.php new file mode 100644 index 00000000..73a886df --- /dev/null +++ b/database/seeders/LivechatUpdateLogSeeder.php @@ -0,0 +1,19 @@ + '/case_management/inpatient_monitoring', 'permission' => 'final-log-list' ], + [ + 'title' => 'Approval Inpatient', + 'path' => '/case_management/approval_inpatient_monitoring', + 'permission' => 'approval-log-list' + ], ], 'permission' => null ], diff --git a/database/seeders/PermissionTableSeeder.php b/database/seeders/PermissionTableSeeder.php index 0013f925..7256f115 100755 --- a/database/seeders/PermissionTableSeeder.php +++ b/database/seeders/PermissionTableSeeder.php @@ -76,6 +76,7 @@ class PermissionTableSeeder extends Seeder 'user-access-list', 'report-katalog-dokter', 'invoice-payment-list', + 'approval-log-list', ] ], ####################### CLIENT PORTAL ######################### diff --git a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx index dee7accc..5add5536 100755 --- a/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx +++ b/frontend/dashboard/src/layouts/dashboard/navbar/NavConfig.tsx @@ -85,6 +85,7 @@ const navConfig = [ { title: 'Daily Monitoring', path: '/case_management/daily_monitoring' }, // { title: 'Laboratorium Result', path: '/case_management/laboratorium_result' }, { title: 'Inpatient Monitoring', path: '/case_management/inpatient_monitoring' }, + { title: 'Approval Monitoring', path: '/case_management/inpatient_monitoring' }, ], }, { diff --git a/frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/Index.tsx b/frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/Index.tsx new file mode 100755 index 00000000..a0f1083d --- /dev/null +++ b/frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; +import Page from "../../../components/Page"; +import List from "./List"; + + + +export default function Claims() { + + const pageTitle = 'Approval Monitoring'; + return ( + + + + + {/* */} + + {/* */} + + ); +} diff --git a/frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/List.tsx b/frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/List.tsx new file mode 100755 index 00000000..216603a7 --- /dev/null +++ b/frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/List.tsx @@ -0,0 +1,699 @@ +// @mui +import { + Box, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + FormControl, + Select, + Link, + Chip, + TableHead, + InputLabel, + Grid, +} from '@mui/material'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import CancelIcon from '@mui/icons-material/Cancel'; + +import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +// hooks +import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { Navigate, useNavigate, useSearchParams } from 'react-router-dom'; +import useSettings from '@/hooks/useSettings'; +// components +import axios from '../../../utils/axios'; +import { LaravelPaginatedData, LaravelPaginatedDataDefault } from '../../../@types/paginated-data'; +import DataTable from '../../../components/LaravelTable'; +import { fCurrency } from '../../../utils/formatNumber'; +import EditRoundedIcon from '@mui/icons-material/EditRounded'; +import { LoadingButton } from '@mui/lab'; +import { enqueueSnackbar } from 'notistack'; +import { Divider } from '@mui/material'; +import Iconify from '@/components/Iconify'; +import DialogDetailClaim from '@/components/dialogs/DialogDetailClaim'; +import { fDateTimesecond } from '@/utils/formatTime'; +import { capitalizeFirstLetter } from '@/utils/formatString'; +import Label from '@/components/Label'; +import TableMoreMenu from '@/components/table/TableMoreMenu'; +import { Import } from '@/@types/claims'; + +import { FinalLogType } from '../../CustomerService/FinalLog/Model/Types'; +import DialogDeleteFinalLOG from '@/pages/CustomerService/FinalLog/Components/DialogDeleteFinalLOG'; +import { Delete } from '@mui/icons-material'; +import useAuth from '@/hooks/useAuth'; +// import LoadingButton from '@/theme/overrides/LoadingButton'; + +export default function List() { + const { themeColorPresets } = useSettings(); + const [searchParams, setSearchParams] = useSearchParams(); + const [importResult, setImportResult] = useState(null); + const { user } = useAuth(); + + const navigate = useNavigate() + + const fileOptions = { + kondisi: 'Dokumen Billing', + diagnosa: 'Dokumen Diagnosa', + result: 'Dokumen Penduk Medis', + none: 'Belum ada Dokumen' + }; + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(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(`claim-requests/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + setImportLoading(false); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + }; + + const handleGetTemplate = (type :string) => { + axios.get('corporates/import-document-example/' + type) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }) + } + + const handleGetData = (type :string) => { + axios.get(`corporates/${corporate_id}/data-plan-benefit`) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }) + } + + return ( +
+ + {!currentImportFileName && ( + + + + + + + File + + + + + + + + Import + {handleGetTemplate('claim-request')}}>Download Template + {handleGetData('data-plan-benefit')}}>Download Claim Request + + {/* */} + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )} + {importResult && ( + + + Last Import Result 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 filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('/customer-service/request?final_log=1&service_code=IP', { params: filter }); + // console.log(response.data); + setDataTableLoading(false); + + setDataTableData(response.data); + }; + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + const filter = Object.fromEntries([...searchParams.entries(), ['page', value]]); + loadDataTableData(filter); + setSearchParams(filter); + }; + + // Handel Delete Final LOG + const [idFinalLog, setidFinalLog] = useState(); + const [openDialogDeleteFinalLog, setDialogDeleteFinalLog] = useState(false) + + useEffect(() => { + loadDataTableData(); + }, []); + + const headStyle = { + fontWeight: 'bold', + }; + + // Called on every row to map the data to the columns + function createData(data: FinalLogType) { + return { + ...data, + }; + } + + const updateApproval = async (id:any) => { + axios + .put(`/customer-service/request/${id}`, { + status_approval: 'approved', + }) + .then((response) => { + enqueueSnackbar('Berhasil Approve', { variant: 'success' }); + window.location.reload(); + }) + .catch(({ response }) => { + enqueueSnackbar(response?.data?.message || 'Something Went Wrong', { variant: 'error' }); + }) + .finally(() => { + }); + }; + + + const updateDecline = async (id:any) => { + axios + .put(`/customer-service/request/${id}`, { + status_approval: 'declined', + }) + .then((response) => { + enqueueSnackbar('Berhasil Approve', { variant: 'success' }); + window.location.reload(); + }) + .catch(({ response }) => { + enqueueSnackbar(response?.data?.message || 'Something Went Wrong', { variant: 'error' }); + }) + .finally(() => { + }); + } + + { + /* ------------------ 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' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */ } + {/* + { + // handleShowClaim(row); + // }} + > + {row.id} + + */} + {row.code} + {row.provider} + {row.member_name} + + {row.service_name} + {row.payment_type_name} + + {row.files_by_type?.final_log_diagnosis?.length > 0 && ( + <> + +
+ + )} + + {row.files_by_type?.final_log_kondisi?.length > 0 && ( + <> + +
+ + )} + + {row.files_by_type?.final_log_result?.length > 0 && ( + + )} +
+ + { row.status_approval == "requested" ? + () : + row.status_approval == "declined" ? + () + : + () + } + + {fCurrency(row.nominal)} + + {row.status_approval !== "approved" && ( + + + + + + )} + + + + {/* navigate(`/claim-requests/edit/${row.id}`)}> + + Edit + */} + navigate ('/custormer-service/final-log/detail/'+row.id+'/'+user.id)}> + + Detail + + { + setidFinalLog(row.id) + setDialogDeleteFinalLog(true) + }} + > + + Delete + + + } /> + + {/* + + { + handleShowClaim(row); + }} + > + + + */} +
+ {/* COLLAPSIBLE ROW */} + + + + + } + spacing={1} + sx={{ marginY: 2 }} + > + + Berkas Hasil Penunjang + {/* {row.files_by_type?.claim_kondisi && + row.files_by_type?.claim_kondisi.map((file, index) => ( + + -{' '} + + {file.name} + + + ))} */} + + {row.files_by_type?.claim_kondisi && ( + <> + - Kondisi + {row.files_by_type?.claim_kondisi.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + + {row.files_by_type?.claim_diagnosis && ( + <> + - Diagnosa + {row.files_by_type?.claim_diagnosis.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + + {row.files_by_type?.claim_result && ( + <> + - Hasil + {row.files_by_type?.claim_result.map((file, index) => ( + + + + {file.name} + + + ))} + + )} + {(!row.files_by_type?.claim_result && !row.files_by_type?.claim_diagnosis && !row.files_by_type?.claim_kondisi)&& Tidak ada berkas} + + + + + + +
+ ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {/* */} + {/* + ID Request LOG + */} + + Code + + + Provider + + + Name + + + Date of Admission + + + Service Type + + + Claim Method + + + File Upload + + + Status + + + Nominal + + + Action + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + No Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + return ( + + + + + + + } + /> + + + {/* Dialog Delete */} + + + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx index c81f524e..6d371ee0 100755 --- a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogEditFinalLOG.tsx @@ -159,7 +159,7 @@ export default function DialogEditFinalLOG({requestLog, setOpenDialog, openDialo // Add more options as needed ]; - console.log(formData.type_of_member) +// console.log(formData.type_of_member) const getContent = () => ( diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogSendWa.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogSendWa.tsx new file mode 100755 index 00000000..ddd123ab --- /dev/null +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogSendWa.tsx @@ -0,0 +1,185 @@ +import { Stack, Typography, Button, Paper, Grid, IconButton, TextField } from "@mui/material"; +import MuiDialog from "@/components/MuiDialog"; +import { fDate, fDateTimesecond } from '@/utils/formatTime'; +import { ContentCopy, WhatsApp, Instagram, Facebook, Telegram } from "@mui/icons-material"; + +type DialogConfirmationType = { + openDialog: boolean; + setOpenDialog: any; + onSubmit?: void; + requestLog: any; + shareLink: boolean; +}; + +export default function DialogSendWa({ + requestLog, + setOpenDialog, + openDialog, + shareLink = false, +}: DialogConfirmationType) { + const data = { + provider: requestLog?.provider || "LOG", + memberId: requestLog?.member_id || "-", + policyNumber: requestLog?.policy_number || "-", + name: requestLog?.name || "-", + submissionDate: requestLog?.submission_date ? fDateTimesecond(requestLog?.submission_date) : "-", + claimMethod: requestLog?.claim_method || "-", + serviceType: requestLog?.service_type || "-", + linkApproval: requestLog?.url_approval || "https://example.com/approval-link", + }; + + const getContent = () => ( + + Are you sure want to send this request ? + + + + + Member ID + + + + {data.memberId} + + + + + Policy Number + + + + {data.policyNumber} + + + + + Name + + + + {data.name} + + + + + Submission Date + + + + {data.submissionDate} + + + + + Claim Method + + + + {data.claimMethod} + + + + + Service Type + + + + {data.serviceType} + + + + {shareLink ? ( + <> + Share this link only with authorized parties! + {/* + + + + + + + + + + + + + */} + + or copy link + + + + + + ): null } + + ); + + const getAction = () => { + if (shareLink) { + return ( + + + + ); + } + + const handleSend = () => { + const message = `*Request Approval* + Yth. Bapak/Ibu, Nama Penerima + Mohon persetujuan atas data berikut: + + Provider: *${data.provider}* + Member ID: ${data.memberId} + Nama: ${data.name} + Policy Number: ${data.policyNumber} + Submission Date: ${data.submissionDate} + Claim Method: ${data.claimMethod} + Service Type: ${data.serviceType} + + Silakan klik link berikut untuk approval: + ${data.linkApproval}`; + + const encodedMessage = encodeURIComponent(message); + const waUrl = `https://wa.me/6283807417196?text=${encodedMessage}`; + window.open(waUrl, "_blank"); + }; + + return ( + + + + + ); + }; + + return ( + + ); +} diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx index 17b46e1e..e3e27566 100755 --- a/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx @@ -1,3 +1,6 @@ +import * as Yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { NumericFormat } from "react-number-format"; import { Container, Grid, @@ -12,15 +15,22 @@ import { AccordionSummary, AccordionDetails, IconButton, + Divider, + ButtonBase } from '@mui/material'; // components import Page from '../../../components/Page'; +import Iconify from '@/components/Iconify'; +import { FormProvider, RHFDatepicker, RHFSelect, RHFTextField } from '@/components/hook-form'; +import RHFTextFieldMoney from '@/components/hook-form/v2/RHFTextFieldMoney'; // utils import useSettings from '../../../hooks/useSettings'; +import { useFieldArray, useForm } from 'react-hook-form'; // react import { useNavigate, useParams, useLocation } from 'react-router-dom'; import { useEffect, useState, useRef, useMemo } from 'react'; import axios from '../../../utils/axios'; +import { enqueueSnackbar } from 'notistack'; // pages import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; import { DetailFinalLogType } from './Model/Types'; @@ -33,6 +43,8 @@ import { Accordion } from '@mui/material'; import { Delete, EditOutlined, ExpandMore } from '@mui/icons-material'; import {BenefitData } from '../FinalLog/Model/Types' import AddIcon from '@mui/icons-material/Add'; +import { LoadingButton } from '@mui/lab'; +import { makeFormData } from '@/utils/jsonToFormData'; // Import Card Detail Final LOG import CardDetail from '../Components/CardDetail'; @@ -57,6 +69,8 @@ import CardFile from '../Components/CardFile'; import DialogEditFinalLOG from './Components/DialogEditFinalLOG'; import DialogDeleteFileLog from './Components/DialogDeleteFileLog'; import DialogUploadFileFinalLog from './Components/DialogUploadFileFinalLog'; +import DialogSendWa from './Components/DialogSendWa'; +import { set } from 'nprogress'; // ---------------------------------------------------------------------- @@ -69,9 +83,78 @@ export default function Detail() { const { themeStretch } = useSettings(); const [requestLog, setRequestLog] = useState(); const [isReversal, setIsReversal] = useState(false); + const [submitLoading, setSubmitLoading] = useState(false); + + const defaultValues: any = {nominal : 0}; + const validationSchema = Yup.object().shape({nominal: Yup.number().typeError('Nominal harus berupa angka').required('Nominal harus diisi')}) + + const methods = useForm({ + resolver: yupResolver(validationSchema), + defaultValues + }); + + const { handleSubmit, reset, watch, setValue, formState: { isDirty, isSubmitting, errors } } = methods; + + const onSubmit = async (data: any) => { + setSubmitLoading(true); + const formData = makeFormData({ + request_logs_id: id, + approval_files: fileApprovals, + nominal: data.nominal, + }); + axios + .post(`/customer-service/request/${id}/approval_files`, formData) + .then((response) => { + enqueueSnackbar('Berhasil membuat data', { variant: 'success' }); + + window.location.reload() + }) + .catch(({ response }) => { + enqueueSnackbar('Something Went Wrong', { variant: 'error' }); + }) + .then(() => { + setSubmitLoading(false); + }); + } + + const updateApproval = async () => { + setSubmitLoading(true); + axios + .put(`/customer-service/request/${id}`, { + status_approval: 'approved', + }) + .then((response) => { + enqueueSnackbar('Berhasil Approve', { variant: 'success' }); + window.location.reload(); + }) + .catch(({ response }) => { + enqueueSnackbar(response?.data?.message || 'Something Went Wrong', { variant: 'error' }); + }) + .finally(() => { + setSubmitLoading(false); + }); + }; - const { id } = useParams(); + const updateDecline = async () => { + setSubmitLoading(true); + axios + .put(`/customer-service/request/${id}`, { + status_approval: 'declined', + }) + .then((response) => { + enqueueSnackbar('Berhasil Approve', { variant: 'success' }); + window.location.reload(); + }) + .catch(({ response }) => { + enqueueSnackbar(response?.data?.message || 'Something Went Wrong', { variant: 'error' }); + }) + .finally(() => { + setSubmitLoading(false); + }); + } + + const { id, approval } = useParams(); useEffect(() => { axios @@ -107,6 +190,8 @@ export default function Detail() { const [openDialogEditDetail, setDialogDEditDetail] = useState(false); const [openDialogBenefit, setDialogBenefit] = useState(false); const [openDialogMedicine, setDialogMedicine] = useState(false); + const [openDialogSendWa, setDialogSendWa] = useState(false); + const [shareLink, setShareLink] = useState(false); // Handel Delete Detail Benefit const [idBenefitData, setIdBenefitData] = useState(); @@ -146,9 +231,27 @@ export default function Detail() { // Handle Delete File LOG const [pathFile, setPathFile] = useState('') const [dialogDeleteFIleLog, setDialogDeleteFileLog] = useState(false) - + // Handle Upload File LOG const [dialogUploadFileLog, setDialogUploadFileLog] = useState(false) + + + const fileDiagnosaInput = useRef(null); + const [fileApprovals, setFileApproval] = useState([]); + const handleDiagnosaInputChange = (event:any) => { + if (event.target.files[0]) { + setFileApproval([...fileApprovals, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + const removeApprovalFiles = (filesState:any, index:any) => { + setFileApproval( + filesState.filter((file:any, fileIndex:any) => { + return fileIndex != index; + }) + ); + }; return ( @@ -323,6 +426,223 @@ export default function Detail() { */} + {/* Surat persetujuan Tindakan */} + + + + + Tindakan Persetujuan + + + + {!isReversal && ( + + + + + Upload Tindakan Persetujuan + + + {fileApprovals?.map((file: any, index: number) => ( + + + {file.name} + + removeApprovalFiles(fileApprovals, index)} + /> + + ))} + + fileDiagnosaInput.current?.click()} + > + + + + Upload Tindakan Persetujuan + + + + + + + + {/* + Simpan + */} + + {approval ? ( + <> + + {/* GRUP TOMBOL DI KANAN */} + {requestLog?.status_approval !== 'approved' && ( + + + + + + )} + + ) : ( + <> + {/* TOMBOL SIMPAN DI KIRI */} + + Simpan + + + {/* Ini adalah spacer untuk mendorong tombol berikutnya ke kanan */} + + + {/* GRUP TOMBOL DI KANAN */} + + + + + + + )} + + + + + + )} + + {/* FILE YANG SUDAH TERUPLOAD */} + {requestLog?.files + ?.filter((document) => document.type === 'approval') + ?.map((documentType, index) => ( + + + + {documentType.original_name || '-'} + + + + {!isReversal && ( + { + setDialogDeleteFileLog(true); + setPathFile(documentType.path); + }} + size="small" + > + + + )} + + ))} + + {/* DIALOG */} + + + + + + + {/* Benefit */} @@ -597,6 +917,16 @@ export default function Detail() { requestLog={requestLog} openDialog={openDialogEditDetail} /> + + + + {/* Medicine */} @@ -662,7 +992,9 @@ export default function Detail() { ) : null } - {requestLog?.files?.map((documentType, index) => ( + {requestLog?.files + ?.filter((document) => document.type !== 'approval') + ?.map((documentType, index) => ( { - setOpenDialogSubmit(true); - setApprove('declined'); + }} > Decline diff --git a/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx index e762385b..cb9081c1 100755 --- a/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Model/Types.tsx @@ -21,6 +21,8 @@ export type FinalLogType = { service_name : string, payment_type_name : string, status_final_log : string, + status_approval : string, + nominal : number, provider : string, status : string, files_by_type : files_by_type, @@ -48,6 +50,7 @@ export type DetailFinalLogType = { claim_method : string, status : string, status_final_log : string, + status_approval : string, no_identitas : string, keterangan : string, hak_kamar_pasien : string, @@ -65,6 +68,7 @@ export type DetailFinalLogType = { files : file[], member_usage_benefit : number, corporate_id : number + nominal : number } export type Diagnosis = { diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index a44879f0..9082b104 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -269,6 +269,10 @@ export default function Router() { path: 'inpatient_monitoring', // Inpatient Monitoring element: }, + { + path: 'approval_inpatient_monitoring', // Approval Monitoring + element: + }, ] }, { @@ -555,6 +559,10 @@ export default function Router() { { path: 'custormer-service/final-log/detail/:id', element: , + }, + { + path: 'custormer-service/final-log/detail/:id/:approval', + element: , }, { path: 'e-prescription/live-chat', @@ -714,6 +722,7 @@ const DetailLabResultForm = Loadable(lazy(() => import('../pages/CaseManage const DetailLabResultList = Loadable(lazy(() => import('../pages/CaseManagement/LaboratoriumResult/Components/DetailLabResultList'))) // Inpatient Monitoring const InpatientMonitoring = Loadable(lazy(() => import('../pages/CaseManagement/InpatientMonitoring/Index'))) +const ApprovalMonitoring = Loadable(lazy(() => import('../pages/CaseManagement/ApprovalMonitoring/Index'))) /**