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}
+{formatDate(startDate)} - {formatDate(endDate)}
+Status: {payload[0]?.name || "Tidak ada data"}
+Jumlah Pasien: {payload[0]?.value || 0}
+