From 6deaf27bcaf8ab505ed4326e4ef882d7f1d6d08d Mon Sep 17 00:00:00 2001 From: ivan-sim Date: Thu, 27 Feb 2025 16:55:25 +0700 Subject: [PATCH 1/7] Invoice Payment --- .../Http/Controllers/Api/ClaimController.php | 63 + .../Api/InvoicePaymentController.php | 627 +++++++ Modules/Internal/Routes/api.php | 11 +- app/Models/File.php | 1 + ...1_140114_create_invoice_payments_table.php | 40 + ...3_create_invoice_payment_details_table.php | 37 + .../pages/InvoicePayment/CreateInvoice.tsx | 1573 +++++++++++++++++ .../src/pages/InvoicePayment/Detail.tsx | 323 ++++ .../src/pages/InvoicePayment/Index.tsx | 30 + .../src/pages/InvoicePayment/List.tsx | 1063 +++++++++++ frontend/dashboard/src/routes/index.tsx | 23 + 11 files changed, 3790 insertions(+), 1 deletion(-) create mode 100644 Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php create mode 100644 database/migrations/2025_02_21_140114_create_invoice_payments_table.php create mode 100644 database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php create mode 100644 frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx create mode 100644 frontend/dashboard/src/pages/InvoicePayment/Detail.tsx create mode 100644 frontend/dashboard/src/pages/InvoicePayment/Index.tsx create mode 100644 frontend/dashboard/src/pages/InvoicePayment/List.tsx diff --git a/Modules/Internal/Http/Controllers/Api/ClaimController.php b/Modules/Internal/Http/Controllers/Api/ClaimController.php index 9202ecb7..2a4bfe3f 100755 --- a/Modules/Internal/Http/Controllers/Api/ClaimController.php +++ b/Modules/Internal/Http/Controllers/Api/ClaimController.php @@ -127,6 +127,69 @@ class ClaimController extends Controller return response()->json(Helper::paginateResources($results)); } + public function getClaimDetails(Request $request) + { + // Get the 'ids' array from the request + $ids = $request->input('ids'); + + // Ensure the 'ids' array is not empty + if (empty($ids)) { + return response()->json(['error' => 'No IDs provided'], 400); + } + + // Use the SQL Query Builder with whereIn to fetch data + $claimDetails = DB::table('claim_requests') + ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->whereIn('claim_requests.id', $ids) + ->select( + 'claim_requests.id', + 'request_logs.invoice_no', + 'request_logs.id AS id_log', + 'request_logs.code AS code_log', + 'claim_requests.code as code', + 'members.name', + DB::raw(' + (SELECT members.member_id FROM members WHERE members.id = claim_requests.member_id LIMIT 1) AS member_id + '), + 'request_logs.created_at as submission_date', + 'request_logs.submission_date as addmission_date', + 'request_logs.discharge_date', + // DB::raw(' + // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code + // '), + DB::raw(' + (SELECT plans.code + FROM plans + WHERE plans.id IN ( + SELECT member_plans.plan_id + FROM member_plans + WHERE member_plans.member_id = claim_requests.member_id + ) + AND plans.service_code = claim_requests.service_code) AS plan_code + '), + DB::raw(' + (SELECT services.description FROM services WHERE services.code = claim_requests.service_code LIMIT 1) AS service_code + '), + DB::raw(' + (SELECT corporate_policies.code FROM corporate_policies WHERE corporate_policies.id = claim_requests.policy_id LIMIT 1) AS corporate_policies + '), + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + DB::raw(' + (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits + WHERE request_log_benefits.request_log_id = request_logs.id LIMIT 1) AS tot_bill + '), + 'claim_requests.status_claim_management as status', + ) + ->get(); + + // Return the fetched claim details as a JSON response + return response()->json($claimDetails); + } + + public function filesProvider(Request $request) { $limit = $request->has('per_page') ? $request->input('per_page') : 50; diff --git a/Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php b/Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php new file mode 100644 index 00000000..02f085b8 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/InvoicePaymentController.php @@ -0,0 +1,627 @@ +has('per_page') ? $request->input('per_page') : 10; + $results = DB::table('invoice_payments') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('invoice_payments.invoice_number', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('invoice_payments.created_at', '>=', $start_date); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('invoice_payments.created_at', '<=', $end_date); + }); + }) + ->when($request->input('status') , function ($query, $status) { + $query->where(function ($query) use ($status) { + $query->where('invoice_payments.status', '=', $status); + }); + }) + // ->where('claim_management', '=', 1) + ->select( + 'invoice_payments.id', + 'invoice_payments.invoice_number', + 'invoice_payments.start_date', + 'invoice_payments.end_date', + 'invoice_payments.created_at', + 'invoice_payments.status', + ) + ->groupBy('invoice_payments.invoice_number') + ->paginate($limit); + + + + return response()->json(Helper::paginateResources($results)); + } + public function claim(Request $request) + { + $limit = $request->has('per_page') ? $request->input('per_page') : 10; + $results = DB::table('claim_requests') + ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->leftJoin('invoice_payment_details', function ($join) { + $join->on('invoice_payment_details.claim_request_id', '=', 'claim_requests.id') + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0); + }) + // ->leftJoin('member_plans', 'member_plans.member_id', '=', 'members.id') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('members.name', 'like', "%" . $search . "%"); + $query->orWhere('claim_requests.code', 'like', "%" . $search . "%"); + $query->orWhere('request_logs.code', 'like', "%" . $search . "%"); + $query->orWhere('members.member_id', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('claim_requests.created_at', '>=', $start_date); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('claim_requests.created_at', '<=', $end_date); + }); + }) + ->when($request->input('provider') , function ($query, $provider) { + $query->where(function ($query) use ($provider) { + $query->where('request_logs.organization_id', '=', $provider); + }); + }) + ->where('claim_management', '=', 1) + ->when($request->input('param') !== 'Edit', function ($query) { + $query->whereNotIn('claim_requests.id', function ($query) { + $query->select('claim_request_id') + ->from('invoice_payment_details'); + }); + }) + ->when($request->input('param') === 'Edit', function ($query) use ($request) { + $query->where(function ($q) use ($request) { + $q->whereNotIn('claim_requests.id', function ($subquery) { + $subquery->select('claim_request_id') + ->from('invoice_payment_details') + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0); + }) + ->orWhereIn('claim_requests.id', function ($subquery) use ($request) { + $subquery->select('claim_request_id') + ->from('invoice_payment_details') + ->where('invoice_payment_details.invoice_payment_id', $request->input('invoiceID')) + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0); + }); + }); + }) + ->select( + 'claim_requests.id', + 'request_logs.id AS id_log', + 'request_logs.code AS code_log', + 'claim_requests.code as code', + 'members.name', + DB::raw(' + (SELECT members.member_id FROM members WHERE members.id = claim_requests.member_id LIMIT 1) AS member_id + '), + 'claim_requests.created_at', + // DB::raw(' + // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code + // '), + DB::raw(' + (SELECT plans.code + FROM plans + WHERE plans.id IN ( + SELECT member_plans.plan_id + FROM member_plans + WHERE member_plans.member_id = claim_requests.member_id + ) + AND plans.service_code = claim_requests.service_code) AS plan_code + '), + DB::raw(' + (SELECT services.description FROM services WHERE services.code = claim_requests.service_code LIMIT 1) AS service_code + '), + DB::raw(' + (SELECT corporate_policies.code FROM corporate_policies WHERE corporate_policies.id = claim_requests.policy_id LIMIT 1) AS corporate_policies + '), + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + DB::raw(' + (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits + WHERE request_log_benefits.request_log_id = request_logs.id LIMIT 1) AS tot_bill + '), + 'claim_requests.status_claim_management as status', + ) + ->groupBy('claim_requests.id') + ->paginate($limit); + + + + return response()->json(Helper::paginateResources($results)); + } + public function detail($id) + { + $invoice['invoice_payments'] = DB::table('invoice_payments') + ->where('invoice_payments.id', $id) + ->select('invoice_payments.*') + ->get(); + + $invoice['invoice_payment_details'] = DB::table('invoice_payment_details') + ->leftJoin('claim_requests', 'claim_requests.id', '=', 'invoice_payment_details.claim_request_id') + ->leftJoin('request_logs', 'claim_requests.request_log_id','=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->where('invoice_payment_details.invoice_payment_id', $id) + ->whereNull('invoice_payment_details.deleted_by') + ->orWhere('invoice_payment_details.deleted_by', 0) + ->select( + 'claim_requests.id', + 'request_logs.invoice_no', + 'request_logs.id AS id_log', + 'request_logs.code AS code_log', + 'claim_requests.code as code', + 'members.name', + DB::raw(' + (SELECT members.member_id FROM members WHERE members.id = claim_requests.member_id LIMIT 1) AS member_id + '), + 'request_logs.created_at as submission_date', + 'request_logs.submission_date as addmission_date', + 'request_logs.discharge_date', + // DB::raw(' + // (SELECT plans.code FROM plans WHERE plans.id = member_plans.plan_id LIMIT 1) AS plan_code + // '), + DB::raw(' + (SELECT plans.code + FROM plans + WHERE plans.id IN ( + SELECT member_plans.plan_id + FROM member_plans + WHERE member_plans.member_id = claim_requests.member_id + ) + AND plans.service_code = claim_requests.service_code) AS plan_code + '), + DB::raw(' + (SELECT services.description FROM services WHERE services.code = claim_requests.service_code LIMIT 1) AS service_code + '), + DB::raw(' + (SELECT corporate_policies.code FROM corporate_policies WHERE corporate_policies.id = claim_requests.policy_id LIMIT 1) AS corporate_policies + '), + DB::raw(' + (SELECT organizations.name FROM organizations WHERE organizations.id = request_logs.organization_id LIMIT 1) AS provider + '), + DB::raw(' + (Select SUM(request_log_benefits.amount_approved) as tot_bill FROM request_log_benefits + WHERE request_log_benefits.request_log_id = request_logs.id LIMIT 1) AS tot_bill + '), + 'claim_requests.status_claim_management as status', + ) + ->groupBy('claim_requests.id') + ->get(); + $payments = DB::table('invoice_payments') + ->leftJoin('files', function ($join) { + $join->on('files.fileable_id', '=', 'invoice_payments.id') + ->where('files.type', 'files-proof-payment') + ->whereNull('files.deleted_by') + ->orWhere('files.deleted_by', 0); + }) + ->where('invoice_payments.invoice_number', $invoice['invoice_payments'][0]->invoice_number) + ->select( + 'invoice_payments.id', + 'files.id as file_id', + 'invoice_payments.amount_paid', + 'invoice_payments.payment_number', + DB::raw("CONCAT('" . env('APP_URL') . "/storage/', files.path) as path"), + 'files.original_name' + ) + ->orderBy('invoice_payments.payment_number', 'asc') + ->get(); + + $invoice['files'] = $payments->groupBy('payment_number')->map(function ($group) { + return [ + 'id' => $group->first()->id, + 'amount_paid' => $group->first()->amount_paid, + 'payment_number' => $group->first()->payment_number, + 'files' => $group->map(function ($file) { + return [ + 'file_id' => $file->file_id, + 'path' => $file->path, + 'original_name' => $file->original_name + ]; + })->toArray() + ]; + })->values()->toArray(); + + if ($invoice['invoice_payments']->isEmpty()) { + return response()->json(['message' => 'Invoice tidak ditemukan'], 404); + } + + return response()->json($invoice); + } + + public function create(Request $request) + { + $data = [ + 'invoice_number' => $request->invoice_number, + 'invoice_date' => $request->invoice_date, + 'start_date' => $request->start_date, + 'end_date' => $request->end_date, + 'payments' => $request->payments + ]; + $validator = Validator::make($request->all(), [ + 'invoice_number' => 'required', + 'invoice_date' => 'required', + 'start_date' => 'required', + 'end_date' => 'required', + 'payments' => 'required', + 'invoice_payment_details' => 'required', + ], [ + 'invoice_number.required' => trans('Validation.required',['attribute' => 'Nomor Invoice']), + 'invoice_date.required' => trans('Validation.required',['attribute' => 'Tanggal Invoice']), + 'start_date.required' => trans('Validation.required',['attribute' => 'Tanggal Mulai']), + 'end_date.required' => trans('Validation.required',['attribute' => 'Tanggal Akhir']), + 'payments.required' => trans('Validation.required',['attribute' => 'Jumlah Bayar']), + 'invoice_payment_details.required' => trans('Validation.required',['attribute' => 'Nomor Claim']), + ]); + if ($validator->fails()) + { + return response()->json(['message' => $validator->errors()], 400); + } + else + { + try { + DB::beginTransaction(); + $invoiceNumber = $request->invoice_number; + foreach ($request->payments as $valuePayments) { + if($request->param === 'Edit') + { + $invoiceNumber = DB::table('invoice_payments') + ->where('id', $valuePayments['id']) + ->value('invoice_number'); + } + // **Cek apakah invoice_number sudah ada di database** + $existingInvoice = DB::table('invoice_payments') + ->where('invoice_number', $invoiceNumber) + ->where('payment_number', $valuePayments['paymentNumber']) + // ->where('amount_paid', $this->normalizeCurrency($valuePayments['amount'])) + ->exists(); + + //Insert + if (!$existingInvoice) { + $lastInsertedId = DB::table('invoice_payments') + ->insertGetId([ + 'invoice_number' => $request->invoice_number, + 'payment_number' => $valuePayments['paymentNumber'], + 'invoice_date' => $request->invoice_date, + 'start_date' => $request->start_date, + 'end_date' => $request->start_date, + 'amount_paid' => $this->normalizeCurrency($valuePayments['amount']), + 'status' => 'submitted', + 'created_by' => auth()->user()->id, + 'created_at' => date('Y-m-d H:i:s'), + ]); + + foreach ($request->invoice_payment_details as $value) + { + // **Cek apakah claim_request_id sudah ada untuk invoice_payment_id ini** + $existingClaim = DB::table('invoice_payment_details') + ->where('claim_request_id', $value) + ->exists(); + + if (!$existingClaim) { + DB::table('invoice_payment_details') + ->insert([ + 'invoice_payment_id' => $lastInsertedId, + 'claim_request_id' => $value, + 'created_by' => auth()->user()->id, + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + } + if (!empty($valuePayments['files']) && is_array($valuePayments['files'])) { + foreach ($valuePayments['files'] as $file) { + $pathFile = File::storeFile('files-proof-payment', $lastInsertedId, $file); + File::updateOrCreate([ + 'fileable_type' => 'App\Models\InvoicePayment', + 'fileable_id' => $lastInsertedId, + 'type' => 'files-proof-payment', + 'name' => File::getFileName('files-proof-payment', $lastInsertedId, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + } + else + { + //Edit + $invoicePaymentId = DB::table('invoice_payments') + ->where('invoice_number', $invoiceNumber) + ->where('payment_number', $valuePayments['paymentNumber']) + ->value('id'); + + DB::table('invoice_payments') + ->where('id', $invoicePaymentId) + ->update([ + 'invoice_number' => $request->invoice_number, + 'invoice_date' => $request->invoice_date, + 'start_date' => $request->start_date, + 'end_date' => $request->end_date, + 'amount_paid' => $this->normalizeCurrency($valuePayments['amount']), + 'updated_by' => auth()->user()->id, + 'updated_at' => date('Y-m-d H:i:s'), + ]); + + $existingClaims = DB::table('invoice_payment_details') + ->where('invoice_payment_id', $invoicePaymentId) + ->pluck('claim_request_id') + ->toArray(); + + $newClaims = $request->invoice_payment_details; + + // Data yang mau di-insert + $claimsToInsert = array_diff($newClaims, $existingClaims); + foreach ($claimsToInsert as $claim) { + DB::table('invoice_payment_details')->insert([ + 'invoice_payment_id' => $invoicePaymentId, + 'claim_request_id' => $claim, + 'updated_by' => auth()->user()->id, + 'updated_at' => now(), + ]); + } + + // Data yang mau di-delete (tidak ada di data baru) + $claimsToDelete = array_diff($existingClaims, $newClaims); + if (!empty($claimsToDelete)) { + DB::table('invoice_payment_details') + ->where('invoice_payment_id', $invoicePaymentId) + ->whereIn('claim_request_id', $claimsToDelete) + ->update([ + 'deleted_by' => auth()->user()->id, + 'deleted_at' => now(), + ]); + } + + // Handle existing files + $existingFiles = DB::table('files') + ->where('files.fileable_id', $invoicePaymentId) + + ->where('files.type', 'files-proof-payment') + ->pluck('id') + ->toArray(); + + $newFiles = []; + if (!empty($valuePayments['existingFiles']) && is_array($valuePayments['existingFiles'])) { + $newFiles = array_column($valuePayments['existingFiles'], 'file_id'); + } + + + $filesToDelete = array_diff($existingFiles, $newFiles); + + if (!empty($filesToDelete)) { + DB::table('files') + ->whereIn('id', $filesToDelete) + ->update([ + 'deleted_by' => auth()->user()->id, + 'deleted_at' => now(), + ]); + } + + + //File New + if (!empty($valuePayments['files']) && is_array($valuePayments['files'])) { + foreach ($valuePayments['files'] as $file) { + $pathFile = File::storeFile('files-proof-payment', $invoicePaymentId, $file); + File::updateOrCreate([ + 'fileable_type' => 'App\Models\InvoicePayment', + 'fileable_id' => $invoicePaymentId, + 'type' => 'files-proof-payment', + 'name' => File::getFileName('files-proof-payment', $invoicePaymentId, $file), + 'original_name' => $file->getClientOriginalName(), + 'extension' => $file->getClientOriginalExtension(), + 'path' => $pathFile, + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + ]); + } + } + } + } + + + DB::commit(); + + return response()->json(['message' => 'Data berhasil disimpan'], 200); + } + catch (\Exception $e) { + DB::rollback(); + return response()->json(['message' => $e->getMessage()], 500); + } + } + } + + function normalizeCurrency($amount) { + return (int) preg_replace('/\D/', '', $amount); + } + + public function submitStatus(Request $request) + { + $request->validate([ + 'valueStatus' => 'required|in:submitted,accepted,partial_paid,paid,decline', + 'valueID' => 'required|integer|exists:invoice_payments,id', + ]); + + $update = DB::table('invoice_payments') + ->where('id', $request->valueID) + ->update([ + 'status' => $request->valueStatus, + 'updated_at' => now(), + 'updated_by' => auth()->user()->id, + ]); + + if (!$update) { + return response()->json(['message' => 'Data tidak ditemukan atau tidak diperbarui'], 404); + } + + return response()->json(['message' => 'Status berhasil diperbarui']); + } + + public function export(Request $request) + { + $start_date = $request->input('start_date') ? $request->input('start_date') : 'all'; + $end_date = $request->input('end_date') ? $request->input('end_date') : 'all'; + $writer = WriterEntityFactory::createXLSXWriter(); + $writer->openToFile(public_path('files/Report-Data-Invoice-Management-'.$start_date.'-'.$end_date.'.xlsx')); + $header = [ + 'No', + 'Nomor Invoice', + 'Pembayaran Ke', + 'Tanggal Invoice', + 'Start Date', + 'End Date', + 'Nominal Pembayaran', + 'Created At', + 'Updated At', + ]; + $style = (new StyleBuilder()) + ->setFontBold() + // ->setFontSize(15) + // ->setFontColor(Color::BLUE) + // ->setShouldWrapText() + ->setCellAlignment(CellAlignment::LEFT) + // ->setBackgroundColor(Color::YELLOW) + ->build(); + + $headerRow = WriterEntityFactory::createRowFromArray($header, $style); + $writer->addRow($headerRow); + // ============================ + $results = DB::table('invoice_payments') + // ->leftJoin('member_plans', 'member_plans.member_id', '=', 'members.id') + ->when($request->input('search'), function ($query, $search) { + $query->where(function ($query) use ($search) { + $query->orWhere('invoice_payments.invoice_number', 'like', "%" . $search . "%"); + }); + }) + ->when($request->has('orderBy'), function ($query) use ($request) { + $orderBy = $request->orderBy; + $direction = $request->order ?? 'asc'; + + $query->orderBy($orderBy, $direction); + }) + ->when($request->input('start_date') , function ($query, $start_date) { + $query->where(function ($query) use ($start_date) { + $query->where('claim_requests.created_at', '>=', $start_date); + }); + }) + ->when($request->input('end_date') , function ($query, $end_date) { + $query->where(function ($query) use ($end_date) { + $query->where('claim_requests.created_at', '<=', $end_date); + }); + }) + ->select( + 'invoice_payments.*') + ->get(); + $no=0; + $gr_total = 0; + $header = [ + 'No', + 'Nomor Invoice', + 'Pembayaran Ke', + 'Tanggal Invoice', + 'Start Date', + 'End Date', + 'Nominal Pembayaran', + 'Created At', + 'Updated At', + ]; + foreach($results as $item) + { + $gr_total += $item->amount_paid; + $no++; + $rowData = [ + $no, + $item->invoice_number, + $item->payment_number, + $item->invoice_date, + $item->start_date, + $item->end_date, + $item->amount_paid, + $item->created_at, + $item->updated_at + ]; + $style = (new StyleBuilder()) + //->setFontBold() + // ->setFontSize(15) + // ->setFontColor(Color::BLUE) + // ->setShouldWrapText() + ->setCellAlignment(CellAlignment::LEFT) + // ->setBackgroundColor(Color::YELLOW) + ->build(); + $row = WriterEntityFactory::createRowFromArray($rowData, $style); + $writer->addRow($row); + } + $footer = [ + 'Grand Total', + '', + '', + '', + '', + '', + $gr_total, + '', + '', + ]; + $style = (new StyleBuilder()) + ->setFontBold() + // ->setFontSize(15) + // ->setFontColor(Color::BLUE) + // ->setShouldWrapText() + ->setCellAlignment(CellAlignment::LEFT) + // ->setBackgroundColor(Color::YELLOW) + ->build(); + + $footerRow = WriterEntityFactory::createRowFromArray($footer, $style); + $writer->addRow($footerRow); + + $writer->close(); + + return Helper::responseJson([ + 'file_name' => 'Report-Data-Invoice-Management-'. $start_date.'-'.$end_date, + "file_url" => url('files/Report-Data-Invoice-Management-'. $start_date.'-'.$end_date.'.xlsx') + ]); + } + +} diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 61abb29d..0f6fde0a 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -8,6 +8,7 @@ use Modules\Internal\Http\Controllers\Api\BenefitController; use Modules\Internal\Http\Controllers\Api\CityController; use Modules\Internal\Http\Controllers\Api\ClaimController; use Modules\Internal\Http\Controllers\Api\ClaimRequestController; +use Modules\Internal\Http\Controllers\Api\InvoicePaymentController; use Modules\Internal\Http\Controllers\Api\KatalogDokterController; use Modules\Internal\Http\Controllers\Api\RequestLogController; use Modules\Internal\Http\Controllers\Api\RequestLogBenefitController; @@ -86,7 +87,7 @@ Route::prefix('internal')->group(function () { Route::get('drugs', [AutocompleteController::class, 'drugList']); Route::get('units', [AutocompleteController::class, 'unitList']); - + Route::get('signa', [AutocompleteController::class, 'signaList']); Route::post('signa-add', [AutocompleteController::class, 'signaAdd']); @@ -268,7 +269,15 @@ Route::prefix('internal')->group(function () { Route::post('claims/{claim_id}/encounters/{encounter_id}/update', [ClaimEncounterController::class, 'update']); Route::post('claims/{claim_id}/set-final-encounter', [ClaimEncounterController::class, 'setFinalEncounter']); + Route::post('invoice_payments/create', [InvoicePaymentController::class, 'create']); + Route::get('invoice_payments/list', [InvoicePaymentController::class, 'index']); + Route::post('invoice-payment/submit-status', [InvoicePaymentController::class, 'submitStatus']); + Route::get('invoice-payment/detail/{id}', [InvoicePaymentController::class, 'detail']); + Route::get('invoice-payment/claim', [InvoicePaymentController::class, 'claim']); + Route::get('invoice-payment/export', [InvoicePaymentController::class, 'export']); + Route::get('claims', [ClaimController::class, 'index']); + Route::post('claim-details', [ClaimController::class, 'getClaimDetails']); Route::get('claims-files-provider', [ClaimController::class, 'filesProvider']); Route::post('download-zip', [ClaimController::class, 'downloadZip']); Route::get('claims/download-template', [ClaimController::class, 'downloadTemplate']); diff --git a/app/Models/File.php b/app/Models/File.php index 9e43e0aa..6b029667 100755 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -53,6 +53,7 @@ class File extends Model 'docs' => 'docs/', 'additional-files' => 'additional-files/', 'chat' => 'chat/', + 'files-proof-payment' => 'files-proof-payment/', ]; public function fileable() diff --git a/database/migrations/2025_02_21_140114_create_invoice_payments_table.php b/database/migrations/2025_02_21_140114_create_invoice_payments_table.php new file mode 100644 index 00000000..eda3c236 --- /dev/null +++ b/database/migrations/2025_02_21_140114_create_invoice_payments_table.php @@ -0,0 +1,40 @@ +id(); + $table->string('invoice_number'); + $table->string('payment_number'); + $table->date('invoice_date'); + $table->date('start_date'); + $table->date('end_date'); + $table->decimal('amount_paid', 15, 2); + $table->string('status')->comment('submitted = Pengajuan, accepted = Belum Dibayar, partial_paid = Bayar Sebagian, paid = Sudah Dibayar'); + $table->bigInteger('created_by'); + $table->bigInteger('updated_by'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('invoice_payments'); + } +}; diff --git a/database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php b/database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php new file mode 100644 index 00000000..3248584d --- /dev/null +++ b/database/migrations/2025_02_21_141523_create_invoice_payment_details_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('invoice_payment_id')->constrained('invoice_payments')->onDelete('cascade'); + $table->foreignId('claim_request_id')->constrained('claim_requests')->onDelete('cascade'); + $table->bigInteger('created_by'); + $table->bigInteger('updated_by'); + $table->bigInteger('deleted_by'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('invoice_payment_details'); + } +}; diff --git a/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx b/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx new file mode 100644 index 00000000..2e7beccc --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx @@ -0,0 +1,1573 @@ +// @mui +import { + Box, + Grid, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TableContainer, + Paper, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Tooltip, + TableHead, + Checkbox, + InputAdornment, + TableSortLabel, + FormControl, + Divider, + ButtonBase + } from '@mui/material'; + import { visuallyHidden } from '@mui/utils'; + import { fPostFormat } from '@/utils/formatTime'; + import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; + +import { makeFormData } from '@/utils/jsonToFormData'; + +import { fNumber } from '@/utils/formatNumber'; + + import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; + import { fDateOnly } from '@/utils/formatTime'; + +import { format } from 'date-fns'; + + import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + import AssessmentIcon from '@mui/icons-material/Assessment'; + // hooks + import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; + import { Link, Navigate, useParams, useNavigate, useSearchParams } from 'react-router-dom'; + + import { LoadingButton } from '@mui/lab'; + // 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 { Chip } from '@mui/material'; + import Iconify from '@/components/Iconify'; + import { enqueueSnackbar } from 'notistack'; + import { fDate, fDateTime } from '../../utils/formatTime'; + import { Claims } from '@/@types/claims'; + import Label from '@/components/Label'; + import { capitalizeFirstLetter } from '@/utils/formatString'; + import TableMoreMenu from '@/components/table/TableMoreMenu'; + import Edit from '@mui/icons-material/Edit'; + import { Download } from '@mui/icons-material'; + import { Add, Search } from '@mui/icons-material'; + import Autocomplete from '@mui/material/Autocomplete'; + + import DownloadIcon from '@mui/icons-material/Download'; + + import UploadIcon from '@mui/icons-material/Upload'; + import CancelIcon from '@mui/icons-material/Cancel'; + import CheckCircleIcon from '@mui/icons-material/CheckCircle'; + + import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; + import CloseIcon from '@mui/icons-material/Close'; + +import { DatePicker, MobileDatePicker } from '@mui/x-date-pickers'; + + + export default function List() { + const [selectAll, setSelectAll] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + + const [providers, setProviders] = useState(null); + // const [searchText, setSearchText] = useState(''); + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('created_at'); + const [perPage, setPerPage] = useState(0); + const [invoicePayment, setInvoicePayment] = useState([]); + const [invoicePaymentDetail, setInvoicePaymentDetail] = useState([]); + const [invoicePaymentFile, setInvoicePaymentFile] = useState([]); + + const { invoiceID } = useParams(); + + useEffect(() => { + if (!invoiceID) return; + axios + .get('invoice-payment/detail/'+invoiceID) + .then((response) => { + setInvoicePayment(response.data.invoice_payments); + setInvoicePaymentDetail(response.data.invoice_payment_details); + setInvoicePaymentFile(response.data.files); + }) + .catch((error) => { + console.error(error); + }); + }, [invoiceID]); + + const handleSelectAll = () => { + setSelectAll(!selectAll); + if (!selectAll) { + const requestedIds = dataTableData.data + .filter(row => row.status === 'approved') + .map(row => row.id); + setSelectedRows(requestedIds); + } else { + setSelectedRows([]); + } + }; + + const handleRowSelect = (id) => { + if (selectedRows.includes(id)) { + setSelectedRows(selectedRows.filter(rowId => rowId !== id)); + } else { + setSelectedRows([...selectedRows, id]); + } + }; + + const [searchParams, setSearchParams] = useSearchParams(); + const [startDate, setStartDate] = useState(null); + const [searchText, setSearchText] = useState(''); + const [endDate, setEndDate] = useState(null); + const navigate = useNavigate(); + const [dataProvider, setDataProvider] = useState(null); + + useEffect(() => { + if (startDate !== null || endDate !== null || dataProvider !== null + || order !== null || orderBy !== null || perPage !== 0) { + loadDataTableData(); + getProvider(); + } + }, [startDate, endDate, dataProvider, order, orderBy, perPage]); + + useEffect(() => { + if (dataProvider) { + const newParams = new URLSearchParams(searchParams); + newParams.set("provider_id", dataProvider); // Set query param + navigate(`invoice-payment/claim?${newParams.toString()}`, { replace: true }); // Update URL + } + }, [dataProvider, navigate, searchParams]); + + const [isLoading, setIsLoading] = useState(false); + const [isLoadingImport, setIsLoadingImport] = useState(false); + const handleExportReport = async () => { + + + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + + + var filter = Object.fromEntries([...searchParams.entries()]); + setIsLoading(true) + await axios + .get('/claims/export-claim-management',{ + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + order: order, + orderBy: orderBy, + page: perPage, + } + }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({ search: searchText }); // Trigger to Parent + }; + + const handleGetData = (type :string) => { + axios.get(`claims/1/data-claim`) + .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(); + }) + } + + useEffect(() => { + // Trigger First Search + // setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + + +
+ ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + + return ( +
+ + + {/* */} + +
+ ); + } + + const searchInput = useRef(null); + + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + loadDataTableData(); + }; + + + + useEffect(() => { + // Trigger First Search + //setSearchText(searchText); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + // const handleClick = () => { + + // } + + + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('invoice-payment/claim', { + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + order: order, + param: invoiceID ? 'Edit' : 'Add', + invoiceID: invoiceID, + orderBy: orderBy, + page: perPage, + } + }); + + setDataTableLoading(false); + + setDataTableData(response.data); + }; + const id = searchParams.get("provider_id"); + const provider_id = id ? parseInt(id, 10) : null; + const getProvider = async () => { + const response = await axios.get('/claims/get-provider'); + setProviders(response.data); + var id = response.data[0].id; + // if(provider_id) + // { + // id = provider_id; + // } + // setDataProvider(dataProvider); + + + + } + + // if(provider_id) { + // setDataProvider(provider_id); + // } + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + setPerPage(value); + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [approve, setApprove] = useState(''); + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const handleChooseClaim = () => { + setOpenDialogSubmit(false); + handleGetClaimDetails(); + }; + + const handleSubmitData = () => { + setIsLoading(true) + + //approve or decline + if(noInvoice == '') + { + setIsLoading(false) + enqueueSnackbar('Mohon isi nomor invoice terlebih dahulu', { variant: 'warning' }); + return false; + } + const formData = makeFormData({ + invoice_number: noInvoice, + invoice_payment_details: selectedRows, + invoice_date: fPostFormat(tglInvoice), + start_date: fPostFormat(tglMulai), + end_date: fPostFormat(tglAkhir), + payments: payments, + param: invoiceID ? 'Edit' : 'Add', + invoiceID: invoiceID, + + }); + axios + .post('invoice_payments/create', formData) + .then((response) => { + setIsLoading(false) + enqueueSnackbar(response.data.message, { variant: 'success' }); + setTimeout(() => { + window.location.reload(); + }, 1000); + }) + .catch(({ response }) => { + setIsLoading(false) + enqueueSnackbar(response.data.message.invoice_payment_details[0], { variant: 'error' }); + }) + .then(() => { + }); + // console.log(formData); + // if (!reasonDecline && approve == 'decline') { + // enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + // return false; + // } + // Promise.all(selectedRows.map(send_bulk)) + // .then(() => { + // enqueueSnackbar('All requests processed successfully', { variant: 'success' }); + // setOpenDialogSubmit(false); + // }) + // .catch((error) => { + // enqueueSnackbar(error.response?.data?.message ?? 'Something went wrong!', { variant: 'error' }); + // }); + }; + + function send_bulk(id) { + return true; + } + + + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importClaimManagement = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + const [importResult, setImportResult] = useState(null); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const handleImportButton = () => { + if (importClaimManagement?.current) { + handleClose(); + importClaimManagement.current ? importClaimManagement.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + const handleCancelImportButton = () => { + if(importClaimManagement.current) + { + importClaimManagement.current.value = ''; + importClaimManagement.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(importClaimManagement.current && importClaimManagement.current.files) + { + if (importClaimManagement.current?.files.length) { + const formData = new FormData(); + formData.append('file', importClaimManagement.current?.files[0]); + setImportLoading(true); + axios + .post('claims/import', formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + setImportLoading(false); + enqueueSnackbar('Success Import Claim Managemenet', { variant: 'success' }); + }) + .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 = () => { + axios.get('claims/download-template').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 handleExportReportFiled = async () => { + + await axios + .post('claims/exportFiled', { params: importResult?.data.result_rows }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + // useEffect(() => { + // loadDataTableData(); + // getProvider(); + // }, []); + + const headStyle = { + fontWeight: 'bold', + }; + const headCells = [ + { + id: 'code', + align: 'left', + label: 'Code / Code LOG', + isSort: true, + }, + { + id: 'name', + align: 'left', + label: 'Name', + isSort: false, + }, + + { + id: 'member_id', + align: 'left', + label: 'Member ID', + isSort: false, + }, + { + id: 'created_at', + align: 'left', + label: 'Date Submission', + isSort: true, + }, + { + id: 'plan_code', + align: 'left', + label: 'Plan ID', + isSort: true, + }, + { + id: 'service_code', + align: 'left', + label: 'Service', + isSort: false, + }, + { + id: 'corporate_policies', + align: 'left', + label: 'Policy Number', + isSort: true, + }, + { + id: 'provider', + align: 'left', + label: 'Provider', + isSort: false, + }, + { + id: 'tot_bill', + align: 'left', + label: 'Total Billing', + isSort: false, + }, + { + id: 'status', + align: 'left', + label: 'Status', + isSort: false, + }, + { + id: 'action', + align: 'left', + label: '', + isSort: false, + }, + ]; + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + }; + // Called on every row to map the data to the columns + function createData(data: Claims): Claims { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType, isSelected: boolean, onSelect: (id: string) => void }) { + const { row, isSelected, onSelect } = props; + // Memperbaiki destrukturisasi props + + const handleRowCheckboxChange = () => { + onSelect(row.id); // Panggil fungsi onSelect dari komponen induk dengan id baris saat checkbox di baris diklik + }; + + const [open, setOpen] = React.useState(false); + + const test = 1000; + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */} + + {row?.status == 'approved' ? ( + + ):''} + + {row?.code} / {row.code_log} + {/* {row.code} */} + {row?.name} + {row?.member_id} + {row?.created_at ? fDateTime(row?.created_at) : ''} + {row?.plan_code} + {row?.service_code} + {row?.corporate_policies} + {row?.provider} + Rp. {row?.tot_bill?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".")} + + {row.status == 'draft' && ()} + {row.status == 'requested' && ()} + {row.status == 'received' && ()} + {row.status == 'approved' && ()} + {row.status == 'postpone' && ()} + {row.status == 'paid' && ()} + {row.status == 'declined' && ()} + + + + + navigate('/claims/detail/'+row.id_log+'/'+row.id+'') }> + + Detail + + + } /> + + + + + + {/* COLLAPSIBLE ROW */} + + + + {/* + + Description : {row.description} + + */} + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + + + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {selectedRows.length > 0 ? ( + <> + + + + {selectedRows.length > 0 ? selectedRows.length : '0'}  Selected + + + + + + + {/* */} + + + {/* */} + + + + ) : ( + <> + + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + )} + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + Data tidak ditemukan, silahkan pilih provider lainnya + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + // Data contoh + + const [dataLogs, setDataLogs] = useState([]); + +const handleGetClaimDetails = async () => { + if (!selectedRows.length) { + console.warn("No selected rows"); + setDataLogs([]); + return; + } + + try { + const response = await axios.post('/claim-details', { ids: selectedRows }); + const claimDetails = response.data; + + // Map the response data into the new format and set state + const logs = claimDetails.map((item) => { + const formatDate = (dateString) => { + if (dateString && !isNaN(new Date(dateString).getTime())) { + return fDateTime(dateString.split(" ")[0]); + } + return '-'; + }; + + return { + code: item.code_log, + dateSubmission: formatDate(item.submission_date), + dateAdmission: formatDate(item.addmission_date), + dateDischarge: formatDate(item.discharge_date), + provider: item.provider, + memberName: item.name, + policyNumber: item.corporate_policies, + totalBilling: item.tot_bill || 0, + }; + }); + + // Update state + setDataLogs(logs); + + console.log("Claim Details:", logs); + } catch (error) { + console.error("Error fetching claim details:", error); + } +}; + + + + // Hitung Grand Total + const grandTotal = dataLogs.reduce((sum, item) => sum + item.totalBilling, 0); + const [fileBukti, setFileBukti] = useState([]); + const removeBuktiFiles = (filesState:any, index:any) => { + setFileBukti( + filesState.filter((file:any, fileIndex:any) => { + return fileIndex != index; + }) + ); + }; +// const fileBuktiInput = useRef(null); + const handleBuktiInputChange = (event:any) => { + if (event.target.files[0]) { + setFileBukti([...fileBukti, ...event.target.files]); + } else { + console.log('NO FILE'); + } + }; + + const [jmlBayar, setJmlBayar] = useState(""); + const [noInvoice, setNoInvoice] = useState(''); + + useEffect(() => { + if (invoicePayment[0]?.invoice_number) { + setNoInvoice(invoicePayment[0].invoice_number); + } + }, [invoicePayment]); + + useEffect(() => { + // Ambil semua id dari invoicePaymentDetail + const ids = invoicePaymentDetail.map(item => item.id); + setSelectedRows(ids); + }, [invoicePaymentDetail]); + + // Gunakan useEffect lain untuk menjalankan handleGetClaimDetails setelah selectedRows berubah + useEffect(() => { + if (selectedRows.length > 0) { + handleGetClaimDetails(); + } + }, [selectedRows]); + + + + + const [noTagihan, setNoTagihan] = useState(""); + + const [tglBayar, setTglBayar] = useState(format(new Date(), "yyyy MMM d HH:mm:ss")); + const [tglMulai, setTglMulai] = useState(format(new Date(), "yyyy MMM d")); + useEffect(() => { + if (invoicePayment[0]?.start_date) { + setTglMulai(format(new Date(invoicePayment[0].start_date), "yyyy MMM d")); + } + }, [invoicePayment]); + const [tglInvoice, setTglInvoice] = useState(format(new Date(), "yyyy MMM d")); + useEffect(() => { + if (invoicePayment[0]?.invoice_date) { + setTglInvoice(format(new Date(invoicePayment[0].invoice_date), "yyyy MMM d")); + } + }, [invoicePayment]); + const [tglAkhir, setTglAkhir] = useState(format(new Date(), "yyyy MMM d")); + useEffect(() => { + if (invoicePayment[0]?.end_date) { + setTglAkhir(format(new Date(invoicePayment[0].end_date), "yyyy MMM d")); + } + }, [invoicePayment]); + + const [payments, setPayments] = useState([{ id: Date.now(), amount: "", existingFiles: [], files: [], paymentNumber: 1 }]); + const fileInputRefs = useRef({}); + + useEffect(() => { + if (invoicePaymentFile?.length) { + const mappedPayments = invoicePaymentFile.map((payment) => ({ + id: payment.id, + amount: fCurrency(parseFloat(payment.amount_paid)), + existingFiles: Array.isArray(payment.files) ? payment.files : [], + files: [], + paymentNumber: payment.payment_number + })); + setPayments(mappedPayments); + } + }, [invoicePaymentFile]); + + + const handleAddPayment = () => { + const newId = Date.now(); + setPayments([...payments, { id: newId, amount: "", existingFiles: [], files: [], paymentNumber: payments.length + 1 }]); + fileInputRefs.current[newId] = null; + }; + + const handleAmountChange = (index, value) => { + const updatedPayments = [...payments]; + updatedPayments[index].amount = fCurrency(value); + setPayments(updatedPayments); + }; + + const handleFileChange = (index, event) => { + const newFiles = Array.from(event.target.files); + const updatedPayments = [...payments]; + updatedPayments[index].files = [...updatedPayments[index].files, ...newFiles]; + setPayments(updatedPayments); + }; + + const handleRemoveFile = (paymentIndex, fileIndex, isNew = false) => { + const updatedPayments = [...payments]; + if (isNew) { + updatedPayments[paymentIndex].files.splice(fileIndex, 1); + } else { + updatedPayments[paymentIndex].existingFiles.splice(fileIndex, 1); + } + setPayments(updatedPayments); + }; + + const handleRemovePayment = (index) => { + const updatedPayments = [...payments]; + updatedPayments.splice(index, 1); + setPayments(updatedPayments); + }; + + const totalAmount = payments.reduce((sum, payment) => { + const num = parseFloat(payment.amount.replace(/[^\d]/g, "")) || 0; + return sum + num; + }, 0); + + + return ( + <> + + navigate(`/invoice-payment/`)} + sx={{ cursor: 'pointer' }} + /> + + { (invoiceID ? 'Edit Invoice' : 'Create Invoice')} + + + + + + + Tanggal Invoice + + + { + setTglInvoice(newValue); + }} + inputFormat="dd-MM-yyyy" + renderInput={(params) => } + /> + + + + + + Nomor Invoice * + setNoInvoice(event.target.value)} + /> + + {/* + Tanggal Bayar + + { + setTglBayar( (newValue)); + }} + inputFormat="dd-MM-yyyy HH:mm" + renderInput={(params) => } + /> + + */} + + Periode Pembayaran + + { + setTglMulai( (newValue)); + }} + inputFormat="dd-MM-yyyy" + renderInput={(params) => } + /> + + + +   + + { + setTglAkhir( (newValue)); + }} + inputFormat="dd-MM-yyyy" + renderInput={(params) => } + /> + + + + + + Claim + setOpenDialogSubmit(true)} + > + + { !selectedRows.length ? 'Add Claim' : 'Edit Claim'} + + + + + + + Code LOG + Invoice Provider + Date Submission + Date Admission + Date Discharge + Provider + Member Name + Policy Number + Total Billing + + + + {dataLogs.map((row, index) => ( + + {row.code} + {row.invoice_no} + {row.dateSubmission} + {row.dateAdmission} + {row.dateDischarge} + {row.provider} + {row.memberName} + {row.policyNumber} + {fCurrency(row.totalBilling)} + + ))} + {/* Grand Total */} + + Grand Total + {fCurrency(grandTotal)} + + +
+
+ + + Catatan Pembayaran + + + Add Payment + + + + {payments.map((payment, index) => ( + + + Pembayaran {payment.paymentNumber} + {payments.length > 1 && !invoiceID ? ( + handleRemovePayment(index)} sx={{ color: "darkred" }}> + + + ) : ''} + + + {/* Input Jumlah Bayar */} + + + Nominal Pembayaran + handleAmountChange(index, e.target.value)} + /> + + + {/* Input File Bukti */} + + Upload Bukti Pembayaran + + } spacing={1}> + {payment.existingFiles.map((file, fileIndex) => + file.original_name && ( // No need for ternary, just use short-circuiting + + {file.original_name} + handleRemoveFile(index, fileIndex)} sx={{ color: "darkred" }}> + + + + ) + )} + + {payment.files.map((file, fileIndex) => ( + + {file.name} + handleRemoveFile(index, fileIndex, true)} sx={{ color: "darkred" }}> + + + + ))} + + + fileInputRefs.current[payment.id]?.click()} + > + + + Tambah File + + + + (fileInputRefs.current[payment.id] = el)} + style={{ display: "none" }} + multiple + onChange={(e) => handleFileChange(index, e)} + accept="application/pdf,image/*" + /> + + + + + ))} + + Jumlah Tagihan + + {fCurrency(grandTotal)} + + + {/* Total Jumlah Bayar */} + + Total Dibayar + + {fCurrency(totalAmount.toString())} + + + + Sisa Pembayaran + + {fCurrency((grandTotal - totalAmount))} + + + + {/* Submit Button */} + } + loading={isLoading} + > + Submit Pembayaran ({fCurrency(totalAmount.toString())}) + + + + {/* + + Nomor LOG * + + {nomorLogs.map((log, index) => ( + + {log} + + ))} + + + + + + Nomor LOG * + + {nomorLogs.map((log, index) => ( + + {log} + + ))} + + + */} +
+ + + + + Daftar Claim + + + + + + + + + +
+ + + {!currentImportFileName && ( + <> + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search Code or Name', + }} + /> + + + + { + + // loadDataTableData(); + setStartDate(value); + }} + renderInput={(params) => } + /> + + + + { + setEndDate(value); + }} + renderInput={(params) => ( + + )} + /> + + + + { + providers && ( + option.name || ''} + value={providers.find((item) => item.id === dataProvider) || null} + onChange={(event, value) => { + if (value) { + setDataProvider(value.id); + } else { + setDataProvider(null); + } + }} + renderInput={(params) => ( + + )} + /> + ) + } + + + + } + sx={{ p: 1.8 }} + loading={isLoadingImport} + onClick={handleClick} + > + + Import + + + + + Import + + { + handleGetTemplate(); + }} + > + Download Template + + + + + } + sx={{ p: 1.8 }} + onClick={handleExportReport} + loading={isLoading} + > + + Export + + + + + + )} + {currentImportFileName && ( + + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + + )} + {importResult && ( + + + Last Import Result :{' '} + + {importResult.data.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.data.total_failed_row} + {' '} + Failed, + {/* {importResult.data.failed_rows.map((row, index) => ( + [Code={row.code ? row.code : 'Required'}] + ))} */} +  Report: +  Download Data Result Import + + + )} + +
+
+
+ + } + /> + +
+ + + + +
+
+ + + ); + } diff --git a/frontend/dashboard/src/pages/InvoicePayment/Detail.tsx b/frontend/dashboard/src/pages/InvoicePayment/Detail.tsx new file mode 100644 index 00000000..aa2c9a19 --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/Detail.tsx @@ -0,0 +1,323 @@ +import { + Container, + Grid, + Stack, + Typography, + Card, + TableRow, + Tab, + TableCell, + Collapse, + AccordionSummary, + AccordionDetails, + IconButton, + TextField + } from '@mui/material'; + import { fCurrency } from '../../utils/formatNumber'; + import { Table, TableBody, TableContainer, TableHead, Paper } from '@mui/material'; +// components +import Page from '@/components/Page'; +// utils +import useSettings from '@/hooks/useSettings'; +// react +import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useEffect, useState, useRef, useMemo } from 'react'; +import axios from '@/utils/axios'; +// pages +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; +import { fDate, fDateTimesecond, fDateTime } from '@/utils/formatTime'; +import { Button } from '@mui/material'; +import Label from '@/components/Label'; +import { Box } from '@mui/system'; +import { Accordion } from '@mui/material'; +import { Delete, EditOutlined, ExpandMore } from '@mui/icons-material'; +import AddIcon from '@mui/icons-material/Add'; + +import MoreMenu from '@/components/MoreMenu'; +import { MenuItem } from '@mui/material'; +import { fNumber } from '@/utils/formatNumber'; +import palette from '@/theme/palette'; + +import CloseIcon from '@mui/icons-material/Close'; +import { enqueueSnackbar } from 'notistack'; +import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; +import { List, ListItem, Link, Divider } from '@mui/material'; + + + +// ---------------------------------------------------------------------- + +export default function Detail() { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + + const navigate = useNavigate(); + const { themeStretch } = useSettings(); + const [invoicePayment, setInvoicePayment] = useState([]); + const [invoicePaymentDetail, setInvoicePaymentDetail] = useState([]); + const [invoicePaymentFile, setInvoicePaymentFile] = useState([]); + const [statusClaim, setStatusClaim] = useState(''); + const [dataMember, setDataMember] = useState(''); + + + const { id } = useParams(); + + useEffect(() => { + axios + .get('invoice-payment/detail/'+id) + .then((response) => { + setInvoicePayment(response.data.invoice_payments); + setInvoicePaymentDetail(response.data.invoice_payment_details); + setInvoicePaymentFile(response.data.files); + }) + .catch((error) => { + console.error(error); + }); + }, [id]); + + console.log(invoicePayment); + console.log(invoicePaymentDetail); + console.log(invoicePaymentFile); + + const style1 = { + color: '#919EAB', + width: '30%' + } + const style3 = { + color: '#919EAB', + width: '35%' + } + const style2 = { + width: '70%' + } + const marginBottom1 = { + marginBottom: 1, + } + const marginBottom2 = { + marginBottom: 2, + } + + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + const [decline, setDeclaine] = useState(''); + + // const handleSubmitData = () => { + // //approve or decline + // if(!reasonDecline && approve == 'decline') + // { + // enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + // return false; + // } + // axios + // .post('claims/'+id_claim+'/'+approve, {reasonDecline:reasonDecline}) + // .then((response) => { + // enqueueSnackbar('Success '+toTitleCase(approve)+' Claim Request', { variant: 'success' }); + // setOpenDialogSubmit(false); + // // window.location.reload(); + + // }) + // .catch(({ response }) => { + // enqueueSnackbar(response.data.message ?? 'Something went wrong!', { variant: 'error' }); + // }); + + // setTimeout(() => + // { + // window.location.reload(); + // }, 5000); + + // }; + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const [openDialogEditDetail, setDialogDEditDetail] = useState(false); + const [openDialogBenefit, setDialogBenefit] = useState(false); + const [openDialogMedicine, setDialogMedicine] = useState(false); + + // Handel Delete Detail Benefit + const [idBenefitData, setIdBenefitData] = useState(); + const [openDialogDeleteBenefit, setDialogDeleteBenefit] = useState(false) + + const [idMedicineData, setIdMedicineData] = useState(); + const [openDialogDeleteMedicine, setDialogDeleteMedicine] = useState(false) + + const [approve, setApprove] = useState('') + + // Handle Edit Detail Benefit + const [openDialogEditBenefit, setDialogEditBenefit] = useState(false) + const [BenefitConfigurationData, setBenefitConfigurationData] = useState(); + + // Buat total data + + + // Handle Delete File LOG + const [pathFile, setPathFile] = useState('') + const [dialogDeleteFIleLog, setDialogDeleteFileLog] = useState(false) + + // Handle Upload File LOG + const [dialogUploadFileLog, setDialogUploadFileLog] = useState(false) + + const totalAmount = invoicePaymentFile.reduce((sum, payment) => { + const num = parseFloat(payment.amount_paid) || 0; + return parseFloat(sum) + parseFloat(num); + }, 0); + const grandTotal = invoicePaymentDetail.reduce((sum, item) => sum + parseFloat(item.tot_bill ? item.tot_bill : 0), 0); + + return ( + + + + navigate(-1)} sx={{cursor:'pointer'}}/> + + {/* {invoicePayment.length > 0 ? invoicePayment[0].invoice_number : 'Detail'} */} + Detail Invoice + + + + {/* Detail */} + + + + + + Detail + + + + + Tanggal Invoice + {invoicePayment.length > 0 ? fDate(invoicePayment[0].invoice_date) : '-'} + + + Nomor Invoice + {invoicePayment.length > 0 ? invoicePayment[0].invoice_number : '-'} + + + Periode Pembayaran + {invoicePayment.length > 0 ? fDate(invoicePayment[0].start_date) : '-'}-{invoicePayment.length > 0 ? fDate(invoicePayment[0].end_date) : '-'} + + + + + + {/* Benefit */} + + + + Claim + + + + + + Code Claim/Code Log + Invoice No + Name + Member ID + Policy Number + Provider + Total Bill + + + + {invoicePaymentDetail.map((detail) => ( + + {detail.code} / {detail.code_log} + {detail.invoice_no || '-'} + {detail.name} + {detail.member_id} + {detail.corporate_policies} + {detail.provider} + {fCurrency(detail.tot_bill)} + + ))} + +
+
+
+ + + +
+ + {/* Catatan Pembayaran */} + + + + Catatan Pembayaran + + + {invoicePaymentFile.map((file, index) => ( + + + Pembayaran {file.payment_number} + + + {/* Input Jumlah Bayar */} + + + Nominal Pembayaran + {fCurrency(parseFloat(file.amount_paid))} + + + {/* Input File Bukti */} + + Bukti Pembayaran + + } spacing={1}> + {file.files.map((file1, fileIndex) => ( + + + {file1.original_name ? file1.original_name : '-'} + + + ))} + + + + + + ))} + + Jumlah Tagihan + + {fCurrency(grandTotal)} + + + {/* Total Jumlah Bayar */} + + Total Dibayar + + {fCurrency(totalAmount.toString())} + + + + Sisa Pembayaran + + {fCurrency((grandTotal - totalAmount))} + + + + +
+
+
+ ); +} diff --git a/frontend/dashboard/src/pages/InvoicePayment/Index.tsx b/frontend/dashboard/src/pages/InvoicePayment/Index.tsx new file mode 100644 index 00000000..b3fc9cc7 --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/Index.tsx @@ -0,0 +1,30 @@ +import { Card, Stack } from "@mui/material"; +import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs"; +import Page from "../../components/Page"; +import List from "./List"; + + + +export default function Invoice() { + + const pageTitle = 'Invoice Management'; + return ( + + + + + {/* */} + + {/* */} + + ); +} diff --git a/frontend/dashboard/src/pages/InvoicePayment/List.tsx b/frontend/dashboard/src/pages/InvoicePayment/List.tsx new file mode 100644 index 00000000..8074f438 --- /dev/null +++ b/frontend/dashboard/src/pages/InvoicePayment/List.tsx @@ -0,0 +1,1063 @@ +// @mui +import { + Box, + Grid, + Button, + Card, + Collapse, + IconButton, + MenuItem, + Table, + TableBody, + TableCell, + TableRow, + TextField, + Typography, + Stack, + Menu, + ButtonGroup, + Tooltip, + TableHead, + Checkbox, + InputAdornment, + TableSortLabel, + FormControl + } from '@mui/material'; +import { ClearOutlined, LoopOutlined } from "@mui/icons-material"; + import { visuallyHidden } from '@mui/utils'; + + import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; + import { fDateOnly } from '@/utils/formatTime'; + + import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; + import FindInPageOutlinedIcon from '@mui/icons-material/FindInPageOutlined'; + import AssessmentIcon from '@mui/icons-material/Assessment'; + // hooks + import React, { ChangeEvent, useEffect, useRef, useState } from 'react'; + import { Link, Navigate, useNavigate, useSearchParams } from 'react-router-dom'; + + import { LoadingButton } from '@mui/lab'; + // 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 { Chip } from '@mui/material'; + import Iconify from '@/components/Iconify'; + import { enqueueSnackbar } from 'notistack'; + import { fDate, fDateTime } from '../../utils/formatTime'; + import { Claims } from '@/@types/claims'; + import Label from '@/components/Label'; + import { capitalizeFirstLetter } from '@/utils/formatString'; + import TableMoreMenu from '@/components/table/TableMoreMenu'; + import Edit from '@mui/icons-material/Edit'; + import { Download } from '@mui/icons-material'; + import { Add, Search } from '@mui/icons-material'; + import Autocomplete from '@mui/material/Autocomplete'; + + import DownloadIcon from '@mui/icons-material/Download'; + + import UploadIcon from '@mui/icons-material/Upload'; + import CancelIcon from '@mui/icons-material/Cancel'; + import CheckCircleIcon from '@mui/icons-material/CheckCircle'; + + import { Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'; + import CloseIcon from '@mui/icons-material/Close'; + import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; + + + export default function List() { + const [selectAll, setSelectAll] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + const [providers, setProviders] = useState(null); + const [filterData, setStatusData] = useState([]); + // const [searchText, setSearchText] = useState(''); + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('created_at'); + const [perPage, setPerPage] = useState(0); + + const handleChange = (event, newValue) => { + // Jika newValue tidak undefined, atur nilai dataProvider + if (newValue !== undefined) { + setDataProvider(newValue.service_code); + } else { + // Jika tidak ada yang dipilih, set dataProvider menjadi string kosong + setDataProvider(null); + } + }; + + + + const handleSelectAll = () => { + setSelectAll(!selectAll); + if (!selectAll) { + const requestedIds = dataTableData.data + .filter(row => row.status === 'received') // Memfilter baris dengan status 'requested' + .map(row => row.id); // Mengambil hanya ID dari baris-baris yang memenuhi kondisi + setSelectedRows(requestedIds); + } else { + setSelectedRows([]); + } + }; + + const handleRowSelect = (id) => { + if (selectedRows.includes(id)) { + setSelectedRows(selectedRows.filter(rowId => rowId !== id)); + } else { + setSelectedRows([...selectedRows, id]); + } + }; + + const [searchParams, setSearchParams] = useSearchParams(); + const [startDate, setStartDate] = useState(null); + const [searchText, setSearchText] = useState(''); + const [endDate, setEndDate] = useState(null); + const navigate = useNavigate(); + const [dataProvider, setDataProvider] = useState(null); + const [dataStatus, setDataStatus] = useState(null); + + useEffect(() => { + if (startDate !== null || endDate !== null || dataProvider !== null || dataStatus !== null + || order !== null || orderBy !== null || perPage !== 0) { + loadDataTableData(); + getProvider(); + } + }, [startDate, endDate, dataProvider, dataStatus, order, orderBy, perPage]); + + const [isLoading, setIsLoading] = useState(false); + const [isLoadingImport, setIsLoadingImport] = useState(false); + const handleExportReport = async () => { + + + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + + + var filter = Object.fromEntries([...searchParams.entries()]); + setIsLoading(true) + await axios + .get('/invoice-payment/export',{ + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + status: dataStatus, + order: order, + orderBy: orderBy, + page: perPage, + } + }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + props.onSearch({ search: searchText }); // Trigger to Parent + }; + + const handleGetData = (type :string) => { + axios.get(`claims/1/data-claim`) + .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(); + }) + } + + useEffect(() => { + // Trigger First Search + // setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + + +
+ ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + + return ( +
+ + + {/* */} + +
+ ); + } + + const searchInput = useRef(null); + + + //handle search + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + const handleSearchSubmit = (event: any) => { + event.preventDefault(); + loadDataTableData(); + }; + + + + useEffect(() => { + // Trigger First Search + //setSearchText(searchText); + }, []); + + const item = [ + { + id: '', + value: '', + name: 'Semua', + }, + ]; + + // const handleClick = () => { + + // } + + + + // Dummy Default Data + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + + + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const year = startDate?.getFullYear(); + const month = (startDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day = startDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate = year && month && day ? `${year}-${month}-${day}` : ''; + + const year1 = endDate?.getFullYear(); + const month1 = (endDate?.getMonth() + 1).toString().padStart(2, '0'); // Tambahkan 1 karena bulan dimulai dari 0, dan padStart untuk memastikan 2 digit + const day1 = endDate?.getDate().toString().padStart(2, '0'); // padStart untuk memastikan 2 digit + + const formattedDate1 = year1 && month1 && day1 ? `${year1}-${month1}-${day1}` : ''; + + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + const response = await axios.get('invoice_payments/list', { + params: { + search: searchText, + start_date: formattedDate ? formattedDate : null, + end_date:formattedDate1, + provider: dataProvider, + status: dataStatus, + order: order, + orderBy: orderBy, + page: perPage, + } + }); + + setDataTableLoading(false); + + setDataTableData(response.data); + + const status: any = [ + { "id": "submitted", "name": "Pengajuan" }, // Sudah di-submit + { "id": "accepted", "name": "Belum Dibayar" }, // Sudah diterima keuangan + { "id": "partial_paid", "name": "Bayar Sebagian" }, // Sudah dibayar, namun belum lunas + { "id": "paid", "name": "Sudah Dibayar" } // Lunas + ]; + setStatusData(status); + }; + + const getProvider = async () => { + const response = await axios.get('/claims/get-provider'); + setProviders(response.data) + } + + + const applyFilter = async (searchFilter: { search: string }) => { + await loadDataTableData(searchFilter); + setSearchParams(searchFilter); + }; + + const handlePageChange = (event: ChangeEvent, value: number): void => { + setPerPage(value); + }; + + const [openDialogSubmit, setOpenDialogSubmit] = useState(false); + const handleCloseDialogSubmit = () => { + setOpenDialogSubmit(false); + } + + function toTitleCase(str: string | null) { + return str.replace(/\w\S*/g, function(txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + const [approve, setApprove] = useState(''); + const [valueStatus, setValueStatus] = useState(''); + const [valueID, setValueID] = useState(); + const [alertMessage, setAlertMessage] = useState(''); + + const [reasonDecline, setReasonDecline] = useState(''); + + const handleReasonDeclineChange = (event) => { + setReasonDecline(event.target.value); + // Tambahkan logika yang diperlukan di sini + }; + + const handleSubmitData = async () => { + try { + + const payload = { + valueStatus: valueStatus, + valueID: valueID + }; + + const response = await axios.post('/invoice-payment/submit-status', payload); + + if (response.status === 200) { + enqueueSnackbar('Status berhasil diperbarui', { variant: 'success' }); + handleCloseDialogSubmit(); + loadDataTableData(); + } else { + enqueueSnackbar('Gagal memperbarui status', { variant: 'error' }); + } + } catch (error) { + enqueueSnackbar('Terjadi kesalahan, coba lagi nanti', { variant: 'error' }); + console.error('Error:', error); + } + }; + + // const handleSubmitData = () => { + // //approve or decline + // if (!reasonDecline && approve == 'decline') { + // enqueueSnackbar('Mohon isi alasan', { variant: 'warning' }); + // return false; + // } + // Promise.all(selectedRows.map(send_bulk)) + // .then(() => { + // enqueueSnackbar('Status Berhasil diubah', { variant: 'success' }); + // setOpenDialogSubmit(false);loadDataTableData(); + // }) + // .catch((error) => { + // enqueueSnackbar(error.response?.data?.message ?? 'Something went wrong!', { variant: 'error' }); + // }); + // }; + + // function send_bulk(id) { + // return axios.post(`claims/${id}/${approve}`, { reasonDecline: reasonDecline }); + // } + + + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importClaimManagement = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + const [importResult, setImportResult] = useState(null); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const handleImportButton = () => { + if (importClaimManagement?.current) { + handleClose(); + importClaimManagement.current ? importClaimManagement.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + const handleCancelImportButton = () => { + if(importClaimManagement.current) + { + importClaimManagement.current.value = ''; + importClaimManagement.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(importClaimManagement.current && importClaimManagement.current.files) + { + if (importClaimManagement.current?.files.length) { + const formData = new FormData(); + formData.append('file', importClaimManagement.current?.files[0]); + setImportLoading(true); + axios + .post('claims/import', formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + setImportLoading(false); + enqueueSnackbar('Success Import Claim Managemenet', { variant: 'success' }); + }) + .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 = () => { + axios.get('claims/download-template').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 handleExportReportFiled = async () => { + + await axios + .post('claims/exportFiled', { params: importResult?.data.result_rows }) + .then((res) => { + enqueueSnackbar('Data berhasil di Export', { + variant: 'success', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }); + setIsLoading(false) + + document.location.href = res.data.data.file_url; + }) + .catch((err) => + enqueueSnackbar('Data Gagal di Export', { + variant: 'error', + anchorOrigin: { horizontal: 'right', vertical: 'top' }, + }) + + ); + }; + + + // useEffect(() => { + // loadDataTableData(); + // getProvider(); + // }, []); + + const headStyle = { + fontWeight: 'bold', + }; + const headCells = [ + { + id: 'invoice_number', + align: 'left', + label: 'No Invoice', + isSort: true, + }, + { + id: 'created_at', + align: 'left', + label: 'Date of Submission', + isSort: false, + }, + + { + id: 'periode', + align: 'left', + label: 'Periode Pembayaran', + isSort: false, + }, + { + id: 'status', + align: 'left', + label: 'Status', + isSort: true, + }, + { + id: 'action', + align: 'left', + label: '', + isSort: false, + }, + ]; + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + }; + // Called on every row to map the data to the columns + function createData(data: Claims): Claims { + return { + ...data, + }; + } + + { + /* ------------------ TABLE ROW ------------------ */ + } + function Row(props: { row: ReturnType, isSelected: boolean, onSelect: (id: string) => void }) { + const { row, isSelected, onSelect } = props; + // Memperbaiki destrukturisasi props + + const handleRowCheckboxChange = () => { + onSelect(row.id); // Panggil fungsi onSelect dari komponen induk dengan id baris saat checkbox di baris diklik + }; + + const [open, setOpen] = React.useState(false); + + const test = 1000; + + return ( + + *': { borderBottom: 'unset' } }}> + {/* + setOpen(!open)}> + {open ? : } + + */} + {row.invoice_number} + {/* {row.code} */} + {row?.created_at ? fDateTime(row?.created_at) : ''} + {row?.start_date ? fDate(row?.start_date) : ''} - {row?.end_date ? fDate(row?.end_date) : ''} + + {row.status === 'submitted' && ( + + )} + {row.status === 'accepted' && ( + + )} + {row.status === 'partial_paid' && ( + + )} + {row.status === 'paid' && ( + + )} + + + + + + navigate('/invoice-payment/detail/'+row.id+'') }> + + Detail + + navigate('/invoice-payment/edit/'+row.id+'') }> + + Edit + + {row.status !== 'paid' ? ( + { + setOpenDialogSubmit(true); + setApprove('approve'); + setValueID(row.id); + let message = ''; + if (row.status === 'submitted') { + message = 'Apakah Anda yakin ingin mengubah status Pembayaran dari Pengajuan menjadi Belum Bayar?'; + setValueStatus('accepted'); + } else if (row.status === 'accepted') { + message = 'Apakah Anda yakin ingin mengubah status Pembayaran dari Belum Bayar menjadi Bayar Sebagian?'; + setValueStatus('partial_paid'); + } else if (row.status === 'partial_paid') { + message = 'Apakah Anda yakin ingin mengubah status Pembayaran dari Bayar Sebagian menjadi Sudah Dibayar?'; + setValueStatus('paid'); + } else if (row.status === 'paid') { + message = 'Lunas, Terima Kasih!'; + } + + if (message) { + setAlertMessage(message); + } + + }}> + + Update Status + + + ):''} + + + } /> + + + + + + {/* COLLAPSIBLE ROW */} + + + + {/* + + Description : {row.description} + + */} + + + + + ); + } + { + /* ------------------ END TABLE ROW ------------------ */ + } + + + + function TableContent() { + return ( + + {/* ------------------ TABLE HEADER ------------------ */} + + + {selectedRows.length > 0 ? ( + <> + + + + {selectedRows.length > 0 ? selectedRows.length : '0'}  Selected + + + + + + + + + + + + + + ) : ( + <> + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + )} + + + + + {/* ------------------ END TABLE HEADER ------------------ */} + + {/* ------------------ TABLE ROW ------------------ */} + {dataTableIsLoading ? ( + + + + Loading + + + + ) : dataTableData.data.length === 0 ? ( + + + + Tidak Ada Data + + + + ) : ( + + {dataTableData.data.map((row) => ( + + ))} + + )} + {/* ------------------ END TABLE ROW ------------------ */} +
+ ); + } + + return ( + + + +
+ + + {!currentImportFileName && ( + <> + + { + if (event.key === 'Enter') { + handleSearchSubmit(event); + } + }} + InputProps={{ + startAdornment: ( + + + + ), + placeholder: 'Search Code or Name', + }} + /> + + + + { + + // loadDataTableData(); + setStartDate(value); + }} + renderInput={(params) => } + /> + + + + { + setEndDate(value); + }} + renderInput={(params) => ( + + )} + /> + + + + { + // providers && ( + // option.name || ''} + // value={providers.find((item) => item.id === dataProvider) || null} + // onChange={(event, value) => { + // if (value) { + // setDataProvider(value.id); + // } else { + // setDataProvider(null); + // } + // }} + // renderInput={(params) => ( + // + // )} + // /> + // ) + filterData && ( + option.name || ''} + value={filterData.find((item) => item.id === dataStatus) || null} + onChange={(event, value) => { + if (value) { + setDataStatus(value.id); + } else { + setDataStatus(null); + } + }} + renderInput={(params) => ( + + )} + /> + ) + } + + + + } + sx={{ p: 1.8 }} + onClick={handleExportReport} + loading={isLoading} + > + + Export + + + + + + + )} + {currentImportFileName && ( + + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + + )} + {importResult && ( + + + Last Import Result :{' '} + + {importResult.data.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.data.total_failed_row} + {' '} + Failed, + {/* {importResult.data.failed_rows.map((row, index) => ( + [Code={row.code ? row.code : 'Required'}] + ))} */} +  Report: +  Download Data Result Import + + + )} + +
+
+
+ + } + /> + + + + + Confirmation + + + + + + + + + + {alertMessage} + {approve == "decline" ? ( + + + + ): ''} + + + + + + + +
+ ); + } diff --git a/frontend/dashboard/src/routes/index.tsx b/frontend/dashboard/src/routes/index.tsx index bd69b2ce..a44879f0 100755 --- a/frontend/dashboard/src/routes/index.tsx +++ b/frontend/dashboard/src/routes/index.tsx @@ -480,6 +480,22 @@ export default function Router() { path: 'report/rujukan', element: , }, + { + path: 'invoice-payment', + element: , + }, + { + path: 'invoice-payment/create', + element: , + }, + { + path: 'invoice-payment/detail/:id', + element: , + }, + { + path: 'invoice-payment/edit/:invoiceID', + element: , + }, { path: 'claims', element: , @@ -780,6 +796,13 @@ const CorporateHistories = Loadable( const Profile = Loadable(lazy(() => import('../pages/Profile/Index'))); +//Invoice_Payments +const InvoicePayments = Loadable(lazy(() => import('../pages/InvoicePayment/Index'))); +const CreateInvoicePayments = Loadable(lazy(() => import('../pages/InvoicePayment/CreateInvoice'))); +const InvoicePaymentsDetail = Loadable(lazy(() => import('../pages/InvoicePayment/Detail'))); +const InvoicePaymentsEdit = Loadable(lazy(() => import('../pages/InvoicePayment/CreateInvoice'))); + + const Claims = Loadable(lazy(() => import('../pages/Claims/Index'))); const ClaimsCreate = Loadable(lazy(() => import('../pages/Claims/CreateUpdate'))); const ClaimsDetail = Loadable(lazy(() => import('../pages/Claims/Detail'))); From 6c170c19de69daa9ddbef64aec609f709166e745 Mon Sep 17 00:00:00 2001 From: ivan-sim Date: Fri, 28 Feb 2025 09:33:39 +0700 Subject: [PATCH 2/7] Update filter --- .../src/pages/InvoicePayment/CreateInvoice.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx b/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx index 2e7beccc..6c734648 100644 --- a/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx +++ b/frontend/dashboard/src/pages/InvoicePayment/CreateInvoice.tsx @@ -143,13 +143,13 @@ import { DatePicker, MobileDatePicker } from '@mui/x-date-pickers'; } }, [startDate, endDate, dataProvider, order, orderBy, perPage]); - useEffect(() => { - if (dataProvider) { - const newParams = new URLSearchParams(searchParams); - newParams.set("provider_id", dataProvider); // Set query param - navigate(`invoice-payment/claim?${newParams.toString()}`, { replace: true }); // Update URL - } - }, [dataProvider, navigate, searchParams]); + // useEffect(() => { + // if (dataProvider) { + // const newParams = new URLSearchParams(searchParams); + // newParams.set("provider_id", dataProvider); // Set query param + // navigate(`invoice-payment/claim?${newParams.toString()}`, { replace: true }); // Update URL + // } + // }, [dataProvider, navigate, searchParams]); const [isLoading, setIsLoading] = useState(false); const [isLoadingImport, setIsLoadingImport] = useState(false); From ca2c397c5169f8e8e49dd471cb766d7c66057590 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Thu, 6 Mar 2025 11:24:03 +0700 Subject: [PATCH 3/7] update dashboard --- .../Controllers/Api/DashboardController.php | 503 ++++++++++++++++ Modules/Internal/Routes/api.php | 6 + database/seeders/NavigationSeeder.php | 15 +- database/seeders/PermissionTableSeeder.php | 2 + frontend/dashboard/src/pages/Dashboard.tsx | 565 ++++++++++++++++-- 5 files changed, 1052 insertions(+), 39 deletions(-) create mode 100755 Modules/Internal/Http/Controllers/Api/DashboardController.php diff --git a/Modules/Internal/Http/Controllers/Api/DashboardController.php b/Modules/Internal/Http/Controllers/Api/DashboardController.php new file mode 100755 index 00000000..12ee1610 --- /dev/null +++ b/Modules/Internal/Http/Controllers/Api/DashboardController.php @@ -0,0 +1,503 @@ +start_date + ? Carbon::parse($request->start_date)->startOfDay()->toDateTimeString() + : null; + + $end_date = $request->end_date + ? Carbon::parse($request->end_date)->endOfDay()->toDateTimeString() + : null; + + $type = $request->type; // 0 = harian, 1 = mingguan, 2 = bulanan + $status = $request->status; // 0 = semua, 1 = berhasil, 2 = abandon, 3 = gagal + + // Menyesuaikan filter berdasarkan type (harian, mingguan, bulanan) + // if ($type == 1) { + // // Filter mingguan + // $start_date = Carbon::now()->startOfWeek()->toDateTimeString(); + // $end_date = Carbon::now()->endOfWeek()->toDateTimeString(); + + // } elseif ($type == 2) { + // // Filter bulanan + // $start_date = Carbon::now()->startOfMonth()->toDateTimeString(); + // $end_date = Carbon::now()->endOfMonth()->toDateTimeString(); + // } + + // Query awal + $query = Livechat::query(); + + // Filter berdasarkan tanggal + if ($start_date && $end_date) { + $query->whereBetween('dRequestTime', [$start_date, $end_date]); + } + + // Filter berdasarkan status + if ($status == 1) { + $query->where('sStatus', '2'); // Berhasil + } elseif ($status == 2) { + $query->where('sStatus', '1'); // Abandon + } elseif ($status == 3) { + $query->whereNotIn('sStatus', ['1', '2']); // Gagal (selain 1 dan 2) + } + + $liveChat = $query->get(); + + // Mapping status transaksi + $statusMapping = [ + "2" => "Berhasil", + "1" => "Abandon", + ]; + + // Inisialisasi counter status + $statusCount = [ + "Berhasil" => 0, + "Abandon" => 0, + "Gagal" => 0, + ]; + + // Hitung jumlah status + foreach ($liveChat as $chat) { + $statusLabel = isset($statusMapping[$chat->sStatus]) + ? $statusMapping[$chat->sStatus] + : "Gagal"; + $statusCount[$statusLabel]++; + } + + // Format response seperti yang diminta + $transaksiData = [ + ["name" => "Berhasil", "value" => $statusCount["Berhasil"], "color" => "#4CAF50"], + ["name" => "Gagal", "value" => $statusCount["Gagal"], "color" => "#F44336"], + ["name" => "Abandon", "value" => $statusCount["Abandon"], "color" => "#9E9E9E"], + ]; + + return response()->json($transaksiData); + } + + public function listBarChart(Request $request) + { + $start_date = $request->start_date + ? Carbon::parse($request->start_date)->startOfDay() + : Carbon::now()->startOfMonth(); + + $end_date = $request->end_date + ? Carbon::parse($request->end_date)->endOfDay() + : Carbon::now()->endOfMonth(); + + $status = $request->status; // 0 = semua, 1 = berhasil, 2 = abandon, 3 = gagal + $type = $request->type; // 0 = harian, 1 = mingguan, 2 = bulanan + + // Query awal + $query = Livechat::query(); + + // Filter berdasarkan rentang tanggal yang dimasukkan user + $query->whereBetween('dRequestTime', [$start_date, $end_date]); + + // Filter berdasarkan status + if ($status == 1) { + $query->where('sStatus', '2'); // Berhasil + } elseif ($status == 2) { + $query->where('sStatus', '1'); // Abandon + } elseif ($status == 3) { + $query->whereNotIn('sStatus', ['1', '2']); // Gagal (selain 1 dan 2) + } + + $liveChat = $query->get(); + + // Mengelompokkan data berdasarkan tipe request (harian, mingguan, atau bulanan) + $groupedData = []; + + foreach ($liveChat as $chat) { + if ($type == 1) { + // Mingguan (contoh: "01 Jan 2025 - 07 Jan 2025") + $weekStart = Carbon::parse($chat->dRequestTime)->startOfWeek(); + $weekEnd = Carbon::parse($chat->dRequestTime)->endOfWeek(); + $groupKey = $weekStart->format('d M Y') . ' - ' . $weekEnd->format('d M Y'); + } elseif ($type == 2) { + // Bulanan (contoh: "Jan 2025") + $groupKey = Carbon::parse($chat->dRequestTime)->translatedFormat('M Y'); + } else { + // Harian (format "1 Jan 2025 - 2 Feb 2025") + $groupKey = Carbon::parse($chat->dRequestTime)->format('j M Y'); + } + + if (!isset($groupedData[$groupKey])) { + $groupedData[$groupKey] = [ + "date" => $groupKey, + "Berhasil" => 0, + "Abandon" => 0, + "Gagal" => 0, + ]; + } + + if ($chat->sStatus == "2") { + $groupedData[$groupKey]["Berhasil"]++; + } elseif ($chat->sStatus == "1") { + $groupedData[$groupKey]["Abandon"]++; + } else { + $groupedData[$groupKey]["Gagal"]++; + } + } + + // Konversi hasil ke dalam array untuk response JSON + $result = array_values($groupedData); + + return response()->json($result); + } + + + public function listDokter(Request $request) + { + $idDokter = [ + '68268', + '75047', + '75046', + '75045', + '75044', + '75043', + '75027', + '75021', + '75020', + ]; // List dokter + + $listDokters = Dokter::with([])->whereIn('nIDUser', $idDokter)->get(); + + $result = $listDokters->map(function ($dokter) { + return [ + 'id' => $dokter->nIDUser, + 'code' => $dokter->nIDUser, + 'name' => $dokter->user->fullName, + 'online' => $dokter->sStatus, + ]; + }); + + return response()->json($result); + } + public function listPerformaDokter(Request $request) + { + $start_date = $request->start_date + ? Carbon::parse($request->start_date)->startOfDay() + : Carbon::now()->startOfMonth(); + + $end_date = $request->end_date + ? Carbon::parse($request->end_date)->endOfDay() + : Carbon::now()->endOfMonth(); + + $status = $request->status; // 0 = semua, 1 = berhasil, 2 = abandon, 3 = gagal + $type = $request->type; // 0 = harian, 1 = mingguan, 2 = bulanan + + $nIDDokter = $request->nIDDokter; + + // Query awal + $query = Livechat::with('doctor'); + + // Filter berdasarkan rentang tanggal yang dimasukkan user + $query->whereBetween('dRequestTime', [$start_date, $end_date]); + + if (!empty($nIDDokter)) { + $query->whereIn('nIDDokter', $nIDDokter); + } + + // Filter berdasarkan status + if ($status == 1) { + $query->where('sStatus', '2'); // Berhasil + } elseif ($status == 2) { + $query->where('sStatus', '1'); // Abandon + } elseif ($status == 3) { + $query->whereNotIn('sStatus', ['1', '2']); // Gagal + } + + // Ambil data livechat + $liveChats = $query->get(); + + // Data akhir yang akan dikembalikan + $groupedData = []; + + foreach ($liveChats as $chat) { + $dokterId = $chat->nIDDokter; + $dokterName = $chat->doctor->user->fullName ?? 'Unknown'; // Ambil nama dokter dari relasi + if (!isset($groupedData[$dokterId])) { + $groupedData[$dokterId] = [ + "name" => $dokterName, + "Berhasil" => 0, + "Abandon" => 0, + "Gagal" => 0, + ]; + } + + if ($chat->sStatus == "2") { + $groupedData[$dokterId]["Berhasil"]++; + } elseif ($chat->sStatus == "1") { + $groupedData[$dokterId]["Abandon"]++; + } else { + $groupedData[$dokterId]["Gagal"]++; + } + } + + // Konversi hasil ke dalam array untuk response JSON + $result = array_values($groupedData); + + return response()->json($result); + } + + + /** + * Show the form for creating a new resource. + * @return Renderable + */ + public function create() + { + return view('internal::create'); + } + + /** + * Store a newly created resource in storage. + * @param Request $request + * @return Renderable + */ + public function store(Request $request) + { + // + } + + /** + * Show the specified resource. + * @param int $id + * @return Renderable + */ + public function show($id) + { + return view('internal::show'); + } + + /** + * Show the form for editing the specified resource. + * @param int $id + * @return Renderable + */ + public function edit($id) + { + return view('internal::edit'); + } + + /** + * Update the specified resource in storage. + * @param Request $request + * @param int $id + * @return Renderable + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * @param int $id + * @return Renderable + */ + public function destroy($id) + { + // + } + + public function search(Request $request) + { + return Icd::when($request->search ?? null, function($icd, $search) { + $icd->where('name', 'LIKE', '%'.$search.'%') + ->orWhere('code', 'LIKE', '%'.$search.'%'); + })->limit(10)->get(); + } + + public function import(Request $request, $id) + { + $request->validate([ + 'file' => 'required|file|mimes:xls,xlsx,csv,txt', + ]); + $file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName(); + $file = $request->file('file')->storeAs('temp', $file_name); + + $import = new ImportService(); + $import->read(Storage::path('temp/'.$file_name)); + $import->write(Storage::disk('public')->path('temp/result-'.$file_name), 'xsls'); + + $imported_icd_data = 0; + $failed_icd_data = []; + foreach ($import->sheetsIterator() as $sheetIndex => $sheet) { + $doc_headers_indexes = []; + foreach ($sheet->getRowIterator() as $index => $row) { + if ($index == 1) { // First Row Must be Header + foreach ($row->getCells() as $index => $cell) { + $title = $cell->getValue(); + $title = preg_replace( "/\r|\n/", " ", $title ); + $title = preg_replace('/\xc2\xa0/', " ", $title ); + $title = rtrim($title); + $title = ltrim($title); + $doc_headers_indexes[$index] = $title; + } + + // Write Header to File + $result_headers = array_merge($doc_headers_indexes, ['Ingest Code', 'Ingest Note']); + $import->addArrayToRow($result_headers); + + // TODO Validate if First Row not Header + } else { // Next Row Should be Data + $row_data = []; + $row_map = [ + 0 => 'ICD_Code', + 1 => 'Description', + ]; + + foreach ($row->getCells() as $header_index => $cell) { + if (isset($row_map[$header_index])) { + $value = $cell->getValue(); + $value = preg_replace( "/\r|\n/", " ", $value ); + $value = preg_replace('/\xc2\xa0/', " ", $value ); + $value = rtrim($value); + $value = ltrim($value); + $row_data[$row_map[$header_index]] = $cell->getValue(); + } + } + + try { // Process the Row Data + if ( + empty($row_data['ICD_Code']) && + empty($row_data['Description']) + ) { + continue; + } + + // Save the Row + $icdService = new IcdService(); + $icdService->handleIcdRow($row_data, $id); + + // Write Success Result to File + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => 200, + 'Ingest Note' => 'Success', + ]), $sheet->getName()); + $imported_icd_data++; + + } catch (ImportRowException $e) { + // Write Data Validation Error to File + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => $e->getCode(), + 'Ingest Note' => $e->getMessage(), + ]), $sheet->getName()); + $failed_icd_data[] = ['row_number' => $index, 'error' => $e->getMessage(), 'data' => $row_data]; + } catch (\Exception $e) { + throw new \Exception($e); + // Write Server Error to File + $import->addArrayToRow(array_merge($row_data, [ + 'Ingest Code' => 500, + 'Ingest Note' => env('APP_DEBUG') ? $e->getMessage() : 'Server Error', + ]), $sheet->getName()); + $failed_icd_data[] = ['row_number' => $index, 'error' => $e->getMessage(), 'data' => $row_data]; + } + } + } + + break; // Only Read First Row + } + $import->reader->close(); + Storage::delete('temp/'.$file_name); + $import->writer->close(); + + return [ + 'total_successed_row' => $imported_icd_data, + 'total_failed_row' => count($failed_icd_data), + 'failed_row' => $failed_icd_data, + 'result_file' => [ + 'url' => Storage::disk('public')->url('temp/result-'.$file_name), + 'name' => 'result-'.$file_name, + ] + ]; + } + + public function activation(Request $request, $diagnosis_id) + { + $request->validate([ + 'active' => 'required' + ]); + + $Icd = Icd::findOrFail($diagnosis_id); + $Icd->active = $request->active == '1'; + + if ($Icd->save()) { + return response()->json([ + 'icd' => $Icd, + 'message' => 'Status Updated Successfully' + ]); + } + } + + public function generateIcdList(Request $request, $diagnosis_id){ + // Mendapatkan data yang akan diekspor (misalnya, dari database) + $data = Icd::where('icd_template_id', $diagnosis_id)->get()->toArray(); + + // Membuat penulis entitas Spout + $writer = WriterEntityFactory::createXLSXWriter(); + + // Membuka penulis untuk menulis ke file + $writer->openToFile(public_path('files/TemplateICDList.xlsx')); + /** Create a style with the StyleBuilder */ + $style = (new StyleBuilder()) + ->setFontBold() + ->build(); + + // Menulis header kolom + $headers_map_to_table_fields = $this->icdService->listing_doc_headers; + $headerRow = WriterEntityFactory::createRowFromArray($headers_map_to_table_fields, $style); + + $writer->addRow($headerRow); + + // Menulis data + if (!empty($data)) { + foreach ($data as $item) { + $rowData = [ + // $item['rev'], // Rev + // $item['version'], // Version + $item['code'], // Code + // $item['parent_code'], // Parent Code + $item['name'], // Name + // $item['description'], // Description + // $item['active'] == 1 ? 'Active' : 'Inactive', // Status + // $item['type'], // Type + ]; + + $row = WriterEntityFactory::createRowFromArray($rowData); + $writer->addRow($row); + } + } + + // Menutup penulis + $writer->close(); + + // Mengembalikan response untuk mengunduh file + $filePath = public_path('files/TemplateICDList.xlsx'); + + return Helper::responseJson([ + 'file_name' => "Diagnosis ICD List " . date('Y-m-d h:i:s'), + "file_url" => url('files/TemplateICDList.xlsx') + ]); + + } +} diff --git a/Modules/Internal/Routes/api.php b/Modules/Internal/Routes/api.php index 61abb29d..1e0a9ea0 100755 --- a/Modules/Internal/Routes/api.php +++ b/Modules/Internal/Routes/api.php @@ -27,6 +27,7 @@ use Modules\Internal\Http\Controllers\Api\HospitalController; use Modules\Internal\Http\Controllers\Api\DoctorController; use Modules\Internal\Http\Controllers\Api\DoctorRatingController; use Modules\Internal\Http\Controllers\Api\DoctorOnlineController; +use Modules\Internal\Http\Controllers\Api\DashboardController; use Modules\Internal\Http\Controllers\Api\DrugController; use Modules\Internal\Http\Controllers\Api\FormulariumController; use Modules\Internal\Http\Controllers\Api\FormulariumTemplateController; @@ -411,6 +412,11 @@ Route::prefix('internal')->group(function () { // Navigation Route::get('navigations', [NavigationController::class, 'index']); + // Dashboard + Route::get('dashboard/transaksi', [DashboardController::class, 'index']); + Route::get('dashboard/transaksi-bar-chart', [DashboardController::class, 'listBarChart']); + Route::get('dashboard/list-dokter', [DashboardController::class, 'listDokter']); + Route::get('dashboard/list-performa-dokter', [DashboardController::class, 'listPerformaDokter']); }); Route::get('province', [ProvinceController::class, 'index']); diff --git a/database/seeders/NavigationSeeder.php b/database/seeders/NavigationSeeder.php index 7e1cf74b..fcd6cfaa 100755 --- a/database/seeders/NavigationSeeder.php +++ b/database/seeders/NavigationSeeder.php @@ -19,8 +19,14 @@ class NavigationSeeder extends Seeder // DOCTORS & HOSPITALS [ 'title' => 'Dashboard', - 'path' => '/dashboard', - 'permission' => 'dashboard' + 'children' => [ + [ + 'title' => 'Dashboard', + 'path' => '/dashboard', + 'permission' => 'doctor-list' + ], + ], + 'permission' => 'dashboard', ], // DOCTORS & HOSPITALS [ @@ -263,6 +269,11 @@ class NavigationSeeder extends Seeder 'path' => '/alarm-center', 'permission' => 'alarm-center-list-client-portal' ], + [ + 'title' => 'Daily Monitoring', + 'path' => '/daily-monitoring', + 'permission' => 'daily-monitoring-list-client-portal' + ], [ 'title' => 'Formularium', 'path' => '/master/formularium-template-v2', diff --git a/database/seeders/PermissionTableSeeder.php b/database/seeders/PermissionTableSeeder.php index f2d6af41..8291218a 100755 --- a/database/seeders/PermissionTableSeeder.php +++ b/database/seeders/PermissionTableSeeder.php @@ -41,6 +41,7 @@ class PermissionTableSeeder extends Seeder 'formularium-create', 'formularium-edit', 'formularium-delete', + 'dashboard', 'diagnosis-list', 'diagnosis-create', 'diagnosis-edit', @@ -97,6 +98,7 @@ class PermissionTableSeeder extends Seeder 'export-alarm-center-client-portal', 'filter-alarm-center-client-portal', 'benefit-client-portal', + 'daily-monitoring-list-client-portal' ] ], ####################### HOSPITAL PORTAL ######################### diff --git a/frontend/dashboard/src/pages/Dashboard.tsx b/frontend/dashboard/src/pages/Dashboard.tsx index 96a89fb0..07dfcb8d 100755 --- a/frontend/dashboard/src/pages/Dashboard.tsx +++ b/frontend/dashboard/src/pages/Dashboard.tsx @@ -1,66 +1,557 @@ // @mui -import { Button, Container, Grid, styled, Typography, Card, Stack } from '@mui/material'; +import { useEffect, useState } from 'react'; +import { + Button, + Container, + Grid, + styled, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Card, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Checkbox, + +} from '@mui/material'; // hooks import useSettings from '../hooks/useSettings'; // components +import { PieChart, Pie, Cell, Tooltip, BarChart, Bar, XAxis, YAxis, Legend, ResponsiveContainer } from 'recharts'; import Page from '../components/Page'; import axios from '../utils/axios'; import useAuth from '../hooks/useAuth'; import SomethingUsage from '../sections/dashboard/SomethingUsage'; import { fCurrency } from '../utils/formatNumber'; +import dayjs from "dayjs"; +import {DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; +import { green, red } from "@mui/material/colors"; +import MuiDialog from '@/components/MuiDialog'; +import SearchIcon from "@mui/icons-material/Search"; // ---------------------------------------------------------------------- +const COLORS = ['#229A16', '#919EAB', '#FF4842']; + +// const performaDokterData = [ +// { name: 'Dr. John', Berhasil: 70, Gagal: 8, Abandon: 4 }, +// { name: 'Dr. Richard', Berhasil: 68, Gagal: 10, Abandon: 2 }, +// { name: 'Dr. Harman', Berhasil: 75, Gagal: 5, Abandon: 3 }, +// { name: 'Dr. Emma', Berhasil: 80, Gagal: 7, Abandon: 1 }, +// { name: 'Dr. tb', Berhasil: 80, Gagal: 7, Abandon: 1 }, +// { name: 'Dr. test', Berhasil: 80, Gagal: 7, Abandon: 1 }, +// { name: 'Dr. yayan', Berhasil: 80, Gagal: 7, Abandon: 1 }, +// { name: 'Dr. intan', Berhasil: 80, Gagal: 7, Abandon: 1 }, +// { name: 'Dr. fajri', Berhasil: 80, Gagal: 7, Abandon: 1 }, +// ]; + +// Custom Tooltip +const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + const totalPasien = + payload[0].value + payload[1].value + payload[2].value; + return ( +
+

{label}

+

Total Pasien: {totalPasien}

+

Berhasil: {payload[0].value}

+

Gagal: {payload[1].value}

+

Abandon: {payload[2].value}

+
+ ); + } + return null; +}; + +// Custom Tooltip +const CustomTooltipPie = ({ active, payload, startDate, endDate }) => { + if (!active || !payload || payload.length === 0) return null; + // Fungsi format tanggal ke "2 Jan 2025" + const formatDate = (date) => { + if (!date) return "N/A"; + return new Date(date).toLocaleDateString("id-ID", { + day: "numeric", + month: "short", + year: "numeric", + }); + }; + return ( +
+

