From 3c9066edc611910c57bfea21617eab45dad28f2e Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Thu, 11 Sep 2025 09:47:42 +0700 Subject: [PATCH] backend dan penyesian upload file dinamis approval notifikasi --- .../Controllers/Api/RequestLogController.php | 28 +++ Modules/Internal/Routes/api.php | 1 + .../Transformers/RequestLogShowResource.php | 1 + app/Models/File.php | 54 ++++- app/Models/RequestLog.php | 3 +- ...d_columns_nominal_to_table_request_log.php | 32 +++ .../FinalLog/Components/DialogSendWa.tsx | 185 ++++++++++++++++++ .../pages/CustomerService/FinalLog/Detail.tsx | 94 +++++++-- .../CustomerService/FinalLog/Model/Types.tsx | 1 + 9 files changed, 375 insertions(+), 24 deletions(-) create mode 100644 database/migrations/2025_09_10_103219_add_columns_nominal_to_table_request_log.php create mode 100755 frontend/dashboard/src/pages/CustomerService/FinalLog/Components/DialogSendWa.tsx diff --git a/Modules/Internal/Http/Controllers/Api/RequestLogController.php b/Modules/Internal/Http/Controllers/Api/RequestLogController.php index ba9ab91f..4058a280 100755 --- a/Modules/Internal/Http/Controllers/Api/RequestLogController.php +++ b/Modules/Internal/Http/Controllers/Api/RequestLogController.php @@ -1224,6 +1224,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 0445b0d2..6709a385 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/RequestLogShowResource.php b/Modules/Internal/Transformers/RequestLogShowResource.php index 647de78d..9349c037 100755 --- a/Modules/Internal/Transformers/RequestLogShowResource.php +++ b/Modules/Internal/Transformers/RequestLogShowResource.php @@ -174,6 +174,7 @@ class RequestLogShowResource extends JsonResource 'keterangan' => $requestLog['keterangan'], 'hak_kamar_pasien' => $requestLog['hak_kamar_pasien'], 'penempatan_kamar' => $requestLog['penempatan_kamar'], + 'nominal' => $requestLog['nominal'], 'catatan' => $requestLog['catatan'], 'reason' => $requestLog['reason'], 'diagnosis' => $icd, 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/RequestLog.php b/app/Models/RequestLog.php index bd180522..0ae461e9 100755 --- a/app/Models/RequestLog.php +++ b/app/Models/RequestLog.php @@ -53,7 +53,8 @@ class RequestLog extends Model 'created_final_by', 'specialities_id', 'dppj', - 'type_of_member' + 'type_of_member', + 'nominal', ]; protected $hidden = [ diff --git a/database/migrations/2025_09_10_103219_add_columns_nominal_to_table_request_log.php b/database/migrations/2025_09_10_103219_add_columns_nominal_to_table_request_log.php new file mode 100644 index 00000000..f2294eab --- /dev/null +++ b/database/migrations/2025_09_10_103219_add_columns_nominal_to_table_request_log.php @@ -0,0 +1,32 @@ +integer('nominal')->default(0)->after('total_cob'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('table_request_log', function (Blueprint $table) { + $table->dropColumn('nominal'); + }); + } +}; 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 6c5eeb16..fb45bb08 100755 --- a/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx +++ b/frontend/dashboard/src/pages/CustomerService/FinalLog/Detail.tsx @@ -30,6 +30,7 @@ import { useFieldArray, useForm } from 'react-hook-form'; 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'; @@ -43,6 +44,7 @@ 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'; @@ -67,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'; // ---------------------------------------------------------------------- @@ -79,6 +83,7 @@ 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')}) @@ -91,7 +96,25 @@ export default function Detail() { const { handleSubmit, reset, watch, setValue, formState: { isDirty, isSubmitting, errors } } = methods; const onSubmit = async (data: any) => { - alert('Nominal: ' + data.nominal); + 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 { id } = useParams(); @@ -130,6 +153,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(); @@ -175,16 +200,16 @@ export default function Detail() { const fileDiagnosaInput = useRef(null); - const [fileDiagnosas, setFileDiagnosas] = useState([]); + const [fileApprovals, setFileApproval] = useState([]); const handleDiagnosaInputChange = (event:any) => { if (event.target.files[0]) { - setFileDiagnosas([...fileDiagnosas, ...event.target.files]); + setFileApproval([...fileApprovals, ...event.target.files]); } else { console.log('NO FILE'); } }; - const removeDiagnosaFiles = (filesState:any, index:any) => { - setFileDiagnosas( + const removeApprovalFiles = (filesState:any, index:any) => { + setFileApproval( filesState.filter((file:any, fileIndex:any) => { return fileIndex != index; }) @@ -381,7 +406,7 @@ export default function Detail() { Upload Tindakan Persetujuan - {fileDiagnosas?.map((file: any, index: number) => ( + {fileApprovals?.map((file: any, index: number) => ( removeDiagnosaFiles(fileDiagnosas, index)} + onClick={() => removeApprovalFiles(fileApprovals, index)} /> ))} @@ -438,29 +463,60 @@ export default function Detail() { label="Nominal" required placeholder="Nominal" + value={requestLog?.nominal || 0} /> - Simpan - + */} + + {/* TOMBOL SIMPAN DI KIRI */} + + Simpan + + + {/* Ini adalah spacer untuk mendorong tombol berikutnya ke kanan */} + + + {/* GRUP TOMBOL DI KANAN */} + + + + + - + + )} {/* FILE YANG SUDAH TERUPLOAD */} - {requestLog?.files?.map((documentType, index) => ( + {requestLog?.files + ?.filter((document) => document.type === 'approval') + ?.map((documentType, index) => ( + + + + {/* Medicine */} @@ -843,7 +909,9 @@ export default function Detail() { ) : null } - {requestLog?.files?.map((documentType, index) => ( + {requestLog?.files + ?.filter((document) => document.type !== 'approval') + ?.map((documentType, index) => (