Merge remote-tracking branch 'origin/staging' into origin/production
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
62
app/Console/Commands/UpdateNoSjp.php
Normal file
62
app/Console/Commands/UpdateNoSjp.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\LivechatUpdateLog;
|
||||
use App\Models\OLDLMS\Livechat;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use GuzzleHttp\Client;
|
||||
class UpdateNoSJP extends Command {
|
||||
protected $signature = 'update:no-sjp';
|
||||
|
||||
protected $description = 'Cek data Livechat yang sNoSpj kosong lalu generate SJP lewat API';
|
||||
|
||||
public function handle() {
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
18
app/Models/LivechatUpdateLog.php
Normal file
18
app/Models/LivechatUpdateLog.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LivechatUpdateLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'nIDLivechat',
|
||||
'nIDSummary',
|
||||
'status',
|
||||
'response'
|
||||
];
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class RequestLog extends Model
|
||||
'final_log',
|
||||
'status',
|
||||
'status_final_log',
|
||||
'status_approval',
|
||||
'source',
|
||||
'claim_id',
|
||||
'organization_id',
|
||||
@@ -53,7 +54,9 @@ class RequestLog extends Model
|
||||
'created_final_by',
|
||||
'specialities_id',
|
||||
'dppj',
|
||||
'type_of_member'
|
||||
'type_of_member',
|
||||
'nominal',
|
||||
'approval_nominal_by',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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');
|
||||
$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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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::create('livechat_update_logs', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
19
database/seeders/LivechatUpdateLogSeeder.php
Normal file
19
database/seeders/LivechatUpdateLogSeeder.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class LivechatUpdateLogSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,11 @@ class NavigationSeeder extends Seeder
|
||||
'path' => '/case_management/inpatient_monitoring',
|
||||
'permission' => 'final-log-list'
|
||||
],
|
||||
[
|
||||
'title' => 'Approval Inpatient',
|
||||
'path' => '/case_management/approval_inpatient_monitoring',
|
||||
'permission' => 'approval-log-list'
|
||||
],
|
||||
],
|
||||
'permission' => null
|
||||
],
|
||||
|
||||
@@ -76,6 +76,7 @@ class PermissionTableSeeder extends Seeder
|
||||
'user-access-list',
|
||||
'report-katalog-dokter',
|
||||
'invoice-payment-list',
|
||||
'approval-log-list',
|
||||
]
|
||||
],
|
||||
####################### CLIENT PORTAL #########################
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
30
frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/Index.tsx
Executable file
30
frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/Index.tsx
Executable file
@@ -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 (
|
||||
<Page title={ pageTitle } sx={{ mx: 2}}>
|
||||
|
||||
<HeaderBreadcrumbs
|
||||
heading={ pageTitle }
|
||||
links={[
|
||||
{ name: 'Dashboard', href: '/dashboard' },
|
||||
{
|
||||
name: 'Approval Monitoring',
|
||||
href: '/approval_inpatient_monitoring',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* <Stack> */}
|
||||
<List />
|
||||
{/* </Stack> */}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
699
frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/List.tsx
Executable file
699
frontend/dashboard/src/pages/CaseManagement/ApprovalMonitoring/List.tsx
Executable file
@@ -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<Import>(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<HTMLInputElement>(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 (
|
||||
<form onSubmit={handleSearchSubmit} style={{ width: '100%' }}>
|
||||
<TextField
|
||||
id="search-input"
|
||||
ref={searchInput}
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onChange={handleSearchChange}
|
||||
value={searchText}
|
||||
placeholder='Search Code or Name...'
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportForm(props: any) {
|
||||
// IMPORT
|
||||
// Create Button Menu
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const createMenu = Boolean(anchorEl);
|
||||
const importForm = useRef<HTMLInputElement>(null);
|
||||
const [currentImportFileName, setCurrentImportFileName] = useState(null);
|
||||
const [importLoading, setImportLoading] = useState(false);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
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 (
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
id="file"
|
||||
ref={importForm}
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleImportChange}
|
||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
|
||||
/>
|
||||
{!currentImportFileName && (
|
||||
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
|
||||
<Grid item md={9}>
|
||||
<SearchInput onSearch={applyFilter} />
|
||||
</Grid>
|
||||
<Grid item md={2}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>File</InputLabel>
|
||||
<Select
|
||||
value={searchParams.get('file') ?? 'semua'} // Pastikan menggunakan kunci 'file'
|
||||
label="File"
|
||||
onChange={(el) => {
|
||||
const selectedValue = el.target.value;
|
||||
const filter = Object.fromEntries(searchParams.entries());
|
||||
|
||||
if (selectedValue === 'semua') {
|
||||
delete filter.file; // Menghapus filter 'file' jika memilih 'semua'
|
||||
} else {
|
||||
filter.file = selectedValue; // Menambahkan atau memperbarui filter 'file'
|
||||
}
|
||||
|
||||
setSearchParams(filter); // Update state searchParams
|
||||
loadDataTableData(filter); // Memuat data sesuai filter
|
||||
}}
|
||||
>
|
||||
<MenuItem value={'semua'}>Semua</MenuItem>
|
||||
{Object.entries(fileOptions).map((option, index) => (
|
||||
<MenuItem value={option[0]} key={index}>
|
||||
{option[1]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item md={1}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<UploadIcon />}
|
||||
sx={{ p: 1.8 }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
</Grid>
|
||||
<Menu
|
||||
id="import-button"
|
||||
anchorEl={anchorEl}
|
||||
open={createMenu}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'basic-button',
|
||||
}}
|
||||
>
|
||||
<MenuItem onClick={handleImportButton}>Import</MenuItem>
|
||||
<MenuItem onClick={() => {handleGetTemplate('claim-request')}}>Download Template</MenuItem>
|
||||
<MenuItem onClick={() => {handleGetData('data-plan-benefit')}}>Download Claim Request</MenuItem>
|
||||
</Menu>
|
||||
{/* <Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
sx={{ p: 1.8 }}
|
||||
onClick={() => {
|
||||
navigate('/claim-requests/create');
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button> */}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{currentImportFileName && (
|
||||
<Stack direction={'row'} spacing={2} sx={{ p: 2 }}>
|
||||
<ButtonGroup variant="outlined" aria-label="outlined button group" fullWidth>
|
||||
<Button onClick={handleImportButton} fullWidth>
|
||||
{currentImportFileName ?? 'No File Selected'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCancelImportButton}
|
||||
size="small"
|
||||
fullWidth={false}
|
||||
sx={{ p: 1.8 }}
|
||||
>
|
||||
<CancelIcon color="error" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<LoadingButton
|
||||
id="upload-button"
|
||||
variant="outlined"
|
||||
startIcon={<UploadIcon />}
|
||||
sx={{ p: 1.8 }}
|
||||
onClick={handleUpload}
|
||||
loading={importLoading}
|
||||
>
|
||||
Upload
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
)}
|
||||
{importResult && (
|
||||
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
|
||||
<Box sx={{ color: 'text.secondary' }}>
|
||||
Last Import Result Report :{' '}
|
||||
<a href={importResult.result_file?.url ?? '#'}>
|
||||
{importResult.result_file?.name ?? '-'}
|
||||
</a>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Dummy Default Data
|
||||
const [dataTableIsLoading, setDataTableLoading] = useState(true);
|
||||
const [dataTableData, setDataTableData] = useState<LaravelPaginatedData>(
|
||||
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<number>();
|
||||
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<typeof createData> }) {
|
||||
const { row } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [loadingApprove, setLoadingApprove] = React.useState(false);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
||||
{/* <TableCell>
|
||||
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
|
||||
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
|
||||
</IconButton>
|
||||
</TableCell> */ }
|
||||
{/* <TableCell align="left">
|
||||
<Typography
|
||||
// onClick={() => {
|
||||
// handleShowClaim(row);
|
||||
// }}
|
||||
>
|
||||
{row.id}
|
||||
</Typography>
|
||||
</TableCell> */}
|
||||
<TableCell align="left">{row.code}</TableCell>
|
||||
<TableCell align="left">{row.provider}</TableCell>
|
||||
<TableCell align="left">{row.member_name}</TableCell>
|
||||
<TableCell align="left"><Label>{fDateTimesecond(row.admission_date)}</Label></TableCell>
|
||||
<TableCell align="left">{row.service_name}</TableCell>
|
||||
<TableCell align="left">{row.payment_type_name}</TableCell>
|
||||
<TableCell align="left">
|
||||
{row.files_by_type?.final_log_diagnosis?.length > 0 && (
|
||||
<>
|
||||
<Label variant='ghost' color='primary'>
|
||||
{row.files_by_type.final_log_diagnosis.length} File Diagnosa
|
||||
</Label>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
|
||||
{row.files_by_type?.final_log_kondisi?.length > 0 && (
|
||||
<>
|
||||
<Label variant='ghost' color='success'>
|
||||
{row.files_by_type.final_log_kondisi.length} File Billing
|
||||
</Label>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
|
||||
{row.files_by_type?.final_log_result?.length > 0 && (
|
||||
<Label variant='ghost' color='warning'>
|
||||
{row.files_by_type.final_log_result.length} File Pendukung Medis
|
||||
</Label>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{ row.status_approval == "requested" ?
|
||||
(<Label variant='ghost' color='primary'>{capitalizeFirstLetter(row.status_approval)}</Label>) :
|
||||
row.status_approval == "declined" ?
|
||||
(<Label color='error'> {capitalizeFirstLetter(row.status_approval)}</Label>)
|
||||
:
|
||||
(<Label color='success'> {capitalizeFirstLetter(row.status_approval)}</Label>)
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell align="left">{fCurrency(row.nominal)}</TableCell>
|
||||
<TableCell align="left">
|
||||
{row.status_approval !== "approved" && (
|
||||
<Stack direction="row" spacing={1.5} mt={2}>
|
||||
<Button
|
||||
color="error"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => updateDecline(row.id)}
|
||||
>
|
||||
Decline
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => updateApproval(row.id)}
|
||||
>
|
||||
Approve
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<TableMoreMenu actions={
|
||||
<>
|
||||
{/* <MenuItem onClick={() => navigate(`/claim-requests/edit/${row.id}`)}>
|
||||
<EditOutlinedIcon />
|
||||
Edit
|
||||
</MenuItem> */}
|
||||
<MenuItem onClick={() => navigate ('/custormer-service/final-log/detail/'+row.id+'/'+user.id)}>
|
||||
<FindInPageOutlinedIcon />
|
||||
Detail
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => {
|
||||
setidFinalLog(row.id)
|
||||
setDialogDeleteFinalLog(true)
|
||||
}}
|
||||
>
|
||||
<Delete color='error'/>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
} />
|
||||
</TableCell>
|
||||
{/* <TableCell>
|
||||
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handleShowClaim(row);
|
||||
}}
|
||||
>
|
||||
<Iconify icon="eva:eye-fill" />
|
||||
</IconButton>
|
||||
</TableCell> */}
|
||||
</TableRow>
|
||||
{/* COLLAPSIBLE ROW */}
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={99}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ borderBottom: 1 }}>
|
||||
<Stack
|
||||
divider={<Divider orientation="horizontal" flexItem />}
|
||||
spacing={1}
|
||||
sx={{ marginY: 2 }}
|
||||
>
|
||||
<Box>
|
||||
<Typography fontWeight={600}>Berkas Hasil Penunjang</Typography>
|
||||
{/* {row.files_by_type?.claim_kondisi &&
|
||||
row.files_by_type?.claim_kondisi.map((file, index) => (
|
||||
<Stack direction="row" key={index}>
|
||||
<Typography sx={{ marginRight: 2 }}>-</Typography>{' '}
|
||||
<a href={file.url} target="_blank">
|
||||
{file.name}
|
||||
</a>
|
||||
</Stack>
|
||||
))} */}
|
||||
|
||||
{row.files_by_type?.claim_kondisi && (
|
||||
<>
|
||||
<Typography fontWeight={600} sx={{ marginRight: 4 }}> - Kondisi</Typography>
|
||||
{row.files_by_type?.claim_kondisi.map((file, index) => (
|
||||
|
||||
<Stack direction="row" key={index}>
|
||||
<a href={file.url} target="_blank">
|
||||
{file.name}
|
||||
</a>
|
||||
</Stack>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{row.files_by_type?.claim_diagnosis && (
|
||||
<>
|
||||
<Typography fontWeight={600} sx={{ marginRight: 4 }}> - Diagnosa</Typography>
|
||||
{row.files_by_type?.claim_diagnosis.map((file, index) => (
|
||||
|
||||
<Stack direction="row" key={index}>
|
||||
<a href={file.url} target="_blank">
|
||||
{file.name}
|
||||
</a>
|
||||
</Stack>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{row.files_by_type?.claim_result && (
|
||||
<>
|
||||
<Typography fontWeight={600} sx={{ marginRight: 4 }}> - Hasil</Typography>
|
||||
{row.files_by_type?.claim_result.map((file, index) => (
|
||||
|
||||
<Stack direction="row" key={index}>
|
||||
<a href={file.url} target="_blank">
|
||||
{file.name}
|
||||
</a>
|
||||
</Stack>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{(!row.files_by_type?.claim_result && !row.files_by_type?.claim_diagnosis && !row.files_by_type?.claim_kondisi)&& <Typography>Tidak ada berkas</Typography>}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
{
|
||||
/* ------------------ END TABLE ROW ------------------ */
|
||||
}
|
||||
|
||||
function TableContent() {
|
||||
return (
|
||||
<Table aria-label="collapsible table">
|
||||
{/* ------------------ TABLE HEADER ------------------ */}
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{/* <TableCell style={headStyle} align="left" /> */}
|
||||
{/* <TableCell style={headStyle} align="left">
|
||||
ID Request LOG
|
||||
</TableCell> */}
|
||||
<TableCell style={headStyle} align="left">
|
||||
Code
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Provider
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Name
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Date of Admission
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Service Type
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Claim Method
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
File Upload
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Status
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Nominal
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="left">
|
||||
Action
|
||||
</TableCell>
|
||||
<TableCell style={headStyle} align="right"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
{/* ------------------ END TABLE HEADER ------------------ */}
|
||||
|
||||
{/* ------------------ TABLE ROW ------------------ */}
|
||||
{dataTableIsLoading ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
Loading
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : dataTableData.data.length === 0 ? (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
No Data
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
) : (
|
||||
<TableBody>
|
||||
{dataTableData.data.map((row) => (
|
||||
<Row key={row.id} row={row} />
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
{/* ------------------ END TABLE ROW ------------------ */}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item sm={12}>
|
||||
<ImportForm />
|
||||
</Grid>
|
||||
|
||||
<Grid item sm={12}>
|
||||
<DataTable
|
||||
isLoading={dataTableIsLoading}
|
||||
lastRequest={0}
|
||||
data={dataTableData}
|
||||
handlePageChange={handlePageChange}
|
||||
TableContent={<TableContent />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item sm={12}>
|
||||
{/* Dialog Delete */}
|
||||
<DialogDeleteFinalLOG
|
||||
id={idFinalLog}
|
||||
openDialog={openDialogDeleteFinalLog}
|
||||
setOpenDialog={setDialogDeleteFinalLog}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -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 = () => (
|
||||
<Stack spacing={1} marginTop={2}>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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<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')})
|
||||
|
||||
const methods = useForm<any>({
|
||||
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<number>();
|
||||
@@ -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<HTMLInputElement>(null);
|
||||
const [fileApprovals, setFileApproval] = useState<any>([]);
|
||||
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 (
|
||||
<Page title='Detail'>
|
||||
@@ -323,6 +426,223 @@ export default function Detail() {
|
||||
</DialogHospitalCare>
|
||||
</Grid> */}
|
||||
|
||||
{/* Surat persetujuan Tindakan */}
|
||||
<Grid item xs={12}>
|
||||
<Card sx={{ p: 3 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle1" sx={{ color: '#19BBBB', fontWeight: 'bold' }}>
|
||||
Tindakan Persetujuan
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{!isReversal && (
|
||||
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={2} sx={{ mb: 3 }}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
Upload Tindakan Persetujuan
|
||||
</Typography>
|
||||
|
||||
{fileApprovals?.map((file: any, index: number) => (
|
||||
<Stack
|
||||
key={index}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{file.name}
|
||||
</Typography>
|
||||
<Iconify
|
||||
icon="eva:trash-2-outline"
|
||||
color="darkred"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => removeApprovalFiles(fileApprovals, index)}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
p: 2,
|
||||
border: "2px dashed #F9FAFB",
|
||||
bgcolor: "#F4F6F8",
|
||||
borderRadius: 2,
|
||||
width: "100%",
|
||||
height: 60,
|
||||
}}
|
||||
onClick={() => fileDiagnosaInput.current?.click()}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gap={1}
|
||||
>
|
||||
<Iconify icon="icon-park-outline:upload-one" fontSize="2rem" />
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
Upload Tindakan Persetujuan
|
||||
</Typography>
|
||||
</Box>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileDiagnosaInput}
|
||||
style={{ display: "none" }}
|
||||
multiple
|
||||
onChange={handleDiagnosaInputChange}
|
||||
accept="application/pdf,image/*"
|
||||
/>
|
||||
</ButtonBase>
|
||||
</Stack>
|
||||
<RHFTextFieldMoney
|
||||
id="nominal"
|
||||
name="nominal"
|
||||
label="Nominal"
|
||||
required
|
||||
placeholder="Nominal"
|
||||
value={requestLog?.nominal || 0}
|
||||
disabled={!!approval}
|
||||
/>
|
||||
|
||||
{/* <LoadingButton
|
||||
type="submit" // ✅ supaya ikut submit
|
||||
variant="contained"
|
||||
sx={{ marginTop: 2, p: 2, backgroundColor: "#19BBBB" }}
|
||||
loading={false}
|
||||
>
|
||||
Simpan
|
||||
</LoadingButton> */}
|
||||
<Stack direction="row" spacing={2} sx={{ mt: 6 }}>
|
||||
{approval ? (
|
||||
<>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
{/* GRUP TOMBOL DI KANAN */}
|
||||
{requestLog?.status_approval !== 'approved' && (
|
||||
<Stack direction="row" spacing={1.5} mt={2}>
|
||||
<Button
|
||||
color="error"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={updateDecline}
|
||||
>
|
||||
Decline
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={updateApproval}
|
||||
>
|
||||
Approve
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* 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>
|
||||
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
|
||||
)}
|
||||
|
||||
{/* FILE YANG SUDAH TERUPLOAD */}
|
||||
{requestLog?.files
|
||||
?.filter((document) => document.type === 'approval')
|
||||
?.map((documentType, index) => (
|
||||
<Stack
|
||||
key={index}
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<a
|
||||
href={documentType.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: 'none', color: '#19BBBB' }}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
{documentType.original_name || '-'}
|
||||
</Typography>
|
||||
</a>
|
||||
|
||||
{!isReversal && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDialogDeleteFileLog(true);
|
||||
setPathFile(documentType.path);
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<Delete color="error" fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{/* DIALOG */}
|
||||
<DialogDeleteFileLog
|
||||
id={requestLog?.id}
|
||||
path={pathFile}
|
||||
setOpenDialog={setDialogDeleteFileLog}
|
||||
openDialog={dialogDeleteFIleLog}
|
||||
/>
|
||||
|
||||
<DialogUploadFileFinalLog
|
||||
id={requestLog?.id}
|
||||
setOpenDialog={setDialogUploadFileLog}
|
||||
openDialog={dialogUploadFileLog}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
|
||||
{/* Benefit */}
|
||||
<Grid item xs={12} md={12}>
|
||||
<Card sx={{padding:2}} >
|
||||
@@ -597,6 +917,16 @@ export default function Detail() {
|
||||
requestLog={requestLog}
|
||||
openDialog={openDialogEditDetail}
|
||||
/>
|
||||
|
||||
<DialogSendWa
|
||||
requestLog={requestLog}
|
||||
openDialog={openDialogSendWa}
|
||||
setOpenDialog={setDialogSendWa}
|
||||
shareLink={shareLink}
|
||||
|
||||
/>
|
||||
|
||||
|
||||
</Grid>
|
||||
|
||||
{/* Medicine */}
|
||||
@@ -662,7 +992,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
|
||||
@@ -711,8 +1043,7 @@ export default function Detail() {
|
||||
variant="outlined"
|
||||
sx={{ color: '#FF4842', borderColor: '#FF4842' }}
|
||||
onClick={() => {
|
||||
setOpenDialogSubmit(true);
|
||||
setApprove('declined');
|
||||
|
||||
}}
|
||||
>
|
||||
Decline
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -269,6 +269,10 @@ export default function Router() {
|
||||
path: 'inpatient_monitoring', // Inpatient Monitoring
|
||||
element: <InpatientMonitoring />
|
||||
},
|
||||
{
|
||||
path: 'approval_inpatient_monitoring', // Approval Monitoring
|
||||
element: <ApprovalMonitoring />
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -555,6 +559,10 @@ export default function Router() {
|
||||
{
|
||||
path: 'custormer-service/final-log/detail/:id',
|
||||
element: <FinalLogDetail />,
|
||||
},
|
||||
{
|
||||
path: 'custormer-service/final-log/detail/:id/:approval',
|
||||
element: <FinalLogDetail />,
|
||||
},
|
||||
{
|
||||
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')))
|
||||
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user