{formatDate(startDate)} - {formatDate(endDate)}

+

Status: {payload[0]?.name || "Tidak ada data"}

+

Jumlah Pasien: {payload[0]?.value || 0}

+
+ ); +}; + + export default function Dashboard() { - const { themeStretch } = useSettings(); - - const { logout } = useAuth(); - const loadSomething = () => { - axios.get('/user') +const transaksiDataDefault = [ + { name: "Berhasil", value: 0, color: "#4CAF50" }, + { name: "Gagal", value: 0, color: "#F44336" }, + { name: "Abandon", value: 0, color: "#9E9E9E" }, +]; + const { themeStretch } = useSettings(); + const [startDate, setStartDate] = useState(dayjs().startOf('month').format('YYYY-MM-DD')); + const [endDate, setEndDate] = useState(dayjs().format('YYYY-MM-DD')); + const [startDateDokter, setStartDateDokter] = useState(dayjs().startOf('month').format('YYYY-MM-DD')); + const [endDateDokter, setEndDateDokter] = useState(dayjs().format('YYYY-MM-DD')); + const [transaksiData, setTransaksiData] = useState(transaksiDataDefault); + const [transaksiDataBar, setTransaksiDataBar] = useState([]); + const [type, setType] = useState(0); + const [status, setStatus] = useState(0); + const [statusDokter, setStatusDokter] = useState(0); + const [view, setView] = useState(["day", "month", "year"]); + const [performaDokterData, setListPerformaDoctors] = useState([]); + const [doctors, setListDoctors] = useState([]); + const [selectedDoctors, setSelectedDoctors] = useState(doctors.map((doctor) => doctor.id)); // State untuk dokter yang dipilih + + + const fetchData = async () => { + try { + const response = await axios.get(`dashboard/transaksi`, { + params: { + start_date: startDate, + end_date: endDate, + type, + status + } + }); + if (response.data) { + setTransaksiData(response.data); + } + if (type === 0) { + setView(["day", "month", "year"]); // Urutan yang benar: day, month, year + } else { + setView(["month"]); // Hanya menampilkan bulan & tahun + } + } catch (error) { + console.error('Error fetching transaksi data:', error); + } }; - const DangerCard = styled(Card)(({ theme }) => ({ - boxShadow: 'none', - padding: theme.spacing(3), - color: theme.palette.error.main, - backgroundColor: theme.palette.error.lighter, - })); + const fetchDataBar = async () => { + try { + const response = await axios.get(`dashboard/transaksi-bar-chart`, { + params: { + start_date: startDate, + end_date: endDate, + type, + status + } + }); + if (response.data) { + setTransaksiDataBar(response.data); + } + if (type === 0) { + setView(["day", "month", "year"]); // Urutan yang benar: day, month, year + } else { + setView(["month"]); // Hanya menampilkan bulan & tahun + } + } catch (error) { + console.error('Error fetching transaksi data:', error); + } + } - const SuccessCard = styled(Card)(({ theme }) => ({ - boxShadow: 'none', - padding: theme.spacing(3), - color: theme.palette.success.darker, - backgroundColor: theme.palette.success.lighter, - })); + const fetchListPerfomaDokter = async () => { + try { + const response = await axios.get(`dashboard/list-performa-dokter`, { + params: { + start_date: startDateDokter, + end_date: endDateDokter, + type, + statusDokter, + nIDDokter: selectedDoctors + } + } ); + if (response.data) { + setListPerformaDoctors(response.data); + } + } catch (error) { + console.error('Error fetching transaksi data:', error); + } + }; + + + const fetchListDokter = async () => { + try { + const response = await axios.get(`dashboard/list-dokter`); + if (response.data) { + setListDoctors(response.data); + } + } catch (error) { + console.error('Error fetching transaksi data:', error); + } + }; + + // Fetch data saat pertama kali halaman dimuat dan ketika filter berubah + useEffect(() => { + fetchData(); + fetchDataBar(); + // fetchListPerfomaDokter(); + }, [startDate, endDate, type, status]); + + // Fetch + useEffect(() => { + fetchListPerfomaDokter(); + }, [selectedDoctors, startDateDokter, endDateDokter]) + + useEffect(() => { + fetchListDokter(); + }, []); + + + // Performa dokter + const [openDialog, setOpenDialog] = useState(false); + const handleOpen = () => setOpenDialog(true); + + + const getContent = () => { + const [search, setSearch] = useState(""); // State untuk pencarian dokter + + // Filter dokter berdasarkan pencarian + const filteredDoctors = doctors.filter((doctor) => + doctor.name.toLowerCase().includes(search.toLowerCase()) + ); + + // Handle pilih satu dokter + const handleSelectDoctor = (id) => { + setSelectedDoctors((prev) => + prev.includes(id) ? prev.filter((docId) => docId !== id) : [...prev, id] + ); + }; + + // Handle pilih semua dokter + const handleSelectAll = () => { + if (selectedDoctors.length === doctors.length) { + setSelectedDoctors([]); + } else { + setSelectedDoctors(doctors.map((doctor) => doctor.id)); + } + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + setSelectedDoctors([]); + } + + const handlePilihDokter = () => { + setOpenDialog(false); + console.log(selectedDoctors); + } + return ( + <> + {/* Search Bar */} + setSearch(e.target.value)} + InputProps={{ + endAdornment: , + }} + sx={{ mb: 2, mt:2 }} + /> + + {/* Table Dokter */} + + + + + + + + Kode + Nama + Status + + + + {filteredDoctors.map((doctor) => ( + + + handleSelectDoctor(doctor.id)} + /> + + {doctor.code} + {doctor.name} + + + {doctor.online === 1 ? "🟢 Online" : "🔴 Offline"} + + + + ))} + +
+
+ + + + + + + + ); + }; + return ( - + Dashboard - - - + {/* Jumlah Transaksi */} + + + Jumlah Transaksi + + + {/* Pilih */} + + + Tipe + + + + + + { + if (!value) return; // Hindari error jika value null atau undefined + if (type == 0) { + setStartDate(value); + } else { + setStartDate(new Date(value.getFullYear(), value.getMonth(), 1)); + } + }} + renderInput={(params) => } + /> + + + + + { + if (!value) return; // Hindari error jika value null atau undefined + if (type == 0) { + setEndDate(value); + } else { + setEndDate(new Date(value.getFullYear(), value.getMonth(), 1)); + } + }} + renderInput={(params) => } + /> + + + {/* Pilih Status */} + + + Status + + + + + + + + `${(percent * 100).toFixed(0)}%`} + > + {transaksiData.map((entry, index) => ( + + ))} + + } /> + + + + + +
+ + + date} + /> + + } /> + + + + + + +
+
- - - - This Month Usages - {fCurrency(15000000)} (57) - - -
- - - Remaining Balance Estimation - November 2022 - - +
+ + {/* Performa Dokter */} + + + Performa Dokter + + + {/* Pilih Dokter*/} + + + + {/* Pilih */} + + + Tipe + + + + + + { + if (!value) return; // Hindari error jika value null atau undefined + if (type == 0) { + setStartDateDokter(value); + } else { + setStartDateDokter(new Date(value.getFullYear(), value.getMonth(), 1)); + } + }} + renderInput={(params) => } + /> + + + + + { + if (!value) return; // Hindari error jika value null atau undefined + if (type == 0) { + setEndDateDokter(value); + } else { + setEndDateDokter(new Date(value.getFullYear(), value.getMonth(), 1)); + } + }} + renderInput={(params) => } + /> + + + {/* Pilih Status */} + + + Status + + + + + + + + + + + + + + + + + -
+ + {/* Dialog Pilih Dokter */} + + +
); From 47ab0d2d614ad0c7b37571570a83b9970788e3d8 Mon Sep 17 00:00:00 2001 From: Fadila Date: Thu, 6 Mar 2025 15:23:54 +0700 Subject: [PATCH 4/7] tambah menu daily monitoring pada client portal --- .../Api/DailyMonitoringController.php | 913 ++++++++++++++++++ Modules/Client/Routes/api.php | 13 + database/seeders/NavigationSeeder.php | 5 + database/seeders/PermissionTableSeeder.php | 1 + frontend/client-portal/.env.development | 3 +- frontend/client-portal/src/@types/claims.ts | 116 +++ .../Components/DailyMonitoringList.tsx | 457 +++++++++ .../Components/DailyMonitoringListRow.tsx | 84 ++ .../Components/DetailMonitoringList.tsx | 526 ++++++++++ .../pages/DailyMonitoring/Model/Functions.ts | 178 ++++ .../src/pages/DailyMonitoring/Model/Types.ts | 120 +++ .../src/pages/DailyMonitoring/index.tsx | 48 + frontend/client-portal/src/routes/index.tsx | 25 +- .../client-portal/src/utils/formatTime.ts | 4 + 14 files changed, 2491 insertions(+), 2 deletions(-) create mode 100755 Modules/Client/Http/Controllers/Api/DailyMonitoringController.php create mode 100755 frontend/client-portal/src/@types/claims.ts create mode 100755 frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx create mode 100755 frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx create mode 100755 frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx create mode 100755 frontend/client-portal/src/pages/DailyMonitoring/Model/Functions.ts create mode 100755 frontend/client-portal/src/pages/DailyMonitoring/Model/Types.ts create mode 100755 frontend/client-portal/src/pages/DailyMonitoring/index.tsx diff --git a/Modules/Client/Http/Controllers/Api/DailyMonitoringController.php b/Modules/Client/Http/Controllers/Api/DailyMonitoringController.php new file mode 100755 index 00000000..331611fd --- /dev/null +++ b/Modules/Client/Http/Controllers/Api/DailyMonitoringController.php @@ -0,0 +1,913 @@ + ':attribute harus diisi', + 'integer' => ':attribute harus angka', + 'unique' => ':attribute (:input) sudah ada', + 'max' => ':attribute maximal :max karakter', + 'exists' => ':attribute (:input) tidak ditemukan', + 'numeric' => ':attribute harus angka', + 'digits_between'=> ':attribute maximal :max digit minimal :min digit' + ]; + } + + public function GetMemberList(Request $request) + { + $startDate = $request->start_date ? Carbon::parse($request->start_date) : Carbon::today(); + $endDate = $request->end_date ? Carbon::parse($request->end_date)->addDay() : Carbon::today()->addDay(); + + $memberList = DB::table('request_log_daily_monitorings') + ->leftJoin('request_logs', 'request_log_daily_monitorings.request_log_id', '=', 'request_logs.id') + ->leftJoin('members', 'request_logs.member_id', '=', 'members.id') + ->leftJoin('organizations', 'organizations.id', '=', 'request_logs.organization_id') + ->select( + 'members.member_id', + 'members.name', + 'members.birth_date', + 'request_logs.type_of_member as member_type', + 'members.members_effective_date AS startdate', + 'members.members_expire_date AS enddate', + 'request_logs.submission_date as addmision_date', + 'organizations.name as provider', + 'request_logs.organization_id', + 'request_logs.code', + // Using a subquery to fetch medical_plan + DB::raw('(SELECT plan FROM request_log_medical_plan rdm WHERE rdm.request_log_daily_monitoring_id = request_log_daily_monitorings.id AND type = 1 LIMIT 1) as medical_plan'), + DB::raw('(SELECT plan FROM request_log_medical_plan rdm WHERE rdm.request_log_daily_monitoring_id = request_log_daily_monitorings.id AND type = 2 LIMIT 1) as non_medical_plan'), + 'request_log_daily_monitorings.*' + ) + ->whereNull('request_logs.deleted_at') // Use whereNull() for checking NULL + ->when($request->search, function ($q, $search) { + $q->where(function ($subQ) use ($search) { + $subQ->where('members.member_id', 'LIKE', "%{$search}%"); + $subQ->orWhere('members.name', 'LIKE', "%{$search}%"); + }); + }) + ->when($startDate, function ($q) use ($startDate) { + $q->where('request_log_daily_monitorings.submission_date', '>=', $startDate); + }) + ->when($endDate, function ($q) use ($endDate) { + $q->where('request_log_daily_monitorings.submission_date', '<=', Carbon::parse($endDate)->addDay()); + }) + ->orderBy('request_logs.created_at', 'desc') + ->paginate(); + + return Helper::paginateResources(DailyMonitoringResource::collection($memberList)); + } + + /** + * Claim List - by member id + */ + public function GetClaimList(Request $request, $member_id) + { + $memberDetail = DB::table('members') + ->select('id','member_id','name') + ->where('member_id', $member_id) + ->first(); + + $claimList = DB::table('request_logs') + ->leftJoin('services', 'services.code', '=', 'request_logs.service_code') + ->leftJoin('members', 'members.id', '=', 'request_logs.member_id') + ->select('request_logs.id','request_logs.submission_date AS admission_date','request_logs.discharge_date','request_logs.code','services.name as service_name','request_logs.status','members.name', 'members.member_id') + ->where('request_logs.service_code', 'IP') + ->where('request_logs.deleted_at', null) + // ->where('request_logs.status_final_log', 'approved') + ->where("request_logs.member_id", "=", $memberDetail->id) + ->where("request_logs.organization_id", "=", $request->organization_id) + ->when($request->search, function ($q, $search) { + $q->where('request_logs.code', 'LIKE', "%".$search."%"); + }) + ->orderBy("request_logs.created_at", "desc") + // ->get() + ->paginate(); + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [ + 'member_detail'=> $memberDetail, + 'claim_list' => $claimList, + ] + ],200); + } + + /** + * Detail Monitoring List - by claim_code + */ + public function GetDetailMonitoringList(Request $request, $request_code) + { + // get id request log + $request_logs = DB::table('request_logs') + ->select('id','organization_id') + ->where('code', $request_code) + ->first(); + + $detail_list = RequestDailyMonitoring::where('request_log_id', empty($request_logs) == false ? $request_logs->id : '') + ->orderBy("submission_date", "desc") + ->get(); + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [ + 'detail_list'=> $detail_list, + 'organization_id' => $request_logs ? $request_logs->organization_id : 0 + ] + ],200); + } + + public function GetDetailMonitoringListbyID(Request $request, $id) + { + + $detail = RequestDailyMonitoring::where('id', $id) + ->orderBy("created_at", "desc") + ->first(); + + if ($detail) { + // Ubah menjadi array agar bisa dimodifikasi + $detailArray = $detail->toArray(); + + // Ubah nama key dari request_log_id menjadi log_id + $detailArray['log_code'] = $detailArray['request_log_id']; + unset($detailArray['request_log_id']); + } else { + $detailArray = null; + } + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => $detailArray, + ], 200); + + } + + public function UpdateDetailMonitoringbyID(Request $request) + { + // validation rule + $validator = Validator::make($request->all(),[ + 'log_code' => 'required', + 'reason' => 'required', + ],$this->messages()); + + // validation error + if ($validator->fails()) { + return response()->json([ + 'error' => true, + 'message' => $validator->getMessageBag() + ],400); + } + try { + // insert claim daily monitoring + $db_response = RequestDailyMonitoring::where('id', $request->id) + ->update([ + 'request_log_id' => $request->log_code, + 'submission_date' => $request->submission_date, + 'subject' => $request->subject, + 'object' => $request->objective, + 'sistole' => $request->sistole, + 'diastole' => $request->diastole, + 'body_temperature' => $request->body_temperature, + 'respiration_rate' => $request->respiration_rate, + 'analysis' => $request->analysis, + 'lab_date' => $request->lab_date, + 'provider' => $request->provider, + 'examination' => $request->examination, + 'doctor_1' => $request->doctor_1, + 'doctor_2' => $request->doctor_2, + 'temp_diagnosis' => $request->temp_diagnosis, + 'final_diagnosis' => $request->final_diagnosis, + 'approval_pendamping' => $request->approval_pendamping, + 'description' => $request->keterangan, + 'note' => $request->catatan, + 'reason' => $request->reason, + 'created_by' => auth()->user()->id, + ]); + + // cek medical plan + $num_medical_plan = 0; + foreach ($request->medical_plan as $row) { + if ($row['medical_plan_str']) { + $num_medical_plan++; + } + } + + if ($num_medical_plan == 0) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => [ + 'medical_plan' => ['medical plan harus diisi'] + ], + 'data' => [] + ],400); + } + if ($request->medical_plan){ + // delete medical plan + DB::table('request_log_medical_plan') + ->where([ + 'request_log_daily_monitoring_id' => $request->id, + 'type' => 1 + ]) + ->delete(); + // insert medical plan + foreach ($request->medical_plan as $row) { + DB::table('request_log_medical_plan')->insert([ + 'request_log_daily_monitoring_id' => $request->id, + 'plan' => $row['medical_plan_str'], + 'type' => 1, + 'created_at' => date('Y-m-d'), + ]); + } + } + + if ($request->non_medikamentosa_plan){ + // delete medical plan + DB::table('request_log_medical_plan') + ->where([ + 'request_log_daily_monitoring_id' => $request->id, + 'type' => 2 + ]) + ->delete(); + // insert non medical plan + foreach ($request->non_medikamentosa_plan as $row) { + DB::table('request_log_medical_plan')->insert([ + 'request_log_daily_monitoring_id' => $request->id, + 'plan' => $row['non_medikamentosa_plan_str'], + 'type' => 2, + 'created_at' => date('Y-m-d'), + ]); + } + } + + // insert file result + if ($request->confirmation_medical_leter){ + // $fileCurrents = File::where([ + // 'fileable_id' => $request->id, + // 'type' => 'confirmation-medical-letter', + // ])->get(); + // if ($fileCurrents){ + // foreach($fileCurrents as $fileCurrent){ + // if (Files::exists($fileCurrent->path)) { + // Files::delete(); + // } + // File::find($fileCurrent->id)->delete(); + // } + // } + foreach ($request->confirmation_medical_leter as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $fileName = $name . '.' . $extension; + $orignalName = $file->getClientOriginalName(); + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $request->id, + 'type' => 'confirmation-medical-letter', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + } + } + if ($request->medical_action_letter){ + // $fileCurrents = File::where([ + // 'fileable_id' => $request->id, + // 'type' => 'medical-action-letter', + // ])->get(); + // if ($fileCurrents){ + // foreach($fileCurrents as $fileCurrent){ + // if (Files::exists($fileCurrent->path)) { + // Files::delete(); + // } + // File::find($fileCurrent->id)->delete(); + // } + // } + foreach ($request->medical_action_letter as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $fileName = $name . '.' . $extension; + $orignalName = $file->getClientOriginalName(); + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $request->id, + 'type' => 'medical-action-letter', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + // $file->storeAs($this->path_for_store, $fileName); + } + } + if ($request->result){ + // $fileCurrents = File::where([ + // 'fileable_id' => $request->id, + // 'type' => 'laboratorium-result', + // ])->get(); + // if ($fileCurrents){ + // foreach($fileCurrents as $fileCurrent){ + // if (Files::exists($fileCurrent->path)) { + // Files::delete(); + // } + // File::find($fileCurrent->id)->delete(); + // } + // } + foreach ($request->result as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $orignalName = $file->getClientOriginalName(); + $fileName = $name . '.' . $extension; + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $request->id, + 'type' => 'laboratorium-result', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + // $file->storeAs($this->path_for_store, $fileName); + } + } + DB::commit(); + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [] + ],200); + } + catch (Exception $e) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => $e->getMessage(), + 'data' => [] + ],500); + } + } + + /** + * Add Detail Monitoring List + */ + public function AddDetailMonitoringList(Request $request, $claim_code) + { + $request->merge(['claim_code' => $claim_code]); + + // validation rule + $validator = Validator::make($request->all(),[ + 'claim_code' => 'required|exists:claim_requests,code', + 'subject' => 'required', + 'sistole' => 'required|numeric', + 'diastole' => 'required|numeric', + 'body_temperature' => 'required|numeric', + 'respiration_rate' => 'required|numeric', + 'analysis' => 'required', + 'complaints' => 'required', + 'medical_plan' => 'required', + ],$this->messages()); + + // validation error + if ($validator->fails()) { + return response()->json([ + 'error' => true, + 'message' => $validator->getMessageBag() + ],400); + } + + // get claim request + $claim_request = DB::table('claim_requests') + ->select('id') + ->where('code', $claim_code) + ->first(); + + // get claim + $claim = DB::table('claims') + ->select('id') + ->where('claim_request_id', $claim_request->id) + ->first(); + DB::beginTransaction(); + try { + // insert claim daily monitoring + $db_response = DailyMonitoring::create([ + 'claim_id' => $claim->id, + 'subject' => $request->subject, + 'objective' => $request->objective, + 'sistole' => $request->sistole, + 'diastole' => $request->diastole, + 'body_temperature' => $request->body_temperature, + 'respiration_rate' => $request->respiration_rate, + 'analysis' => $request->analysis, + 'complaints' => $request->complaints, + ]); + + + // cek medical plan + $num_medical_plan = 0; + + foreach ($request->medical_plan as $row) { + if ($row['medical_plan_str']) { + $num_medical_plan++; + } + } + + if ($num_medical_plan == 0) { + DB::rollBack(); + + return response()->json([ + 'error' => true, + 'message' => [ + 'medical_plan' => ['medical plan harus diisi'] + ], + 'data' => [] + ],400); + } + + // insert medical plan + foreach ($request->medical_plan as $row) { + MedicalPlan::create([ + 'claim_daily_monitoring_id' => $db_response->id, + 'plan' => $row['medical_plan_str'], + ]); + } + + DB::commit(); + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [] + ],200); + } + catch (Exception $e) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => $e->getMessage(), + 'data' => [] + ],500); + } + } + + /** + * Add Detail Request LOG LIST + */ + public function AddDetailMonitoringListRequestLog(Request $request, $request_code) + { + $request->merge(['request_code' => $request_code]); + + // validation rule + $validator = Validator::make($request->all(),[ + 'request_code' => 'required|exists:request_logs,code', + 'subject' => 'required', + 'submission_date' => 'required', + 'body_temperature' => 'required', + 'sistole' => 'required', + 'diastole' => 'required', + 'respiration_rate' => 'required', + 'analysis' => 'required', + 'medical_plan' => 'required', + 'non_medikamentosa_plan' => 'required', + ],$this->messages()); + + // validation error + if ($validator->fails()) { + return response()->json([ + 'error' => true, + 'message' => $validator->getMessageBag() + ],400); + } + + // get claim request + $request_log = DB::table('request_logs') + ->select('id') + ->where('code', $request_code) + ->first(); + DB::beginTransaction(); + try { + // insert claim daily monitoring + $db_response = RequestDailyMonitoring::create([ + 'request_log_id' => $request_log->id, + 'submission_date' => $request->submission_date, + 'subject' => $request->subject, + 'object' => $request->objective, + 'sistole' => $request->sistole, + 'diastole' => $request->diastole, + 'body_temperature' => $request->body_temperature, + 'respiration_rate' => $request->respiration_rate, + 'analysis' => $request->analysis, + 'lab_date' => $request->lab_date, + 'provider' => $request->provider, + 'examination' => $request->examination, + 'created_by' => auth()->user()->id, + ]); + + + // cek medical plan + $num_medical_plan = 0; + foreach ($request->medical_plan as $row) { + if ($row['medical_plan_str']) { + $num_medical_plan++; + } + } + + if ($num_medical_plan == 0) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => [ + 'medical_plan' => ['medical plan harus diisi'] + ], + 'data' => [] + ],400); + } + + // insert medical plan + foreach ($request->medical_plan as $row) { + DB::table('request_log_medical_plan')->insert([ + 'request_log_daily_monitoring_id' => $db_response->id, + 'plan' => $row['medical_plan_str'], + 'type' => 1, + 'created_at' => date('Y-m-d'), + ]); + } + + // insert non medical plan + foreach ($request->non_medikamentosa_plan as $row) { + DB::table('request_log_medical_plan')->insert([ + 'request_log_daily_monitoring_id' => $db_response->id, + 'plan' => $row['non_medikamentosa_plan_str'], + 'type' => 2, + 'created_at' => date('Y-m-d'), + ]); + } + + // insert file result + if ($request->confirmation_medical_leter){ + foreach ($request->confirmation_medical_leter as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $fileName = $name . '.' . $extension; + $orignalName = $file->getClientOriginalName(); + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $db_response->id, + 'type' => 'confirmation-medical-letter', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + } + } + if ($request->medical_action_letter){ + foreach ($request->medical_action_letter as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $fileName = $name . '.' . $extension; + $orignalName = $file->getClientOriginalName(); + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $db_response->id, + 'type' => 'medical-action-letter', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + // $file->storeAs($this->path_for_store, $fileName); + } + } + if ($request->result){ + foreach ($request->result as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $orignalName = $file->getClientOriginalName(); + $fileName = $name . '.' . $extension; + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $db_response->id, + 'type' => 'laboratorium-result', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + // $file->storeAs($this->path_for_store, $fileName); + } + } + + + DB::commit(); + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [] + ],200); + } + catch (Exception $e) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => $e->getMessage(), + 'data' => [] + ],500); + } + } + + public function AddListRequestLog(Request $request) + { + // validation rule + $validator = Validator::make($request->all(),[ + 'log_code' => 'required|exists:request_logs,id', + 'subject' => 'required', + 'submission_date' => 'required', + 'body_temperature' => 'required', + 'sistole' => 'required', + 'diastole' => 'required', + 'respiration_rate' => 'required', + 'analysis' => 'required', + 'medical_plan' => 'required', + 'non_medikamentosa_plan' => 'required', + ],$this->messages()); + + // validation error + if ($validator->fails()) { + return response()->json([ + 'error' => true, + 'message' => $validator->getMessageBag() + ],400); + } + + // get claim request + $request_log = DB::table('request_logs') + ->select('id') + ->where('id', $request->log_code) + ->first(); + DB::beginTransaction(); + try { + // insert claim daily monitoring + $db_response = RequestDailyMonitoring::create([ + 'request_log_id' => $request->log_code, + 'submission_date' => $request->submission_date, + 'doctor_1' => $request->doctor_1, + 'doctor_2' => $request->doctor_2, + 'temp_diagnosis' => $request->temp_diagnosis, + 'final_diagnosis' => $request->final_diagnosis, + 'approval_pendamping' => $request->approval_pendamping, + 'description' => $request->keterangan, + 'note' => $request->catatan, + 'subject' => $request->subject, + 'object' => $request->objective, + 'sistole' => $request->sistole, + 'diastole' => $request->diastole, + 'body_temperature' => $request->body_temperature, + 'respiration_rate' => $request->respiration_rate, + 'analysis' => $request->analysis, + 'lab_date' => $request->lab_date, + 'provider' => $request->provider, + 'examination' => $request->examination, + 'created_by' => auth()->user()->id, + ]); + + + // cek medical plan + $num_medical_plan = 0; + foreach ($request->medical_plan as $row) { + if ($row['medical_plan_str']) { + $num_medical_plan++; + } + } + + if ($num_medical_plan == 0) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => [ + 'medical_plan' => ['medical plan harus diisi'] + ], + 'data' => [] + ],400); + } + + // insert medical plan + foreach ($request->medical_plan as $row) { + DB::table('request_log_medical_plan')->insert([ + 'request_log_daily_monitoring_id' => $db_response->id, + 'plan' => $row['medical_plan_str'], + 'type' => 1, + 'created_at' => date('Y-m-d'), + ]); + } + + // insert non medical plan + foreach ($request->non_medikamentosa_plan as $row) { + DB::table('request_log_medical_plan')->insert([ + 'request_log_daily_monitoring_id' => $db_response->id, + 'plan' => $row['non_medikamentosa_plan_str'], + 'type' => 2, + 'created_at' => date('Y-m-d'), + ]); + } + + // insert file result + if ($request->confirmation_medical_leter){ + foreach ($request->confirmation_medical_leter as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $fileName = $name . '.' . $extension; + $orignalName = $file->getClientOriginalName(); + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $db_response->id, + 'type' => 'confirmation-medical-letter', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + } + } + if ($request->medical_action_letter){ + foreach ($request->medical_action_letter as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $fileName = $name . '.' . $extension; + $orignalName = $file->getClientOriginalName(); + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $db_response->id, + 'type' => 'medical-action-letter', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + // $file->storeAs($this->path_for_store, $fileName); + } + } + if ($request->result){ + foreach ($request->result as $file) { + $name = 'labresult-' . uniqid(); + $extension= $file->getClientOriginalExtension(); + $orignalName = $file->getClientOriginalName(); + $fileName = $name . '.' . $extension; + $path = $file->storeAs($this->path_for_store, $fileName); + File::create([ + 'fileable_type' => 'App\Models\LaboratoriumResult', + 'fileable_id' => $db_response->id, + 'type' => 'laboratorium-result', + 'name' => $name, + 'original_name' => $orignalName, + 'extension' => $extension, + 'path' => $path, + ]); + + // $file->storeAs($this->path_for_store, $fileName); + } + } + + + DB::commit(); + + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [] + ],200); + } + catch (Exception $e) { + DB::rollBack(); + return response()->json([ + 'error' => true, + 'message' => $e->getMessage(), + 'data' => [] + ],500); + } + } + + /** + * Delete Listing Daily Monitoring + */ + public function deleteDetailMonitoringListRequestLog(Request $request, $id) + { + $listDailyMonitoring = RequestDailyMonitoring::find($id); + $listDailyMonitoring->reason = $request->reason; + $listDailyMonitoring->save(); + + if (!$listDailyMonitoring) { + return response()->json([ + 'error' => true, + 'message' => "Data not found.", + ], 404); + } + + $listDailyMonitoring->delete(); + + return response()->json([ + 'error' => false, + 'message' => "Delete success", + 'data' => $listDailyMonitoring + ], 200); + } + + /** + * Delete File Daily Monitoring + */ + public function deleteFileDetailMonitoringListRequestLog(Request $request, $id){ + $fileCurrent = File::where([ + 'id' => $id, + ])->first(); + if ($fileCurrent){ + if (Files::exists($fileCurrent->path)) { + Files::delete(); + } + $fileCurrent->deleted_at = now(); + $fileCurrent->reason = $request->reason; + $fileCurrent->deleted_by = auth()->user()->id; + $fileCurrent->save(); + + return response()->json([ + 'error' => false, + 'message' => "Delete success", + 'data' => $fileCurrent + ], 200); + } else { + return response()->json([ + 'error' => true, + 'message' => "Data not found.", + ], 404); + } + } + + /** + * Update Status Request LOG + */ + public function UpdateListRequestLog(Request $request, $request_code) + { + // get claim request + $request_log = DB::table('request_logs') + ->where('code', $request_code) + ->update([ + 'discharge_date' => now(), + 'updated_by' => auth()->user()->id, + 'updated_at' => now() + ]); + if ($request_log) { + return response()->json([ + 'error' => false, + 'message' => "success", + 'data' => [] + ], 200); + } else { + return response()->json([ + 'error' => true, + 'message' => $e->getMessage(), + 'data' => [] + ],500); + } + } +} diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php index bf4635c0..27443755 100755 --- a/Modules/Client/Routes/api.php +++ b/Modules/Client/Routes/api.php @@ -19,6 +19,8 @@ use Modules\Internal\Http\Controllers\Api\FormulariumTemplateController; use Modules\Internal\Http\Controllers\Api\AuditTrailController; use Modules\Internal\Http\Controllers\Api\CorporateController; use Modules\Internal\Http\Controllers\Api\NavigationController; +use Modules\Client\Http\Controllers\Api\DailyMonitoringController; +use Modules\Internal\Http\Controllers\Api\RequestLogController; use Modules\Internal\Http\Controllers\Api\UserManagementController; @@ -35,6 +37,8 @@ use Modules\Internal\Http\Controllers\Api\UserManagementController; Route::prefix('client')->group(function () { + Route::get('codeLog', [RequestLogController::class, 'codeLog']); + Route::controller(AuthController::class)->group(function () { Route::post('login', 'login'); Route::post('verify-code', 'validateOtp'); @@ -80,6 +84,15 @@ Route::prefix('client')->group(function () { Route::get('download-ecard/{member_id}', [CorporateMemberController::class, 'downloadEcard']); Route::get('view_card/{member_id}', [CorporateMemberController::class, 'viewECard']); }); + + Route::get('memberlist', [DailyMonitoringController::class, 'GetMemberList']); + + // Daily Monitoring + Route::prefix('daily_monitoring')->group(function () { + Route::get('detail/{claim_code}/list', [DailyMonitoringController::class, 'GetDetailMonitoringList']); + Route::get('detail/{id}/edit', [DailyMonitoringController::class, 'GetDetailMonitoringListbyID']); + }); + Route::get('claims/{id}', [ClaimController::class, 'show']); Route::post('claim-requests', [ClaimRequestController::class, 'store'])->name('claim-requests.store'); diff --git a/database/seeders/NavigationSeeder.php b/database/seeders/NavigationSeeder.php index 7e1cf74b..32f368bf 100755 --- a/database/seeders/NavigationSeeder.php +++ b/database/seeders/NavigationSeeder.php @@ -263,6 +263,11 @@ class NavigationSeeder extends Seeder 'path' => '/alarm-center', 'permission' => 'alarm-center-list-client-portal' ], + [ + 'title' => 'Daily Monitoring', + 'path' => '/daily_monitoring', + 'permission' => 'daily-monitoring-list-client-portal' + ], [ 'title' => 'Formularium', 'path' => '/master/formularium-template-v2', diff --git a/database/seeders/PermissionTableSeeder.php b/database/seeders/PermissionTableSeeder.php index f2d6af41..dbbfabec 100755 --- a/database/seeders/PermissionTableSeeder.php +++ b/database/seeders/PermissionTableSeeder.php @@ -85,6 +85,7 @@ class PermissionTableSeeder extends Seeder 'employee-data-list-client-portal', 'corporate-client-portal', 'alarm-center-list-client-portal', + 'daily-monitoring-list-client-portal', 'formularium-list-client-portal', 'case-management-client-portal', 'service-monitoring-limit-client-portal', diff --git a/frontend/client-portal/.env.development b/frontend/client-portal/.env.development index 91cbcee5..1e537983 100755 --- a/frontend/client-portal/.env.development +++ b/frontend/client-portal/.env.development @@ -5,5 +5,6 @@ PORT=8083 REACT_APP_HOST_API_URL="https://aso-api.linksehat.dev/api/client" # VITE_API_URL="https://aso-api.linksehat.dev/api/client" -VITE_API_URL="https://aso-api.linksehat.dev/api/client" +# VITE_API_URL="https://aso-api.linksehat.dev/api/client" +VITE_API_URL="http://localhost:8000/api/client" \ No newline at end of file diff --git a/frontend/client-portal/src/@types/claims.ts b/frontend/client-portal/src/@types/claims.ts new file mode 100755 index 00000000..40e282a0 --- /dev/null +++ b/frontend/client-portal/src/@types/claims.ts @@ -0,0 +1,116 @@ +import { Benefit } from "./corporates"; +import { Member } from "./member"; + +export type ClaimRequest = { + id: number; + code: string; + name: string; + submission_date: string; + payment_type: string; + service_code: string; + claim_method: string; + service_type: string; + service_name: string; + code_provider: string; + file_condition: Files; + member: Member; + claim: { + organization: Organizations + } + provider_code: string, + organization: { + code: string + } + }; + +export type Claims = { + id: number; + code: string; + plan: Plan; + payor_id: string; + corporate_id: string; + policy_number: string; + benefit_desc: string; + member: Member; + benefit: Benefit | boolean; + status: string; + claim_request: ClaimRequest; +}; + +export type ClaimsEdit = { + id: number; + plan_id: string; + payor_id: string; + corporate_id: string; + policy_number: string; + member_id: string; + benefit_code: string; + benefit_desc: string; + amount_incurred: number; + amount_approved: number; + amount_not_approved: number; + excess_paid: number; +} + +export type Files = { + name: string; + url: string; + path: string; +} + +export type Plan = { + code: string; +} + +export type ClaimHistoryCare = { + id: number; + claim_id: number; + service_code: string; + admission_date: string; + discharge_date: string; + main_diagnosis_id: number; + main_diagnosis_name: string; + medical_record_number: string; + organization_id: number; + practitioner_id: number; + organization_name: string; + practitioner_name: string; + secondary_diagnosis_id: number[]; + sign: string; + symptoms: string; + name: any; +} + +export type Organizations = { + id: number; + code: string; + name: string; + address: string; + type: string; + lat: string; + lng: string; + phone: string; + timezone: string; + active: boolean | number; + province_id: number; + city_id: number; + district_id: number; + village_id: number; + postal_code: string; + description: string; + technology: string; + support_services: string; + merchant_code: string; + merchant_key: string; + image_url: string; + region_groups: string; +}; + +export type Import = { + result_file: { + url: string, + name: string, + total_success_row: number, + total_failed_row: number + } +} diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx new file mode 100755 index 00000000..3c997f77 --- /dev/null +++ b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx @@ -0,0 +1,457 @@ +/** + * Core + * ============================================ + */ +import React, { ChangeEvent, useEffect, useRef, useState } from "react"; +import { Box, Paper, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Stack, TextField, Button, Menu, Typography, ButtonGroup, } from "@mui/material"; + +/** + * Types & Functions + * ============================================ + */ +import { getDailyMonitoringList } from "../Model/Functions"; +import { DailyMonitoringListType } from "../Model/Types"; +import DailyMonitoringListRow from "./DailyMonitoringListRow"; +import { LaravelPaginatedData, LaravelPaginatedDataDefault } from "../../../@types/paginated-data"; +import { Grid } from "@mui/material"; +import DataTable from '../../../components/LaravelTable'; +import DownloadIcon from '@mui/icons-material/Download'; +import CancelIcon from '@mui/icons-material/Cancel'; +import UploadIcon from '@mui/icons-material/Upload'; +import { MenuItem } from "@mui/material"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import axios from "../../../utils/axios"; +import { DesktopDatePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; +import { fDate } from "../../../utils/formatTime"; +import { LoadingButton } from "@mui/lab"; +import { Import } from "../../../@types/claims"; +import { HeadCell, Order } from '../../../@types/table'; +import { fDateOnly } from "../../../utils/formatTime"; + +export default function DailyMonitoringList() { + const [searchParams, setSearchParams] = useSearchParams(); + // State + // -------------------- + const [dataTableIsLoading, setDataTableLoading] = useState(true); + const [dataTableData, setDataTableData] = useState( + LaravelPaginatedDataDefault + ); + + // Tabel Style + // -------------------- + const TableHeadStyle = { + fontWeight: 'bold', + }; + + const [importResult, setImportResult] = useState(null); + // Load Data + // ------------------- + const loadDataTableData = async (appliedFilter: any | null = null) => { + setDataTableLoading(true); + const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]); + + const response = await axios.get('/memberlist', {params: filter}) + 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); + }; + + + /* ------------------------------ handle params ----------------------------- */ + const [appliedParams, setAppliedParams] = useState({}); + + const params = { + searchParams: searchParams, + setSearchParams: setSearchParams, + appliedParams: appliedParams, + setAppliedParams: setAppliedParams, + }; + + /* ------------------------------ handle order ------------------------------ */ + const [order, setOrder] = useState('desc'); + const [orderBy, setOrderBy] = useState('submission_date'); + + const orders = { + order: order, + setOrder: setOrder, + orderBy: orderBy, + setOrderBy: setOrderBy, + }; + + /* ------------------------------- handle sort ------------------------------ */ + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['order', isAsc ? 'desc' : 'asc'], + ['orderBy', property], + ]); + params?.setAppliedParams(parameters); + }; + + useEffect(() => { + loadDataTableData(); + }, [appliedParams, searchParams, order, orderBy, setSearchParams]) + + function SearchInput(props: any) { + // SEARCH + const searchInput = useRef(null); + const [searchText, setSearchText] = useState(''); + // Start Date + // con + + const handleSearchChange = (event: any) => { + const newSearchText = event.target.value ?? ''; + setSearchText(newSearchText); + }; + + + const today = new Date(); // Default ke hari ini + + const [startDate, setStartDate] = useState(today); + const [endDate, setEndDate] = useState(today); + + useEffect(() => { + // Set nilai default saat pertama kali load jika searchParams kosong + const paramStartDate = searchParams.get('start_date'); + const paramEndDate = searchParams.get('end_date'); + + if (paramStartDate) { + setStartDate(new Date(paramStartDate)); + } + if (paramEndDate) { + setEndDate(new Date(paramEndDate)); + } + }, []); + + useEffect(() => { + // Trigger First Search + setSearchText(searchParams.get('search') ?? ''); + }, []); + + return ( +
+ + + { + if (event.key === 'Enter') { + // handleSearchSubmit(event); + + const filter = Object.fromEntries([ + ...searchParams.entries(), + ['search', searchText], + ]); + setSearchParams(filter); + loadDataTableData(filter); + } + }} + value={searchText} + placeholder='Search Code or Name...' + /> + + {/* Start Date */} + + + { + if (value) { + setStartDate(value); + const dateStr = fDateOnly(value); + const filter = Object.fromEntries([...searchParams.entries(), ['start_date', dateStr]]); + setSearchParams(filter); + loadDataTableData(filter); + } + }} + renderInput={(params) => } + /> + + + + {/* End Date */} + + + { + if (value) { + setEndDate(value); + const dateStr = fDateOnly(value); + const filter = Object.fromEntries([...searchParams.entries(), ['end_date', dateStr]]); + setSearchParams(filter); + loadDataTableData(filter); + } + }} + renderInput={(params) => } + /> + + + + +
+ ); + } + + function ImportForm(props: any) { + // IMPORT + // Create Button Menu + const [anchorEl, setAnchorEl] = React.useState(null); + const createMenu = Boolean(anchorEl); + const importForm = useRef(null); + const [currentImportFileName, setCurrentImportFileName] = useState(null); + const [importLoading, setImportLoading] = useState(false); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleImportButton = () => { + if (importForm?.current) { + handleClose(); + importForm.current ? importForm.current.click() : console.log('No File selected'); + } else { + alert('No file selected'); + } + }; + + const handleCancelImportButton = () => { + importForm.current.value = ''; + importForm.current.dispatchEvent(new Event('change', { bubbles: true })); + }; + + const handleImportChange = (event: any) => { + if (event.target.files[0]) { + setCurrentImportFileName(event.target.files[0].name); + } else { + setCurrentImportFileName(null); + } + }; + + const handleUpload = () => { + if (importForm.current?.files.length) { + const formData = new FormData(); + formData.append('file', importForm.current?.files[0]); + + setImportLoading(true); + axios + .post(`customer-service/request/import`, formData) + .then((response) => { + handleCancelImportButton(); + loadDataTableData(); + setImportResult(response.data); + // alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows'); + setImportLoading(false); + }) + .catch((response) => { + enqueueSnackbar( + 'Looks like something went wrong. Please check your data and try again. ' + + response.message, + { variant: 'error' } + ); + setImportLoading(false); + }); + } else { + enqueueSnackbar('No File Selected', { variant: 'warning' }); + } + }; + + const handleGetTemplate = (type :string) => { + axios.get('corporates/import-document-example/' + type) + .then((response) => { + const link = document.createElement('a'); + link.href = response.data.data.file_url; + link.setAttribute('download', response.data.data.file_name); + document.body.appendChild(link); + link.click(); + handleClose(); + }) + } + + const handleGetData = (type :string) => { + + } + + const navigate = useNavigate() + + return ( +
+ + {!currentImportFileName && ( + + + + + Import + + { + handleGetTemplate('member'); + }} + > + Download Template + + navigate(`/daily_monitoring/add_monitoring`)}> + Tambah + + + + )} + + {currentImportFileName && ( + + + + + + + } + sx={{ p: 1.8 }} + onClick={handleUpload} + loading={importLoading} + > + Upload + + + )} + + {importResult && ( + + + Last Import Result :{' '} + + {importResult.total_success_row ?? 0} + {' '} + Row Processed,{' '} + + {importResult.total_failed_row} + {' '} + Failed, Report :{' '} + + {importResult.result_file?.name ?? '-'} + + + + )} +
+ ); + } + + function TableContent(){ + return ( + + {/* Head Table */} + + + + Code + Admission Date + Member ID + Name + Tanggal Lahir + Member Type + Medical Plan + Non Medical Plan + + + + + {/* Body Table */} + {dataTableIsLoading ? ( + + + Loading + + + ) : dataTableData.data.length == 0 ? ( + + + No Data + + + ) : dataTableData.data && dataTableData.data.length > 0 ? ( + + {dataTableData.data.map((row: DailyMonitoringListType, index) => ( + + ))} + + ) : null} + +
+ ) + } + + return ( + + + + + + + } + /> + + + ); + +} diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx new file mode 100755 index 00000000..d17788a5 --- /dev/null +++ b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx @@ -0,0 +1,84 @@ +/** + * Core + * ============================================ + */ +import React, { useState } from "react"; +import { useNavigate } from "react-router"; +import { Box, Collapse, MenuItem, TableCell, TableRow, Stack } from "@mui/material"; +import Visibility from '@mui/icons-material/Visibility'; + +/** + * Component + * ============================================ + */ +// - Global - +import Label from "../../../components/Label"; +import TableMoreMenu from '../../../components/table/TableMoreMenu'; + +/** + * Utils, Types, Functions + * ============================================ + */ +import { fDate } from "../../../utils/formatTime"; +import { DailyMonitoringListType } from "../Model/Types"; +import { Edit } from "@mui/icons-material"; + +type Props = { + row: DailyMonitoringListType, + number: number +} + +export default function DailyMonitoringListRow ({ ...props }: Props) { + const navigate = useNavigate() + + return ( + + + td': { borderBottom: '1' } }}> + + {props.row.code} + + + + {props.row.member_id} + {props.row.name} + + + + + {props.row.member_type} + + + {props.row.medical_plan} + + + {props.row.non_medical_plan} + + + e.stopPropagation()}> + + + navigate(`/daily_monitoring/${props.row.member_id}/claims/${props.row.code}/list_monitoring`)}> + + View + + + } /> + + + + + + ); +} diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx b/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx new file mode 100755 index 00000000..8cb81a30 --- /dev/null +++ b/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx @@ -0,0 +1,526 @@ +/** + * Core + * ============================================ + */ +import React, { useEffect, useState } from 'react'; +import { useFieldArray, useForm } from 'react-hook-form'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Box, IconButton, Typography, Grid, Card, List, ListItem, Stack, Autocomplete, TextField, Button } from '@mui/material'; +import { LoadingButton } from "@mui/lab"; + +/** + * Components + * ============================================ +*/ +// - Global - +import Page from '../../../components/Page'; +import Label from "../../../components/Label"; + +/** + * Icon + * ============================================ + */ +import ArrowBackIosNew from '@mui/icons-material/ArrowBackIosNew'; +import FiberManualRecord from '@mui/icons-material/FiberManualRecord'; + +/** + * Utils, Types, Functions + * ============================================ + */ +import { fDate, fDateOnly } from '../../../utils/formatTime'; +import { getMonitoringDetailList } from '../Model/Functions'; +import { getOrganizationId } from '../Model/Functions'; +import { DetailMonitoringListType } from '../Model/Types'; +import TableMoreMenu from '../../../components/table/TableMoreMenu'; +import { MenuItem } from '@mui/material'; +import { Delete, DeleteForever, Edit, LoopOutlined } from '@mui/icons-material'; +import MuiDialog from '../../../components/MuiDialog'; +import { DialogActions } from '@mui/material'; +import axios from '../../../utils/axios'; +import { enqueueSnackbar } from 'notistack'; +import { escape } from 'lodash'; + + +export default function DetailMonitoringList() { + const { member_id, claim_code } = useParams(); + const navigate = useNavigate() + const pageTitle = claim_code??'_ _ _ _'; + + // State + // -------------------- + const [detailMonitoringList, setDetailMonitoringList] = useState(); + const [organizationId, setOrganizationId] = useState(); + + const [openDialog, setOpenDialog] = useState(false) + + // Use Effect + // -------------------- + useEffect(() => { + loadDataTableData(); + }, []) + + // Dialog + const marginBottom2 = { + marginBottom: 2, + } + const [selectedReason, setSelectedReason] = useState({value:'-', label:''}); + const reason = [ + { value: 'Wrong Setting', label: 'Wrong Setting' }, + { value: 'Hospital Request', label: 'Hospital Request' } + ]; + const [error, setError] = useState(''); + const [id, setId] = useState(null); + const [id_file, setIdFile] = useState(null); + + const handleCloseDialog = () => { + setOpenDialog(false); + } + const handleDelete = () => { + if(selectedReason.value != '-'){ + const parameters = { + 'reason' : selectedReason.value + } + if (id){ + const response = axios.get(`case_management/daily_monitoring/detail/${id}/delete`, { + params: { ...parameters }, + }); + + if (!response.error){ + enqueueSnackbar('Claim Request Updated Successfully!', { variant: 'success' }); + window.location.reload(); + setOpenDialog(false) + } else { + enqueueSnackbar('Claim Request Updated Error!', { variant: 'error' }); + } + + } else { + axios.get(`case_management/daily_monitoring/detail/${id_file}/delete-file`, { + params: { ...parameters }, + }) + .then(response => { + if (!response.error) { + enqueueSnackbar('File Successfully deleted!', { variant: 'success' }); + window.location.reload(); + setOpenDialog(false); + } else { + enqueueSnackbar('Deleted File Error!', { variant: 'error' }); + } + }) + .catch(error => { + console.error('Error:', error); + }); + } + } else { + setError('Please select a reason') + } + } + + + const handleEdit = (id:number|undefined) => { + navigate(`/case_management/daily_monitoring/${member_id}/claims/${claim_code}/${id}`) + } + + const getContent = () => ( + + Are you sure to delete this {id_file ? 'File ' : '' } Daily Monitoring ? + + + Reason for Delete* + + option.label} + fullWidth + value={selectedReason} + onChange={(event, newValue) => { + setSelectedReason(newValue); + // Validasi jika newValue adalah null + if (!newValue) { + setError('Please select a reason'); + } else { + setError(''); + } + }} + renderInput={(params) => ( + + )} + /> + + + + + + + + + ) + + + // Load Data + // ------------------- + const loadDataTableData = async () => { + const response = await getMonitoringDetailList(claim_code??''); + const organization_id = await getOrganizationId(claim_code??''); + + setDetailMonitoringList(response); + setOrganizationId(organization_id); + } + + function renderHTML(data: string) { + return ( +
+ ); + } + + return ( + + + {/* back button */} + + + navigate(`/daily_monitoring`)} > + + + + + {pageTitle} + + + + + {/* tabel claims */} + + + { + detailMonitoringList?.map((row, index) => { + return ( + + + {/* card header */} + + + + {row.discharge_date ? ( + + ) : ( + + )} + + {/* card body */} + + + + + + Subject : + + + + + {renderHTML(row.subject)} + + + + + + + + + + Object : + + + + + {renderHTML(row.object)} + + + + + + + Body Temperature + + + + + {row.body_temperature} Cel + + + + + Sistole + + + + + {row.sistole} mm[Hg] + + + + + Diastole + + + + + {row.diastole} mm[Hg] + + + + + Respiration Rate + + + + + {row.respiration_rate} / min + + + + + + + + + + + + Analysis : + + + + + {renderHTML(row.analysis)} + + + + + + + + + + Medical Plan : + + + + + { + row.medical_plan?.map((data, index) => { + return ( + + {renderHTML(data.medical_plan_str)} + + ) + }) + } + + + + + + + + + + Non Medikamentosa Plan : + + + + + { + row.non_medikamentosa_plan?.map((data, index) => { + return ( + + {renderHTML(data.non_medikamentosa_plan_str)} + + ) + }) + } + + + + + + + + + + Laboratorium Result : + + + + + + Date + + + + + { row.lab_date != null ? fDate(row.lab_date) : '-'} + + + + + + + Location + + + + + {row.provider != null ? row.provider : '-' } + + + + + + + Examination + + + + + {row.examination != null ? renderHTML(row.examination) : '-' } + + + + + + + + + + + Document Confirmation Medical Letter: + + + + + {row.document?.map((data, index) => ( + data.type === 'confirmation-medical-letter' ? ( + + + + ) : null + ))} + + + + + Document Medical Action Letter: + + + + + {row.document?.map((data, index) => ( + data.type === 'medical-action-letter' ? ( + + + + ) : null + ))} + + + + + + Document Examination Result: + + + + + {row.document?.map((data, index) => ( + data.type === 'laboratorium-result' ? ( + + + + ) : null + ))} + + + + + + + + + + + ) + }) + } + + + + {/* Dialog Delete */} + + + + ); +} diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Model/Functions.ts b/frontend/client-portal/src/pages/DailyMonitoring/Model/Functions.ts new file mode 100755 index 00000000..cf1e18a4 --- /dev/null +++ b/frontend/client-portal/src/pages/DailyMonitoring/Model/Functions.ts @@ -0,0 +1,178 @@ +import axios from '../../../utils/axios'; +import { makeFormData } from '../../../utils/jsonToFormData'; +import { enqueueSnackbar } from 'notistack'; +import { DailyMonitoringListType, DetailMonitoringListType, ResponseListingClaimType } from "./Types"; +import { fDate, fDateOnly } from '../../../utils/formatTime'; + +/** + * Listing Daily Monitoring + */ +export const getDailyMonitoringList = async ( param: any) => { + const response = await axios.get('/case_management/memberlist', {params: param}) + .then((res) =>{ + return res.data; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return []; + }); + + return response; +}; + +/** + * Listing Claim + */ +export const getClaimList = async ( member_id: string ): Promise => { + const response = await axios.get(`/case_management/claimlist/${member_id}`) + .then((res) =>{ + return res.data.data; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return null; + }); + + return response; +}; + +/** + * Add Monitoring Detail + */ +export const AddMonitoringDetail = async ( claim_code: string,data: DetailMonitoringListType ): Promise => { + data.lab_date = data.lab_date != '' && data.lab_date != null ? fDateOnly(data.lab_date) : ''; + data.submission_date = data.submission_date != '' && data.submission_date != null ? fDateOnly(data.submission_date) : ''; + + const formData = makeFormData({...data}); + + const response = await axios.post(`/daily_monitoring/add-request`, formData) + .then((res) =>{ + enqueueSnackbar(res.data.message, { + variant: 'success', + }); + + return true; + }) + .catch((res) => { + if (res.response.status == 400) { + let arr_message = res.response.data.message; + + for (const key in arr_message) { + enqueueSnackbar(arr_message[key][0], { + variant: 'warning', + }); + } + } + else { + enqueueSnackbar("server error !", { + variant: 'error', + }); + } + + return false; + }); + + return response; +}; + +/** + * Get Monitoring Detail List + */ +export const getMonitoringDetailList = async ( claim_code: string ): Promise => { + const response = await axios.get(`/daily_monitoring/detail/${claim_code}/list`) + .then((res) =>{ + return res.data.data.detail_list; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return []; + }); + + return response; +}; + +/** + * Get Monitoring Detail List + */ +export const getOrganizationId = async ( claim_code: string ): Promise => { + const response = await axios.get(`/daily_monitoring/detail/${claim_code}/list`) + .then((res) =>{ + return res.data.data.organization_id; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return []; + }); + + return response; +}; + +/** + * Get detail monitoring + */ +export const getMonitoringDetailById = async ( id: string) => { + const response = await axios.get(`/daily_monitoring/detail/${id}/edit`) + .then((res) =>{ + return res.data.data; + }) + .catch((res) => { + enqueueSnackbar("server error !", { + variant: 'error', + }); + + return []; + }); + + return response; +} + +/** + * Update detail monitoring + */ +export const UpdateMonitoringDetail = async (data: DetailMonitoringListType) => { + data.lab_date = data.lab_date != '' && data.lab_date != null ? fDateOnly(data.lab_date) : ''; + data.submission_date = data.submission_date != '' && data.submission_date != null ? fDateOnly(data.submission_date) : ''; + + const formData = makeFormData({...data}); + + const response = await axios.post(`/daily_monitoring/detail/update-request`, formData) + .then((res) =>{ + enqueueSnackbar(res.data.message, { + variant: 'success', + }); + + return true; + }) + .catch((res) => { + if (res.response.status == 400) { + let arr_message = res.response.data.message; + + for (const key in arr_message) { + enqueueSnackbar(arr_message[key][0], { + variant: 'warning', + }); + } + } + else { + enqueueSnackbar("server error !", { + variant: 'error', + }); + } + + return false; + }); + + return response; +} diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Model/Types.ts b/frontend/client-portal/src/pages/DailyMonitoring/Model/Types.ts new file mode 100755 index 00000000..30560d16 --- /dev/null +++ b/frontend/client-portal/src/pages/DailyMonitoring/Model/Types.ts @@ -0,0 +1,120 @@ +/** + * List Daily Monitoring + */ +export type DailyMonitoringListType = { + member_id : string, + member_type : string, + birth_date : string, + name : string, + start_date : string, + end_date : string, + description : string, + doctor_1 : string, + doctor_2 : string, + temp_diagnosis : string, + final_diagnosis : string, + approval_pendamping : string, + note : string, + addmision_date : string, + provider : string, + organization_id : number, + medical_plan : string, + non_medical_plan : string, +} + +/** + * Response Listing Claim + */ +export type ResponseListingClaimType = { + member_detail : MemberDetailType, + claim_list : ClaimListType[], +} + +/** + * Member Detail + */ +export type MemberDetailType = { + id : string, + member_id : string, + name : string, +} + +/** + * List Claim + */ +export type ClaimListType = { + claim_id : number, + admission_date : string, + discharge_date : string, + claim_code : string, + name : string, + code : string, + service_name : string, + claim_status : string, + service_type : string, + member_id : string +} + +/** + * Detail Monitoring List + */ +export type DetailMonitoringListType = { + id : number|null, + claim_id : string|null, + log_code : string|null, + request_log_id : string|null, + claim_code : string|undefined, + doctor_1 : string|undefined, + doctor_2 : string|undefined, + temp_diagnosis : string|undefined, + final_diagnosis : string|undefined, + approval_pendamping : string|undefined, + keterangan : string|undefined, + catatan : string|undefined, + description : string|undefined, + note : string|undefined, + subject : string|undefined, + object : string|undefined, + objective : string|undefined, + body_temperature: string|undefined, + respiration_rate: string|undefined, + sistole : string|undefined, + diastole : string|undefined + analysis : string|undefined, + complaints : string|undefined, + submission_date : string|undefined, + discharge_date : string|undefined, + lab_date : string|undefined, + provider : string|undefined, + examination : string|undefined, + reason : string|undefined, + medical_plan : MedicalPlanStrType[], + non_medikamentosa_plan : NonMedikamentosaPlanType[], + confirmation_medical_leter : files[], + medical_action_letter : files[], + result : files[], + document : document[], + created_at : string|null + data : { + organization_id : number + } +} + +export type MedicalPlanStrType = { + medical_plan_str: string +} + +export type NonMedikamentosaPlanType = { + non_medikamentosa_plan_str: string +} + +export type files = { + file: string +} + +export type document = { + id: number + file_name: string, + path: string, + type: string +} diff --git a/frontend/client-portal/src/pages/DailyMonitoring/index.tsx b/frontend/client-portal/src/pages/DailyMonitoring/index.tsx new file mode 100755 index 00000000..e6643f6b --- /dev/null +++ b/frontend/client-portal/src/pages/DailyMonitoring/index.tsx @@ -0,0 +1,48 @@ +/** + * Core + * ============================================ + */ +import { Box, Grid } from '@mui/material'; + +/** + * Components + * ============================================ +*/ +// - Global - +import Page from '../../components/Page'; +import HeaderBreadcrumbs from "../../components/HeaderBreadcrumbs"; +// - Local - +import DailyMonitoringList from './Components/DailyMonitoringList'; + +export default function DailyMonitoring() { + const pageTitle = "Daily Monitoring"; + + return ( + + + {/* page header */} + + + + + {/* tabel daily monitoring */} + + + + + + ); +} diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index d3d4bc87..c7ef9a1d 100755 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -117,7 +117,26 @@ export default function Router() { }, ], }, - + { + path: '/daily_monitoring', + element: ( + + + + + + ), + children: [ + { + element: , + index: true, + }, + { + path: ':member_id/claims/:claim_code/list_monitoring', + element: + }, + ], + }, { path: '/claim-submit', element: ( @@ -463,3 +482,7 @@ const UserRole = Loadable(lazy(() => import('../pages/UserManagement/UserRole/In const UserRoleCreate = Loadable(lazy(() => import('../pages/UserManagement/UserRole/CreateUpdate'))); const UserAccess = Loadable(lazy(() => import('../pages/UserManagement/UserAccess/Index'))); const UserAccessCreate = Loadable(lazy(() => import('../pages/UserManagement/UserAccess/CreateUpdate'))); + +// Daily Monitoring +const DailyMonitoring = Loadable(lazy(() => import('../pages/DailyMonitoring/index'))) +const DetailMonitoringList = Loadable(lazy(() => import('../pages/DailyMonitoring/Components/DetailMonitoringList'))) \ No newline at end of file diff --git a/frontend/client-portal/src/utils/formatTime.ts b/frontend/client-portal/src/utils/formatTime.ts index 65e936d4..03444595 100755 --- a/frontend/client-portal/src/utils/formatTime.ts +++ b/frontend/client-portal/src/utils/formatTime.ts @@ -37,6 +37,10 @@ export function fPostFormat(date: Date | string | number, dateFormat = 'yyyy-MM- return format(new Date(date), dateFormat); } +export function fDateOnly(date: Date | string | number) { + return format(new Date(date), 'yyyy-MM-dd'); +} + // export function fDateString(date) { // const dateObj = parseISO(date); // const formattedDate = format(dateObj, 'dd MMMM yyyy'); From 21dc2a8df8671b269eae9b6d9eff94a482cb3ffa Mon Sep 17 00:00:00 2001 From: Fadila Date: Thu, 6 Mar 2025 16:17:25 +0700 Subject: [PATCH 5/7] buat path menjadi daily-monitoring --- database/seeders/NavigationSeeder.php | 2 +- .../DailyMonitoring/Components/DailyMonitoringList.tsx | 2 +- .../DailyMonitoring/Components/DailyMonitoringListRow.tsx | 2 +- .../DailyMonitoring/Components/DetailMonitoringList.tsx | 8 ++++---- frontend/client-portal/src/routes/index.tsx | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/database/seeders/NavigationSeeder.php b/database/seeders/NavigationSeeder.php index 32f368bf..f3b4209d 100755 --- a/database/seeders/NavigationSeeder.php +++ b/database/seeders/NavigationSeeder.php @@ -265,7 +265,7 @@ class NavigationSeeder extends Seeder ], [ 'title' => 'Daily Monitoring', - 'path' => '/daily_monitoring', + 'path' => '/daily-monitoring', 'permission' => 'daily-monitoring-list-client-portal' ], [ diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx index 3c997f77..41935c02 100755 --- a/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx +++ b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringList.tsx @@ -334,7 +334,7 @@ export default function DailyMonitoringList() { > Download Template - navigate(`/daily_monitoring/add_monitoring`)}> + navigate(`/daily-monitoring/add_monitoring`)}> Tambah diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx index d17788a5..148ea000 100755 --- a/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx +++ b/frontend/client-portal/src/pages/DailyMonitoring/Components/DailyMonitoringListRow.tsx @@ -69,7 +69,7 @@ export default function DailyMonitoringListRow ({ ...props }: Props) { - navigate(`/daily_monitoring/${props.row.member_id}/claims/${props.row.code}/list_monitoring`)}> + navigate(`/daily-monitoring/${props.row.member_id}/claims/${props.row.code}/list_monitoring`)}> View diff --git a/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx b/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx index 8cb81a30..43de7c5a 100755 --- a/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx +++ b/frontend/client-portal/src/pages/DailyMonitoring/Components/DetailMonitoringList.tsx @@ -81,7 +81,7 @@ export default function DetailMonitoringList() { 'reason' : selectedReason.value } if (id){ - const response = axios.get(`case_management/daily_monitoring/detail/${id}/delete`, { + const response = axios.get(`case_management/daily-monitoring/detail/${id}/delete`, { params: { ...parameters }, }); @@ -94,7 +94,7 @@ export default function DetailMonitoringList() { } } else { - axios.get(`case_management/daily_monitoring/detail/${id_file}/delete-file`, { + axios.get(`case_management/daily-monitoring/detail/${id_file}/delete-file`, { params: { ...parameters }, }) .then(response => { @@ -117,7 +117,7 @@ export default function DetailMonitoringList() { const handleEdit = (id:number|undefined) => { - navigate(`/case_management/daily_monitoring/${member_id}/claims/${claim_code}/${id}`) + navigate(`/case_management/daily-monitoring/${member_id}/claims/${claim_code}/${id}`) } const getContent = () => ( @@ -186,7 +186,7 @@ export default function DetailMonitoringList() { {/* back button */} - navigate(`/daily_monitoring`)} > + navigate(`/daily-monitoring`)} > diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx index c7ef9a1d..59f32953 100755 --- a/frontend/client-portal/src/routes/index.tsx +++ b/frontend/client-portal/src/routes/index.tsx @@ -118,7 +118,7 @@ export default function Router() { ], }, { - path: '/daily_monitoring', + path: '/daily-monitoring', element: ( From e3369542f4da73715f391ba2b87992166055c1f6 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 11 Mar 2025 08:37:34 +0700 Subject: [PATCH 6/7] permision dan navigation --- database/seeders/NavigationSeeder.php | 12 ++++++++++++ database/seeders/PermissionTableSeeder.php | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/database/seeders/NavigationSeeder.php b/database/seeders/NavigationSeeder.php index fcd6cfaa..80409594 100755 --- a/database/seeders/NavigationSeeder.php +++ b/database/seeders/NavigationSeeder.php @@ -126,6 +126,18 @@ class NavigationSeeder extends Seeder ], 'permission' => null ], + // Invoice + [ + 'title' => 'INVOICE', + 'children' => [ + [ + 'title' => 'Invoice', + 'path' => '/invoice-payment', + 'permission' => 'invoice-payment-list' + ], + ], + 'permission' => null + ], // CUSTOMER SERVICES [ 'title' => 'CUSTOMER SERVICES', diff --git a/database/seeders/PermissionTableSeeder.php b/database/seeders/PermissionTableSeeder.php index 59479411..8c666750 100755 --- a/database/seeders/PermissionTableSeeder.php +++ b/database/seeders/PermissionTableSeeder.php @@ -73,7 +73,8 @@ class PermissionTableSeeder extends Seeder 'report-doctor-online', 'user-role-list', 'user-access-list', - 'report-katalog-dokter' + 'report-katalog-dokter', + 'invoice-payment-list', ] ], ####################### CLIENT PORTAL ######################### From c8efb2d82b9ef1e2a9e8317324b3e78a43ff8461 Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Fri, 14 Mar 2025 14:21:28 +0700 Subject: [PATCH 7/7] report monthly --- .../Controllers/Api/LivechatController.php | 308 ++++++++++++++++++ .../src/pages/Report/Livechat/List.tsx | 90 ++++- 2 files changed, 386 insertions(+), 12 deletions(-) diff --git a/Modules/Internal/Http/Controllers/Api/LivechatController.php b/Modules/Internal/Http/Controllers/Api/LivechatController.php index d26e1e28..13f607bc 100755 --- a/Modules/Internal/Http/Controllers/Api/LivechatController.php +++ b/Modules/Internal/Http/Controllers/Api/LivechatController.php @@ -63,6 +63,7 @@ class LivechatController extends Controller $startDate = $request->has('startDate') ? $request->input('startDate') : ''; $endDate = $request->has('endDate') ? $request->input('endDate') : ''; + $type = $request->has('type') ? $request->has('type') : ''; $liveChats = Livechat::with('user:nID,sFirstName,sLastName,sEmail,sPhone,nIDUser', 'doctor:nID,nIDSpesialis,nIDUser', 'doctor.user:nID,sFirstName,sLastName', 'appointment:nID,sPaymentStatus,sPaymentMethod', 'appointment.appointmentDetail:nID,nIDAppointment,dTanggalAppointment,tTimeAppointment', 'healthCare:nID,sHealthCare') ->where(function (Builder $query) use ($startDate, $endDate) { // $query->where('nIDAppointment', '!=', null); @@ -209,6 +210,313 @@ class LivechatController extends Controller $fileUrl = url('storage/temp/' . $fileName); + return Helper::responseJson([ + "file_url" => $fileUrl + ]); + } + + public function exportMonthly(Request $request) + { + $startDate = $request->has('startDate') ? $request->input('startDate') : ''; + $endDate = $request->has('endDate') ? $request->input('endDate') : ''; + + $liveChats = Livechat::with('userInsurance', + 'user:nID,sFirstName,sLastName,sEmail,sPhone,nIDUser,nIDHubunganKeluarga', + 'doctor:nID,nIDSpesialis,nIDUser', 'doctor.user:nID,sFirstName,sLastName', + 'appointment:nID,sPaymentStatus,sPaymentMethod', + 'appointment.appointmentDetail:nID,nIDAppointment,dTanggalAppointment,tTimeAppointment', + 'healthCare:nID,sHealthCare', + 'prescription', + 'rujukan' + ) + ->whereHas('userInsurance', function (Builder $query) { + // Kondisi pada relasi userInsurance + $query->where('nIDInsurance', 107); // khusus inhealth + }) + ->where(function (Builder $query) use ($startDate, $endDate) { + // $query->where('nIDAppointment', '!=', null); + // $query->where('nIDAppointment', '!=', ''); + if ($startDate) { + $query->where('dCreateOn', '>=', $startDate); + } + if ($endDate) { + $endDate = date('Y-m-d', strtotime($endDate . ' +1 day')); + $query->where('dCreateOn', '<', $endDate); + } + }) + ->orderBy('nID', 'desc') + ->get(['nID', 'nIDUser', 'nIDDokter', 'nIDHealthCare', 'nIDAppointment', 'sStatus', 'sMediaDokter', 'sMedia', 'dCreateOn', 'sNoSpj', 'dRequestTime', 'dAcceptTime', 'dStartTime', 'dEndTime']); + + $headers = [ + ['value' => 'ConsultationId', 'cell' => 'A1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'No SJP', 'cell' => 'B1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'PKSKD', 'cell' => 'C1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'PKSNM', 'cell' => 'D1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Card Number', 'cell' => 'E1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'NamaPasien', 'cell' => 'F1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'JenisKelamin', 'cell' => 'G1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'TanggalLahir', 'cell' => 'H1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Usia', 'cell' => 'I1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Produk', 'cell' => 'J1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Plan', 'cell' => 'K1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'DependencyStatus', 'cell' => 'L1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'TanggalKonsultasi', 'cell' => 'M1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Keluhan', 'cell' => 'N1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Kode Diagnosa', 'cell' => 'O1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Diagnosa', 'cell' => 'P1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'StartTime', 'cell' => 'Q1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'EndTime', 'cell' => 'R1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'DurasiKonsultasi', 'cell' => 'S1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'StatusKonsultasi', 'cell' => 'T1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'PrescriptionNumber', 'cell' => 'U1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'NamaDokter', 'cell' => 'V1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Jenis Dokter', 'cell' => 'W1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Rujuk (Ya/Tidak)', 'cell' => 'X1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Poli', 'cell' => 'Y1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Total', 'cell' => 'Z1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'Obat', 'cell' => 'AA1', 'mergeCell' => false, 'mergeToCell' => ''], + ['value' => 'By', 'cell' => 'AB1', 'mergeCell' => false, 'mergeToCell' => ''], + ]; + + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + foreach ($headers as $header) { + $sheet->setCellValue($header['cell'], $header['value']); + + // if ($header['mergeCell'] === true) { + // $sheet->mergeCells($header['cell'] . ':' . $header['mergeToCell']); + // } + + $sheet->getStyle($header['cell'])->getFont()->setBold(true); + $sheet->getStyle($header['cell'])->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER)->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER); + } + + $startFrom = 2; + foreach ($liveChats as $indexLiveChat => $liveChat) { + $phone = $liveChat->user->sPhone ?? '-'; + $status = $liveChat->sStatus; + $nIDUser = $liveChat->user->nIDUser ?? 0; // Principal or Dependent + $paymentMethod = $liveChat->appointment ? Helper::sPaymentMethod($liveChat->appointment->sPaymentMethod) : 'N/A'; + $fullNameDoctor = '-'; + if (!empty($liveChat->doctor->user)) { + $fullNameDoctor = ''; + + if ($liveChat->doctor->user->detail !== null) { + if ($liveChat->doctor->user->detail->sTitlePrefix !== null) { + $fullNameDoctor .= $liveChat->doctor->user->detail->sTitlePrefix . '. '; + } + + if ($liveChat->doctor->user->full_name !== null) { + $fullNameDoctor .= $liveChat->doctor->user->full_name . ' '; + } + + if ($liveChat->doctor->user->detail->sTitleSuffix !== null) { + $fullNameDoctor .= $liveChat->doctor->user->detail->sTitleSuffix; + } + } + } + + $recordType = 'P'; + if ($nIDUser){ + $recordType = 'D'; + } + switch ($status) { + case 0: + $statusLivechat = "Request TC"; + break; + case 1: + $statusLivechat = "Accepted by Doctor but User not Payment"; + break; + case 2: + $statusLivechat = "Payment Success"; + break; + case 3: + $statusLivechat = "Decline by Doctor"; + break; + case 4: + $statusLivechat = "Payment Expired"; + break; + default: + $statusLivechat = "Cancel by Patient"; + } + + $requestTime = Carbon::parse($liveChat->dRequestTime); + $acceptTime = Carbon::parse($liveChat->dAcceptTime); + $startTime = Carbon::parse($liveChat->dStartTime); + $endTime = Carbon::parse($liveChat->dEndTime); + + // Hitung selisih waktu response + if ($requestTime && $acceptTime) { + $diff = $requestTime->diff($acceptTime); + $responseTimeDiff = sprintf('%02d:%02d:%02d', $diff->h, $diff->i, $diff->s); + } else { + $responseTimeDiff = '00:00:00'; // Default jika data kosong + } + + // Hitung selisih waktu chat + if ($startTime && $endTime) { + $diffChatTime = $startTime->diff($endTime); + $chatTimeDiff = sprintf('%02d:%02d:%02d', $diffChatTime->h, $diffChatTime->i, $diffChatTime->s); + } else { + $chatTimeDiff = '00:00:00'; // Default jika data kosong + } + + // Set nilai responseTime dan chatTime + $responseTime = $responseTimeDiff; + $chatTime = $chatTimeDiff; + + + $diagnosa = '-'; + $diagnosaCode = '-'; + if($liveChat->summary){ + $diagnosis = explode(', ', $liveChat->summary->sAssessment); + + if ($diagnosis) { + $diagnosaArray = []; + $diagnosaCodeArray = []; + foreach($diagnosis as $data){ + $diagnosaArray[] = Helper::diagnosisName($data); // Tambahkan diagnosis ke array + $diagnosaCodeArray[] = $data; // Tambahkan diagnosis ke array + } + + $diagnosa = implode('; ', $diagnosaArray); // Gabungkan array dengan tanda koma + $diagnosaCode = implode('; ', $diagnosaCodeArray); // Gabungkan array dengan tanda koma + } + } + + $tebusResep = 'Belum Ditebus'; + $paymentTebus = '-'; + if ($liveChat->prescription){ + // Tanggal target (misalnya, dari database atau input) + $tanggalResep = Carbon::parse($liveChat->prescription->dTanggalResep); + + // Tanggal hari ini + $tanggalNow = Carbon::now(); + + // Menghitung selisih hari + $selisihHari = $tanggalNow->diffInDays($tanggalResep); + if ($selisihHari > 1){ + $tebusResep = 'Resep Kadaluarsa'; + } + + if ($liveChat->prescription->sIsDownload == 1){ + $tebusResep = 'Offline'; + } + if($liveChat->prescription->payment){ + $tebusResep = 'Online'; + if ($liveChat->prescription->payment->sPaymentStatus == 'paid'){ + $paymentTebus = Carbon::parse($liveChat->prescription->payment->dCreateOn)->format('d-m-Y H:i:s'); + } + }; + } else { + $tebusResep = '-'; + } + + switch ($liveChat->user->nIDHubunganKeluarga) { + case 9: + $nIDHubunganKeluarga = "Peserta"; // Parent + break; + case 4: + $nIDHubunganKeluarga = 'Istri'; // Spouse + break; + case 5: + $nIDHubunganKeluarga = 'Anak'; // Child + break; + case 3: + $nIDHubunganKeluarga = 'Suami'; // Husband + break; + default: + $nIDHubunganKeluarga = '-'; // No need to set $nIDHubunganKeluarga as it's already set to default value + break; + } + + if ($liveChat->user->relation && $nIDHubunganKeluarga == '-'){ + $nIDHubunganKeluarga = $liveChat->user->relation->sHubunganKeluarga; + } + + $obat = '-'; + if($liveChat->prescription){ + if ($liveChat->prescription->items){ + $obatArray = []; + foreach($liveChat->prescription->items as $data){ + $obatArray[] = $data->sItemName .' - '. $data->nQty; + } + + $obat = implode('; ',$obatArray); + } + }; + $sheet->setCellValue('A' . $startFrom, $liveChat->nID ?? '-'); + $sheet->setCellValue('B' . $startFrom, $liveChat->sNoSpj ?? '-'); + $sheet->setCellValue('C' . $startFrom, (string)($liveChat->userInsurance->sCorporateCode ?? '-')); + $sheet->setCellValue('D' . $startFrom, (string)($liveChat->userInsurance->sCorporateName ?? '-')); + $sheet->setCellValue('E' . $startFrom, (string)($liveChat->userInsurance->sNoPolis ?? '-')); + $sheet->setCellValue('F' . $startFrom, $liveChat->user->full_name ?? '-'); + $sheet->setCellValue('G' . $startFrom, $liveChat->user->detail->dTanggalLahir ?? '-'); + $sheet->setCellValue('H' . $startFrom, $liveChat->user->detail->nIDJenisKelamin == 1 ? 'Laki-laki' : 'Wanita'); + $sheet->setCellValue('I' . $startFrom, Helper::calculateAge($liveChat->user->detail->dTanggalLahir) ?? '-'); + $sheet->setCellValue('J' . $startFrom, (string)($liveChat->userInsurance->sProductCode ?? '-')); + $sheet->setCellValue('K' . $startFrom, (string)($liveChat->userInsurance->sPlanCode ?? '-')); + $sheet->setCellValue('L' . $startFrom, $nIDHubunganKeluarga ?? '-'); + $sheet->setCellValue('M' . $startFrom, $requestTime->format('Y-m-d')); + $sheet->setCellValue('N' . $startFrom, $liveChat->summary->sSubjective ?? '-'); + $sheet->setCellValue('O' . $startFrom, $diagnosaCode ?? '-'); + $sheet->setCellValue('P' . $startFrom, $diagnosa ?? '-'); + $sheet->setCellValue('Q' . $startFrom, $startTime->format('H:i:s')); + $sheet->setCellValue('R' . $startFrom, $endTime->format('H:i:s')); + $sheet->setCellValue('S' . $startFrom, $chatTime); + // $sheet->setCellValue('O' . $startFrom, $recordType); + // $sheet->setCellValue('P' . $startFrom, $nIDUser ?? '-'); + // $sheet->setCellValue('Q' . $startFrom, $paymentMethod ?? '-'); + $sheet->setCellValue('T' . $startFrom, $statusLivechat); + $sheet->setCellValue('U' . $startFrom, $liveChat->prescription->sKodeResep ?? '-'); + $sheet->setCellValue('V' . $startFrom, $fullNameDoctor); + $sheet->setCellValue('W' . $startFrom, $liveChat->doctor->speciality->sSpesialis ?? '-'); + $sheet->setCellValue('X' . $startFrom, $liveChat->rujukan ? 'Ya' : 'Tidak'); + $sheet->setCellValue('Y' . $startFrom, $liveChat->rujukan->sDepartement ?? '-' ); + + $sheet->setCellValue('Z' . $startFrom, '-'); + $sheet->setCellValue('AA' . $startFrom, $tebusResep); + + $sheet->setCellValue('AB' . $startFrom, 'LMS'); + // $sheet->setCellValue('AC' . $startFrom, $liveChat->prescription->dCreateOn ?? '-'); + // $sheet->setCellValue('AD' . $startFrom, $obat); + // $sheet->setCellValue('AE' . $startFrom, $tebusResep); + + // $sheet->setCellValue('AF' . $startFrom, $paymentTebus); + // $sheet->setCellValue('AG' . $startFrom, $liveChat->rujukan->nIDHealthcare ?? '-'); + // $sheet->setCellValue('AH' . $startFrom, $liveChat->rujukan->sDepartement ?? '-'); + // $sheet->setCellValue('AI' . $startFrom, $liveChat->summary->sSubjective ?? '-'); + // $sheet->setCellValue('AJ' . $startFrom, $liveChat->sNoSpj ?? '-'); + $startFrom++; + } + + foreach (['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J','K', 'L', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB'] as $header) { + // if ($header === 'A') { + // $spreadsheet->getActiveSheet()->getColumnDimension($header)->setWidth(35, 'px'); + // } elseif ($header === 'H' || $header === 'I') { + // $spreadsheet->getActiveSheet()->getColumnDimension($header)->setWidth(100, 'px'); + // } else { + $spreadsheet->getActiveSheet()->getColumnDimension($header)->setAutoSize(true); + // } + } + + // $spreadsheet->getActiveSheet()->getStyle('A2:A' . $startFrom)->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER)->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER); + + $sheet->getDefaultRowDimension()->setRowHeight(-1); + $sheet->setTitle('Live Chat Report'); + + $writer = new Xlsx($spreadsheet); + ob_start(); + $writer->save('php://output'); + $content = ob_get_contents(); + ob_end_clean(); + + $fileName = 'result-' . now()->getPreciseTimestamp(3) . '-livechat-report.xlsx'; + Storage::disk('public')->put('temp/' . $fileName, $content); + + $fileUrl = url('storage/temp/' . $fileName); + return Helper::responseJson([ "file_url" => $fileUrl ]); diff --git a/frontend/dashboard/src/pages/Report/Livechat/List.tsx b/frontend/dashboard/src/pages/Report/Livechat/List.tsx index 48626fdd..c97ae1d7 100755 --- a/frontend/dashboard/src/pages/Report/Livechat/List.tsx +++ b/frontend/dashboard/src/pages/Report/Livechat/List.tsx @@ -26,6 +26,8 @@ import { Autocomplete, InputAdornment, IconButton, + InputLabel, + MenuItem, } from '@mui/material'; import { @@ -61,6 +63,7 @@ import { RHFDatepicker } from '@/components/hook-form'; import { DesktopDatePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { fDateOnly } from '@/utils/formatTime'; +import { LoadingButton } from '@mui/lab'; // ---------------------------------------------------------------------- @@ -73,7 +76,9 @@ export default function List() { const [searchParamsOrganizations, setSearchParamsOrganizations] = useSearchParams(); const [searchParamsSpecialities, setSearchParamsSpecialities] = useSearchParams(); const [searchParamsFilter, setSearchParamsFilter] = useSearchParams(); - + const [type, setType] = useState(0); + const [view, setView] = useState(["day", "month", "year"]); + function Filter(props: any) { // SEARCH const searchInput = useRef(null); @@ -91,9 +96,27 @@ export default function List() { props.onSearch(searchText); }; + const handleTypeChange = (value: string) => { // Perbaikan di parameter + setType(value); // Mengatur state type dengan nilai baru + if (value === "0") { + setView(["day", "month", "year"]); // Urutan benar + } else { + setView(["month"]); // Hanya menampilkan bulan & tahun + } + + let entries = [...searchParams.entries(), ['type', value ?? '']]; + if (!searchParams.get('type')) { + entries = [...entries, ['type', value ?? '']]; + } + const filter = Object.fromEntries(entries); + + setSearchParams(filter); + }; + useEffect(() => { // Trigger First Search setSearchText(searchParams.get('search') ?? ''); + }, []); const item = [ @@ -107,7 +130,7 @@ export default function List() { return (
- + + + + Tipe + + + { try { if (value && !!Date.parse(value)) { - const date = value ? fDateOnly(value) : ''; - var entries = [...searchParams.entries(), ['startDate', date ?? '']]; + let date = fDateOnly(value); + + // Jika view adalah "month", set tanggal ke 1 + if (view.length === 1 && view.includes("month")) { + const dateObj = new Date(value); + dateObj.setDate(1); + date = fDateOnly(dateObj); + } + + let entries = [...searchParams.entries(), ['startDate', date ?? '']]; if (!searchParams.get('endDate')) { entries = [...entries, ['endDate', date ?? '']]; } const filter = Object.fromEntries(entries); - + setSearchParams(filter); loadDataTableData(filter); } @@ -157,18 +201,28 @@ export default function List() { { try { if (value && !!Date.parse(value)) { - const date = fDateOnly(value); - var entries = [...searchParams.entries(), ['endDate', date ?? '']]; + let date = fDateOnly(value); + + // Jika mode monthly, set endDate ke akhir bulan dari startDate + if (view.length === 1 && view.includes("month")) { + const dateObj = new Date(value); + dateObj.setMonth(dateObj.getMonth() + 1); // Pindah ke bulan berikutnya + dateObj.setDate(0); // Set ke tanggal terakhir bulan sebelumnya (akhir bulan) + date = fDateOnly(dateObj); + } + + let entries = [...searchParams.entries(), ['endDate', date ?? '']]; if (!searchParams.get('startDate')) { - entries = [...entries, ['startDate', date ?? '']]; + entries = [...entries, ['startDate', date ?? ''] ]; } const filter = Object.fromEntries(entries); - + setSearchParams(filter); loadDataTableData(filter); } @@ -188,15 +242,25 @@ export default function List() { - + @@ -383,6 +447,7 @@ export default function List() { const [dataTableIsLoading, setDataTableLoading] = useState(true); const [dataTableLastRequest, setDataTableLastRequest] = useState(0); const [dataTableResponseState, setDataTableResponseState] = useState('idle'); + const [isSubmitting, setIsSubmitting] = useState(false) const [dataTableData, setDataTableData] = useState({ current_page: 1, data: [], @@ -411,10 +476,11 @@ export default function List() { const exportExcel = async () => { var filter = Object.fromEntries([...searchParams.entries()]); - + setIsSubmitting(true) await axios .get('live-chat/export', { params: filter }) .then((res) => { + setIsSubmitting(false) enqueueSnackbar('Data berhasil di Export', { variant: 'success', anchorOrigin: { horizontal: 'right', vertical: 'top' },