backend dan penyesian upload file dinamis approval notifikasi
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,7 +53,8 @@ class RequestLog extends Model
|
||||
'created_final_by',
|
||||
'specialities_id',
|
||||
'dppj',
|
||||
'type_of_member'
|
||||
'type_of_member',
|
||||
'nominal',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('request_logs', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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 = () => (
|
||||
<Stack spacing={2} sx={{ marginTop: 2, padding: 2 }}>
|
||||
<Typography>Are you sure want to send this request ?</Typography>
|
||||
<Paper variant="outlined" sx={{ p: 2 }}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={5}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Member ID
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Typography>{data.memberId}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={5}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Policy Number
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Typography fontWeight="bold">{data.policyNumber}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={5}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Name
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Typography>{data.name}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={5}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Submission Date
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Typography>{data.submissionDate}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={5}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Claim Method
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Typography>{data.claimMethod}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={5}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Service Type
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Typography>{data.serviceType}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
{shareLink ? (
|
||||
<>
|
||||
<Typography>Share this link only with authorized parties!</Typography>
|
||||
{/* <Stack direction="row" spacing={2}>
|
||||
<IconButton color="success">
|
||||
<WhatsApp />
|
||||
</IconButton>
|
||||
<IconButton color="primary">
|
||||
<Instagram />
|
||||
</IconButton>
|
||||
<IconButton color="primary">
|
||||
<Telegram />
|
||||
</IconButton>
|
||||
<IconButton color="primary">
|
||||
<Facebook />
|
||||
</IconButton>
|
||||
</Stack> */}
|
||||
|
||||
<Typography variant="body2">or copy link</Typography>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
value={data.linkApproval}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => navigator.clipboard.writeText(data.linkApproval)}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
): null }
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const getAction = () => {
|
||||
if (shareLink) {
|
||||
return (
|
||||
<Stack direction="row" justifyContent="flex-end">
|
||||
<Button variant="outlined" onClick={() => setOpenDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Stack direction="row" justifyContent="space-between" spacing={2}>
|
||||
<Button variant="outlined" onClick={() => setOpenDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSend}>
|
||||
Send
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<MuiDialog
|
||||
title={{ name: "Confirmation", variant: "h4" }}
|
||||
openDialog={openDialog}
|
||||
setOpenDialog={setOpenDialog}
|
||||
content={getContent()}
|
||||
action={getAction()}
|
||||
maxWidth="sm"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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<DetailFinalLogType>();
|
||||
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<number>();
|
||||
@@ -175,16 +200,16 @@ export default function Detail() {
|
||||
|
||||
|
||||
const fileDiagnosaInput = useRef<HTMLInputElement>(null);
|
||||
const [fileDiagnosas, setFileDiagnosas] = useState<any>([]);
|
||||
const [fileApprovals, setFileApproval] = useState<any>([]);
|
||||
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
|
||||
</Typography>
|
||||
|
||||
{fileDiagnosas?.map((file: any, index: number) => (
|
||||
{fileApprovals?.map((file: any, index: number) => (
|
||||
<Stack
|
||||
key={index}
|
||||
direction="row"
|
||||
@@ -395,7 +420,7 @@ export default function Detail() {
|
||||
icon="eva:trash-2-outline"
|
||||
color="darkred"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => removeDiagnosaFiles(fileDiagnosas, index)}
|
||||
onClick={() => removeApprovalFiles(fileApprovals, index)}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
@@ -438,29 +463,60 @@ export default function Detail() {
|
||||
label="Nominal"
|
||||
required
|
||||
placeholder="Nominal"
|
||||
value={requestLog?.nominal || 0}
|
||||
/>
|
||||
|
||||
<LoadingButton
|
||||
{/* <LoadingButton
|
||||
type="submit" // ✅ supaya ikut submit
|
||||
variant="contained"
|
||||
sx={{ marginTop: 2, p: 2, backgroundColor: "#19BBBB" }}
|
||||
loading={false}
|
||||
>
|
||||
Simpan
|
||||
</LoadingButton>
|
||||
</LoadingButton> */}
|
||||
<Stack direction="row" spacing={2} sx={{ mt: 6 }}>
|
||||
{/* TOMBOL SIMPAN DI KIRI */}
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
variant="contained"
|
||||
sx={{ p: 2, backgroundColor: "#19BBBB" }}
|
||||
loading={false}
|
||||
size='small'
|
||||
>
|
||||
Simpan
|
||||
</LoadingButton>
|
||||
|
||||
{/* Ini adalah spacer untuk mendorong tombol berikutnya ke kanan */}
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
{/* GRUP TOMBOL DI KANAN */}
|
||||
<Stack direction="row" spacing={1.5} mt={2}>
|
||||
<Button variant="contained" size="small" sx={{ p: 2, backgroundColor: "#19BBBB" }}
|
||||
onClick={() => {setDialogSendWa(true); setShareLink(false); }}>
|
||||
Kirim (WA Chatbot)
|
||||
</Button>
|
||||
|
||||
<Button variant="contained" size="small" sx={{ p: 2, backgroundColor: "#19BBBB" }}
|
||||
onClick={() => {setDialogSendWa(true); setShareLink(true); }}>
|
||||
Share Link
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
|
||||
)}
|
||||
|
||||
{/* FILE YANG SUDAH TERUPLOAD */}
|
||||
{requestLog?.files?.map((documentType, index) => (
|
||||
{requestLog?.files
|
||||
?.filter((document) => document.type === 'approval')
|
||||
?.map((documentType, index) => (
|
||||
<Stack
|
||||
key={index}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
sx={{ mb: 2 }}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<a
|
||||
href={documentType.url}
|
||||
@@ -778,6 +834,16 @@ export default function Detail() {
|
||||
requestLog={requestLog}
|
||||
openDialog={openDialogEditDetail}
|
||||
/>
|
||||
|
||||
<DialogSendWa
|
||||
requestLog={requestLog}
|
||||
openDialog={openDialogSendWa}
|
||||
setOpenDialog={setDialogSendWa}
|
||||
shareLink={shareLink}
|
||||
|
||||
/>
|
||||
|
||||
|
||||
</Grid>
|
||||
|
||||
{/* Medicine */}
|
||||
@@ -843,7 +909,9 @@ export default function Detail() {
|
||||
) : null }
|
||||
|
||||
</Stack>
|
||||
{requestLog?.files?.map((documentType, index) => (
|
||||
{requestLog?.files
|
||||
?.filter((document) => document.type !== 'approval')
|
||||
?.map((documentType, index) => (
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{marginBottom: 2}} key={index}>
|
||||
<Stack direction="column" spacing={2} >
|
||||
<a
|
||||
|
||||
@@ -66,6 +66,7 @@ export type DetailFinalLogType = {
|
||||
files : file[],
|
||||
member_usage_benefit : number,
|
||||
corporate_id : number
|
||||
nominal : number
|
||||
}
|
||||
|
||||
export type Diagnosis = {
|
||||
|
||||
Reference in New Issue
Block a user